Merge pull request #113 from strongloop/shelbys-master
Pick up enhancements from shelbys master
This commit is contained in:
commit
8f2dc9966e
|
@ -10,9 +10,12 @@ lib-cov
|
||||||
*.iml
|
*.iml
|
||||||
*.tgz
|
*.tgz
|
||||||
|
|
||||||
|
.idea
|
||||||
pids
|
pids
|
||||||
logs
|
logs
|
||||||
results
|
results
|
||||||
|
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
|
LoopBackExplorer.iml
|
||||||
|
|
|
@ -29,7 +29,7 @@ var classHelper = module.exports = {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
apiVersion: opts.version,
|
apiVersion: opts.version || '1',
|
||||||
swaggerVersion: opts.swaggerVersion,
|
swaggerVersion: opts.swaggerVersion,
|
||||||
basePath: opts.basePath,
|
basePath: opts.basePath,
|
||||||
resourcePath: urlJoin('/', resourcePath),
|
resourcePath: urlJoin('/', resourcePath),
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
var _cloneDeep = require('lodash').cloneDeep;
|
var _cloneDeep = require('lodash').cloneDeep;
|
||||||
var _pick = require('lodash').pick;
|
var _pick = require('lodash').pick;
|
||||||
var translateDataTypeKeys = require('./translate-data-type-keys');
|
var translateDataTypeKeys = require('./translate-data-type-keys');
|
||||||
|
var TYPES_PRIMITIVE = ['array', 'boolean', 'integer', 'number', 'null', 'object', 'string', 'any'];
|
||||||
var typeConverter = require('./type-converter');
|
var typeConverter = require('./type-converter');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,6 +21,25 @@ var modelHelper = module.exports = {
|
||||||
* @return {Object} Associated model definition.
|
* @return {Object} Associated model definition.
|
||||||
*/
|
*/
|
||||||
generateModelDefinition: function generateModelDefinition(modelClass, definitions) {
|
generateModelDefinition: function generateModelDefinition(modelClass, definitions) {
|
||||||
|
var processType = function(app, modelName, referencedModels) {
|
||||||
|
if (app && modelName) {
|
||||||
|
if (modelName.indexOf('[') == 0) {
|
||||||
|
modelName = modelName.replace(/[\[\]]/g, '');
|
||||||
|
}
|
||||||
|
var model = app.models[modelName];
|
||||||
|
if (model && referencedModels.indexOf(model) === -1) {
|
||||||
|
referencedModels.push(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var convertTypeTo$Ref = function convertTypeTo$Ref(prop){
|
||||||
|
if (prop.type && TYPES_PRIMITIVE.indexOf(prop.type) === -1 ){
|
||||||
|
prop.$ref = prop.type;
|
||||||
|
delete prop.type;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var def = modelClass.definition;
|
var def = modelClass.definition;
|
||||||
var out = definitions || {};
|
var out = definitions || {};
|
||||||
|
|
||||||
|
@ -36,7 +56,7 @@ var modelHelper = module.exports = {
|
||||||
}
|
}
|
||||||
var required = [];
|
var required = [];
|
||||||
// Don't modify original properties.
|
// Don't modify original properties.
|
||||||
var properties = _cloneDeep(def.properties);
|
var properties = _cloneDeep(def.rawProperties || def.properties);
|
||||||
|
|
||||||
var referencedModels = [];
|
var referencedModels = [];
|
||||||
// Add models from settings
|
// Add models from settings
|
||||||
|
@ -44,7 +64,7 @@ var modelHelper = module.exports = {
|
||||||
for (var m in def.settings.models) {
|
for (var m in def.settings.models) {
|
||||||
var model = modelClass[m];
|
var model = modelClass[m];
|
||||||
if (typeof model === 'function' && model.modelName) {
|
if (typeof model === 'function' && model.modelName) {
|
||||||
if (referencedModels.indexOf(model) === -1) {
|
if (model && referencedModels.indexOf(model) === -1) {
|
||||||
referencedModels.push(model);
|
referencedModels.push(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,6 +87,12 @@ var modelHelper = module.exports = {
|
||||||
|
|
||||||
// Eke a type out of the constructors we were passed.
|
// Eke a type out of the constructors we were passed.
|
||||||
var swaggerType = modelHelper.LDLPropToSwaggerDataType(prop);
|
var swaggerType = modelHelper.LDLPropToSwaggerDataType(prop);
|
||||||
|
processType(modelClass.app, swaggerType.type, referencedModels);
|
||||||
|
convertTypeTo$Ref(swaggerType);
|
||||||
|
if (swaggerType.items) {
|
||||||
|
processType(modelClass.app, swaggerType.items.type, referencedModels);
|
||||||
|
convertTypeTo$Ref(swaggerType.items);
|
||||||
|
}
|
||||||
|
|
||||||
var desc = typeConverter.convertText(prop.description || prop.doc);
|
var desc = typeConverter.convertText(prop.description || prop.doc);
|
||||||
if (desc) swaggerType.description = desc;
|
if (desc) swaggerType.description = desc;
|
||||||
|
@ -76,6 +102,16 @@ var modelHelper = module.exports = {
|
||||||
required.push(key);
|
required.push(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Change mismatched keys.
|
||||||
|
prop = translateDataTypeKeys(prop);
|
||||||
|
|
||||||
|
delete prop.required;
|
||||||
|
delete prop.id;
|
||||||
|
|
||||||
|
if (prop.description){
|
||||||
|
prop.description = typeConverter.convertText(prop.description);
|
||||||
|
}
|
||||||
|
|
||||||
// Assign this back to the properties object.
|
// Assign this back to the properties object.
|
||||||
properties[key] = swaggerType;
|
properties[key] = swaggerType;
|
||||||
|
|
||||||
|
@ -95,27 +131,70 @@ var modelHelper = module.exports = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var additionalProperties = undefined;
|
||||||
|
if (def.settings){
|
||||||
|
var strict = def.settings.strict;
|
||||||
|
additionalProperties = def.settings.additionalProperties;
|
||||||
|
var notAllowAdditionalProperties = strict || (additionalProperties !== true);
|
||||||
|
if (notAllowAdditionalProperties){
|
||||||
|
additionalProperties = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
out[name] = {
|
out[name] = {
|
||||||
id: name,
|
id: name,
|
||||||
|
additionalProperties: additionalProperties,
|
||||||
description: typeConverter.convertText(
|
description: typeConverter.convertText(
|
||||||
def.description || (def.settings && def.settings.description)),
|
def.description || (def.settings && def.settings.description)),
|
||||||
properties: properties,
|
properties: properties,
|
||||||
required: required
|
required: required
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (def.description){
|
||||||
|
out[name].description = typeConverter.convertText(def.description);
|
||||||
|
}
|
||||||
|
|
||||||
// Generate model definitions for related models
|
// Generate model definitions for related models
|
||||||
for (var r in modelClass.relations) {
|
for (var r in modelClass.relations) {
|
||||||
var rel = modelClass.relations[r];
|
var rel = modelClass.relations[r];
|
||||||
if (rel.modelTo){
|
if (rel.modelTo && referencedModels.indexOf(rel.modelTo) === -1) {
|
||||||
generateModelDefinition(rel.modelTo, out);
|
referencedModels.push(rel.modelTo);
|
||||||
}
|
}
|
||||||
if (rel.modelThrough) {
|
if (rel.modelThrough && referencedModels.indexOf(rel.modelThrough) === -1) {
|
||||||
generateModelDefinition(rel.modelThrough, out);
|
referencedModels.push(rel.modelThrough);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (modelClass.sharedClass) {
|
||||||
|
var remotes = modelClass.sharedClass.methods();
|
||||||
|
for (var remoteIdx in remotes) {
|
||||||
|
var remote = remotes[remoteIdx];
|
||||||
|
var accepts = remote.accepts;
|
||||||
|
if (accepts) {
|
||||||
|
for (var acceptIdx in accepts) {
|
||||||
|
processType(modelClass.app, accepts[acceptIdx].type, referencedModels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var returns = remote.returns;
|
||||||
|
if (returns) {
|
||||||
|
for (var returnIdx in returns) {
|
||||||
|
processType(modelClass.app, returns[returnIdx].type, referencedModels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var errors = remote.errors;
|
||||||
|
if (errors) {
|
||||||
|
for (var errorIdx in errors) {
|
||||||
|
processType(modelClass.app, errors[errorIdx].responseModel, referencedModels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0, n = referencedModels.length; i < n; i++) {
|
for (var i = 0, n = referencedModels.length; i < n; i++) {
|
||||||
|
if (referencedModels[i].definition) {
|
||||||
generateModelDefinition(referencedModels[i], out);
|
generateModelDefinition(referencedModels[i], out);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -153,8 +232,13 @@ var modelHelper = module.exports = {
|
||||||
'format',
|
'format',
|
||||||
'defaultValue',
|
'defaultValue',
|
||||||
'enum',
|
'enum',
|
||||||
|
'items',
|
||||||
'minimum',
|
'minimum',
|
||||||
|
'minItems',
|
||||||
|
'minLength',
|
||||||
'maximum',
|
'maximum',
|
||||||
|
'maxItems',
|
||||||
|
'maxLength',
|
||||||
'uniqueItems',
|
'uniqueItems',
|
||||||
// loopback-explorer extensions
|
// loopback-explorer extensions
|
||||||
'length',
|
'length',
|
||||||
|
@ -168,21 +252,28 @@ var modelHelper = module.exports = {
|
||||||
// Pick only keys supported by Swagger
|
// Pick only keys supported by Swagger
|
||||||
var swaggerType = _pick(ldlType, SWAGGER_DATA_TYPE_FIELDS);
|
var swaggerType = _pick(ldlType, SWAGGER_DATA_TYPE_FIELDS);
|
||||||
|
|
||||||
swaggerType.type = modelHelper.getPropType(ldlType.type);
|
swaggerType.type = modelHelper.getPropType(ldlType.type || ldlType);
|
||||||
|
|
||||||
if (swaggerType.type === 'array') {
|
if (swaggerType.type === 'array') {
|
||||||
var hasItemType = Array.isArray(ldlType.type) && ldlType.type.length;
|
var hasItemType = Array.isArray(ldlType.type) && ldlType.type.length;
|
||||||
var arrayItem = hasItemType && ldlType.type[0];
|
var arrayItem = hasItemType && ldlType.type[0];
|
||||||
|
|
||||||
|
var newItems = null;
|
||||||
if (arrayItem) {
|
if (arrayItem) {
|
||||||
if(typeof arrayItem === 'object') {
|
if(typeof arrayItem === 'object') {
|
||||||
swaggerType.items = modelHelper.LDLPropToSwaggerDataType(arrayItem);
|
newItems = modelHelper.LDLPropToSwaggerDataType(arrayItem);
|
||||||
} else {
|
} else {
|
||||||
swaggerType.items = { type: modelHelper.getPropType(arrayItem) };
|
newItems = { type: modelHelper.getPropType(arrayItem) };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// NOTE: `any` is not a supported type in swagger 1.2
|
// NOTE: `any` is not a supported type in swagger 1.2
|
||||||
swaggerType.items = { type: 'any' };
|
newItems = { type: 'any' };
|
||||||
|
}
|
||||||
|
if (typeof swaggerType.items !== 'object') {
|
||||||
|
swaggerType.items = {};
|
||||||
|
}
|
||||||
|
for (var key in newItems) {
|
||||||
|
swaggerType.items[key] = newItems[key];
|
||||||
}
|
}
|
||||||
} else if (swaggerType.type === 'date') {
|
} else if (swaggerType.type === 'date') {
|
||||||
swaggerType.type = 'string';
|
swaggerType.type = 'string';
|
||||||
|
@ -196,6 +287,3 @@ var modelHelper = module.exports = {
|
||||||
return swaggerType;
|
return swaggerType;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -116,24 +116,76 @@ var routeHelper = module.exports = {
|
||||||
* See https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#523-operation-object
|
* See https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#523-operation-object
|
||||||
*/
|
*/
|
||||||
routeToAPIDoc: function routeToAPIDoc(route, classDef) {
|
routeToAPIDoc: function routeToAPIDoc(route, classDef) {
|
||||||
|
/**
|
||||||
|
* Converts from an sl-remoting data type to a Swagger dataType.
|
||||||
|
*/
|
||||||
|
function prepareDataType(type) {
|
||||||
|
if (!type) {
|
||||||
|
return 'void';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Array.isArray(type)) {
|
||||||
|
if (type.length > 0) {
|
||||||
|
if (typeof type[0] === 'string') {
|
||||||
|
return '[' + type[0] + ']';
|
||||||
|
} else if (typeof type[0] === 'function') {
|
||||||
|
return '[' + type[0].name + ']';
|
||||||
|
} else if (typeof type[0] === 'object') {
|
||||||
|
if (typeof type[0].type === 'function') {
|
||||||
|
return '[' + type[0].type.name + ']';
|
||||||
|
} else {
|
||||||
|
return '[' + type[0].type + ']';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return '[' + type + ']';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'array';
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(schoon) - Add support for complex dataTypes, "models", etc.
|
||||||
|
switch (type) {
|
||||||
|
case 'Array':
|
||||||
|
return 'array';
|
||||||
|
case 'Boolean':
|
||||||
|
return 'boolean';
|
||||||
|
case 'buffer':
|
||||||
|
return 'string';
|
||||||
|
case 'Date':
|
||||||
|
return 'date';
|
||||||
|
case 'number':
|
||||||
|
case 'Number':
|
||||||
|
return 'double';
|
||||||
|
case 'Object':
|
||||||
|
return 'object';
|
||||||
|
case 'String':
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
var returnDesc;
|
||||||
|
|
||||||
// Some parameters need to be altered; eventually most of this should
|
// Some parameters need to be altered; eventually most of this should
|
||||||
// be removed.
|
// be removed.
|
||||||
var accepts = routeHelper.convertAcceptsToSwagger(route, classDef);
|
var accepts = routeHelper.convertAcceptsToSwagger(route, classDef);
|
||||||
var returns = routeHelper.convertReturnsToSwagger(route, classDef);
|
var returns = routeHelper.convertReturnsToSwagger(route, classDef);
|
||||||
|
var responseMessages = [
|
||||||
|
{
|
||||||
|
code: route.returns && route.returns.length ? 200 : 204,
|
||||||
|
message: 'Request was successful',
|
||||||
|
responseModel: returns.model || prepareDataType(returns.type) || 'void'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
if (route.errors) {
|
||||||
|
responseMessages.push.apply(responseMessages, route.errors);
|
||||||
|
}
|
||||||
|
|
||||||
debug('route %j', route);
|
debug('route %j', route);
|
||||||
|
|
||||||
var responseDoc = modelHelper.LDLPropToSwaggerDataType(returns);
|
var responseDoc = modelHelper.LDLPropToSwaggerDataType(returns);
|
||||||
|
|
||||||
var responseMessages = [{
|
|
||||||
code: route.returns && route.returns.length ? 200 : 204,
|
|
||||||
message: 'Request was successful'
|
|
||||||
}];
|
|
||||||
|
|
||||||
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.
|
// Create the operation doc.
|
||||||
|
@ -146,11 +198,14 @@ var routeHelper = module.exports = {
|
||||||
// path as class name so it remains unique between models.
|
// path as class name so it remains unique between models.
|
||||||
// route.method is always #{className}.#{methodName}
|
// route.method is always #{className}.#{methodName}
|
||||||
nickname: route.method.replace(/.*?\./, ''),
|
nickname: route.method.replace(/.*?\./, ''),
|
||||||
|
deprecated: route.deprecated,
|
||||||
|
consumes: ['application/json', 'application/xml', 'text/xml'],
|
||||||
|
produces: ['application/json', 'application/javascript', 'application/xml', 'text/javascript', 'text/xml'],
|
||||||
parameters: accepts,
|
parameters: accepts,
|
||||||
responseMessages: responseMessages,
|
responseMessages: responseMessages,
|
||||||
|
type: returns.model || returns.type || 'void',
|
||||||
summary: typeConverter.convertText(route.description),
|
summary: typeConverter.convertText(route.description),
|
||||||
notes: typeConverter.convertText(route.notes),
|
notes: typeConverter.convertText(route.notes)
|
||||||
deprecated: route.deprecated
|
|
||||||
}, returns)]
|
}, returns)]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -204,11 +259,21 @@ var routeHelper = module.exports = {
|
||||||
}
|
}
|
||||||
|
|
||||||
var out = {
|
var out = {
|
||||||
paramType: paramType || type,
|
|
||||||
name: name,
|
name: name,
|
||||||
description: typeConverter.convertText(accepts.description),
|
|
||||||
required: !!accepts.required,
|
required: !!accepts.required,
|
||||||
allowMultiple: false
|
paramType: paramType || type,
|
||||||
|
type: accepts.type,
|
||||||
|
$ref: accepts.model,
|
||||||
|
items: accepts.items,
|
||||||
|
uniqueItems: accepts.uniqueItems,
|
||||||
|
format: accepts.format,
|
||||||
|
pattern: accepts.pattern,
|
||||||
|
defaultValue: accepts.defaultValue,
|
||||||
|
enum: accepts.enum,
|
||||||
|
minimum: accepts.minimum,
|
||||||
|
maximum: accepts.maximum,
|
||||||
|
allowMultiple: accepts.allowMultiple,
|
||||||
|
description: typeConverter.convertText(accepts.description)
|
||||||
};
|
};
|
||||||
|
|
||||||
out = routeHelper.extendWithType(out, accepts);
|
out = routeHelper.extendWithType(out, accepts);
|
||||||
|
@ -238,6 +303,25 @@ var routeHelper = module.exports = {
|
||||||
|
|
||||||
// The `typeDesc` may have additional attributes, such as
|
// The `typeDesc` may have additional attributes, such as
|
||||||
// `format` for non-primitive types.
|
// `format` for non-primitive types.
|
||||||
|
Object.keys(typeDesc).forEach(function(key){
|
||||||
|
obj[key] = typeDesc[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
//Ensure brief properties are first
|
||||||
|
if (typeof obj === 'object') {
|
||||||
|
var keysToSink = ['authorizations', 'consumes', 'notes', 'produces',
|
||||||
|
'parameters', 'responseMessages', 'summary'];
|
||||||
|
var outKeys = Object.keys(obj);
|
||||||
|
for (var outKeyIdx in outKeys) {
|
||||||
|
var outKey = outKeys[outKeyIdx];
|
||||||
|
if (keysToSink.indexOf(outKey) != -1) {
|
||||||
|
var outValue = obj[outKey];
|
||||||
|
delete obj[outKey];
|
||||||
|
obj[outKey] = outValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_assign(obj, typeDesc);
|
_assign(obj, typeDesc);
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
|
|
|
@ -12,8 +12,10 @@ var urlJoin = require('./url-join');
|
||||||
var _defaults = require('lodash').defaults;
|
var _defaults = require('lodash').defaults;
|
||||||
var classHelper = require('./class-helper');
|
var classHelper = require('./class-helper');
|
||||||
var routeHelper = require('./route-helper');
|
var routeHelper = require('./route-helper');
|
||||||
|
var _cloneDeep = require('lodash').cloneDeep;
|
||||||
var modelHelper = require('./model-helper');
|
var modelHelper = require('./model-helper');
|
||||||
var cors = require('cors');
|
var cors = require('cors');
|
||||||
|
var typeConverter = require('./type-converter');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a remotable Swagger module for plugging into `RemoteObjects`.
|
* Create a remotable Swagger module for plugging into `RemoteObjects`.
|
||||||
|
@ -61,7 +63,17 @@ function Swagger(loopbackApplication, swaggerApp, opts) {
|
||||||
// A class is an endpoint root; e.g. /users, /products, and so on.
|
// A class is an endpoint root; e.g. /users, /products, and so on.
|
||||||
classes.forEach(function (aClass) {
|
classes.forEach(function (aClass) {
|
||||||
var doc = apiDocs[aClass.name] = classHelper.generateAPIDoc(aClass, opts);
|
var doc = apiDocs[aClass.name] = classHelper.generateAPIDoc(aClass, opts);
|
||||||
|
var hasDocumented = false;
|
||||||
|
var methods = aClass.methods()
|
||||||
|
for (var methodKey in methods) {
|
||||||
|
hasDocumented = methods[methodKey].documented;
|
||||||
|
if (hasDocumented) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasDocumented) {
|
||||||
resourceDoc.apis.push(classHelper.generateResourceDocAPIEntry(aClass));
|
resourceDoc.apis.push(classHelper.generateResourceDocAPIEntry(aClass));
|
||||||
|
}
|
||||||
|
|
||||||
// Add the getter for this doc.
|
// Add the getter for this doc.
|
||||||
var docPath = urlJoin(opts.resourcePath, aClass.http.path);
|
var docPath = urlJoin(opts.resourcePath, aClass.http.path);
|
||||||
|
@ -82,7 +94,9 @@ function Swagger(loopbackApplication, swaggerApp, opts) {
|
||||||
return item.name === className;
|
return item.name === className;
|
||||||
})[0];
|
})[0];
|
||||||
|
|
||||||
|
if (route.documented) {
|
||||||
routeHelper.addRouteToAPIDeclaration(route, classDef, doc);
|
routeHelper.addRouteToAPIDeclaration(route, classDef, doc);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add models referenced from routes (e.g. accepts/returns)
|
// Add models referenced from routes (e.g. accepts/returns)
|
||||||
|
@ -136,6 +150,8 @@ function Swagger(loopbackApplication, swaggerApp, opts) {
|
||||||
* information about them.
|
* information about them.
|
||||||
*/
|
*/
|
||||||
addRoute(swaggerApp, opts.resourcePath, resourceDoc, opts);
|
addRoute(swaggerApp, opts.resourcePath, resourceDoc, opts);
|
||||||
|
|
||||||
|
loopbackApplication.emit('swaggerResources', resourceDoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupCors(swaggerApp, remotes) {
|
function setupCors(swaggerApp, remotes) {
|
||||||
|
@ -191,16 +207,23 @@ function addRoute(app, uri, doc, opts) {
|
||||||
* @return {Object} Resource doc.
|
* @return {Object} Resource doc.
|
||||||
*/
|
*/
|
||||||
function generateResourceDoc(opts) {
|
function generateResourceDoc(opts) {
|
||||||
|
var apiInfo = _cloneDeep(opts.apiInfo);
|
||||||
|
for (var propertyName in apiInfo) {
|
||||||
|
var property = apiInfo[propertyName];
|
||||||
|
apiInfo[propertyName] = typeConverter.convertText(property);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
swaggerVersion: opts.swaggerVersion,
|
swaggerVersion: opts.swaggerVersion,
|
||||||
apiVersion: opts.version,
|
apiVersion: opts.version,
|
||||||
apis: [],
|
|
||||||
// See https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#513-info-object
|
// See https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#513-info-object
|
||||||
info: opts.apiInfo
|
info: apiInfo,
|
||||||
// TODO Authorizations
|
// TODO Authorizations
|
||||||
// https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#514-authorizations-object
|
// https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#514-authorizations-object
|
||||||
// TODO Produces/Consumes
|
consumes: ['application/json', 'application/xml', 'text/xml'],
|
||||||
// https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#52-api-declaration
|
produces: ['application/json', 'application/javascript', 'application/xml', 'text/javascript', 'text/xml'],
|
||||||
|
apis: [],
|
||||||
|
models: opts.models
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
package.json
20
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "loopback-explorer",
|
"name": "loopback-explorer",
|
||||||
"version": "1.8.0",
|
"version": "1.8.2",
|
||||||
"description": "Browse and test your LoopBack app's APIs",
|
"description": "Browse and test your LoopBack app's APIs",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -21,20 +21,20 @@
|
||||||
"url": "https://github.com/strongloop/loopback-explorer/issues"
|
"url": "https://github.com/strongloop/loopback-explorer/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"loopback": "^2.4.1",
|
"loopback": "^2.19.1",
|
||||||
"mocha": "^1.21.5",
|
"mocha": "^2.2.5",
|
||||||
"supertest": "~0.14.0",
|
"supertest": "^1.0.1",
|
||||||
"chai": "^1.9.1"
|
"chai": "^3.2.0"
|
||||||
},
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"name": "Dual MIT/StrongLoop",
|
"name": "Dual MIT/StrongLoop",
|
||||||
"url": "https://github.com/strongloop/loopback-explorer/blob/master/LICENSE"
|
"url": "https://github.com/strongloop/loopback-explorer/blob/master/LICENSE"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cors": "^2.4.2",
|
"cors": "^2.7.1",
|
||||||
"debug": "~1.0.3",
|
"debug": "^2.2.0",
|
||||||
"express": "3.x",
|
"express": "4.x",
|
||||||
"lodash": "^2.4.1",
|
"lodash": "^3.10.0",
|
||||||
"strong-swagger-ui": "^20.0.0"
|
"strong-swagger-ui": "^20.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -350,6 +350,14 @@ li.operation.delete .content > .content-type > div > label {
|
||||||
color: #080;
|
color: #080;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contentWell {
|
||||||
|
padding-left: 30px;
|
||||||
|
padding-right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FIXME: Separate the overrides from the rest of the styles, rather than override screen.css entirely.
|
||||||
|
*/
|
||||||
/* Improve spacing when the browser window is small */
|
/* Improve spacing when the browser window is small */
|
||||||
#message-bar, #swagger-ui-container {
|
#message-bar, #swagger-ui-container {
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
|
|
|
@ -39,7 +39,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="contentWell">
|
||||||
<div id="message-bar" class="swagger-ui-wrap"> </div>
|
<div id="message-bar" class="swagger-ui-wrap"> </div>
|
||||||
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
|
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -24,6 +24,45 @@ describe('class-helper', function() {
|
||||||
|
|
||||||
expect(doc.resourcePath).to.equal('/test');
|
expect(doc.resourcePath).to.equal('/test');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#generateResourceDocAPIEntry', function() {
|
||||||
|
describe('when ctor.settings.description is an array of string', function() {
|
||||||
|
it('should return description as a string', function() {
|
||||||
|
var aClass = {
|
||||||
|
ctor: {
|
||||||
|
settings: {
|
||||||
|
description: ['1','2','3']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
http:{
|
||||||
|
path: 'path'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = classHelper.generateResourceDocAPIEntry(aClass);
|
||||||
|
expect(result.description).to.eql("1\n2\n3");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when ctor.sharedCtor.description is an array of string', function() {
|
||||||
|
it('should return description as a string', function() {
|
||||||
|
var aClass = {
|
||||||
|
ctor: {
|
||||||
|
settings: {},
|
||||||
|
sharedCtor: {
|
||||||
|
description: ['1','2','3']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
http:{
|
||||||
|
path: 'path'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = classHelper.generateResourceDocAPIEntry(aClass);
|
||||||
|
expect(result.description).to.eql("1\n2\n3");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Easy wrapper around createRoute
|
// Easy wrapper around createRoute
|
||||||
|
|
|
@ -112,14 +112,14 @@ describe('model-helper', function() {
|
||||||
expect(prop).to.eql({ type: 'array', items: { type: 'any' } });
|
expect(prop).to.eql({ type: 'array', items: { type: 'any' } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('converts Model type', function() {
|
it('converts Model type to $ref', function() {
|
||||||
var Address = loopback.createModel('Address', {street: String});
|
var Address = loopback.createModel('Address', {street: String});
|
||||||
var def = buildSwaggerModels({
|
var def = buildSwaggerModels({
|
||||||
str: String,
|
str: String,
|
||||||
address: Address
|
address: Address
|
||||||
});
|
});
|
||||||
var prop = def.properties.address;
|
var prop = def.properties.address;
|
||||||
expect(prop).to.eql({ type: 'Address' });
|
expect(prop).to.eql({ $ref: 'Address' });
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -160,7 +160,7 @@ describe('model-helper', function() {
|
||||||
var Model1 = loopback.createModel('Model1', {
|
var Model1 = loopback.createModel('Model1', {
|
||||||
str: String, // 'string'
|
str: String, // 'string'
|
||||||
address: Model2
|
address: Model2
|
||||||
});
|
}, { models: { Model2: Model2 } });
|
||||||
var defs = modelHelper.generateModelDefinition(Model1, {});
|
var defs = modelHelper.generateModelDefinition(Model1, {});
|
||||||
expect(defs).has.property('Model1');
|
expect(defs).has.property('Model1');
|
||||||
expect(defs).has.property('Model2');
|
expect(defs).has.property('Model2');
|
||||||
|
@ -169,7 +169,7 @@ describe('model-helper', function() {
|
||||||
it('should include used models', function() {
|
it('should include used models', function() {
|
||||||
var Model4 = loopback.createModel('Model4', {street: String});
|
var Model4 = loopback.createModel('Model4', {street: String});
|
||||||
var Model3 = loopback.createModel('Model3', {
|
var Model3 = loopback.createModel('Model3', {
|
||||||
str: String, // 'string'
|
str: String // 'string'
|
||||||
}, {models: {model4: 'Model4'}});
|
}, {models: {model4: 'Model4'}});
|
||||||
var defs = modelHelper.generateModelDefinition(Model3, {});
|
var defs = modelHelper.generateModelDefinition(Model3, {});
|
||||||
expect(defs).has.property('Model3');
|
expect(defs).has.property('Model3');
|
||||||
|
@ -181,7 +181,7 @@ describe('model-helper', function() {
|
||||||
var Model5 = loopback.createModel('Model5', {
|
var Model5 = loopback.createModel('Model5', {
|
||||||
str: String, // 'string'
|
str: String, // 'string'
|
||||||
addresses: [Model6]
|
addresses: [Model6]
|
||||||
});
|
}, { models: { Model6: Model6 } });
|
||||||
var defs = modelHelper.generateModelDefinition(Model5, {});
|
var defs = modelHelper.generateModelDefinition(Model5, {});
|
||||||
expect(defs).has.property('Model5');
|
expect(defs).has.property('Model5');
|
||||||
expect(defs).has.property('Model6');
|
expect(defs).has.property('Model6');
|
||||||
|
@ -227,6 +227,36 @@ describe('model-helper', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#generateModelDefinition', function() {
|
||||||
|
it('should convert top level array description to string', function () {
|
||||||
|
var model = {};
|
||||||
|
model.definition = {
|
||||||
|
name: 'test',
|
||||||
|
description: ['1', '2', '3'],
|
||||||
|
properties: {}
|
||||||
|
};
|
||||||
|
var models = {};
|
||||||
|
modelHelper.generateModelDefinition(model, models);
|
||||||
|
expect(models.test.description).to.equal("1\n2\n3");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert property level array description to string', function () {
|
||||||
|
var model = {};
|
||||||
|
model.definition = {
|
||||||
|
name: 'test',
|
||||||
|
properties: {
|
||||||
|
prop1: {
|
||||||
|
type: 'string',
|
||||||
|
description: ['1', '2', '3']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var models = {};
|
||||||
|
modelHelper.generateModelDefinition(model, models);
|
||||||
|
expect(models.test.properties.prop1.description).to.equal("1\n2\n3");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getPropType', function() {
|
describe('getPropType', function() {
|
||||||
it('converts anonymous object types', function() {
|
it('converts anonymous object types', function() {
|
||||||
var type = modelHelper.getPropType({ name: 'string', value: 'string' });
|
var type = modelHelper.getPropType({ name: 'string', value: 'string' });
|
||||||
|
|
|
@ -14,7 +14,7 @@ describe('route-helper', function() {
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
expect(doc.operations[0].type).to.equal('object');
|
expect(doc.operations[0].type).to.equal('object');
|
||||||
expect(getResponseType(doc.operations[0])).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() {
|
||||||
|
@ -61,7 +61,7 @@ describe('route-helper', function() {
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
var opDoc = doc.operations[0];
|
var opDoc = doc.operations[0];
|
||||||
expect(getResponseType(opDoc)).to.equal(undefined);
|
expect(getResponseType(opDoc)).to.eql('[customType]');
|
||||||
|
|
||||||
// NOTE(bajtos) this would be the case if there was a single response type
|
// NOTE(bajtos) this would be the case if there was a single response type
|
||||||
expect(opDoc.type).to.equal('array');
|
expect(opDoc.type).to.equal('array');
|
||||||
|
@ -86,6 +86,36 @@ describe('route-helper', function() {
|
||||||
expect(doc.operations[0].notes).to.equal('some notes');
|
expect(doc.operations[0].notes).to.equal('some notes');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#acceptToParameter', function(){
|
||||||
|
it('should return function that converts accepts.description from array of string to string', function(){
|
||||||
|
var f = routeHelper.acceptToParameter({verb: 'get', path: 'path'});
|
||||||
|
var result = f({description: ['1','2','3']});
|
||||||
|
expect(result.description).to.eql("1\n2\n3");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#routeToAPIDoc', function() {
|
||||||
|
it('should convert route.description from array of string to string', function () {
|
||||||
|
var result = routeHelper.routeToAPIDoc({
|
||||||
|
method: 'someMethod',
|
||||||
|
verb: 'get',
|
||||||
|
path: 'path',
|
||||||
|
description: ['1', '2', '3']
|
||||||
|
});
|
||||||
|
expect(result.operations[0].summary).to.eql("1\n2\n3");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert route.notes from array of string to string', function () {
|
||||||
|
var result = routeHelper.routeToAPIDoc({
|
||||||
|
method: 'someMethod',
|
||||||
|
verb: 'get',
|
||||||
|
path: 'path',
|
||||||
|
notes: ['1', '2', '3']
|
||||||
|
});
|
||||||
|
expect(result.operations[0].notes).to.eql("1\n2\n3");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('includes `deprecated` metadata', function() {
|
it('includes `deprecated` metadata', function() {
|
||||||
var doc = createAPIDoc({
|
var doc = createAPIDoc({
|
||||||
deprecated: 'true'
|
deprecated: 'true'
|
||||||
|
@ -163,7 +193,8 @@ describe('route-helper', function() {
|
||||||
expect(doc.operations[0].responseMessages).to.eql([
|
expect(doc.operations[0].responseMessages).to.eql([
|
||||||
{
|
{
|
||||||
code: 200,
|
code: 200,
|
||||||
message: 'Request was successful'
|
message: 'Request was successful',
|
||||||
|
responseModel: 'object'
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
@ -176,7 +207,8 @@ describe('route-helper', function() {
|
||||||
expect(doc.operations[0].responseMessages).to.eql([
|
expect(doc.operations[0].responseMessages).to.eql([
|
||||||
{
|
{
|
||||||
code: 204,
|
code: 204,
|
||||||
message: 'Request was successful'
|
message: 'Request was successful',
|
||||||
|
responseModel: 'void'
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue