2014-07-05 19:32:00 +00:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Module dependencies.
|
|
|
|
*/
|
2014-07-06 14:50:24 +00:00
|
|
|
var _cloneDeep = require('lodash.clonedeep');
|
2014-10-15 11:58:13 +00:00
|
|
|
var _pick = require('lodash.pick');
|
2014-07-10 18:17:26 +00:00
|
|
|
var translateDataTypeKeys = require('./translate-data-type-keys');
|
2014-10-15 11:58:13 +00:00
|
|
|
var typeConverter = require('./type-converter');
|
2014-07-05 19:32:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Export the modelHelper singleton.
|
|
|
|
*/
|
|
|
|
var modelHelper = module.exports = {
|
|
|
|
/**
|
|
|
|
* Given a class (from remotes.classes()), generate a model definition.
|
|
|
|
* This is used to generate the schema at the top of many endpoints.
|
2014-07-24 20:35:02 +00:00
|
|
|
* @param {Class} modelClass Model class.
|
|
|
|
* @param {Object} definitions Model definitions
|
|
|
|
* @return {Object} Associated model definition.
|
2014-07-05 19:32:00 +00:00
|
|
|
*/
|
2014-07-24 20:35:02 +00:00
|
|
|
generateModelDefinition: function generateModelDefinition(modelClass, definitions) {
|
|
|
|
var def = modelClass.definition;
|
2014-07-05 19:32:00 +00:00
|
|
|
var name = def.name;
|
2014-07-24 20:35:02 +00:00
|
|
|
var out = definitions || {};
|
|
|
|
if (out[name]) {
|
|
|
|
// The model is already included
|
|
|
|
return out;
|
|
|
|
}
|
2014-07-05 19:32:00 +00:00
|
|
|
var required = [];
|
2014-07-06 14:50:24 +00:00
|
|
|
// Don't modify original properties.
|
|
|
|
var properties = _cloneDeep(def.properties);
|
2014-07-05 19:32:00 +00:00
|
|
|
|
2014-08-28 22:20:03 +00:00
|
|
|
var referencedModels = [];
|
|
|
|
// Add models from settings
|
|
|
|
if (def.settings && def.settings.models) {
|
|
|
|
for (var m in def.settings.models) {
|
|
|
|
var model = modelClass[m];
|
|
|
|
if (typeof model === 'function' && model.modelName) {
|
|
|
|
if (referencedModels.indexOf(model) === -1) {
|
|
|
|
referencedModels.push(model);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-05 19:32:00 +00:00
|
|
|
// Iterate through each property in the model definition.
|
2014-07-10 16:32:58 +00:00
|
|
|
// Types may be defined as constructors (e.g. String, Date, etc.),
|
|
|
|
// or as strings; getPropType() will take care of the conversion.
|
|
|
|
// See more on types:
|
|
|
|
// https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#431-primitives
|
|
|
|
Object.keys(properties).forEach(function(key) {
|
2014-07-06 14:50:24 +00:00
|
|
|
var prop = properties[key];
|
2014-07-05 19:32:00 +00:00
|
|
|
|
2014-07-26 17:12:05 +00:00
|
|
|
// Hide hidden properties.
|
|
|
|
if (modelHelper.isHiddenProperty(def, key)) {
|
|
|
|
delete properties[key];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-07-05 19:32:00 +00:00
|
|
|
// Eke a type out of the constructors we were passed.
|
2014-10-15 11:58:13 +00:00
|
|
|
var swaggerType = modelHelper.LDLPropToSwaggerDataType(prop);
|
|
|
|
|
|
|
|
var desc = typeConverter.convertText(prop.description || prop.doc);
|
|
|
|
if (desc) swaggerType.description = desc;
|
2014-07-05 19:32:00 +00:00
|
|
|
|
|
|
|
// Required props sit in a per-model array.
|
2014-07-10 16:32:58 +00:00
|
|
|
if (prop.required || (prop.id && !prop.generated)) {
|
2014-07-05 19:32:00 +00:00
|
|
|
required.push(key);
|
|
|
|
}
|
|
|
|
|
2014-07-10 16:32:58 +00:00
|
|
|
// Assign this back to the properties object.
|
2014-10-15 11:58:13 +00:00
|
|
|
properties[key] = swaggerType;
|
2014-08-28 22:20:03 +00:00
|
|
|
|
2014-10-15 11:58:13 +00:00
|
|
|
var propType = prop.type;
|
2014-08-28 22:20:03 +00:00
|
|
|
if (typeof propType === 'function' && propType.modelName) {
|
2014-08-29 08:06:05 +00:00
|
|
|
if (referencedModels.indexOf(propType) === -1) {
|
2014-08-28 22:20:03 +00:00
|
|
|
referencedModels.push(propType);
|
|
|
|
}
|
|
|
|
}
|
2014-08-29 08:06:05 +00:00
|
|
|
if (Array.isArray(propType) && propType.length) {
|
|
|
|
var itemType = propType[0];
|
|
|
|
if (typeof itemType === 'function' && itemType.modelName) {
|
|
|
|
if (referencedModels.indexOf(itemType) === -1) {
|
|
|
|
referencedModels.push(itemType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-07-05 19:32:00 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
out[name] = {
|
|
|
|
id: name,
|
2014-10-15 12:07:23 +00:00
|
|
|
description: typeConverter.convertText(def.description),
|
2014-07-06 14:50:24 +00:00
|
|
|
properties: properties,
|
2014-07-05 19:32:00 +00:00
|
|
|
required: required
|
|
|
|
};
|
2014-07-24 20:35:02 +00:00
|
|
|
|
|
|
|
// Generate model definitions for related models
|
|
|
|
for (var r in modelClass.relations) {
|
|
|
|
var rel = modelClass.relations[r];
|
2014-08-10 06:46:24 +00:00
|
|
|
if (rel.modelTo){
|
|
|
|
generateModelDefinition(rel.modelTo, out);
|
|
|
|
}
|
2014-07-24 20:35:02 +00:00
|
|
|
if (rel.modelThrough) {
|
|
|
|
generateModelDefinition(rel.modelThrough, out);
|
|
|
|
}
|
|
|
|
}
|
2014-09-19 16:35:11 +00:00
|
|
|
for (var i = 0, n = referencedModels.length; i < n; i++) {
|
|
|
|
generateModelDefinition(referencedModels[i], out);
|
2014-08-28 22:20:03 +00:00
|
|
|
}
|
2014-07-05 19:32:00 +00:00
|
|
|
return out;
|
2014-07-10 16:32:58 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a propType (which may be a function, string, or array),
|
|
|
|
* get a string type.
|
|
|
|
* @param {*} propType Prop type description.
|
|
|
|
* @return {String} Prop type string.
|
|
|
|
*/
|
|
|
|
getPropType: function getPropType(propType) {
|
|
|
|
if (typeof propType === 'function') {
|
2014-08-05 06:22:41 +00:00
|
|
|
// See https://github.com/strongloop/loopback-explorer/issues/32
|
|
|
|
// The type can be a model class
|
2014-10-16 11:49:58 +00:00
|
|
|
return propType.modelName || propType.name.toLowerCase();
|
|
|
|
} else if (Array.isArray(propType)) {
|
|
|
|
return 'array';
|
|
|
|
} else if (typeof propType === 'object') {
|
|
|
|
// Anonymous objects, they are allowed e.g. in accepts/returns definitions
|
|
|
|
return 'object';
|
2014-07-10 16:32:58 +00:00
|
|
|
}
|
|
|
|
return propType;
|
|
|
|
},
|
|
|
|
|
2014-07-26 17:12:05 +00:00
|
|
|
isHiddenProperty: function(definition, propName) {
|
|
|
|
return definition.settings &&
|
|
|
|
Array.isArray(definition.settings.hidden) &&
|
|
|
|
definition.settings.hidden.indexOf(propName) !== -1;
|
|
|
|
},
|
|
|
|
|
2014-07-10 16:32:58 +00:00
|
|
|
// Converts a prop defined with the LDL spec to one conforming to the
|
|
|
|
// Swagger spec.
|
|
|
|
// https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md#431-primitives
|
2014-10-15 11:58:13 +00:00
|
|
|
LDLPropToSwaggerDataType: function LDLPropToSwaggerDataType(ldlType) {
|
|
|
|
var SWAGGER_DATA_TYPE_FIELDS = [
|
|
|
|
'format',
|
|
|
|
'defaultValue',
|
|
|
|
'enum',
|
|
|
|
'minimum',
|
|
|
|
'maximum',
|
|
|
|
'uniqueItems',
|
|
|
|
// loopback-explorer extensions
|
|
|
|
'length',
|
|
|
|
// https://www.npmjs.org/package/swagger-validation
|
|
|
|
'pattern'
|
|
|
|
];
|
|
|
|
|
|
|
|
// Rename LoopBack keys to Swagger keys
|
|
|
|
ldlType = translateDataTypeKeys(ldlType);
|
|
|
|
|
|
|
|
// Pick only keys supported by Swagger
|
|
|
|
var swaggerType = _pick(ldlType, SWAGGER_DATA_TYPE_FIELDS);
|
|
|
|
|
|
|
|
swaggerType.type = modelHelper.getPropType(ldlType.type);
|
|
|
|
|
|
|
|
if (swaggerType.type === 'array') {
|
|
|
|
var hasItemType = Array.isArray(ldlType.type) && ldlType.type.length;
|
|
|
|
var arrayItem = hasItemType && ldlType.type[0];
|
2014-07-31 22:52:52 +00:00
|
|
|
|
|
|
|
if (arrayItem) {
|
|
|
|
if(typeof arrayItem === 'object') {
|
2014-10-15 11:58:13 +00:00
|
|
|
swaggerType.items = modelHelper.LDLPropToSwaggerDataType(arrayItem);
|
2014-07-31 22:52:52 +00:00
|
|
|
} else {
|
2014-10-15 11:58:13 +00:00
|
|
|
swaggerType.items = { type: modelHelper.getPropType(arrayItem) };
|
2014-07-31 22:52:52 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// NOTE: `any` is not a supported type in swagger 1.2
|
2014-10-15 11:58:13 +00:00
|
|
|
swaggerType.items = { type: 'any' };
|
2014-07-22 19:34:42 +00:00
|
|
|
}
|
2014-10-15 11:58:13 +00:00
|
|
|
} else if (swaggerType.type === 'date') {
|
|
|
|
swaggerType.type = 'string';
|
|
|
|
swaggerType.format = 'date';
|
|
|
|
} else if (swaggerType.type === 'buffer') {
|
|
|
|
swaggerType.type = 'string';
|
|
|
|
swaggerType.format = 'byte';
|
|
|
|
} else if (swaggerType.type === 'number') {
|
|
|
|
swaggerType.format = 'double'; // Since all JS numbers are doubles
|
2014-07-10 16:32:58 +00:00
|
|
|
}
|
2014-10-15 11:58:13 +00:00
|
|
|
return swaggerType;
|
2014-07-05 19:32:00 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-07-10 16:32:58 +00:00
|
|
|
|