Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle null input #2618

Closed
jefjos opened this issue Oct 14, 2020 · 22 comments
Closed

Handle null input #2618

jefjos opened this issue Oct 14, 2020 · 22 comments
Labels
duplicate This issue or PR already exists good first issue Good for newcomers help wanted Help the community by contributing to this issue

Comments

@jefjos
Copy link

jefjos commented Oct 14, 2020

The REST API I have written takes a payload optionally. Each field in the payload is optional. So I have a schema configured that validates the fields if they are sent in payload.

  const server = require("fastify")({
    logger: logger.pino,
  });

server.route({
        method: "POST",
        url: "/api/resources", 
        schema: {
            body: {
              type: 'object',
              properties: {
                someKey: { type: 'string' , minLength:3}
              }
         }
       },
        handler: async function(request, reply) {
            reply.send({result: 0});
        }
    });

However I want users to be able to call the API with no payload at all. This is the error I get when I call the API without a payload -

POST http://localhost:3000/api/resources


{"type":"SyntaxError","message":"Unexpected token } in JSON at position 0","stack":"SyntaxError: Unexpected token } in JSON at position 0\n    at JSON.parse (<anonymous>)\n    at Object.parse (C:\\Users\\jejosep\\Dev\\ABS-Gateway\\src\\CA\\node_modules\\secure-json-parse\\index.js:26:20)\n    at Parser.defaultJsonParser [as fn] (C:\\Users\\jejosep\\Dev\\ABS-Gateway\\src\\CA\\node_modules\\fastify\\lib\\contentTypeParser.js:216:29)\n    at IncomingMessage.onEnd (C:\\Users\\jejosep\\Dev\\ABS-Gateway\\src\\CA\\node_modules\\fastify\\lib\\contentTypeParser.js:200:25)\n    at IncomingMessage.emit (events.js:315:20)\n    at endReadableNT (_stream_readable.js:1218:12)\n    at processTicksAndRejections (internal/process/task_queues.js:84:21)","statusCode":400},"msg":"[\"Error Handler handling the runtime error\"] 
"}

Is there a way to treat null payload the same as empty json object - {} ?

Expected result of POST call
{"result":0}

@Eomm
Copy link
Member

Eomm commented Oct 14, 2020

Could you add a Minimal, Reproducible Example?

Without it, we are unable to help you.

@jefjos
Copy link
Author

jefjos commented Oct 14, 2020

I've added some code to reproduce the issue

@jsumners jsumners added the duplicate This issue or PR already exists label Oct 14, 2020
@jsumners
Copy link
Member

Duplicate: #297 #537

@mcollina
Copy link
Member

Maybe we should add a FAQ?

@jsumners
Copy link
Member

I go back to my argument that we should just accept an empty body as valid. It seems to be common for people to want to ping a POST/PUT/etc with an empty body as an RPC invocation.

@mcollina
Copy link
Member

Let's do that. Validation would fail then, but it's a more meaningful error.

@jsumners jsumners added good first issue Good for newcomers help wanted Help the community by contributing to this issue labels Oct 14, 2020
@plaanupam
Copy link

Can I try this? Just wanted to start contributing.
As I understand, we have to accept the body as a null/undefined object too, in methods except get.

@mcollina
Copy link
Member

go for it!

@jsumners
Copy link
Member

jsumners commented Oct 19, 2020

Can I try this? Just wanted to start contributing.
As I understand, we have to accept the body as a null/undefined object too, in methods except get.

Correct. Basically, this should work:

curl -v -X POST http://example.com/post-handler

pgcalixto added a commit to pgcalixto/fastify that referenced this issue Oct 24, 2020
User can set an option for the body to be null when it its supposed
to be an object according to its schema.

Fixes fastify#2618.
@pgcalixto
Copy link
Contributor

pgcalixto commented Oct 24, 2020

I've spent some time inspecting the code to implement the option for the object to be nullable: #2646.

However, by the end of writing the code of this PR, I realized:
"wouldn't it be enough to use type: ['object', 'null'] instead of type: 'object', without the need for a PR to do it?"

I ran some local tests with the example provided by @jefjos and it worked:

const fastify = require('fastify')()

fastify.route({
  method: 'POST',
  url: '/api/resources',
  schema: {
    body: {
      type: ['object', 'null'],
      properties: {
        someKey: { type: 'string' , minLength: 3 }
      }
    }
  },
  handler: async function(request, reply) {
    reply.send({result: 0, requestBody: request.body })
  }
});

fastify.listen(3000)

==>

$ curl -v -X POST http://localhost:3000/api/resources

*   Trying 127.0.0.1:3000...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 3000 (#0)
> POST /api/resources HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-type: application/json; charset=utf-8
< content-length: 31
< Date: Sat, 24 Oct 2020 20:45:15 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< 
* Connection #0 to host localhost left intact
{"result":0,"requestBody":null}

@pgcalixto
Copy link
Contributor

Also, I set the PR as a draft, for it to be closed it type: ['object', 'null'] resolves this issue.

@RafaelGSS
Copy link
Member

Since body.type can be an array with null. We should check if validation has the same behavior and add a test to body.type null

pgcalixto added a commit to pgcalixto/fastify that referenced this issue Oct 25, 2020
Test the 2 possible variants for nullable body:
- type: ['object', 'null']
- type: 'object', nullable: true

See: fastify#2618.
@pgcalixto pgcalixto mentioned this issue Oct 25, 2020
4 tasks
@pgcalixto
Copy link
Contributor

pgcalixto commented Oct 25, 2020

Good idea, @RafaelGSS! I just added 2 tests to validate it: #2648

Also, I saw that not only body.type can be ['object', 'null'], but ajv also allows a type: 'object' with an extra property nullable: true. The documentation already has these details:

@jsumners
Copy link
Member

🤔

'use strict'

const fastify = require('fastify')({ logger: { level: 'debug' } })

fastify.route({
  path: '/',
  method: 'POST',
  handler (req, res) {
    req.log.debug(`received body: ${JSON.stringify(req.body)}`)
    res.code(204).send()
  }
})

fastify.listen(3000)

Issuing curl -v -X POST 127.0.0.1:3000 results in:

{"level":20,"time":1603714610937,"pid":58943,"hostname":"mini2018","reqId":1,"msg":"received body: null"}

I'm not sure that is correct. null is a valid JSON body. So it is ambiguous if the user posted a null or the framework has supplied a null default. The default should be undefined in my opinion.

@mcollina
Copy link
Member

The default should be undefined in my opinion.

I agree

@pgcalixto
Copy link
Contributor

pgcalixto commented Oct 26, 2020

One thing that we should consider is that null is a more interchangeable information format. undefined is a format strict to JavaScript code.

If we need to reply the request body back in the response (e.g.: res.code(200).send({ requestBody: req.body })), and the user tried to JSON.parse it, it would crash, since JSON.parse does not support undefined.

I am not saying that Fastify shouldn't use undefined when passing the body value through the code, but what are the implications? Will the user have access to this undefined value and try to JSON.parse it? Because then it would break.

Abscence of body could also be represented as an empty string "" instead of undefined, but again there are implications to Fastify that I am not well aware of.

@jsumners
Copy link
Member

jsumners commented Oct 26, 2020

If someone is writing a handler wherein they expect to receive empty payloads, then it is very common practice in JavaScript to at least use a coercion check like if (!req.body) {}. It is also very common to differentiate the absence of a value and a valid "null" value by checking for undefined like so: if (req.body !== undefined) {}.

What I'm saying is, we should document that the the empty payload is req.body === undefined so that people can process them accordingly.

mcollina pushed a commit that referenced this issue Oct 27, 2020
* Test nullable body

Test the 2 possible variants for nullable body:
- type: ['object', 'null']
- type: 'object', nullable: true

See: #2618.

* Improve POST request test

As noticed by @jsumners, `fastify.inject` will pass a null body in
the POST request in the abscence of a body, which may lead to false
positives. What we want is a POST request that pass an empty body,
not a body with "null", which is a valid JSON.

By using sget, it makes a POST request which pass an empty body.

See #2648 (comment).
@mcollina
Copy link
Member

@jsumners To clarify, this is just lacking some docs? Where'd you put them?

@jsumners
Copy link
Member

@mcollina no, it is lacking in implementation as well. We currently default to null for req.body.

@metcoder95
Copy link
Member

metcoder95 commented Dec 9, 2020

If the default changes to null, that will be a major change or just minor?
Asking because currently working on a PR for it and also see most of the tests started failing

@jsumners
Copy link
Member

jsumners commented Dec 9, 2020

Not clear. Maybe major?

@metcoder95
Copy link
Member

Shall we close it as #2731 was merged?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
duplicate This issue or PR already exists good first issue Good for newcomers help wanted Help the community by contributing to this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants