UNPKG

@allgemein/schema-api

Version:
509 lines 20.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.JsonSchema7Unserializer = void 0; const lodash_1 = require("lodash"); const Constants_1 = require("./Constants"); const JsonSchema_1 = require("./JsonSchema"); const RegistryFactory_1 = require("../registry/RegistryFactory"); const Constants_2 = require("../Constants"); const IEntityRef_1 = require("../../api/IEntityRef"); const ClassRef_1 = require("../ClassRef"); const base_1 = require("@allgemein/base"); const MetadataRegistry_1 = require("../registry/MetadataRegistry"); const IParseOptions_1 = require("./IParseOptions"); const SchemaUtils_1 = require("../SchemaUtils"); const skipKeys = ['$id', 'id', 'title', 'type', 'properties', 'allOf', 'anyOf', '$schema', 'patternProperties']; class JsonSchema7Unserializer { constructor(opts) { this.version = Constants_1.DRAFT_07; this.classRefs = []; this.fetched = {}; this.reference = {}; this.issues = []; this.options = opts; } uri() { return `http://json-schema.org/${Constants_1.DRAFT_07}/schema`; } async unserialize(data) { this.data = data; const isRoot = (0, lodash_1.get)(this.options, 'rootAsEntity', true); const opts = { isRoot: isRoot }; IParseOptions_1.PARSE_OPTIONS_KEYS.forEach(x => { if ((0, lodash_1.has)(this.options, x)) { opts[x] = this.options[x]; } }); const ret = this.options.return ? this.options.return : 'default'; return this.parse(data, opts).then(refs => { switch (ret) { case 'class-refs': return this.classRefs; case 'entity-refs': return this.classRefs.filter(x => x.hasEntityRef()).map(x => x.getEntityRef()); default: return refs; } }); } /** * Return namespace if set in passed options or if used in json-schema object. * If no match return default namespace */ getNamespace() { return (0, lodash_1.get)(this.options, 'namespace', (0, lodash_1.get)(this.data, 'namespace', Constants_2.DEFAULT_NAMESPACE)); } /** * Get registry containing elements */ getRegistry(ns = null) { return RegistryFactory_1.RegistryFactory.get(ns ? ns : this.getNamespace()); } getIssues() { return this.issues; } /** * If collectors are defined use them before default * * * @param type * @param key * @param data * @param options * @private */ collectOptions(key, data, options = {}) { const type = (0, lodash_1.get)(options, 'metaType', Constants_2.METATYPE_ENTITY); let ret = null; if (!(0, lodash_1.isUndefined)(data[key]) || !(0, lodash_1.isNull)(data[key])) { ret = {}; ret[key] = data[key]; } if ((0, lodash_1.has)(this.options, 'collector') && this.options.collector.length > 0) { const methods = this.options.collector.filter(x => x.type === type && x.key === key); if (methods) { ret = (0, lodash_1.assign)(ret, ...methods.map(e => e.fn.apply(null, [key, data, options]))); } } return ret; } getClassRef(className, namespace) { if (!namespace) { namespace = this.getNamespace(); } return ClassRef_1.ClassRef.get(className, namespace); } async parse(data, options = null) { let ret = null; if (data.type) { if (data.type === Constants_2.T_OBJECT) { ret = await this.parseTypeObject(data, options); if (ret) { if (options.$ref) { // finish reference if waiting this.reference[options.$ref] = ret; delete options.$ref; } if (this.hasProperties(data)) { await this.parseProperties(ret, data.properties, options); } if (this.hasPatternProperties(data)) { options.isPattern = true; await this.parseProperties(ret, data.patternProperties, options); } } } else { throw new Error('type is not supported at this place'); } } else if (data.$ref) { return this.parseRef(data, options); } else if (data.anyOf && options.isRoot) { // loading multi schema ret = []; for (const anyEntry of data.anyOf) { ret.push(await this.parse(anyEntry, options)); } } else if (data.definitions && (0, lodash_1.keys)(data.definitions).length === 0) { // doing nothing no entries found } else { throw new Error('no valid schema element for further parse, key with name type or $ref must be present.'); } return ret; } async parseRef(data, options) { // refers let ret = null; if ((0, lodash_1.has)(this.reference, data.$ref)) { ret = this.reference[data.$ref]; } else { const res = await this.followRef(data.$ref, this.data); const opts = (0, lodash_1.clone)(options); opts.$ref = data.$ref; if (!opts.className) { opts.className = this.getClassNameFromRef(data.$ref); } ret = await this.parse(res, opts); } return ret; } getDefinitionsKey(data) { for (const k of (0, lodash_1.keys)(data)) { if (k !== 'properties' && (0, lodash_1.isObjectLike)(data[k])) { const exists = (0, lodash_1.keys)(data[k]).find(x => (0, lodash_1.has)(data[k][x], 'type')); if (exists) { return k; } } } return 'definition'; } async followRef($ref, data) { let [addr, anchor] = $ref.split('#'); if (!anchor) { anchor = ''; } if ((0, lodash_1.isEmpty)(addr)) { // local if ((0, lodash_1.isEmpty)(anchor)) { if (data.$ref) { return this.followRef(data.$ref, data); } else { return data; } } else if (/^\//.test(anchor)) { // starts with path const dottedPath = anchor.substr(1).replace(/\//, '.'); const entry = (0, lodash_1.get)(data, dottedPath, null); if (entry) { return entry; } } else { // refs to local id const refHashed = '#' + anchor; if (data.$id === refHashed) { return data; } else { const defKey = this.getDefinitionsKey(data); if (data[defKey]) { for (const k of (0, lodash_1.keys)(data[defKey])) { // @ts-ignore const x = data[defKey][k]; if (x && x.$id && x.$id === refHashed) { return x; } } } } } } else { // remote fetch url if (!this.fetched[addr]) { this.fetched[addr] = await JsonSchema_1.JsonSchema.request(addr, { cwd: this.options.cwd }); } return this.followRef('#' + anchor, this.fetched[addr]); } // TODO create exception throw new Error($ref + ' not found'); } async parseProperties(classRef, properties, options = null) { const _classRef = (0, IEntityRef_1.isEntityRef)(classRef) ? classRef.getClassRef() : classRef; const propertyNames = (0, lodash_1.keys)(properties); for (const propertyName of propertyNames) { await this.parseProperty(_classRef, propertyName, properties[propertyName], options); } } async parseProperty(classRef, propertyName, data, options = null) { const _classRef = (0, IEntityRef_1.isEntityRef)(classRef) ? classRef.getClassRef() : classRef; const propRefExits = _classRef.getRegistry() .find(Constants_2.METATYPE_PROPERTY, (x) => x.getClassRef() === _classRef && x.name === propertyName); const propOptions = { metaType: Constants_2.METATYPE_PROPERTY, propertyName: propertyName, target: _classRef.getClass(true), // default type is object type: Constants_2.T_OBJECT, }; if (options.isPattern) { propOptions[Constants_2.K_PATTERN_PROPERTY] = true; } if ((0, lodash_1.isObjectLike)(data)) { const parseOptions = { isProperty: true, sourceRef: _classRef, propertyName: propertyName, isRoot: false, metaType: 'property', // isPattern: options.isPattern ? true : false }; const dataPointer = data; let collectOptions = {}; collectOptions = this.collectAndProcess(dataPointer, collectOptions, ['type', '$ref', '$schema', '$id'], parseOptions); (0, lodash_1.assign)(propOptions, collectOptions); if (dataPointer.$ref) { const ref = await this.parse(dataPointer, parseOptions); propOptions.type = ref; if (options.isPattern) { propOptions[Constants_2.K_PATTERN_PROPERTY] = true; } } else if (dataPointer.type) { await this.onTypes(dataPointer, propOptions, parseOptions); } } else { // boolean throw new base_1.NotSupportedError('passed boolean value as definition not supported'); } if (!propRefExits || (0, lodash_1.get)(this.options, 'forcePropertyRefCreation', false)) { // remove properties key if exists delete propOptions.properties; try { _classRef.getRegistry().create(Constants_2.METATYPE_PROPERTY, propOptions); } catch (e) { this.issues.push({ msg: e.message, level: 'error' }); } } else if (propRefExits) { propRefExits.setOptions(propOptions); } } hasProperties(datapointer) { return (0, lodash_1.has)(datapointer, 'properties'); } hasPatternProperties(datapointer) { return (0, lodash_1.has)(datapointer, 'patternProperties'); } async onTypes(dataPointer, propOptions, options) { let type = dataPointer.type; if ((0, lodash_1.isString)(type)) { type = type.toLowerCase(); } switch (type) { case Constants_2.T_STRING: const res = this.onTypeString(dataPointer); (0, lodash_1.assign)(propOptions, res); break; case 'boolean': propOptions.type = 'boolean'; break; case 'integer': propOptions.type = 'number'; break; case 'number': propOptions.type = 'number'; break; case Constants_2.T_OBJECT: propOptions.type = Constants_2.T_OBJECT; if (this.hasProperties(dataPointer) || this.hasPatternProperties(dataPointer)) { propOptions.type = await this.parse(dataPointer, options); } break; case Constants_2.T_ARRAY: // check items if (dataPointer.minItems || dataPointer.maxItems) { propOptions.cardinality = { min: (0, lodash_1.get)(dataPointer, 'minItems', 0), max: (0, lodash_1.get)(dataPointer, 'maxItems', 0), }; } else { propOptions.cardinality = 0; } if (dataPointer.items) { if ((0, lodash_1.isArray)(dataPointer.items)) { throw new base_1.NotSupportedError('tuple values for array is not supported'); } else if ((0, lodash_1.isObjectLike)(dataPointer.items)) { options.asArray = true; const items = dataPointer.items; if (items.$ref) { propOptions.type = await this.parse(items, options); } else if (items.type === Constants_2.T_OBJECT) { propOptions.type = items.type; if (this.hasProperties(items) || this.hasPatternProperties(items)) { propOptions.type = await this.parse(items, options); } } else { propOptions.type = items.type; } if (options.isPattern) { propOptions[Constants_2.K_PATTERN_PROPERTY] = true; } } } break; case 'null': throw new base_1.NotSupportedError('null value is not supported for property'); } } onTypeString(dataPointer) { const propOptions = {}; propOptions.type = Constants_2.T_STRING; // or date check format if (dataPointer.format) { propOptions.format = dataPointer.format; switch (dataPointer.format) { case 'date-time': // set date type propOptions.type = 'date'; break; case 'date': // set date type propOptions.type = 'date'; break; } } return propOptions; } async parseInherits(classRef, data, key) { if ((0, lodash_1.has)(data, key)) { const values = data[key]; if ((0, lodash_1.isArray)(values)) { for (const inheritEntry of values) { const extend = await this.parse(inheritEntry, { isRoot: false }); classRef.addExtend(extend); } } else { throw new base_1.NotSupportedError('only array is allowed for ' + key); } } } async parseTypeObject(data, options = null) { let ret = null; const id = (0, lodash_1.get)(data, '$id', (0, lodash_1.get)(data, 'id', null)); let metaType = options.isRoot || !(0, lodash_1.isEmpty)(id) ? Constants_2.METATYPE_ENTITY : Constants_2.METATYPE_CLASS_REF; const title = (0, lodash_1.get)(data, 'title', null); // get namespace or override const namespace = !(0, lodash_1.get)(this.options, 'skipNamespace', false) ? (0, lodash_1.get)(data, '$' + Constants_2.METATYPE_NAMESPACE, this.getNamespace()) : this.getNamespace(); // check if class ref exists else create one or recreate if parse options are set let classRef = null; let className = null; if (options) { if (options.className) { className = options.className; } if (options.ref) { classRef = options.ref; className = options.ref.name; } } // title data override passed className if (title) { if (!className || !(0, lodash_1.get)(this.options, 'ignoreDeclared', false)) { className = (0, lodash_1.upperFirst)((0, lodash_1.camelCase)(title)); } } let entityName = metaType === Constants_2.METATYPE_ENTITY && id ? id.replace(/^#/, '') : className; if (!className && options?.isProperty && options.propertyName) { if ((0, lodash_1.get)(this.options, 'prependClass', false)) { className = [options.sourceRef.name, options.propertyName].map(x => (0, lodash_1.upperFirst)((0, lodash_1.camelCase)(x))).join(''); } else { className = (0, lodash_1.upperFirst)((0, lodash_1.camelCase)(options.propertyName)); } // pass property name as entity name if the object is declared as a property entityName = className; } if (className && !classRef) { const fn = (0, lodash_1.get)(this.options, 'forceClassRefCreation', false) ? SchemaUtils_1.SchemaUtils.clazz(className) : className; classRef = this.getClassRef(fn, namespace); } if (!classRef) { metaType = Constants_2.METATYPE_CLASS_REF; classRef = this.getClassRef(SchemaUtils_1.SchemaUtils.clazzAnonymous(), namespace); } // TODO check extentions! for (const inheritsKey of ['allOf', 'anyOf']) { if ((0, lodash_1.has)(data, inheritsKey)) { await this.parseInherits(classRef, data, inheritsKey); } } if (classRef) { // a named class ref exists if (!this.classRefs.find(x => x === classRef)) { this.classRefs.push(classRef); } const clazz = classRef.getClass(true); const refOptions = { name: entityName, namespace: this.getNamespace(), target: clazz }; const collectorOptions = (0, lodash_1.clone)(options); collectorOptions.metaType = metaType; collectorOptions.ref = classRef; this.collectAndProcess(data, refOptions, skipKeys, collectorOptions); metaType = collectorOptions.metaType; let entityOptions = MetadataRegistry_1.MetadataRegistry.$().find(metaType, (x) => x.target === clazz); if (entityOptions) { // merge existing (0, lodash_1.assign)(entityOptions, refOptions); } else { entityOptions = refOptions; refOptions.metaType = Constants_2.METATYPE_CLASS_REF; const existingOptions = classRef.getOptions(); classRef.setOptions((0, lodash_1.defaults)(refOptions, existingOptions)); } if (metaType === Constants_2.METATYPE_ENTITY) { ret = this.getRegistry(namespace).find(metaType, (x) => x.getClassRef() === classRef); if (!ret || (0, lodash_1.get)(this.options, 'forceEntityRefCreation', false)) { try { ret = this.getRegistry(namespace).create(metaType, entityOptions); } catch (e) { this.issues.push({ msg: e.message, level: 'error' }); } } } else { ret = classRef; } } else { throw new base_1.NotYetImplementedError(); } return ret; } collectAndProcess(data, collectingObject, skipKeys, collectorOptions) { const type = (0, lodash_1.get)(collectorOptions, 'metaType', Constants_2.METATYPE_ENTITY); const _keys = (0, lodash_1.keys)(data); for (const key of _keys) { const entry = this.collectOptions(key, data, collectorOptions); if (skipKeys.includes(key)) { continue; } if (entry && (0, lodash_1.isObjectLike)(entry)) { collectingObject = (0, lodash_1.assign)(collectingObject, entry); } } if ((0, lodash_1.has)(this.options, 'collector') && this.options.collector.length > 0) { const methods = this.options.collector.filter(x => x.type === type && (0, lodash_1.isUndefined)(x.key)); if (methods.length > 0) { collectingObject = (0, lodash_1.assign)(collectingObject, ...methods.map(e => e.fn.apply(null, [null, data, collectorOptions]))); } } return collectingObject; } getClassNameFromRef(data) { return data.split(/#|\//).pop(); } } exports.JsonSchema7Unserializer = JsonSchema7Unserializer; //# sourceMappingURL=JsonSchema7Unserializer.js.map