Skip to content

Commit

Permalink
refactor(schema): move logic for converting SchemaType to JSON schema…
Browse files Browse the repository at this point in the history
… into the individual SchemaType classes

Re: #11162
  • Loading branch information
vkarpov15 committed Jan 20, 2025
1 parent 879443d commit 00a2778
Show file tree
Hide file tree
Showing 19 changed files with 324 additions and 131 deletions.
24 changes: 24 additions & 0 deletions lib/helpers/createJSONSchemaTypeDefinition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

/**
* Handles creating `{ type: 'object' }` vs `{ bsonType: 'object' }` vs `{ bsonType: ['object', 'null'] }`
*
* @param {String} type
* @param {String} bsonType
* @param {Boolean} useBsonType
* @param {Boolean} isRequired
*/

module.exports = function createJSONSchemaTypeArray(type, bsonType, useBsonType, isRequired) {
if (useBsonType) {
if (isRequired) {
return { bsonType };
}
return { bsonType: [bsonType, 'null'] };
} else {
if (isRequired) {
return { type };
}
return { type: [type, 'null'] };
}
};
132 changes: 5 additions & 127 deletions lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -2957,24 +2957,11 @@ Schema.prototype.jsonSchema = function jsonSchema(options) {
jsonSchemaForPath.required.push(lastSubpath);
isRequired = true;
}
const convertedSchemaType = _schemaTypeToJSONSchema(schemaType, isRequired, options);
const { additionalProperties } = convertedSchemaType;
let { bsonType, type } = convertedSchemaType;

if (bsonType) {
if (!isRequired) {
bsonType = [...bsonType, 'null'];
type = [...type, 'null'];
}
jsonSchemaForPath.properties[lastSubpath] = useBsonType
? { bsonType: bsonType.length === 1 ? bsonType[0] : bsonType }
: { type: type.length === 1 ? type[0] : type };
if (schemaType.options.enum) {
jsonSchemaForPath.properties[lastSubpath].enum = isRequired
? schemaType.options.enum
: [...schemaType.options.enum, null];
}
Object.assign(jsonSchemaForPath.properties[lastSubpath], additionalProperties);
jsonSchemaForPath.properties[lastSubpath] = schemaType.toJSONSchema(options);
if (schemaType.options.enum) {
jsonSchemaForPath.properties[lastSubpath].enum = isRequired
? schemaType.options.enum
: [...schemaType.options.enum, null];
}
}

Expand All @@ -2985,115 +2972,6 @@ Schema.prototype.jsonSchema = function jsonSchema(options) {
return result;
};

/*!
* Internal helper for converting an individual schematype to JSON schema properties. Recursively called for
* arrays.
*
* @param {SchemaType} schemaType
* @param {Boolean} isRequired
* @param {Object} options
*/

function _schemaTypeToJSONSchema(schemaType, isRequired, options) {
const useBsonType = options?.useBsonType ?? false;
let bsonType = undefined;
let type = undefined;
let additionalProperties = {};

if (schemaType.instance === 'Number') {
bsonType = ['number'];
type = ['number'];
} else if (schemaType.instance === 'String') {
bsonType = ['string'];
type = ['string'];
} else if (schemaType.instance === 'Boolean') {
bsonType = ['bool'];
type = ['boolean'];
} else if (schemaType.instance === 'Date') {
bsonType = ['date'];
type = ['string'];
} else if (schemaType.instance === 'ObjectId') {
bsonType = ['objectId'];
type = ['string'];
} else if (schemaType.instance === 'Decimal128') {
bsonType = ['decimal'];
type = ['string'];
} else if (schemaType.instance === 'Buffer') {
bsonType = ['binData'];
type = ['string'];
} else if (schemaType.instance === 'UUID') {
bsonType = ['binData'];
type = ['string'];
} else if (schemaType.instance === 'Embedded') {
bsonType = ['object'],
type = ['object'];
additionalProperties = schemaType.schema.jsonSchema(options);
} else if (schemaType.instance === 'Array') {
bsonType = ['array'];
type = ['array'];
if (schemaType.schema) {
// DocumentArray
if (useBsonType) {
additionalProperties.items = { ...schemaType.schema.jsonSchema(options), bsonType: ['object', 'null'] };
} else {
additionalProperties.items = { ...schemaType.schema.jsonSchema(options), type: ['object', 'null'] };
}
} else {
// Primitive array
const embeddedSchemaType = schemaType.getEmbeddedSchemaType();
const isRequired = embeddedSchemaType.options.required && typeof embeddedSchemaType.options.required !== 'function';
const convertedSchemaType = _schemaTypeToJSONSchema(embeddedSchemaType, isRequired, options);
let bsonType = convertedSchemaType.bsonType;
let type = convertedSchemaType.type;
if (!isRequired) {
bsonType = [...bsonType, 'null'];
type = [...type, 'null'];
}
if (useBsonType) {
additionalProperties.items = { ...convertedSchemaType.additionalProperties, bsonType };
} else {
additionalProperties.items = { ...convertedSchemaType.additionalProperties, type };
}
}
} else if (schemaType.instance === 'Map') {
bsonType = ['object'];
type = ['object'];
const embeddedSchemaType = schemaType.getEmbeddedSchemaType();
const isRequired = embeddedSchemaType.options.required && typeof embeddedSchemaType.options.required !== 'function';
if (embeddedSchemaType.schema) {
if (embeddedSchemaType.instance === 'Array') {
// Map of document arrays
additionalProperties.additionalProperties = useBsonType
? { bsonType: ['array', 'null'], items: { bsonType: ['object', 'null'], ...embeddedSchemaType.schema.jsonSchema(options) } }
: { type: ['array', 'null'], items: { type: ['object', 'null'], ...embeddedSchemaType.schema.jsonSchema(options) } };
} else {
// Map of objects
additionalProperties.additionalProperties = useBsonType
? { ...embeddedSchemaType.schema.jsonSchema(options), bsonType: ['object', 'null'] }
: { ...embeddedSchemaType.schema.jsonSchema(options), type: ['object', 'null'] };
}
} else {
// Map of primitives
const convertedSchemaType = _schemaTypeToJSONSchema(embeddedSchemaType, isRequired, options);
let bsonType = convertedSchemaType.bsonType;
let type = convertedSchemaType.type;
if (!isRequired) {
bsonType = [...bsonType, 'null'];
type = [...type, 'null'];
}
if (useBsonType) {
additionalProperties.additionalProperties = { bsonType: bsonType.length === 1 ? bsonType[0] : bsonType, ...convertedSchemaType.additionalProperties };
} else {
additionalProperties.additionalProperties = { type: type.length === 1 ? type[0] : type, ...convertedSchemaType.additionalProperties };
}
}
} else {
throw new Error(`Cannot convert schema to JSON schema: unsupported schematype "${schemaType.instance}"`);
}

return { bsonType, type, additionalProperties };
}

/*!
* Module exports.
*/
Expand Down
18 changes: 18 additions & 0 deletions lib/schema/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const isOperator = require('../helpers/query/isOperator');
const util = require('util');
const utils = require('../utils');
const castToNumber = require('./operators/helpers').castToNumber;
const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
const geospatial = require('./operators/geospatial');
const getDiscriminatorByValue = require('../helpers/discriminator/getDiscriminatorByValue');

Expand Down Expand Up @@ -700,6 +701,23 @@ handle.$ne = SchemaArray.prototype._castForQuery;
handle.$nin = SchemaType.prototype.$conditionalHandlers.$nin;
handle.$in = SchemaType.prototype.$conditionalHandlers.$in;

/**
* Returns this schema type's representation in a JSON schema.
*
* @param [options]
* @param [options.useBsonType=false] If true, return a representation with `bsonType` for use with MongoDB's `$jsonSchema`.
* @returns {Object} JSON schema properties
*/

SchemaArray.prototype.toJSONSchema = function toJSONSchema(options) {
const embeddedSchemaType = this.getEmbeddedSchemaType();
const isRequired = this.options.required && typeof this.options.required !== 'function';
return {
...createJSONSchemaTypeDefinition('array', 'array', options?.useBsonType, isRequired),
items: embeddedSchemaType.toJSONSchema(options)
};
};

/*!
* Module exports.
*/
Expand Down
14 changes: 14 additions & 0 deletions lib/schema/bigint.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
const CastError = require('../error/cast');
const SchemaType = require('../schemaType');
const castBigInt = require('../cast/bigint');
const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');

/**
* BigInt SchemaType constructor.
Expand Down Expand Up @@ -240,6 +241,19 @@ SchemaBigInt.prototype._castNullish = function _castNullish(v) {
return v;
};

/**
* Returns this schema type's representation in a JSON schema.
*
* @param [options]
* @param [options.useBsonType=false] If true, return a representation with `bsonType` for use with MongoDB's `$jsonSchema`.
* @returns {Object} JSON schema properties
*/

SchemaBigInt.prototype.toJSONSchema = function toJSONSchema(options) {
const isRequired = this.options.required && typeof this.options.required !== 'function';
return createJSONSchemaTypeDefinition('string', 'long', options?.useBsonType, isRequired);
};

/*!
* Module exports.
*/
Expand Down
14 changes: 14 additions & 0 deletions lib/schema/boolean.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
const CastError = require('../error/cast');
const SchemaType = require('../schemaType');
const castBoolean = require('../cast/boolean');
const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');

/**
* Boolean SchemaType constructor.
Expand Down Expand Up @@ -290,6 +291,19 @@ SchemaBoolean.prototype._castNullish = function _castNullish(v) {
return v;
};

/**
* Returns this schema type's representation in a JSON schema.
*
* @param [options]
* @param [options.useBsonType=false] If true, return a representation with `bsonType` for use with MongoDB's `$jsonSchema`.
* @returns {Object} JSON schema properties
*/

SchemaBoolean.prototype.toJSONSchema = function toJSONSchema(options) {
const isRequired = this.options.required && typeof this.options.required !== 'function';
return createJSONSchemaTypeDefinition('boolean', 'bool', options?.useBsonType, isRequired);
};

/*!
* Module exports.
*/
Expand Down
14 changes: 14 additions & 0 deletions lib/schema/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
const MongooseBuffer = require('../types/buffer');
const SchemaBufferOptions = require('../options/schemaBufferOptions');
const SchemaType = require('../schemaType');
const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
const handleBitwiseOperator = require('./operators/bitwise');
const utils = require('../utils');

Expand Down Expand Up @@ -300,6 +301,19 @@ SchemaBuffer.prototype.castForQuery = function($conditional, val, context) {
return casted ? casted.toObject({ transform: false, virtuals: false }) : casted;
};

/**
* Returns this schema type's representation in a JSON schema.
*
* @param [options]
* @param [options.useBsonType=false] If true, return a representation with `bsonType` for use with MongoDB's `$jsonSchema`.
* @returns {Object} JSON schema properties
*/

SchemaBuffer.prototype.toJSONSchema = function toJSONSchema(options) {
const isRequired = this.options.required && typeof this.options.required !== 'function';
return createJSONSchemaTypeDefinition('string', 'binData', options?.useBsonType, isRequired);
};

/*!
* Module exports.
*/
Expand Down
14 changes: 14 additions & 0 deletions lib/schema/date.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const MongooseError = require('../error/index');
const SchemaDateOptions = require('../options/schemaDateOptions');
const SchemaType = require('../schemaType');
const castDate = require('../cast/date');
const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
const getConstructorName = require('../helpers/getConstructorName');
const utils = require('../utils');

Expand Down Expand Up @@ -426,6 +427,19 @@ SchemaDate.prototype.castForQuery = function($conditional, val, context) {
return handler.call(this, val);
};

/**
* Returns this schema type's representation in a JSON schema.
*
* @param [options]
* @param [options.useBsonType=false] If true, return a representation with `bsonType` for use with MongoDB's `$jsonSchema`.
* @returns {Object} JSON schema properties
*/

SchemaDate.prototype.toJSONSchema = function toJSONSchema(options) {
const isRequired = this.options.required && typeof this.options.required !== 'function';
return createJSONSchemaTypeDefinition('string', 'date', options?.useBsonType, isRequired);
};

/*!
* Module exports.
*/
Expand Down
14 changes: 14 additions & 0 deletions lib/schema/decimal128.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
const SchemaType = require('../schemaType');
const CastError = SchemaType.CastError;
const castDecimal128 = require('../cast/decimal128');
const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
const isBsonType = require('../helpers/isBsonType');

/**
Expand Down Expand Up @@ -221,6 +222,19 @@ SchemaDecimal128.prototype.$conditionalHandlers = {
$lte: handleSingle
};

/**
* Returns this schema type's representation in a JSON schema.
*
* @param [options]
* @param [options.useBsonType=false] If true, return a representation with `bsonType` for use with MongoDB's `$jsonSchema`.
* @returns {Object} JSON schema properties
*/

SchemaDecimal128.prototype.toJSONSchema = function toJSONSchema(options) {
const isRequired = this.options.required && typeof this.options.required !== 'function';
return createJSONSchemaTypeDefinition('string', 'decimal', options?.useBsonType, isRequired);
};

/*!
* Module exports.
*/
Expand Down
18 changes: 18 additions & 0 deletions lib/schema/documentArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const SchemaDocumentArrayOptions =
require('../options/schemaDocumentArrayOptions');
const SchemaType = require('../schemaType');
const cast = require('../cast');
const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
const discriminator = require('../helpers/model/discriminator');
const handleIdOption = require('../helpers/schema/handleIdOption');
const handleSpreadDoc = require('../helpers/document/handleSpreadDoc');
Expand Down Expand Up @@ -651,6 +652,23 @@ function cast$elemMatch(val, context) {
return cast(schema, val, null, this && this.$$context);
}

/**
* Returns this schema type's representation in a JSON schema.
*
* @param [options]
* @param [options.useBsonType=false] If true, return a representation with `bsonType` for use with MongoDB's `$jsonSchema`.
* @returns {Object} JSON schema properties
*/

SchemaDocumentArray.prototype.toJSONSchema = function toJSONSchema(options) {
const itemsTypeDefinition = createJSONSchemaTypeDefinition('object', 'object', options?.useBsonType, false);
const isRequired = this.options.required && typeof this.options.required !== 'function';
return {
...createJSONSchemaTypeDefinition('array', 'array', options?.useBsonType, isRequired),
items: { ...itemsTypeDefinition, ...this.schema.jsonSchema(options) }
};
};

/*!
* Module exports.
*/
Expand Down
Loading

0 comments on commit 00a2778

Please sign in to comment.