UNPKG

@kitmi/types

Version:

JavaScript semantic data types

244 lines (207 loc) 7.89 kB
import _each from 'lodash/each'; import _every from 'lodash/every'; import _mapValues from 'lodash/mapValues'; import { ValidationError } from './errors'; import { makePath } from '@kitmi/utils/objectPathUtils'; import isPlainObject from '@kitmi/utils/isPlainObject'; import batchAsync_ from '@kitmi/utils/batchAsync_'; import findAsync_ from '@kitmi/utils/findAsync_'; const jsonStarter = new Set(['"', '[', '{']); const jsonEnding = { '"': '"', '[': ']', '{': '}', }; class T_OBJECT { name = 'object'; alias = ['json']; primitive = true; defaultValue = {}; _sanitizeMember = (value, opts, newValue) => (validationObject, fieldName) => { const fieldValue = value[fieldName]; const fieldPath = makePath(opts.path, fieldName); let _fieldValue; if (Array.isArray(validationObject)) { const errors = []; const foundMatched = validationObject.find((_validationObject) => { try { _fieldValue = this.system.sanitize(fieldValue, _validationObject, opts.i18n, { root: opts.root, parent: value, path: fieldPath, }); return true; } catch (error) { errors.push(ValidationError.extractFromError(error)); return false; } }); if (foundMatched == null) { throw new ValidationError('Object member schema validation failed.', { value: fieldValue, path: fieldPath, errors, }); } } else { _fieldValue = this.system.sanitize(fieldValue, validationObject, opts.i18n, { root: opts.root, parent: value, path: fieldPath, }); } if (_fieldValue != null || fieldName in value) { newValue[fieldName] = _fieldValue; } }; _sanitizeMember_ = (value, opts, newValue) => async (validationObject, fieldName) => { const fieldValue = value[fieldName]; const fieldPath = makePath(opts.path, fieldName); let _fieldValue; if (Array.isArray(validationObject)) { const errors = []; const foudMatched = await findAsync_(validationObject, async (_validationObject) => { try { _fieldValue = await this.system.sanitize_(fieldValue, _validationObject, opts.i18n, { root: opts.root, parent: value, path: fieldPath, }); return true; } catch (error) { errors.push(ValidationError.extractFromError(error)); return false; } }); if (foudMatched == null) { throw new ValidationError('Object member schema validation failed.', { value: fieldValue, path: fieldPath, errors, }); } } else { _fieldValue = await this.system.sanitize_(fieldValue, validationObject, opts.i18n, { root: opts.root, parent: value, path: fieldPath, }); } if (_fieldValue != null || fieldName in value) { newValue[fieldName] = _fieldValue; } }; constructor(system) { this.system = system; } validate(value) { return isPlainObject(value); } _sanitize(value, meta, opts) { const type = typeof value; if (type === 'string') { if (value.length > 1 && jsonStarter.has(value[0]) && jsonEnding[value[0]] === value[value.length - 1]) { value = JSON.parse(value); } } if (meta.schema) { if (typeof value !== 'object') { throw new ValidationError('Invalid object value.', { value: opts.rawValue, path: opts.path, }); } let schema = typeof meta.schema === 'function' ? meta.schema() : meta.schema; let newValue; if (Array.isArray(schema)) { const errors = []; const pass = schema.find((altSchema) => { newValue = {}; try { _each(altSchema, this._sanitizeMember(value, opts, newValue)); return true; } catch (error) { errors.push(ValidationError.extractFromError(error)); return false; } }); if (pass == null) { throw new ValidationError('Object schema validation failed.', { value: opts.rawValue, path: opts.path, errors, }); } } else { newValue = {}; _each(schema, this._sanitizeMember(value, opts, newValue)); } if (meta.keepUnsanitized) { return { ...value, ...newValue }; } return newValue; } const { valueSchema, ..._meta } = meta; if (valueSchema) { const schema = _mapValues(value, () => valueSchema); return this._sanitize(value, { schema, ..._meta }, opts); } return value; } async _sanitizeAsync(value, meta, opts) { const type = typeof value; if (type === 'string') { if (value.length > 1 && jsonStarter.has(value[0]) && jsonEnding[value[0]] === value[value.length - 1]) { value = JSON.parse(value); } } if (meta.schema) { if (typeof value !== 'object') { throw new ValidationError('Invalid object value.', { value: opts.rawValue, path: opts.path, }); } const schema = typeof meta.schema === 'function' ? meta.schema() : meta.schema; let newValue; if (Array.isArray(schema)) { const errors = []; const pass = await findAsync_(schema, async (altSchema) => { newValue = {}; try { await batchAsync_(altSchema, this._sanitizeMember_(value, opts, newValue)); return true; } catch (error) { errors.push(ValidationError.extractFromError(error)); return false; } }); if (pass == null) { throw new ValidationError('Object schema validation failed.', { value: opts.rawValue, path: opts.path, errors, }); } } else { newValue = {}; await batchAsync_(schema, this._sanitizeMember_(value, opts, newValue)); } if (meta.keepUnsanitized) { return { ...value, ...newValue }; } return newValue; } const { valueSchema, ..._meta } = meta; if (valueSchema) { const schema = _mapValues(value, () => valueSchema); return this._sanitizeAsync(value, { schema, ..._meta }, opts); } return value; } serialize(value) { if (value == null) return null; return this.system.safeJsonStringify(value); } } export default T_OBJECT;