UNPKG

@storable/model

Version:

Base class providing typed properties, validation and serialization

180 lines (151 loc) 4.31 kB
import { mapFromOneOrMany } from '@storable/util'; import isEmpty from 'lodash/isEmpty'; import compact from 'lodash/compact'; import { createValue, serializeValue, normalizeValue } from './serialization'; import { runValidators, normalizeValidator, REQUIRED_VALIDATOR_NAME } from './validation'; export class Field { constructor(name, type, { default: defaultValue, validators = [], serializedName } = {}) { if (typeof name !== 'string' || !name) { throw new Error("'name' parameter is missing or invalid"); } if (typeof type !== 'string' || !type) { throw new Error("'type' parameter is missing or invalid"); } if (!Array.isArray(validators)) { throw new Error(`'validators' option must be an array (field: '${name}')`); } validators = validators.map(validator => mapFromOneOrMany(validator, validator => normalizeValidator(validator, { fieldName: name }))); this.name = name; this.serializedName = serializedName || name; this.type = type; let scalarType; let scalarIsOptional; let scalarValidators; const isArray = type.endsWith('[]'); if (isArray) { if (type.includes('?')) { throw new Error(`An array type cannot be optional (field: '${name}')`); } scalarType = type.slice(0, -2); const index = validators.findIndex(validator => Array.isArray(validator)); if (index !== -1) { scalarValidators = validators[index]; validators.splice(index, 1); } else { scalarValidators = []; } } else { scalarType = type; scalarValidators = validators; validators = []; if (scalarType.endsWith('?')) { scalarIsOptional = true; scalarType = scalarType.slice(0, -1); } } this.scalar = new Scalar(scalarType, { isOptional: scalarIsOptional, validators: scalarValidators }); this.validators = validators; this.isArray = isArray; if (defaultValue !== undefined) { this.default = defaultValue; } } createValue(value, { registry, isDeserializing }) { value = normalizeValue(value, { fieldName: this.name }); if (value === undefined) { return undefined; } if (this.isArray && !Array.isArray(value)) { throw new Error(`Type mismatch (field: '${this.name}', expected: 'Array', provided: '${typeof value}')`); } return mapFromOneOrMany(value, value => this.scalar.createValue(value, { registry, fieldName: this.name, isDeserializing })); } serializeValue(value, { filter, _level }) { return mapFromOneOrMany(value, value => this.scalar.serializeValue(value, { filter, _level })); } validateValue(value) { if (this.isArray) { const values = value; let failedValidators = runValidators(values, this.validators); const failedScalarValidators = values.map(value => this.scalar.validateValue(value)); if (!isEmpty(compact(failedScalarValidators))) { if (!failedValidators) { failedValidators = []; } failedValidators.push(failedScalarValidators); } return failedValidators; } return this.scalar.validateValue(value); } } class Scalar { constructor(type, { isOptional, validators }) { if (typeof type !== 'string' || !type) { throw new Error("'type' parameter is missing or invalid"); } this.type = type; this.isOptional = isOptional; this.validators = validators; } createValue(value, { registry, fieldName, isDeserializing }) { return createValue(value, { expectedType: this.type, registry, fieldName, isDeserializing }); } serializeValue(value, { filter, _level }) { return serializeValue(value, { filter, _level }); } validateValue(value) { if (value === undefined) { if (!this.isOptional) { return [REQUIRED_VALIDATOR_NAME]; } return undefined; } if (value.isOfType && value.isOfType('Model')) { return value.constructor.fieldValueIsSubmodel(value) ? value.getFailedValidators() : undefined; } return runValidators(value, this.validators); } }