From 89b2604ee3f0157ab22bb35a07db2ee395be1979 Mon Sep 17 00:00:00 2001 From: Sergey Fomin Date: Thu, 19 Apr 2018 04:15:29 -0700 Subject: [PATCH] Merged https://github.com/simonguest/swagger-mongoose/pull/30 with some linter updates. Updated mongoose dependency to ^5.0.0 --- lib/index.js | 401 ++++++++++++++++++++++++---------------- lib/validators/index.js | 8 +- package.json | 4 +- 3 files changed, 247 insertions(+), 166 deletions(-) diff --git a/lib/index.js b/lib/index.js index 39877bb..28c0043 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,23 +1,34 @@ -'use strict'; -var _ = require('lodash'); -var mongoose = require('mongoose'); -var Schema = mongoose.Schema; -var path = require('path'); - -var allowedTypes = ['number','integer', 'long', 'float', 'double', 'string', 'password', 'boolean', 'date', 'dateTime', 'array']; -var definitions = null; -var swaggerVersion = null; -var v2MongooseProperty = 'x-swagger-mongoose'; -var v1MongooseProperty = '_mongoose'; -var xSwaggerMongoose = { +const _ = require('lodash'); +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; +const path = require('path'); + +const allowedTypes = [ + 'number', + 'integer', + 'long', + 'float', + 'double', + 'string', + 'password', + 'boolean', + 'date', + 'dateTime', + 'array' +]; +let definitions = null; +let swaggerVersion = null; +const v2MongooseProperty = 'x-swagger-mongoose'; +const v1MongooseProperty = '_mongoose'; +const xSwaggerMongoose = { schemaOptions: {}, additionalProperties: {}, excludeSchema: {}, - documentIndex: {}, + documentIndex: {} }; -var validators = {}; +let validators = {}; -var propertyMap = function (property) { +const propertyMap = property => { switch (property.type) { case 'number': switch (property.format) { @@ -27,12 +38,12 @@ var propertyMap = function (property) { case 'double': return Number; default: - throw new Error('Unrecognised schema format: ' + property.format); + throw new Error(`Unrecognised schema format: ${property.format}`); } case 'integer': - case 'long' : - case 'float' : - case 'double' : + case 'long': + case 'float': + case 'double': return Number; case 'string': case 'password': @@ -45,13 +56,13 @@ var propertyMap = function (property) { case 'array': return [propertyMap(property.items)]; default: - throw new Error('Unrecognized schema type: ' + property.type); + throw new Error(`Unrecognized schema type: ${property.type}`); } }; -var convertToJSON = function (spec) { - var swaggerJSON = {}; - var type = typeof(spec); +const convertToJSON = spec => { + let swaggerJSON = {}; + const type = typeof spec; switch (type) { case 'object': if (spec instanceof Buffer) { @@ -65,24 +76,23 @@ var convertToJSON = function (spec) { break; default: throw new Error('Unknown or invalid spec object'); - break; } return swaggerJSON; }; -var isSimpleSchema = function (schema) { +const isSimpleSchema = schema => { return schema.type && isAllowedType(schema.type); }; -var isAllowedType = function (type) { - return allowedTypes.indexOf(type) != -1; +const isAllowedType = type => { + return allowedTypes.indexOf(type) !== -1; }; -var isPropertyHasRef = function (property) { - return property['$ref'] || ((property['type'] == 'array') && (property['items']['$ref'])); +const isPropertyHasRef = property => { + return property.$ref || (property.type === 'array' && property.items.$ref); }; -var fillRequired = function (object, key, template) { +const fillRequired = (object, key, template) => { if (template && Array.isArray(template) && template.indexOf(key) >= 0) { object[key].required = true; } else if (typeof template === 'boolean') { @@ -90,65 +100,66 @@ var fillRequired = function (object, key, template) { } }; -var applyExtraDefinitions = function (definitions, _extraDefinitions) { +const applyExtraDefinitions = (defs, _extraDefinitions) => { if (_extraDefinitions) { - //TODO: check for string or object assume object for now. // var extraDefinitions = JSON.parse(_extraDefinitions); - var mongooseProperty = getMongooseProperty(); + const mongooseProperty = getMongooseProperty(); //remove default object from extra, we're going to handle that seperately - var defaultDefs; + let defaultDefs; if (!_extraDefinitions.default) { defaultDefs = null; } else { - defaultDefs = _extraDefinitions.default + defaultDefs = _extraDefinitions.default; delete _extraDefinitions.default; - _.each(definitions, function (val,key){ + _.each(defs, val => { //lets add that default to everything. - val[mongooseProperty] = defaultDefs + val[mongooseProperty] = defaultDefs; }); } - var extraDefinitions = _extraDefinitions; - _.each(extraDefinitions, function (val, key) { - definitions[key][mongooseProperty] = val + const extraDefinitions = _extraDefinitions; + _.each(extraDefinitions, (val, key) => { + defs[key][mongooseProperty] = val; }); - } }; -var isAtLeastSwagger2 = function() { +const isAtLeastSwagger2 = () => { return swaggerVersion >= 2; }; -var getMongooseProperty = function() { - return (isAtLeastSwagger2()) ? v2MongooseProperty : v1MongooseProperty; +const getMongooseProperty = () => { + return isAtLeastSwagger2() ? v2MongooseProperty : v1MongooseProperty; }; -var isMongooseProperty = function (property) { +const isMongooseProperty = property => { return !!property[getMongooseProperty()]; }; -var isMongooseArray = function (property) { +const isMongooseArray = property => { return property.items && property.items[getMongooseProperty()]; }; -var getMongooseSpecific = function (props, property) { - var mongooseProperty = getMongooseProperty(); - var mongooseSpecific = property[mongooseProperty]; - var ref = (isAtLeastSwagger2() && mongooseSpecific) ? mongooseSpecific.$ref : property.$ref; +const getMongooseSpecific = (props, property) => { + const mongooseProperty = getMongooseProperty(); + let mongooseSpecific = property[mongooseProperty]; + let ref = + isAtLeastSwagger2() && mongooseSpecific + ? mongooseSpecific.$ref + : property.$ref; if (!mongooseSpecific && isMongooseArray(property)) { mongooseSpecific = property.items[mongooseProperty]; - ref = (isAtLeastSwagger2()) ? mongooseSpecific.$ref : property.items.$ref; + ref = isAtLeastSwagger2() ? mongooseSpecific.$ref : property.items.$ref; } if (!mongooseSpecific) { return props; } - var ret = {}; + let ret = {}; if (ref) { if (!isAtLeastSwagger2()) { if (mongooseSpecific.type === 'objectId') { @@ -162,8 +173,8 @@ var getMongooseSpecific = function (props, property) { ret.ref = ref.replace('#/definitions/', ''); } } else if (mongooseSpecific.validator) { - var validator = validators[mongooseSpecific.validator]; - ret = _.extend(ret, property, {validate: validator}); + const validator = validators[mongooseSpecific.validator]; + ret = _.extend(ret, property, { validate: validator }); delete ret[mongooseProperty]; } else { ret = _.extend(ret, property, mongooseSpecific); @@ -176,90 +187,144 @@ var getMongooseSpecific = function (props, property) { return ret; }; -var isMongodbReserved = function (fieldKey) { +const isMongodbReserved = fieldKey => { return fieldKey === '_id' || fieldKey === '__v'; }; -var processRef = function (property, objectName, props, key, required) { - var refRegExp = /^#\/definitions\/(\w*)$/; - var refString = property['$ref'] ? property['$ref'] : property['items']['$ref']; - var propType = refString.match(refRegExp)[1]; +const processRef = (property, objectName, props, key, required) => { + const refRegExp = /^#\/definitions\/(\w*)$/; + const refString = property.$ref ? property.$ref : property.items.$ref; + const propType = refString.match(refRegExp)[1]; // NOT circular reference if (propType !== objectName) { - var object = definitions[propType]; + const object = definitions[propType]; if (~['array', 'object'].indexOf(object.type)) { - var schema = getSchema(propType, object['properties'] ? object['properties'] : object); - props[key] = property['items'] || object.type === 'array' ? [schema] : schema; + const schema = getSchema( + propType, + object.properties ? object.properties : object + ); + props[key] = + property.items || object.type === 'array' ? [schema] : schema; } else { - var clone = _.extend({}, object); + const clone = _.extend({}, object); delete clone[getMongooseProperty()]; - var schemaProp = getSchemaProperty(clone, key)[key]; - props[key] = property['items'] ? [schemaProp] : schemaProp; - } - } else { - // circular reference - if (propType) { - props[key] = { - type: Schema.Types.ObjectId, - ref: propType - }; + const schemaProp = getSchemaProperty(clone, key)[key]; + props[key] = property.items ? [schemaProp] : schemaProp; } + } else if (propType) { // circular reference + props[key] = { + type: Schema.Types.ObjectId, + ref: propType + }; } + fillRequired(props, key, required); }; -var getSchema = function (objectName, fullObject) { - var props = {}; - var required = fullObject.required || []; - var object = fullObject['properties'] ? fullObject['properties'] : fullObject; +const isPolymorphic = def => { + return def.allOf; +}; + +/* + * Merges members of 'allOf' into a single 'properties' object, + * and a single 'required' array, and assigns these to the parent definition. + * + * NOTE: needs more thorough testing! + */ +const processPolymorphic = definition => { + const mergedProps = {}; + const mergedReqs = new Set(); + definition.allOf.forEach(polyDef => { + if (polyDef.$ref) { + const refRegExp = /^#\/definitions\/(\w*)$/; + const refString = polyDef.$ref; + const propType = refString.match(refRegExp)[1]; + const object = definitions[propType]; + Object.assign(mergedProps, object.properties); + if (object.required) { + object.required.forEach(req => { + mergedReqs.add(req); + }); + } + } else { + Object.assign(mergedProps, polyDef.properties); + if (polyDef.required) { + polyDef.required.forEach(req => { + mergedReqs.add(req); + }); + } + } + }); + if (!_.isEmpty(mergedProps)) definition.properties = mergedProps; + if (!_.isEmpty(mergedReqs)) definition.required = Array.from(mergedReqs); + delete definition.allOf; +}; + +const getSchema = (objectName, fullObject) => { + let props = {}; + const required = fullObject.required || []; + if (isPolymorphic(fullObject)) { + processPolymorphic(fullObject); + } + + const object = fullObject.properties ? fullObject.properties : fullObject; - _.forEach(object, function (property, key) { - var schemaProperty = getSchemaProperty(property, key, required, objectName, object); + _.forEach(object, (property, key) => { + const schemaProperty = getSchemaProperty( + property, + key, + required, + objectName, + object + ); props = _.extend(props, schemaProperty); }); return props; }; -var getSchemaProperty = function(property, key, required, objectName, object) { - var props = {}; +const getSchemaProperty = (property, key, required, objectName, object) => { + let props = {}; if (isMongodbReserved(key) === true) { return; } if (isMongooseProperty(property)) { props[key] = getMongooseSpecific(props, property); - } - else if (isMongooseArray(property)) { + } else if (isMongooseArray(property)) { props[key] = [getMongooseSpecific(props, property)]; - } - else if (isPropertyHasRef(property)) { + } else if (isPropertyHasRef(property)) { processRef(property, objectName, props, key, required); - } - else if (property.type !== 'object') { - var type = propertyMap(property); + } else if (isPolymorphic(property)) { + processPolymorphic(property); + } else if (!property.type || property.type === 'object') { + props[key] = getSchema(key, property); + } else if (property.type !== 'object') { + const type = propertyMap(property); if (property.enum && _.isArray(property.enum)) { - props[key] = {type: type, enum: property.enum}; + props[key] = { type, enum: property.enum }; } else { - props[key] = {type: type}; + props[key] = { type }; } - - } - else if (property.type === 'object') { + } else if (property.type === 'object') { props[key] = getSchema(key, property); - } - else if (isSimpleSchema(object)) { - props = {type: propertyMap(object)}; + } else if (isSimpleSchema(object)) { + props = { type: propertyMap(object) }; } if (required) { fillRequired(props, key, required); } + + if (!_.isUndefined(property.default)) { + props[key].default = property.default; + } + return props; }; -var processDocumentIndex = function(schema, index){ +const processDocumentIndex = (schema, index) => { //TODO: check indicies are numbers - var isUniqueIndex = false; + let isUniqueIndex = false; if (_.isEmpty(index)) { return; } @@ -268,47 +333,77 @@ var processDocumentIndex = function(schema, index){ } delete index.unique; if (isUniqueIndex) { - schema.index(index, {unique:true}) + schema.index(index, { unique: true }); } else { - schema.index(index) + schema.index(index); } - }; - -module.exports.compileAsync = function (spec, callback) { - try { - callback(null, this.compile(spec)); - } catch (err) { - callback({message: err}, null); +const processMongooseDefinition = (key, customOptions) => { + if (customOptions) { + if (customOptions['schema-options']) { + xSwaggerMongoose.schemaOptions[key] = customOptions['schema-options']; + } + if (customOptions['exclude-schema']) { + xSwaggerMongoose.excludeSchema[key] = customOptions['exclude-schema']; + } + if (customOptions['additional-properties']) { + xSwaggerMongoose.additionalProperties[key] = + customOptions['additional-properties']; + } + if (customOptions.index) { + xSwaggerMongoose.documentIndex[key] = customOptions.index; + } + if (customOptions.validators) { + const validatorsDirectory = path.resolve( + process.cwd(), + customOptions.validators + ); + validators = require(validatorsDirectory); + } } }; -module.exports.compile = function (spec, _extraDefinitions) { +const processAdditionalProperties = (additionalProperties, objectName) => { + let props = {}; + const customMongooseProperty = getMongooseProperty(); + _.each(additionalProperties, (property, key) => { + const modifiedProperty = {}; + modifiedProperty[customMongooseProperty] = property; + props = _.extend( + props, + getSchemaProperty(modifiedProperty, key, property.required, objectName) + ); + }); + return props; +}; + +const compile = (spec, _extraDefinitions) => { if (!spec) throw new Error('Swagger spec not supplied'); - var swaggerJSON = convertToJSON(spec); + const swaggerJSON = convertToJSON(spec); if (swaggerJSON.swagger) { - swaggerVersion = new Number(swaggerJSON.swagger); + swaggerVersion = Number(swaggerJSON.swagger); } - - - definitions = swaggerJSON['definitions']; + definitions = swaggerJSON.definitions; applyExtraDefinitions(definitions, _extraDefinitions); - var customMongooseProperty = getMongooseProperty(); + const customMongooseProperty = getMongooseProperty(); if (swaggerJSON[customMongooseProperty]) { - processMongooseDefinition(customMongooseProperty, swaggerJSON[customMongooseProperty]); + processMongooseDefinition( + customMongooseProperty, + swaggerJSON[customMongooseProperty] + ); } - var schemas = {}; - _.forEach(definitions, function (definition, key) { - var object; - var options = xSwaggerMongoose.schemaOptions; - var excludedSchema = xSwaggerMongoose.excludeSchema; - var documentIndex = xSwaggerMongoose.documentIndex[key]; + const schemas = {}; + _.forEach(definitions, (definition, key) => { + // LOCAL CHANGE Fix index creation at document level + let object = null; + let options = xSwaggerMongoose.schemaOptions; + let excludedSchema = xSwaggerMongoose.excludeSchema; if (definition[customMongooseProperty]) { processMongooseDefinition(key, definition[customMongooseProperty]); @@ -321,58 +416,46 @@ module.exports.compile = function (spec, _extraDefinitions) { options = _.extend({}, options[customMongooseProperty], options[key]); } if (typeof excludedSchema === 'object') { - excludedSchema = excludedSchema[customMongooseProperty] || excludedSchema[key]; + excludedSchema = + excludedSchema[customMongooseProperty] || excludedSchema[key]; } if (object && !excludedSchema) { - var additionalProperties = _.extend({}, xSwaggerMongoose.additionalProperties[customMongooseProperty], xSwaggerMongoose.additionalProperties[key]); - additionalProperties = processAdditionalProperties(additionalProperties, key) + let additionalProperties = _.extend( + {}, + xSwaggerMongoose.additionalProperties[customMongooseProperty], + xSwaggerMongoose.additionalProperties[key] + ); + additionalProperties = processAdditionalProperties( + additionalProperties, + key + ); object = _.extend(object, additionalProperties); - var schema = new mongoose.Schema(object, options); - processDocumentIndex(schema, documentIndex) - schemas[key] = schema + const schema = new mongoose.Schema(object, options); + processDocumentIndex(schema, xSwaggerMongoose.documentIndex[key]); + schemas[key] = schema; } }); - var models = {}; - _.forEach(schemas, function (schema, key) { + const models = {}; + _.forEach(schemas, (schema, key) => { models[key] = mongoose.model(key, schema); }); return { - schemas: schemas, - models: models + schemas, + models }; }; -var processMongooseDefinition = function(key, customOptions) { - if (customOptions) { - if (customOptions['schema-options']) { - xSwaggerMongoose.schemaOptions[key] = customOptions['schema-options']; - } - if (customOptions['exclude-schema']) { - xSwaggerMongoose.excludeSchema[key] = customOptions['exclude-schema']; - } - if (customOptions['additional-properties']) { - xSwaggerMongoose.additionalProperties[key] = customOptions['additional-properties']; - } - if (customOptions['index']) { - xSwaggerMongoose.documentIndex[key] = customOptions['index']; - } - if (customOptions['validators']) { - var validatorsDirectory = path.resolve(process.cwd(),customOptions['validators']) - validators = require(validatorsDirectory) - } - +const compileAsync = (spec, callback) => { + try { + return callback(null, compile(spec)); + } catch (err) { + return callback({ message: err }, null); } }; -var processAdditionalProperties = function(additionalProperties, objectName) { - var props = {}; - var customMongooseProperty = getMongooseProperty(); - _.each(additionalProperties, function (property, key) { - var modifiedProperty = {}; - modifiedProperty[customMongooseProperty] = property; - props = _.extend(props, getSchemaProperty(modifiedProperty, key, property.required, objectName)); - }); - return props; +module.exports = { + compile, + compileAsync }; diff --git a/lib/validators/index.js b/lib/validators/index.js index 28a4522..9dfabf7 100644 --- a/lib/validators/index.js +++ b/lib/validators/index.js @@ -1,8 +1,6 @@ -'use strict'; - module.exports.homePhone = { message: '{VALUE} is not a valid home phone number!', - validator: function(v){ - return /([0-9]{1}[-\.\s])?([\(\[]?[0-9]{3}[\)\]]?[-\.\s])?([0-9]{3})[-\.\s]([0-9]{4})(?:\s?(?:x|ext)\s?([0-9])+)?/.test(v) + validator(v) { + return /([0-9]{1}[-\.\s])?([\(\[]?[0-9]{3}[\)\]]?[-\.\s])?([0-9]{3})[-\.\s]([0-9]{4})(?:\s?(?:x|ext)\s?([0-9])+)?/.test(v); } -} +}; diff --git a/package.json b/package.json index 86088c8..0274168 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,10 @@ "chai": "^3.5.0", "mocha": "^2.4.5", "mockgoose": "^7.0.0", - "mongoose": "^4.8.6" + "mongoose": "^5.0.0" }, "peerDependencies": { - "mongoose": "^4.8.6" + "mongoose": "^5.0.0" }, "scripts": { "test": "mocha --timeout 60000"