UNPKG

@orbit/records

Version:

A flexible data access and synchronization layer.

171 lines (153 loc) 4.45 kB
import { Assertion } from '@orbit/core'; import { StandardValidator, ValidationIssue, Validator, ValidatorForFn } from '@orbit/validators'; import { RecordIdentity } from '../record'; import { AttributeDefinition, RecordSchema } from '../record-schema'; import { RecordFieldDefinitionIssue, RecordFieldDefinitionValidator } from './record-field-definition-validator'; import { StandardRecordValidators, StandardRecordValidator } from './standard-record-validators'; interface BaseIssue extends ValidationIssue { validator: StandardRecordValidators.RecordAttribute; ref: { record: RecordIdentity; attribute: string; value?: unknown; }; } interface TypeIssue extends BaseIssue { validation: 'type'; } interface ValueRequiredIssue extends BaseIssue { validation: 'valueRequired'; } interface ValueNotNullIssue extends BaseIssue { validation: 'valueNotNull'; } interface ValueValidIssue extends BaseIssue { validation: 'valueValid'; details: ValidationIssue[]; } export interface RecordAttributeInput { record: RecordIdentity; attribute: string; value: unknown; } export type RecordAttributeValidationIssue = | TypeIssue | RecordFieldDefinitionIssue | ValueRequiredIssue | ValueNotNullIssue | ValueValidIssue; export interface RecordAttributeValidationOptions { validatorFor: ValidatorForFn<StandardValidator | StandardRecordValidator>; schema?: RecordSchema; attributeDef?: AttributeDefinition; } export type RecordAttributeValidator = Validator< RecordAttributeInput, RecordAttributeValidationOptions, RecordAttributeValidationIssue >; export const validateRecordAttribute: RecordAttributeValidator = ( input: RecordAttributeInput, options?: RecordAttributeValidationOptions ): undefined | RecordAttributeValidationIssue[] => { if (options?.validatorFor === undefined) { throw new Assertion('validateRecordAttribute requires a `validatorFor`'); } const { record, attribute, value } = input; const { type } = record; const { validatorFor, schema } = options; let { attributeDef } = options; // Validate attribute definition, if one is not provided if (attributeDef === undefined) { if (schema === undefined) { throw new Assertion( 'validateRecordAttribute requires either a `schema` or `attributeDef`' ); } const validateRecordFieldDefinition = validatorFor( StandardRecordValidators.RecordFieldDefinition ) as RecordFieldDefinitionValidator; const defIssues = validateRecordFieldDefinition( { kind: 'attribute', type, field: attribute }, { schema } ); if (defIssues) return defIssues; attributeDef = schema.getAttribute(type, attribute); } // Validate value if (value === undefined) { if (attributeDef.validation?.required) { return [ { validator: StandardRecordValidators.RecordAttribute, validation: 'valueRequired', description: 'value is required', ref: { record, attribute, value } } ]; } } else if (value === null) { if (attributeDef.validation?.notNull) { return [ { validator: StandardRecordValidators.RecordAttribute, validation: 'valueNotNull', description: 'value can not be null', ref: { record, attribute, value } } ]; } } else if (attributeDef.type) { const validateRecordValue = validatorFor(attributeDef.type) as Validator; if (validateRecordValue === undefined) { return [ { validator: StandardRecordValidators.RecordAttribute, ref: { record, attribute, value }, validation: 'type', description: `validator has not been provided for attribute '${attribute}' of \`type\` '${attributeDef.type}'` } ]; } else { const valueIssues = validateRecordValue(value, attributeDef.validation); if (valueIssues) { return [ { validator: StandardRecordValidators.RecordAttribute, ref: { record, attribute, value }, validation: 'valueValid', description: 'value is invalid', details: valueIssues } ]; } } } };