mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Added default serializer + handling
refs: https://github.com/TryGhost/Toolbox/issues/245 - Added a serializer called default to the canary API - Ideally, this would be part of the shared framework, but this would change v2/v3 and we're about to get rid of them - Therefore, we change just canary for now, and we can refactor again later. - Added wiring to handler that uses the default serializer, if there is a default, and isn't an explicit serializer for the endpoint - Removed the invites serializer, so that one endpoint now uses the default Note: previous commits have added explicit serializers to every endpoint, this is the first step towards paring that back so that we have less serializers overall, not more!
This commit is contained in:
parent
e6b92aed9b
commit
3bd4d0989a
6 changed files with 292 additions and 30 deletions
35
core/server/api/canary/utils/serializers/output/default.js
Normal file
35
core/server/api/canary/utils/serializers/output/default.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:default');
|
||||
const mappers = require('./mappers');
|
||||
|
||||
const mapResponse = (docName, mappable, frame) => {
|
||||
if (mappers[docName]) {
|
||||
return mappers[docName](mappable, frame);
|
||||
} else if (mappable.toJSON) {
|
||||
return mappable.toJSON(frame.options);
|
||||
}
|
||||
|
||||
return mappable;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
all(response, apiConfig, frame) {
|
||||
const {docName, method} = apiConfig;
|
||||
debug('serializing', docName, method);
|
||||
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
|
||||
frame.response = {};
|
||||
|
||||
if (response.data) {
|
||||
frame.response[docName] = response.data.map(model => mapResponse(docName, model, frame));
|
||||
} else {
|
||||
frame.response[docName] = [mapResponse(docName, response, frame)];
|
||||
}
|
||||
|
||||
if (response.meta) {
|
||||
frame.response.meta = response.meta;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -9,6 +9,10 @@ module.exports = {
|
|||
return require('./all');
|
||||
},
|
||||
|
||||
get default() {
|
||||
return require('./default');
|
||||
},
|
||||
|
||||
get authentication() {
|
||||
return require('./authentication');
|
||||
},
|
||||
|
@ -49,10 +53,6 @@ module.exports = {
|
|||
return require('./posts');
|
||||
},
|
||||
|
||||
get invites() {
|
||||
return require('./invites');
|
||||
},
|
||||
|
||||
get settings() {
|
||||
return require('./settings');
|
||||
},
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:invites');
|
||||
|
||||
module.exports = {
|
||||
all(models, apiConfig, frame) {
|
||||
debug('all');
|
||||
|
||||
if (!models) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (models.meta) {
|
||||
frame.response = {
|
||||
invites: models.data.map(model => model.toJSON(frame.options)),
|
||||
meta: models.meta
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
frame.response = {
|
||||
invites: [models.toJSON(frame.options)]
|
||||
};
|
||||
}
|
||||
};
|
|
@ -101,18 +101,33 @@ module.exports.output = (response = {}, apiConfig, apiSerializers, frame) => {
|
|||
});
|
||||
}
|
||||
|
||||
// CASE: custom serializer exists
|
||||
if (apiSerializers[apiConfig.docName]) {
|
||||
if (apiSerializers[apiConfig.docName].all) {
|
||||
tasks.push(function serializeOptionsShared() {
|
||||
tasks.push(function serialiseCustomAll() {
|
||||
return apiSerializers[apiConfig.docName].all(response, apiConfig, frame);
|
||||
});
|
||||
}
|
||||
|
||||
if (apiSerializers[apiConfig.docName][apiConfig.method]) {
|
||||
tasks.push(function serializeOptionsShared() {
|
||||
tasks.push(function serialiseCustomMethod() {
|
||||
return apiSerializers[apiConfig.docName][apiConfig.method](response, apiConfig, frame);
|
||||
});
|
||||
}
|
||||
|
||||
// CASE: Fall back to default serializer
|
||||
} else if (apiSerializers.default) {
|
||||
if (apiSerializers.default.all) {
|
||||
tasks.push(function serializeDefaultAll() {
|
||||
return apiSerializers.default.all(response, apiConfig, frame);
|
||||
});
|
||||
}
|
||||
|
||||
if (apiSerializers.default[apiConfig.method]) {
|
||||
tasks.push(function serializeDefaultMethod() {
|
||||
return apiSerializers.default[apiConfig.method](response, apiConfig, frame);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (apiSerializers.all && apiSerializers.all.after) {
|
||||
|
|
192
test/unit/api/canary/utils/serializers/output/default.test.js
Normal file
192
test/unit/api/canary/utils/serializers/output/default.test.js
Normal file
|
@ -0,0 +1,192 @@
|
|||
const should = require('should');
|
||||
const sinon = require('sinon');
|
||||
const serializers = require('../../../../../../../core/server/api/canary/utils/serializers');
|
||||
const mappers = require('../../../../../../../core/server/api/canary/utils/serializers/output/mappers');
|
||||
|
||||
describe('Unit: canary/utils/serializers/output/default', function () {
|
||||
let toJSONStub;
|
||||
beforeEach(function () {
|
||||
toJSONStub = sinon.stub().callsFake(function () {
|
||||
return {title: this.title, hello: 'world'};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('.all() can map a pojo with one object', function () {
|
||||
const response = {
|
||||
title: 'foo'
|
||||
};
|
||||
|
||||
const apiConfig = {
|
||||
docName: 'stuffs'
|
||||
};
|
||||
const frame = {};
|
||||
|
||||
serializers.output.default.all(response, apiConfig, frame);
|
||||
|
||||
frame.response.should.eql({
|
||||
stuffs: [response]
|
||||
});
|
||||
});
|
||||
|
||||
it('.all() can map a pojo of many objects', function () {
|
||||
const response = {
|
||||
data: [
|
||||
{
|
||||
title: 'foo'
|
||||
},
|
||||
{
|
||||
title: 'bar'
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
total: 2
|
||||
}
|
||||
};
|
||||
|
||||
const apiConfig = {
|
||||
docName: 'stuffs'
|
||||
};
|
||||
const frame = {};
|
||||
|
||||
serializers.output.default.all(response, apiConfig, frame);
|
||||
|
||||
frame.response.should.eql({
|
||||
stuffs: response.data,
|
||||
meta: response.meta
|
||||
});
|
||||
});
|
||||
|
||||
it('.all() can map a single Bookshelf model', function () {
|
||||
const response = {
|
||||
toJSON: toJSONStub,
|
||||
title: 'foo'
|
||||
};
|
||||
|
||||
const apiConfig = {
|
||||
docName: 'stuffs'
|
||||
};
|
||||
const frame = {};
|
||||
|
||||
serializers.output.default.all(response, apiConfig, frame);
|
||||
|
||||
frame.response.should.eql({
|
||||
stuffs: [
|
||||
{title: 'foo', hello: 'world'}
|
||||
]
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(toJSONStub);
|
||||
});
|
||||
|
||||
it('.all() can map a Bookshelf collection', function () {
|
||||
const response = {
|
||||
data: [
|
||||
{
|
||||
toJSON: toJSONStub,
|
||||
title: 'foo'
|
||||
},
|
||||
{
|
||||
toJSON: toJSONStub,
|
||||
title: 'bar'
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
total: 2
|
||||
}
|
||||
};
|
||||
|
||||
const apiConfig = {
|
||||
docName: 'stuffs'
|
||||
};
|
||||
const frame = {};
|
||||
|
||||
serializers.output.default.all(response, apiConfig, frame);
|
||||
|
||||
frame.response.should.eql({
|
||||
stuffs: [
|
||||
{title: 'foo', hello: 'world'},
|
||||
{title: 'bar', hello: 'world'}
|
||||
],
|
||||
meta: response.meta
|
||||
});
|
||||
|
||||
sinon.assert.calledTwice(toJSONStub);
|
||||
});
|
||||
|
||||
it('.all() can map a single Bookshelf model with custom mapper', function () {
|
||||
mappers.stuffs = sinon.stub().callsFake(function (res) {
|
||||
return {
|
||||
title: res.title,
|
||||
custom: 'thing'
|
||||
};
|
||||
});
|
||||
|
||||
const response = {
|
||||
toJSON: toJSONStub,
|
||||
title: 'foo'
|
||||
};
|
||||
|
||||
const apiConfig = {
|
||||
docName: 'stuffs'
|
||||
};
|
||||
const frame = {};
|
||||
|
||||
serializers.output.default.all(response, apiConfig, frame);
|
||||
|
||||
frame.response.should.eql({
|
||||
stuffs: [
|
||||
{title: 'foo', custom: 'thing'}
|
||||
]
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(mappers.stuffs);
|
||||
sinon.assert.notCalled(toJSONStub);
|
||||
});
|
||||
|
||||
it('.all() can map a Bookshelf collection with custom mapper', function () {
|
||||
mappers.stuffs = sinon.stub().callsFake(function (res) {
|
||||
return {
|
||||
title: res.title,
|
||||
custom: 'thing'
|
||||
};
|
||||
});
|
||||
|
||||
const response = {
|
||||
data: [
|
||||
{
|
||||
toJSON: toJSONStub,
|
||||
title: 'foo'
|
||||
},
|
||||
{
|
||||
toJSON: toJSONStub,
|
||||
title: 'bar'
|
||||
}
|
||||
],
|
||||
meta: {
|
||||
total: 2
|
||||
}
|
||||
};
|
||||
|
||||
const apiConfig = {
|
||||
docName: 'stuffs'
|
||||
};
|
||||
const frame = {};
|
||||
|
||||
serializers.output.default.all(response, apiConfig, frame);
|
||||
|
||||
frame.response.should.eql({
|
||||
stuffs: [
|
||||
{title: 'foo', custom: 'thing'},
|
||||
{title: 'bar', custom: 'thing'}
|
||||
],
|
||||
meta: response.meta
|
||||
});
|
||||
|
||||
sinon.assert.calledTwice(mappers.stuffs);
|
||||
sinon.assert.notCalled(toJSONStub);
|
||||
});
|
||||
});
|
|
@ -142,6 +142,11 @@ describe('Unit: api/shared/serializers/handle', function () {
|
|||
after: sinon.stub().resolves(),
|
||||
before: sinon.stub().resolves()
|
||||
|
||||
},
|
||||
default: {
|
||||
add: sinon.stub().resolves(),
|
||||
all: sinon.stub().resolves()
|
||||
|
||||
},
|
||||
posts: {
|
||||
add: sinon.stub().resolves(),
|
||||
|
@ -169,6 +174,45 @@ describe('Unit: api/shared/serializers/handle', function () {
|
|||
sinon.assert.calledOnceWithExactly(apiSerializers.all.after, apiConfig, frame);
|
||||
|
||||
sinon.assert.callOrder(apiSerializers.all.before, apiSerializers.posts.all, apiSerializers.posts.add, apiSerializers.all.after);
|
||||
|
||||
sinon.assert.notCalled(apiSerializers.default.add);
|
||||
sinon.assert.notCalled(apiSerializers.default.all);
|
||||
});
|
||||
});
|
||||
|
||||
it('correctly calls default serializer when no custom one is set', function () {
|
||||
const apiSerializers = {
|
||||
all: {
|
||||
after: sinon.stub().resolves(),
|
||||
before: sinon.stub().resolves()
|
||||
|
||||
},
|
||||
default: {
|
||||
add: sinon.stub().resolves(),
|
||||
all: sinon.stub().resolves()
|
||||
}
|
||||
};
|
||||
|
||||
const response = [];
|
||||
const apiConfig = {docName: 'posts', method: 'add'};
|
||||
const frame = {};
|
||||
|
||||
const stubsToCheck = [
|
||||
apiSerializers.all.before,
|
||||
apiSerializers.default.all,
|
||||
apiSerializers.default.add
|
||||
];
|
||||
|
||||
return shared.serializers.handle.output(response, apiConfig, apiSerializers, frame)
|
||||
.then(() => {
|
||||
stubsToCheck.forEach((stub) => {
|
||||
sinon.assert.calledOnceWithExactly(stub, response, apiConfig, frame);
|
||||
});
|
||||
|
||||
// After has a different call signature... is this a intentional?
|
||||
sinon.assert.calledOnceWithExactly(apiSerializers.all.after, apiConfig, frame);
|
||||
|
||||
sinon.assert.callOrder(apiSerializers.all.before, apiSerializers.default.all, apiSerializers.default.add, apiSerializers.all.after);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue