mongoose-schema-jsonschema
Version:
Mongoose extension that allows to build json schema for mongoose models, schemas and queries
262 lines (214 loc) • 6.44 kB
JavaScript
const config = require('./config');
const [FIELDS_MAPPING] = require('./options/fieldOptionsMapping');
const { readConstraint } = require('./helpers');
module.exports = {
simpleType_jsonSchema,
objectId_jsonSchema,
date_jsonSchema,
array_jsonSchema,
mixed_jsonSchema,
map_jsonSchema,
};
/**
* simpleType_jsonSchema - returns jsonSchema for simple-type parameter
*
* @memberof mongoose.Schema.Types.String
*
* @return {object} the jsonSchema for parameter
*/
function simpleType_jsonSchema() {
const result = {};
result.type = this.instance.toLowerCase();
__processOptions(result, extendOptions(this));
return checkNullable(result);
}
function map_jsonSchema(name) {
const result = {
type: 'object',
additionalProperties:
this.options.of != null
? __describe(`itemOf_${name}`, this.options.of)
: true,
};
__processOptions(result, extendOptions(this));
delete result.additionalProperties.__required;
return checkNullable(result);
}
/**
* objectId_jsonSchema - returns the jsonSchema for ObjectId parameters
*
* @memberof mongoose.Schema.Types.ObjectId
*
* @param {String} name the name of parameter
* @return {object} the jsonSchema for parameter
*/
function objectId_jsonSchema(name) {
const result = simpleType_jsonSchema.call(this, name);
result.type = 'string';
result.pattern = '^[0-9a-fA-F]{24}$';
return checkNullable(result);
}
/**
* date_jsonSchema - description
*
* @param {type} name description
* @return {type} description
*/
function date_jsonSchema(name) {
const result = simpleType_jsonSchema.call(this, name);
result.type = 'string';
result.format = 'date-time';
return checkNullable(result);
}
/**
* array_jsonSchema - returns jsonSchema for array parameters
*
* @memberof mongoose.Schema.Types.SchemaArray
* @memberof mongoose.Schema.Types.DocumentArray
*
* @param {String} name parameter name
* @return {object} json schema
*/
function array_jsonSchema(name) {
const result = {};
const itemName = `itemOf_${name}`;
result.type = 'array';
if (this.options.required) result.__required = true;
if (this.schema) {
result.items = this.schema.jsonSchema(itemName);
} else {
result.items = this.caster.jsonSchema(itemName);
}
__processOptions(result, extendOptions(this));
delete result.items.__required;
return checkNullable(result);
}
/**
* mixed_jsonSchema - returns jsonSchema for Mixed parameter
*
* @memberof mongoose.Schema.Types.Mixed
*
* @param {String} name parameter name
* @return {object} json schema
*/
function mixed_jsonSchema(name) {
const result = __describe(name, this.options.type);
__processOptions(result, extendOptions(this));
// support for monggose@8.x.x
if (result.type === 'schemamixed') delete result.type;
return checkNullable(result);
}
const transformMatch = match => (
match instanceof RegExp
? match.toString().split('/').slice(1, -1).join('/')
: match.toString()
);
function __processOptions(t, type) {
t.__required = !!readConstraint(type.required);
if (type.enum) {
if (type.enum.slice) t.enum = type.enum.slice();
else if (type.enum.values) t.enum = type.enum.values;
}
if (type.ref) t['x-ref'] = type.ref;
if (type.min != null) t.minimum = readConstraint(type.min);
if (type.max != null) t.maximum = readConstraint(type.max);
if (type.minLength != null) t.minLength = readConstraint(type.minLength);
if (type.maxLength != null) t.maxLength = readConstraint(type.maxLength);
if (type.minlength != null) t.minLength = readConstraint(type.minlength);
if (type.maxlength != null) t.maxLength = readConstraint(type.maxlength);
if (type.examples != null) t.examples = type.examples;
if (type.match != null) t.pattern = transformMatch(readConstraint(type.match));
if (type.default !== undefined) t.default = type.default;
t.description = type.description || type.descr || type.ref && `Refers to ${type.ref}`;
if (!t.description) delete t.description;
if (!t.title && type.title) t.title = type.title;
const customFieldsMapping = config.get(FIELDS_MAPPING);
Object.assign(t, customFieldsMapping(type));
return t;
}
function __describe(name, type) {
if (!type) return {};
if (type.jsonSchema instanceof Function) return type.jsonSchema(name);
if (type.__buildingSchema) {
type.__jsonSchemaId = type.__jsonSchemaId
|| `#subschema_${++__describe.__jsonSchemaIdCounter}`;
return { $ref: type.__jsonSchemaId };
}
if (type instanceof Array) {
type.__buildingSchema = true;
const t = {
type: 'array',
items: __describe(name && (`itemOf_${name}`), type[0]),
};
delete type.__buildingSchema;
delete t.items.__required;
return t;
}
if (type === Date) {
return {
type: 'string',
format: 'date-time',
};
}
if (type instanceof Function) {
type = type.name.toLowerCase();
if (type === 'objectid') {
return {
type: 'string',
pattern: '^[0-9a-fA-F]{24}$',
};
} if (type === 'mixed') {
return { };
}
return {
type,
};
}
if (type.type) {
type.__buildingSchema = true;
const ts = __describe(name, type.type);
delete type.__buildingSchema;
__processOptions(ts, type);
return checkNullable(ts);
}
if (type.constructor.name !== 'Object') {
return {
type: type.constructor.name.toLowerCase(),
};
}
const result = {
type: 'object',
properties: {},
required: [],
};
result.title = name;
const props = Object.keys(type);
type.__buildingSchema = true;
props.forEach(p => {
result.properties[p] = __describe(p, type[p]);
if (result.properties[p].__required) {
result.required.push(p);
}
delete result.properties[p].__required;
});
delete type.__buildingSchema;
if (result.required.length === 0) delete result.required;
return result;
}
__describe.__jsonSchemaIdCounter = 0;
function checkNullable(typeDef) {
if (typeDef.default === null) {
typeDef.type = [typeDef.type, 'null'];
}
return typeDef;
}
function isRequired(options) {
const { required } = options;
return required === true || (Array.isArray(required) && required[0] === true);
}
function extendOptions(mongooseTypeDef) {
return {
...mongooseTypeDef.options,
required: isRequired(mongooseTypeDef.options),
};
}