/*! * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing * permissions and limitations under the License. */ // import Ajv from 'ajv'; import cloneDeep from 'lodash/cloneDeep.js'; import JSONPointer from './JSONPointer.js'; export default class JSONSchema { setSchema(schema) { this._schema = schema; this._fragmentsCache = new Map(); // this._ajv = new Ajv({ // useDefaults: true, // coerceTypes: true // }); // this._validate = this._ajv.compile(schema); } processData(data) { data = cloneDeep(data); // this._validate(data); return data; } // array of names of validation functions setSupportedValidationFunctions(supportedValidationFunctions) { this._supportedValidationFunctions = supportedValidationFunctions; } /* FYI - I was going to support "required" but then found out that server never sends it in its schema (it was a business decision. so leaving the code commented for now */ flattenSchema(supportedValidationFunctions) { if (supportedValidationFunctions) { this.setSupportedValidationFunctions(supportedValidationFunctions); } let genericFieldInfo = {}; if (this._schema && this._schema.properties) { this.travelProperties( this._schema.properties, genericFieldInfo /*, this._schema.required*/ ); } return { genericFieldInfo }; } extractGenericFieldInfo(item) { let validationsArr = []; let additionalInfo = { isValid: true, errorText: '' }; for (let value in item) { if (this._supportedValidationFunctions.includes(value)) { let validationItem = this.extractValidations(item, value); validationsArr[validationsArr.length] = validationItem; } else { let enumResult = this.extractEnum(item, value); if (enumResult !== null) { additionalInfo.enum = enumResult; } else { additionalInfo[value] = item[value]; } /*if (required.includes (property)) { additionalInfo[value].isRequired = true ; }*/ } } additionalInfo.validations = validationsArr; return additionalInfo; } extractValidations(item, value) { let validationItem; let data = item[value]; if (value === 'maximum') { if (item.exclusiveMaximum) { value = 'maximumExclusive'; } } if (value === 'minimum') { if (item.exclusiveMinimum) { value = 'minimumExclusive'; } } validationItem = { type: value, data: data }; return validationItem; } extractEnum(item, value) { let enumResult = null; if (value === 'type' && item[value] === 'array') { let items = item.items; if (items && items.enum && items.enum.length > 0) { let values = items.enum .filter(value => value) .map(value => ({ enum: value, title: value })); enumResult = values; } } else if (value === 'enum') { let items = item[value]; if (items && items.length > 0) { let values = items .filter(value => value) .map(value => ({ enum: value, title: value.toString() })); enumResult = values; } } return enumResult; } travelProperties( properties, genericFieldDefs, /*required = [],*/ pointer = '' ) { let newPointer = pointer; for (let property in properties) { newPointer = newPointer ? newPointer + '/' + property : property; if (properties[property].properties) { this.travelProperties( properties[property].properties, genericFieldDefs /*, properties[property].required*/, newPointer ); } else if (properties[property].$ref) { let fragment = this._getSchemaFragmentByRef( properties[property].$ref ); if (fragment.properties) { this.travelProperties( fragment.properties, genericFieldDefs /*, properties[property].required*/, newPointer ); } else { genericFieldDefs[newPointer] = this.extractGenericFieldInfo( fragment.properties ); } } else { genericFieldDefs[newPointer] = this.extractGenericFieldInfo( properties[property] ); } newPointer = pointer; } } getTitle(pointer) { return this._getSchemaFragment(pointer).title; } exists(pointer) { const fragment = this._getSchemaFragment(pointer); return !!fragment; } getDefault(pointer) { const fragment = this._getSchemaFragment(pointer); return fragment && fragment.default; } getEnum(pointer) { const fragment = this._getSchemaFragment(pointer); return ( fragment && (fragment.type === 'array' ? fragment.items.enum : fragment.enum) ); } isRequired(pointer) { const parentPointer = JSONPointer.extractParentPointer(pointer); const lastPart = JSONPointer.extractLastPart(pointer); let parentFragment = this._getSchemaFragment(parentPointer); return ( parentFragment && parentFragment.required && parentFragment.required.includes(lastPart) ); } isNumber(pointer) { const fragment = this._getSchemaFragment(pointer); return fragment && fragment.type === 'number'; } getMaxValue(pointer) { const fragment = this._getSchemaFragment(pointer); return fragment && fragment.exclusiveMaximum ? fragment.maximum - 1 : fragment.maximum; } getMinValue(pointer) { const fragment = this._getSchemaFragment(pointer); return fragment && fragment.exclusiveMinimum ? fragment.minimum : fragment.minimum; } isString(pointer) { const fragment = this._getSchemaFragment(pointer); return fragment && fragment.type === 'string'; } getPattern(pointer) { const fragment = this._getSchemaFragment(pointer); return fragment && fragment.pattern; } getMaxLength(pointer) { const fragment = this._getSchemaFragment(pointer); return fragment && fragment.maxLength; } getMinLength(pointer) { const fragment = this._getSchemaFragment(pointer); return fragment && fragment.minLength; } isArray(pointer) { const fragment = this._getSchemaFragment(pointer); return fragment && fragment.type === 'array'; } _getSchemaFragment(pointer) { if (this._fragmentsCache.has(pointer)) { return this._fragmentsCache.get(pointer); } let parts = JSONPointer.extractParts(pointer); let fragment = parts.reduce((fragment, part) => { if (fragment === undefined) { return undefined; } if (fragment.$ref) { fragment = this._getSchemaFragmentByRef(fragment.$ref); } switch (fragment.type) { case 'object': return fragment.properties && fragment.properties[part]; case 'array': return fragment.enum && fragment.enum[part]; default: // throw new Error(`Incorrect/unsupported JSONPointer "${pointer}" from "${part}"`); return undefined; } }, this._schema); while (fragment && fragment.$ref) { fragment = this._getSchemaFragmentByRef(fragment.$ref); } this._fragmentsCache.set(pointer, fragment); return fragment; } _getSchemaFragmentByRef($ref) { let pointer = $ref.substr(1); return JSONPointer.getValue(this._schema, pointer); // let fragmentAjv = new Ajv(); // fragmentAjv.addSchema(this._schema); // let compiledFragment = fragmentAjv.compile({$ref}); // let fragment = compiledFragment.refVal[compiledFragment.refs[$ref]]; // return fragment; } }