Skip to content

Commit

Permalink
Fixes #3603 - Validate JSON API documents returned by serializer#norm…
Browse files Browse the repository at this point in the history
…alizeResponse
  • Loading branch information
mike-north committed Jul 29, 2015
1 parent fb79a08 commit 99feb94
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 1 deletion.
32 changes: 31 additions & 1 deletion packages/ember-data/lib/system/store/serializer-response.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,35 @@ import Model from 'ember-data/system/model/model';

const get = Ember.get;

/**
This is a helper method that validates a JSON API top-level document
The format of a document is described here:
http://jsonapi.org/format/#document-top-level
@method isValidJsonApiDocument
@param {Object} doc
@return {Boolean} true if the document is valid, false otherwise
*/
export function hasJsonApiDocumentValidationErrors(doc) {
let errors = [];
if (!doc || typeof doc !== 'object') {
errors.push('Top level of a JSON API document must be an object');
} else {
if (!('data' in doc) &&
!('errors' in doc) &&
!('meta' in doc)) {
errors.push('One or more of the following keys must be present: "data", "errors", "meta".');
} else {
if (('data' in doc) && ('errors' in doc)) {
errors.push('Top level keys "errors" and "data" cannot both be present in a JSON API document');
}
}
}

return errors;
}

/**
This is a helper method that always returns a JSON-API Document.
Expand All @@ -16,7 +45,8 @@ const get = Ember.get;
*/
export function normalizeResponseHelper(serializer, store, modelClass, payload, id, requestType) {
let normalizedResponse = serializer.normalizeResponse(store, modelClass, payload, id, requestType);

let validationErrors = hasJsonApiDocumentValidationErrors(normalizedResponse);
Ember.assert(`normalizeResponse must return a valid JSON API document, ${JSON.stringify(validationErrors)}`, Ember.isEmpty(validationErrors));
// TODO: Remove after metadata refactor
if (normalizedResponse.meta) {
store._setMetadataFor(modelClass.modelName, normalizedResponse.meta);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
var Person, store, env;
var run = Ember.run;

module("integration/store/json-validation", {
setup: function() {
Person = DS.Model.extend({
updatedAt: DS.attr('string'),
name: DS.attr('string'),
firstName: DS.attr('string'),
lastName: DS.attr('string')
});

env = setupStore({
person: Person
});
store = env.store;
},

teardown: function() {
run(store, 'destroy');
}
});

test("when normalizeResponse returns undefined (or doesn't return), throws an error", function() {

env.registry.register('serializer:person', DS.Serializer.extend({
normalizeResponse() {}
}));

env.registry.register('adapter:person', DS.Adapter.extend({
findRecord() {
return Ember.RSVP.resolve({});
}
}));

throws(function () {
run(function() {
store.find('person', 1);
});
}, /Top level of a JSON API document must be an object/);
});

test("when normalizeResponse returns null, throws an error", function() {

env.registry.register('serializer:person', DS.Serializer.extend({
normalizeResponse() {return null;}
}));

env.registry.register('adapter:person', DS.Adapter.extend({
findRecord() {
return Ember.RSVP.resolve({});
}
}));

throws(function () {
run(function() {
store.find('person', 1);
});
}, /Top level of a JSON API document must be an object/);
});


test("when normalizeResponse returns an empty object, throws an error", function() {

env.registry.register('serializer:person', DS.Serializer.extend({
normalizeResponse() {return {};}
}));

env.registry.register('adapter:person', DS.Adapter.extend({
findRecord() {
return Ember.RSVP.resolve({});
}
}));

throws(function () {
run(function() {
store.find('person', 1);
});
}, /One or more of the following keys must be present/);
});

test("when normalizeResponse returns a document with both data and errors, throws an error", function() {

env.registry.register('serializer:person', DS.Serializer.extend({
normalizeResponse() {
return {
data: [],
errors: []
};
}
}));

env.registry.register('adapter:person', DS.Adapter.extend({
findRecord() {
return Ember.RSVP.resolve({});
}
}));

throws(function () {
run(function() {
store.find('person', 1);
});
}, /cannot both be present/);
});

0 comments on commit 99feb94

Please sign in to comment.