route-helper: add `responseMessages`

Add a default "success" response message, the status code is 200 or 204
depending on whether the method returns any data.

Append any error messages as specified in the `errors` property
of method's remoting metadata.

Move the description of operation's return type to the "success"
response message.

Include error message models in the API models.
This commit is contained in:
Miroslav Bajtoš 2014-10-14 13:42:09 +02:00
parent 9a6bd35df7
commit d05dcb71df
4 changed files with 95 additions and 25 deletions

View File

@ -123,21 +123,40 @@ var routeHelper = module.exports = {
debug('route %j', route); debug('route %j', route);
var responseDoc = modelHelper.LDLPropToSwaggerDataType(returns);
// Note: Swagger Spec does not provide a way how to specify
// that the responseModel is "array of X". However,
// Swagger UI converts Arrays to the item types anyways,
// therefore it should be ok to do the same here.
var responseModel = responseDoc.type === 'array' ?
responseDoc.items.type : responseDoc.type;
var responseMessages = [{
code: route.returns && route.returns.length ? 200 : 204,
message: 'Request was successful',
responseModel: responseModel
}];
if (route.errors) {
responseMessages.push.apply(responseMessages, route.errors);
}
var apiDoc = { var apiDoc = {
path: routeHelper.convertPathFragments(route.path), path: routeHelper.convertPathFragments(route.path),
// Create the operation doc. Use `extendWithType` to add the necessary // Create the operation doc.
// `items` and `format` fields. // Note that we are not calling `extendWithType`, as the response type
operations: [routeHelper.extendWithType({ // is specified in the first response message.
operations: [{
method: routeHelper.convertVerb(route.verb), method: routeHelper.convertVerb(route.verb),
// [rfeng] Swagger UI doesn't escape '.' for jQuery selector // [rfeng] Swagger UI doesn't escape '.' for jQuery selector
nickname: route.method.replace(/\./g, '_'), nickname: route.method.replace(/\./g, '_'),
parameters: accepts, parameters: accepts,
// TODO(schoon) - We don't have descriptions for this yet. responseMessages: responseMessages,
responseMessages: [],
summary: typeConverter.convertText(route.description), summary: typeConverter.convertText(route.description),
notes: typeConverter.convertText(route.notes), notes: typeConverter.convertText(route.notes),
deprecated: route.deprecated deprecated: route.deprecated
}, returns)] }]
}; };
return apiDoc; return apiDoc;

View File

@ -96,7 +96,9 @@ function Swagger(loopbackApplication, swaggerApp, opts) {
addTypeToModels(routeDoc.type); addTypeToModels(routeDoc.type);
// TODO(bajtos) handle types used by responseMessages routeDoc.responseMessages.forEach(function(msg) {
addTypeToModels(msg.responseModel);
});
function addTypeToModels(name) { function addTypeToModels(name) {
if (!name || name === 'void') return; if (!name || name === 'void') return;

View File

@ -13,7 +13,8 @@ describe('route-helper', function() {
{ arg: 'avg', type: 'number' } { arg: 'avg', type: 'number' }
] ]
}); });
expect(doc.operations[0].type).to.equal('object'); expect(doc.operations[0].type).to.equal(undefined);
expect(getResponseType(doc.operations[0])).to.equal('object');
}); });
it('converts path params when they exist in the route name', function() { it('converts path params when they exist in the route name', function() {
@ -60,19 +61,12 @@ describe('route-helper', function() {
] ]
}); });
var opDoc = doc.operations[0]; var opDoc = doc.operations[0];
expect(opDoc.type).to.equal('array'); // Note: swagger-ui treat arrays of X the same way as object X
expect(opDoc.items).to.eql({type: 'customType'}); expect(getResponseType(opDoc)).to.equal('customType');
});
it('correctly converts return types (format)', function() { // NOTE(bajtos) this would be the case if there was a single response type
var doc = createAPIDoc({ // expect(opDoc.type).to.equal('array');
returns: [ // expect(opDoc.items).to.eql({type: 'customType'});
{arg: 'data', type: 'buffer'}
]
});
var opDoc = doc.operations[0];
expect(opDoc.type).to.equal('string');
expect(opDoc.format).to.equal('byte');
}); });
it('includes `notes` metadata', function() { it('includes `notes` metadata', function() {
@ -151,12 +145,45 @@ describe('route-helper', function() {
.to.have.property('enum').eql([1,2,3]); .to.have.property('enum').eql([1,2,3]);
}); });
it('preserves `enum` returns arg metadata', function() { it('includes the default response message with code 200', function() {
var doc = createAPIDoc({ var doc = createAPIDoc({
returns: [{ name: 'arg', root: true, type: 'number', enum: [1,2,3] }] returns: [{ name: 'result', type: 'object', root: true }]
});
expect(doc.operations[0].responseMessages).to.eql([
{
code: 200,
message: 'Request was successful',
responseModel: 'object'
}
]);
});
it('uses the response code 204 when `returns` is empty', function() {
var doc = createAPIDoc({
returns: []
});
expect(doc.operations[0].responseMessages).to.eql([
{
code: 204,
message: 'Request was successful',
responseModel: 'void'
}
]);
});
it('includes custom error response in `responseMessages`', function() {
var doc = createAPIDoc({
errors: [{
code: 422,
message: 'Validation failed',
responseModel: 'ValidationError'
}]
});
expect(doc.operations[0].responseMessages[1]).to.eql({
code: 422,
message: 'Validation failed',
responseModel: 'ValidationError'
}); });
expect(doc.operations[0])
.to.have.property('enum').eql([1,2,3]);
}); });
}); });
@ -168,3 +195,7 @@ function createAPIDoc(def) {
method: 'test.get' method: 'test.get'
})); }));
} }
function getResponseType(operationDoc) {
return operationDoc.responseMessages[0].responseModel;
}

View File

@ -185,6 +185,24 @@ describe('swagger definition', function() {
done(); done();
}); });
}); });
it('includes `responseMessages` models', function(done) {
var app = createLoopbackAppWithModel();
loopback.createModel('ValidationError');
givenSharedMethod(app.models.Product, 'setImage', {
errors: [{
code: '422',
message: 'Validation failed',
responseModel: 'ValidationError'
}]
});
mountExplorer(app);
getAPIDeclaration(app, 'products').end(function(err, res) {
expect(Object.keys(res.body.models)).to.include('ValidationError');
done();
});
});
}); });
describe('Cross-origin resource sharing', function() { describe('Cross-origin resource sharing', function() {