UNPKG

@allgemein/schema-api

Version:
501 lines 19.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.JsonSchema7Serializer = void 0; const lodash_1 = require("lodash"); const base_1 = require("@allgemein/base"); const JsonSchema7_1 = require("./JsonSchema7"); const Constants_1 = require("../Constants"); const IClassRef_1 = require("../../api/IClassRef"); const SchemaUtils_1 = require("../SchemaUtils"); const IEntityRef_1 = require("../../api/IEntityRef"); const Constants_2 = require("./Constants"); const MetadataRegistry_1 = require("../registry/MetadataRegistry"); const functions_1 = require("./functions"); const functions_2 = require("../functions"); class JsonSchema7Serializer { constructor(opts) { this.options = opts || {}; (0, lodash_1.defaults)(this.options, { keysToSkip: Constants_1.DEFAULT_KEY_TO_SKIP, ignoreUnknownType: false, defaultTypeHint: Constants_1.T_STRING, [Constants_2.C_ONLY_DECORATED]: false, postProcess: (src, dst, serializer) => { if (src && src.metaType === Constants_1.METATYPE_PROPERTY && (0, lodash_1.has)(dst, 'cardinality')) { const cardinality = dst['cardinality']; delete dst['cardinality']; // TODO handle value corretly minItem/maxItem when item! if (cardinality === 0) { } else { //delete dst['cardinality']; } } } }); this.data = this.getOrCreateSchemaDefinitions(); } uri() { return `http://json-schema.org/${Constants_2.DRAFT_07}/schema`; } isCurrentClass(x) { return (0, lodash_1.snakeCase)(this.current) === (0, lodash_1.snakeCase)(x); } serialize(klass) { if ((0, lodash_1.isFunction)(klass)) { this.current = base_1.ClassUtils.getClassName(klass); this.describeClass(klass); } else if ((0, IClassRef_1.isClassRef)(klass)) { this.current = klass.name; this.describeClassRef(klass); } else if ((0, IEntityRef_1.isEntityRef)(klass)) { this.current = klass.name; this.describeEntityRef(klass); } else { throw new base_1.NotSupportedError('class or function can\'t be used'); } return this.getJsonSchema(); } getJsonSchema() { return this.data; } getOrCreateSchemaDefinitions(schema) { if (!schema) { schema = { $schema: this.uri() + '#', definitions: {} }; } else if (schema && !schema.definitions) { schema.definitions = {}; } return schema; } getOrCreateRoot(entityName, klass) { if (this.data.definitions[entityName]) { // definition is present and this.applyRef(entityName); return null; } const className = (0, functions_2.getClassName)(klass); const appendTarget = this.isAppendTargetSet(); const root = this.data.definitions[entityName] = { title: className ? className : entityName, type: Constants_1.T_OBJECT, }; if ((0, IEntityRef_1.isEntityRef)(klass)) { // when an entity mark with $id! root.$id = '#' + klass.name; } else if ((0, IClassRef_1.isClassRef)(klass) && klass.hasEntityRef()) { root.$id = '#' + klass.getEntityRef().name; } if (((0, IEntityRef_1.isEntityRef)(klass) || (0, IClassRef_1.isClassRef)(klass))) { if ((0, lodash_1.get)(this.options, 'appendNamespace', false)) { (0, lodash_1.set)(root, '$' + Constants_1.METATYPE_NAMESPACE, klass.getRegistry().getLookupRegistry().getNamespace()); } const data = klass.getOptions(); this.appendAdditionalOptions(root, data); } if (appendTarget) { root.$target = SchemaUtils_1.SchemaUtils.getFunction(klass); } this.applyRef(entityName); if (this.options.postProcess) { this.options.postProcess(klass, root, this); } return root; } applyRef(className) { if (!this.data.$ref && !this.data.anyOf) { this.data.$ref = '#/definitions/' + className; } else if (this.data.$ref && !this.data.anyOf) { if (this.isCurrentClass(className)) { this.data.anyOf = [ { $ref: this.data.$ref }, { $ref: '#/definitions/' + className } ]; delete this.data['$ref']; } } else if (!this.data.$ref && this.data.anyOf) { if (this.isCurrentClass(className)) { this.data.anyOf.push({ $ref: '#/definitions/' + className }); } } if (this.data.anyOf) { this.data.anyOf = (0, lodash_1.uniqBy)(this.data.anyOf, x => JSON.stringify(x)); } } describeEntityRef(klass) { return this.describeRef(klass); } describeClassRef(klass) { return this.describeRef(klass); } describeRef(klass) { const clsRef = (0, IEntityRef_1.isEntityRef)(klass) ? klass.getClassRef() : klass; const className = (0, IEntityRef_1.isEntityRef)(klass) ? klass.name : clsRef.name; const root = this.getOrCreateRoot(className, klass); if (!root) { return null; } const rootProps = this.describePropertiesForRef(clsRef); this.appendProperties(root, rootProps); const proto = clsRef.getExtend(); if (proto) { this.describeInheritedClass(root, proto); } return root; } describeInheritedClass(root, proto) { const inheritedClassName = proto.name; root.allOf = [{ $ref: '#/definitions/' + inheritedClassName }]; if (!this.data.definitions[inheritedClassName]) { const inheritedClass = this.getOrCreateRoot(inheritedClassName, proto); const props = this.describePropertiesForRef(proto); this.appendProperties(inheritedClass, props); } } describeClass(klass) { const className = base_1.ClassUtils.getClassName(klass); const root = this.getOrCreateRoot(className, klass); if (!root) { return null; } const rootProps = this.describePropertiesForFunction(klass); this.appendProperties(root, rootProps); const proto = SchemaUtils_1.SchemaUtils.getInherited(klass); if (proto) { const inheritedClassName = base_1.ClassUtils.getClassName(proto); root.allOf = [{ $ref: '#/definitions/' + inheritedClassName }]; if (this.data && !this.data.definitions[inheritedClassName]) { const inheritedClass = this.getOrCreateRoot(inheritedClassName, proto); const props = this.describePropertiesForFunction(proto); this.appendProperties(inheritedClass, props); } } return root; } appendProperties(data, properties) { data.properties = {}; for (const k of (0, lodash_1.keys)(properties)) { const p = properties[k]; if (p[Constants_1.K_PATTERN_PROPERTY]) { if (!data.patternProperties) { data.patternProperties = {}; } data.patternProperties[k] = p; } else { data.properties[k] = p; } delete p[Constants_1.K_PATTERN_PROPERTY]; } } describePropertiesForFunction(klass) { const properties = {}; let _properties = []; let instance = null; try { instance = Reflect.construct(klass, []); _properties = Reflect.ownKeys(instance); } catch (e) { } for (const p of _properties) { if ((0, lodash_1.isString)(p)) { if (!(0, lodash_1.get)(this.options, Constants_2.C_ONLY_DECORATED, false) && this.allowed(p, klass)) { const result = this.describePropertyForFunction(klass, p, instance); properties[p] = result; } } } return properties; } describePropertiesForRef(klass) { const properties = {}; for (const prop of klass.getPropertyRefs()) { if (this.allowed(prop)) { const result = this.describePropertyForRef(klass, prop); if (result) { properties[prop.name] = result; } } } const instance = klass.create(false); // TODO own key const _properties = Reflect.ownKeys(instance); for (const p of _properties) { if ((0, lodash_1.isString)(p) && !(0, lodash_1.has)(properties, p)) { if (!(0, lodash_1.get)(this.options, Constants_2.C_ONLY_DECORATED, false)) { const result = this.describePropertyForFunction(klass, p, instance); if (result) { properties[p] = result; } } } else if ((0, lodash_1.isString)(p) && (0, lodash_1.has)(properties, p)) { const value = instance[p]; if (!((0, lodash_1.isNull)(value) || (0, lodash_1.isUndefined)(value))) { // add default value if exists properties[p].default = SchemaUtils_1.SchemaUtils.normValue(value); } } } return properties; } isAppendTargetSet() { return (0, lodash_1.get)(this.options, 'appendTarget', false); } describePropertyForFunction(klass, propertyName, instance) { const clazz = (0, IClassRef_1.isClassRef)(klass) ? klass.getClass() : klass; const propMeta = {}; // check if property is object let value = instance ? instance[propertyName] : undefined; let typeHint = typeof value; const reflectMetadataType = (0, functions_1.getReflectedType)(clazz, propertyName); if (this.options.typeHint && (0, lodash_1.isFunction)(this.options.typeHint)) { typeHint = this.options.typeHint(klass, propertyName, instance, value); } else { if (typeHint === Constants_1.T_OBJECT) { if (reflectMetadataType && reflectMetadataType !== Constants_1.T_OBJECT) { typeHint = reflectMetadataType; } else { if (value === null || value === undefined) { typeHint = this.getDefaultTypeHint(); } else if (value === false || value === true) { typeHint = Constants_1.T_BOOLEAN; } else { typeHint = Reflect.getPrototypeOf(value)?.constructor; if (typeHint && typeHint.name) { if (typeHint.name === Object.name) { typeHint = Constants_1.T_OBJECT; } else if (typeHint.name === Array.name) { typeHint = Constants_1.T_ARRAY; } } else { typeHint = this.getDefaultTypeHint(); } } } } } let target = null; if (typeHint) { if ((0, lodash_1.isString)(typeHint)) { propMeta.type = typeHint; if (propMeta.type === Constants_1.T_ARRAY) { (0, functions_1.setDefaultArray)(propMeta); } } else if ((0, lodash_1.isFunction)(typeHint)) { if (typeHint === Date) { // propMeta.type = 'date' as any; propMeta.type = Constants_1.T_STRING; propMeta.format = 'date-time'; } else { const name = base_1.ClassUtils.getClassName(typeHint); if (name === '' || name === Function.name) { // Function passing the parameter type // propMeta.$target = typeHint(); target = typeHint(); } else { // propMeta.$target = typeHint as Function; target = typeHint; } // maybe follow the object here if (target.name === Array.name) { (0, functions_1.setDefaultArray)(propMeta); } else { propMeta.type = Constants_1.T_OBJECT; } } } } if (!propMeta.type || ((0, lodash_1.isString)(propMeta.type) && (0, lodash_1.isEmpty)(propMeta.type))) { if (reflectMetadataType) { const className = base_1.ClassUtils.getClassName(reflectMetadataType); if (JsonSchema7_1.JSON_SCHEMA_7_TYPES.includes(className.toLowerCase())) { propMeta.type = className.toLowerCase(); } else if (className === Array.name) { (0, functions_1.setDefaultArray)(propMeta); } else { propMeta.type = Constants_1.T_OBJECT; target = reflectMetadataType; } } else { // not reflection data propMeta.type = this.getDefaultTypeHint(); } } if (target) { if (this.isAppendTargetSet()) { propMeta.$target = target; } const className = base_1.ClassUtils.getClassName(target); let ref = '#/definitions/' + className; if (propMeta.type === Constants_1.T_ARRAY) { propMeta.items = { $ref: ref }; } else { propMeta['$ref'] = ref; delete propMeta['type']; } if (this.data && !this.data.definitions[className]) { this.describeClass(target); } } if (!((0, lodash_1.isUndefined)(value) || (0, lodash_1.isNull)(value))) { propMeta.default = SchemaUtils_1.SchemaUtils.normValue(value); } this.propertyPostproces(propMeta); const data = MetadataRegistry_1.MetadataRegistry.$().find(Constants_1.METATYPE_PROPERTY, (x) => x.target === clazz && x.propertyName === propertyName); this.appendAdditionalOptions(propMeta, data); return propMeta; } appendAdditionalOptions(propMeta, data) { if (data && (0, lodash_1.keys)(data).length > 0) { const metaType = data.metaType; if (metaType === Constants_1.METATYPE_PROPERTY) { if ((0, IClassRef_1.isClassRef)(data.type) || (0, IEntityRef_1.isEntityRef)(data.type)) { // if reference ignore adding if ((0, lodash_1.get)(this.options, 'deleteReferenceKeys', true)) { return; } } } const _keys = (0, lodash_1.keys)(data); for (const k of _keys) { if (this.options.keysToSkip.includes(k)) { continue; } if (!propMeta[k] || (0, lodash_1.get)(this.options, 'allowKeyOverride', false)) { propMeta[k] = data[k]; } } } } /** * General handle to check allow options method * * @param entry */ allowed(entry, klass) { if (this.options && this.options.allowedProperty && (0, lodash_1.isFunction)(this.options.allowedProperty)) { return this.options.allowedProperty(entry, klass); } return true; } describePropertyForRef(klass, property) { const propMeta = {}; if (property.isCollection()) { (0, functions_1.setDefaultArray)(propMeta); if (property.isReference()) { this.describeTargetRef(property, propMeta, 'collection'); } else { const normedType = this.getNormedType(property); propMeta.items = { type: normedType }; this.propertyPostproces(propMeta.items); } } else { propMeta.type = Constants_1.T_OBJECT; if (property.isReference()) { this.describeTargetRef(property, propMeta, 'single'); } else { const normedType = this.getNormedType(property); propMeta.type = normedType; this.propertyPostproces(propMeta); } } const data = property.getOptions(); this.appendAdditionalOptions(propMeta, data); // pass data pattern property for later correct selection if (data[Constants_1.K_PATTERN_PROPERTY]) { propMeta[Constants_1.K_PATTERN_PROPERTY] = true; } if (this.options.postProcess) { this.options.postProcess(property, propMeta, this); } return propMeta; } describeTargetRef(property, propMeta, mode) { let targetRef = property.getTargetRef(); if ((0, lodash_1.get)(this.options, 'deleteReferenceKeys', true)) { (0, lodash_1.keys)(propMeta).map(k => delete propMeta[k]); } else { this.options.keysToSkip.map(x => delete propMeta[x]); } if (targetRef.hasEntityRef()) { targetRef = targetRef.getEntityRef(); if (mode === 'collection') { propMeta.type = Constants_1.T_ARRAY; propMeta.items = { $ref: '#/definitions/' + targetRef.name }; } else { propMeta.$ref = '#/definitions/' + targetRef.name; } if (this.data && !this.data.definitions[targetRef.name]) { this.describeEntityRef(targetRef); } } else { if (mode === 'collection') { propMeta.type = Constants_1.T_ARRAY; propMeta.items = { $ref: '#/definitions/' + targetRef.name }; } else { propMeta.$ref = '#/definitions/' + targetRef.name; } if (this.data && !this.data.definitions[targetRef.name]) { this.describeClassRef(targetRef); } } } propertyPostproces(opt) { if (opt.type === 'date') { opt.type = Constants_1.T_STRING; opt.format = 'date-time'; } } getNormedType(property) { let retType = null; const type = property.getType(); if (this.options.typeConversion) { retType = this.options.typeConversion(type, property); } if (!retType) { retType = (0, lodash_1.isFunction)(type) ? type.name.toLowerCase() : type; } return retType; } getDefaultTypeHint() { return this.options && this.options.defaultTypeHint ? this.options.defaultTypeHint : Constants_1.T_STRING; } } exports.JsonSchema7Serializer = JsonSchema7Serializer; //# sourceMappingURL=JsonSchema7Serializer.js.map