@techntools/sequelize-to-openapi
Version:
OpenAPI 3 schemas from Sequelize models
188 lines • 8.77 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const openapi_1 = __importDefault(require("./strategies/openapi"));
const type_mapper_1 = __importDefault(require("./type-mapper"));
const attribute_validator_1 = __importDefault(require("./attribute-validator"));
const type_checks_1 = require("./utils/type-checks");
const util_1 = require("./utils/util");
const _strategy = new WeakMap();
const _modelOptions = new WeakMap();
const _model = new WeakMap();
class SchemaManager {
generate(model, strategy, options) {
if (model === undefined)
throw new Error('Missing method argument `model`');
if (strategy === undefined)
throw new Error('Mising method argument `strategy`');
this.verifyModelOptions(Object.assign({}, {
include: [],
exclude: [],
associations: false,
includeAssociations: [],
excludeAssociations: [],
}, options));
this.verifyModel(model);
this.verifyStrategy(strategy);
const attributes = this.getAttributes();
const result = this.getModelContainer();
const requiredAttributes = [];
for (const attributeName of Object.keys(attributes)) {
result.properties[attributeName] = this.getAttributeContainer(attributeName, attributes[attributeName]);
if (this.isRequiredProperty(attributes[attributeName])) {
requiredAttributes.push(attributeName);
}
}
if (requiredAttributes.length > 0)
result.required = requiredAttributes;
if (_modelOptions.get(this).associations === false) {
return result;
}
for (const association of Object.keys(model.associations)) {
Object.assign(result.properties, this.getModelPropertyForAssociation(association, model.associations[association]));
}
return result;
}
verifyStrategy(strategy) {
if (!(strategy instanceof openapi_1.default))
throw new TypeError("Strategy must implement the 'OpenApiStrategy'");
_strategy.set(this, strategy);
}
verifyModelOptions(options) {
(0, type_checks_1.checkTypeOptional)('include', options.include, 'array');
(0, type_checks_1.checkTypeOptional)('exclude', options.exclude, 'array');
if (options.include.length > 0 && options.exclude.length > 0) {
throw new Error("Model options 'include' and 'exclude' are mutually exclusive");
}
(0, type_checks_1.checkTypeRequired)('associations', options.associations, 'boolean');
(0, type_checks_1.checkTypeRequired)('includeAssociations', options.includeAssociations, 'array');
(0, type_checks_1.checkTypeRequired)('excludeAssociations', options.excludeAssociations, 'array');
if (options.includeAssociations.length > 0 && options.excludeAssociations.length > 0) {
throw new Error("Model options 'includeAssociations' and 'excludeAssociations' are mutually exclusive");
}
_modelOptions.set(this, options);
}
verifyModel(model) {
if ('getAttributes' in model) {
_model.set(this, model);
return;
}
throw new TypeError('Provided model does not match expected format. Are you sure this is a Sequelize v6+ model ?');
}
getAttributes() {
const model = _model.get(this);
const attributes = model.getAttributes.bind(model)();
if (_modelOptions.get(this).include.length > 0)
return (0, util_1.pick)(attributes, _modelOptions.get(this).include);
if (_modelOptions.get(this).exclude.length > 0)
return (0, util_1.omit)(attributes, _modelOptions.get(this).exclude);
return attributes;
}
getModelContainer() {
const result = {
type: 'object',
properties: {},
additionalProperties: _strategy.get(this).additionalProperties
};
return result;
}
getAttributeContainer(attributeName, attributeProperties) {
const typeMapper = new type_mapper_1.default;
const attributeValidator = new attribute_validator_1.default;
const result = {};
Object.assign(result, typeMapper.map(attributeName, attributeProperties, _strategy.get(this)));
Object.assign(result, attributeValidator.map(attributeProperties, _strategy.get(this)));
Object.assign(result, this.getAttributePropertyTypeOverride(attributeName, attributeProperties));
Object.assign(result, this.getAttributePropertyDescription(attributeName, attributeProperties));
Object.assign(result, this.getPropertyReadOrWriteOnly(attributeName, attributeProperties));
Object.assign(result, this.getAttributeExamples(attributeProperties));
return result;
}
isRequiredProperty(attributeProperties) {
if (attributeProperties.allowNull === false)
return true;
if (attributeProperties.defaultValue !== undefined)
return true;
return false;
}
getAttributeExamples(attributeProperties) {
const examples = this.getCustomPropertyValue('examples', attributeProperties);
if (examples === null)
return null;
if (!Array.isArray(examples)) {
throw new TypeError("The 'examples' property MUST be an array");
}
return _strategy.get(this).getPropertyExamples(examples);
}
getPropertyReadOrWriteOnly(attributeName, attributeProperties) {
const readOnly = this.getCustomPropertyValue('readOnly', attributeProperties);
const writeOnly = this.getCustomPropertyValue('writeOnly', attributeProperties);
if (!(readOnly || writeOnly))
return null;
if (readOnly && writeOnly) {
throw new TypeError(`Custom properties 'readOnly' and 'writeOnly' for sequelize attribute '${attributeName}' are mutually exclusive`);
}
if (readOnly) {
(0, type_checks_1.checkTypeRequired)('readOnly', readOnly, 'boolean');
return {
readOnly: true
};
}
if (writeOnly) {
(0, type_checks_1.checkTypeRequired)('writeOnly', writeOnly, 'boolean');
return {
writeOnly: true
};
}
}
getAttributePropertyDescription(attributeName, attributeProperties) {
const description = this.getCustomPropertyValue('description', attributeProperties);
if (!description)
return null;
(0, type_checks_1.checkTypeRequired)(attributeName, description, 'string');
return { description };
}
getCustomPropertyValue(propertyName, attributeProperties) {
const jsonSchema = attributeProperties['jsonSchema'];
if (!jsonSchema)
return null;
if (!jsonSchema[propertyName])
return null;
return jsonSchema[propertyName];
}
getAttributePropertyTypeOverride(attributeName, attributeProperties) {
const schema = this.getCustomPropertyValue('schema', attributeProperties);
if (!schema)
return null;
if (typeof schema === 'object' && typeof schema.type === 'string')
return schema;
throw new TypeError(`Custom property 'schema' for sequelize attribute '${attributeName}' should be an object with a 'type' key`);
}
getModelPropertyForAssociation(associationName, association) {
const options = _modelOptions.get(this);
if (options.excludeAssociations.length > 0 &&
options.excludeAssociations.includes(associationName)) {
return null;
}
if (options.includeAssociations.length > 0 &&
!options.includeAssociations.includes(associationName)) {
return null;
}
switch (association.associationType) {
case 'HasOne':
return _strategy.get(this).getPropertyForHasOneAssociation(associationName, association);
case 'BelongsTo':
return _strategy.get(this).getPropertyForBelongsToAssociation(associationName, association);
case 'HasMany':
return _strategy.get(this).getPropertyForHasManyAssociation(associationName, association);
case 'BelongsToMany':
return _strategy.get(this).getPropertyForBelongsToManyAssociation(associationName, association);
default:
return null;
}
}
}
exports.default = SchemaManager;
//# sourceMappingURL=schema-manager.js.map