holidayextras / jsonapi-client

Easily consume a json:api service in Javascript.
MIT License
68 stars 19 forks source link

Fatal exception when syncing resource with default of empty string and allow null #32

Closed reecemillsom closed 8 years ago

reecemillsom commented 8 years ago

How to reproduce To reproduce in any resource have a default of an empty string on a property as well as an allow key with null.

This is an example:

email: jsonApi.Joi.string().email()
      .default('')
      .allow(null)

If you create the resource when sync is executed it crashes with the following partial stack trace:

Uncaught TypeError: Cannot read property 'id' of null
      at Resource._construct (lib/Resource.js:19:20)
      at lib/Resource.js:286:10
      at lib/Transport.js:94:12
theninj4 commented 8 years ago

This error originates from here:

Resource.prototype.sync = Promise.denodeify(function(callback) {
  var self = this;
  var target = self._client._update;
  if (!self._getBase().id) {
    target = self._client._remoteCreate;
  }

  target.call(self._client, self, function(err, rawResponse, rawResource) {
    if (err) return callback(err);
    self._construct(rawResource, self._client);

The job of Resource.sync is to either update the resource, or create it if it doesn't exist. Following an update, the remote response should contain the newly modified resource, which we use to synchronise the local Resource.

I'd be interested to know how we're getting no error in combination with a null data payload. I think the only course of action if this scenario is to throw an Error - I'm pretty sure this behaviour isn't allowed in the specification?

reecemillsom commented 8 years ago

The following DEBUG output should help to clarify the sequence of events (this was obtained by tweaking the people resource definition in the jsonapi-server examples with the above mentioned changes):

jsonApi:requestCounter 0 +0ms POST /rest/people
jsonApi:validation:input {"id":"79c7eee6-0078-472e-b5c7-08d13b92b8ca","type":"people","email":"john.doe@example.com"} +3ms
jsonApi:handler:create {"type":"people","data":{"id":null,"type":"people","attributes":{"email":"john.doe@example.com"},"relationships":{}}} +1ms [null,{"id":"79c7eee6-0078-472e-b5c7-08d13b92b8ca","type":"people","email":"john.doe@example.com"}]
jsonApi:handler:find {"type":"people","data":{"id":null,"type":"people","attributes":{"email":"john.doe@example.com"},"relationships":{}},"id":"79c7eee6-0078-472e-b5c7-08d13b92b8ca"} +0ms [null,{"id":"79c7eee6-0078-472e-b5c7-08d13b92b8ca","type":"people","email":"john.doe@example.com"}]
jsonApi:validation:output {"id":"79c7eee6-0078-472e-b5c7-08d13b92b8ca","type":"people","email":"john.doe@example.com"} +1ms
jsonApi:requestCounter 1 +9ms GET /rest/people/79c7eee6-0078-472e-b5c7-08d13b92b8ca
jsonApi:handler:find {"type":"people","id":"79c7eee6-0078-472e-b5c7-08d13b92b8ca"} +1ms [null,{"id":"79c7eee6-0078-472e-b5c7-08d13b92b8ca","type":"people","email":"john.doe@example.com"}]
jsonApi:validation:output {"id":"79c7eee6-0078-472e-b5c7-08d13b92b8ca","type":"people","email":"john.doe@example.com"} +0ms
jsonApi:requestCounter 2 +5ms POST /rest/people
jsonApi:validation:input {"id":"be2d1e75-28a2-4895-aa0a-653d3d803f98","type":"people"} +1ms
jsonApi:handler:create {"type":"people","data":{"id":null,"type":"people","attributes":{},"relationships":{}}} +0ms [null,{"id":"be2d1e75-28a2-4895-aa0a-653d3d803f98","type":"people","email":""}]
jsonApi:handler:find {"type":"people","data":{"id":null,"type":"people","attributes":{},"relationships":{}},"id":"be2d1e75-28a2-4895-aa0a-653d3d803f98"} +0ms [null,{"id":"be2d1e75-28a2-4895-aa0a-653d3d803f98","type":"people","email":""}]
jsonApi:validation:output {"id":"be2d1e75-28a2-4895-aa0a-653d3d803f98","type":"people","email":""} +0ms
jsonApi:validation:error child "email" fails because ["email" is not allowed to be empty] +1ms {"id":"be2d1e75-28a2-4895-aa0a-653d3d803f98","type":"people","email":""}
theninj4 commented 8 years ago

Ok. In this instance, the Joi schema is invalid. a "" value is not accepted by the Joi.string() validator.

> var t = Joi.string().allow(null).default("");
undefined
> Joi.validate("test", t, function() { console.log(arguments) });
{ '0': null, '1': 'test' }
undefined
> Joi.validate(undefined, t, function() { console.log(arguments) });
{ '0': null, '1': '' }
undefined
> Joi.validate('', t, function() { console.log(arguments) });
{ '0': 
   { [ValidationError: "value" is not allowed to be empty]
     name: 'ValidationError',
     details: [ [Object] ],
     _object: '',
     annotate: [Function] },
  '1': '' }

The default you've chosen doesn't match your validation :(

soap commented 7 years ago

I have encountered this type of error. It crashed my client when I try to sync the resource with ' '. How to fix this?