UNPKG

uniforms-bridge-json-schema

Version:

JSONSchema schema bridge for uniforms.

296 lines (295 loc) 12.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const invariant_1 = tslib_1.__importDefault(require("invariant")); const cloneDeep_1 = tslib_1.__importDefault(require("lodash/cloneDeep")); const get_1 = tslib_1.__importDefault(require("lodash/get")); const isEmpty_1 = tslib_1.__importDefault(require("lodash/isEmpty")); const lowerCase_1 = tslib_1.__importDefault(require("lodash/lowerCase")); const memoize_1 = tslib_1.__importDefault(require("lodash/memoize")); const upperFirst_1 = tslib_1.__importDefault(require("lodash/upperFirst")); const uniforms_1 = require("uniforms"); function fieldInvariant(name, condition) { (0, invariant_1.default)(condition, 'Field not found in schema: "%s"', name); } function resolveRef(reference, schema) { (0, invariant_1.default)(reference.startsWith('#'), 'Reference is not an internal reference, and only such are allowed: "%s"', reference); const resolvedReference = reference .split('/') .filter(part => part && part !== '#') .reduce((definition, next) => definition[next], schema); (0, invariant_1.default)(resolvedReference, 'Reference not found in schema: "%s"', reference); return resolvedReference; } function resolveRefIfNeeded(partial, schema) { if (!('$ref' in partial)) { return partial; } const { $ref } = partial, partialWithoutRef = tslib_1.__rest(partial, ["$ref"]); return resolveRefIfNeeded( // @ts-expect-error The `partial` and `schema` should be typed more precisely. Object.assign({}, partialWithoutRef, resolveRef($ref, schema)), schema); } const partialNames = ['allOf', 'anyOf', 'oneOf']; const propsToRemove = [ 'default', 'enum', 'format', 'isRequired', 'title', 'uniforms', ]; const propsToRename = [ ['maxItems', 'maxCount'], ['maximum', 'max'], ['minItems', 'minCount'], ['minimum', 'min'], ['multipleOf', 'step'], ]; function pathToName(path) { path = path.startsWith('/') ? path.replace(/\//g, '.').replace(/~0/g, '~').replace(/~1/g, '/') : path .replace(/\[('|")(.+?)\1\]/g, '.$2') .replace(/\[(.+?)\]/g, '.$1') .replace(/\\'/g, "'"); return path.slice(1); } function isValidatorResult(value) { return (typeof value === 'object' && value !== null && Array.isArray(value.details)); } class JSONSchemaBridge extends uniforms_1.Bridge { constructor({ provideDefaultLabelFromFieldName = true, schema, validator, }) { super(); this.provideDefaultLabelFromFieldName = provideDefaultLabelFromFieldName; this.schema = resolveRefIfNeeded(schema, schema); this._compiledSchema = { '': this.schema }; this.validator = validator; // Memoize for performance and referential equality. this.getField = (0, memoize_1.default)(this.getField.bind(this)); this.getInitialValue = (0, memoize_1.default)(this.getInitialValue.bind(this)); this.getSubfields = (0, memoize_1.default)(this.getSubfields.bind(this)); this.getType = (0, memoize_1.default)(this.getType.bind(this)); } getError(name, error) { const details = isValidatorResult(error) && error.details; if (!details) { return null; } const nameParts = (0, uniforms_1.joinName)(null, name).map(uniforms_1.joinName.unescape); const unescapedName = (0, uniforms_1.joinName)(nameParts); const rootName = (0, uniforms_1.joinName)(nameParts.slice(0, -1)); const baseName = nameParts[nameParts.length - 1]; const scopedError = details.find(error => { var _a; const rawPath = (_a = error.instancePath) !== null && _a !== void 0 ? _a : error.dataPath; const path = rawPath ? pathToName(rawPath) : ''; return (unescapedName === path || (rootName === path && error.params && baseName === error.params.missingProperty)); }); return scopedError || null; } getErrorMessage(name, error) { const scopedError = this.getError(name, error); return (scopedError === null || scopedError === void 0 ? void 0 : scopedError.message) || ''; } getErrorMessages(error) { if (!error) { return []; } if (isValidatorResult(error)) { const { details } = error; return details.map(error => error.message || ''); } if (error instanceof Error) { return [error.message]; } if (typeof error === 'object') { return []; } return [String(error)]; } getField(name) { return (0, uniforms_1.joinName)(null, name).reduce((definition, next, index, array) => { var _a, _b, _c; var _d; const prevName = (0, uniforms_1.joinName)(array.slice(0, index)); const nextName = (0, uniforms_1.joinName)(prevName, next); const definitionCache = ((_a = (_d = this._compiledSchema)[nextName]) !== null && _a !== void 0 ? _a : (_d[nextName] = {})); definitionCache.isRequired = !!(((_b = definition.required) === null || _b === void 0 ? void 0 : _b.includes(next)) || ((_c = this._compiledSchema[prevName].required) === null || _c === void 0 ? void 0 : _c.includes(next))); if (next === '$' || next === '' + parseInt(next, 10)) { fieldInvariant(name, definition.type === 'array'); definition = Array.isArray(definition.items) ? definition.items[parseInt(next, 10)] : definition.items; fieldInvariant(name, !!definition); } else if (definition.type === 'object') { fieldInvariant(name, !!definition.properties); definition = definition.properties[uniforms_1.joinName.unescape(next)]; fieldInvariant(name, !!definition); } else { let nextFound = false; partialNames.forEach(partialName => { var _a; (_a = definition[partialName]) === null || _a === void 0 ? void 0 : _a.forEach((partialElement) => { if (!nextFound) { partialElement = resolveRefIfNeeded(partialElement, this.schema); if (next in partialElement.properties) { definition = partialElement.properties[next]; nextFound = true; } } }); }); fieldInvariant(name, nextFound); } definition = resolveRefIfNeeded(definition, this.schema); // Naive computation of combined type, properties and required. const required = definition.required ? definition.required.slice() : []; const properties = definition.properties ? Object.assign({}, definition.properties) : {}; partialNames.forEach(partialName => { var _a; (_a = definition[partialName]) === null || _a === void 0 ? void 0 : _a.forEach((partial) => { partial = resolveRefIfNeeded(partial, this.schema); if (partial.required) { required.push(...partial.required); } Object.assign(properties, partial.properties); if (!definitionCache.type && partial.type) { definitionCache.type = partial.type; } }); }); if (required.length > 0) { definitionCache.required = required; } if (!(0, isEmpty_1.default)(properties)) { definitionCache.properties = properties; } return definition; }, this.schema); } getInitialValue(name) { var _a; const field = this.getField(name); const { default: defaultValue = (_a = field.default) !== null && _a !== void 0 ? _a : (0, get_1.default)(this.schema.default, name), type = field.type, } = this._compiledSchema[name]; if (defaultValue !== undefined) { return (0, cloneDeep_1.default)(defaultValue); } if (type === 'array') { if (!field.minItems) { return []; } const item = this.getInitialValue((0, uniforms_1.joinName)(name, '$')); if (item === undefined) { return []; } return Array.from({ length: field.minItems }, () => item); } if (type === 'object') { const value = {}; this.getSubfields(name).forEach(key => { const initialValue = this.getInitialValue((0, uniforms_1.joinName)(name, key)); if (initialValue !== undefined) { value[key] = initialValue; } }); return value; } return undefined; } getProps(name) { var _a, _b; const field = this.getField(name); const props = Object.assign({}, field, field.uniforms, this._compiledSchema[name]); (_a = props.label) !== null && _a !== void 0 ? _a : (props.label = props.title); if (this.provideDefaultLabelFromFieldName && props.label === undefined) { props.label = (0, upperFirst_1.default)((0, lowerCase_1.default)((0, uniforms_1.joinName)(null, name).slice(-1)[0])); } if (field.type === 'number') { props.decimal = true; } if (((_b = field.uniforms) === null || _b === void 0 ? void 0 : _b.type) !== undefined) { props.type = field.uniforms.type; } if (props.required === undefined) { props.required = props.isRequired; } if (props.type === field.type) { delete props.type; } let options = props.options; if (options) { if (!Array.isArray(options)) { options = Object.entries(options).map(([key, value]) => ({ key, label: key, value, })); } } else if (props.enum) { options = Object.values(props.enum).map(value => ({ value })); } propsToRename.forEach(([key, newKey]) => { if (key in props) { props[newKey] = props[key]; delete props[key]; } }); propsToRemove.forEach(key => { if (key in props) { delete props[key]; } }); return Object.assign(props, { options }); } getSubfields(name = '') { const field = this.getField(name); const { properties = field.properties, type = field.type } = this._compiledSchema[name]; if (type === 'object' && properties) { return Object.keys(properties).map(uniforms_1.joinName.escape); } return []; } getType(name) { const { type: _type, format: fieldFormat } = this.getField(name); const { type: fieldType = _type } = this._compiledSchema[name]; if (fieldFormat === 'date-time') { return Date; } if (fieldType === 'string') { return String; } if (fieldType === 'number') { return Number; } if (fieldType === 'integer') { return Number; } if (fieldType === 'object') { return Object; } if (fieldType === 'array') { return Array; } if (fieldType === 'boolean') { return Boolean; } (0, invariant_1.default)(fieldType !== 'null', 'Field "%s" can not be represented as a type null', name); return fieldType; } getValidator() { return this.validator; } } exports.default = JSONSchemaBridge;