@orbit/records
Version:
A flexible data access and synchronization layer.
231 lines (211 loc) • 6.26 kB
text/typescript
import { Assertion } from '@orbit/core';
import {
ArrayValidator,
formatValidationDescription,
StandardValidator,
StandardValidators,
ValidationIssue,
Validator,
ValidatorForFn
} from '@orbit/validators';
import { RecordIdentity } from '../record';
import { RecordSchema, RelationshipDefinition } from '../record-schema';
import {
RecordFieldDefinitionIssue,
RecordFieldDefinitionValidator
} from './record-field-definition-validator';
import { RelatedRecordValidator } from './related-record-validator';
import {
StandardRecordValidator,
StandardRecordValidators
} from './standard-record-validators';
interface BaseIssue extends ValidationIssue {
validator: StandardRecordValidators.RecordRelationship;
ref: {
record: RecordIdentity;
relationship: string;
data?: RecordIdentity[] | RecordIdentity | null;
};
}
interface RequiredIssue extends BaseIssue {
validation: 'dataRequired';
}
interface NotNullIssue extends BaseIssue {
validation: 'dataNotNull';
}
interface DataValidIssue extends BaseIssue {
validation: 'dataValid';
details?: ValidationIssue[];
}
export interface RecordRelationshipInput {
record: RecordIdentity;
relationship: string;
data: RecordIdentity[] | RecordIdentity | null;
}
export type RecordRelationshipValidationIssue =
| RecordFieldDefinitionIssue
| RequiredIssue
| NotNullIssue
| DataValidIssue;
export interface RecordRelationshipValidationOptions {
validatorFor: ValidatorForFn<StandardValidator | StandardRecordValidator>;
schema: RecordSchema;
relationshipDef?: RelationshipDefinition;
}
export type RecordRelationshipValidator = Validator<
RecordRelationshipInput,
RecordRelationshipValidationOptions,
RecordRelationshipValidationIssue
>;
export const validateRecordRelationship: RecordRelationshipValidator = (
input: RecordRelationshipInput,
options?: RecordRelationshipValidationOptions
): undefined | RecordRelationshipValidationIssue[] => {
if (options?.validatorFor === undefined || options?.schema === undefined) {
throw new Assertion(
'validateRecordRelationship requires both a `validatorFor` and `schema`'
);
}
const { record, relationship, data } = input;
const { type } = record;
const { validatorFor, schema } = options;
let { relationshipDef } = options;
// Validate relationship definition, if one is not provided
if (relationshipDef === undefined) {
const validateRecordFieldDefinition = validatorFor(
StandardRecordValidators.RecordFieldDefinition
) as RecordFieldDefinitionValidator;
const defIssues = validateRecordFieldDefinition(
{ kind: 'relationship', type, field: relationship },
{ schema }
);
if (defIssues) return defIssues;
relationshipDef = schema.getRelationship(type, relationship);
}
// Validate data
if (data === undefined) {
if (relationshipDef.validation?.required) {
return [
{
validator: StandardRecordValidators.RecordRelationship,
validation: 'dataRequired',
description: 'data is required',
ref: {
record,
relationship,
data
}
}
];
}
} else {
const dataIssues: ValidationIssue[] = [];
const validateRelatedRecord = validatorFor(
StandardRecordValidators.RelatedRecord
) as RelatedRecordValidator;
if (relationshipDef.kind === 'hasMany') {
if (!Array.isArray(data)) {
return [
{
validator: StandardRecordValidators.RecordRelationship,
ref: {
record,
relationship,
data
},
validation: 'dataValid',
description: 'data for a hasMany relationship must be an array'
}
];
} else {
if (relationshipDef.validation) {
// Validate any array constraints
const validateArray = validatorFor(
StandardValidators.Array
) as ArrayValidator;
const arrayIssues = validateArray(data, relationshipDef.validation);
if (arrayIssues) dataIssues.push(...arrayIssues);
}
for (let identity of data) {
let issues = validateRelatedRecord(
{
record,
relationship,
relatedRecord: identity
},
{
validatorFor,
schema,
relationshipDef
}
);
if (issues) dataIssues.push(...issues);
}
}
} else if (relationshipDef.kind === 'hasOne') {
if (data === null) {
if (relationshipDef.validation?.notNull) {
return [
{
validator: StandardRecordValidators.RecordRelationship,
validation: 'dataNotNull',
description: 'data can not be null',
ref: {
record,
relationship,
data
}
}
];
}
} else {
if (Array.isArray(data)) {
return [
{
validator: StandardRecordValidators.RecordRelationship,
ref: {
record,
relationship,
data
},
validation: 'dataValid',
description: 'data for a hasOne relationship can not be an array'
}
];
} else {
let issues = validateRelatedRecord(
{
record,
relationship,
relatedRecord: data
},
{
validatorFor,
schema,
relationshipDef
}
);
if (issues) dataIssues.push(...issues);
}
}
}
if (dataIssues.length > 0) {
return [
{
validator: StandardRecordValidators.RecordRelationship,
ref: {
record,
relationship,
data
},
validation: 'dataValid',
details: dataIssues,
description: formatValidationDescription(
'relationship data is invalid',
dataIssues
)
}
];
}
}
};