dynamodb-toolbox
Version:
Lightweight and type-safe query builder for DynamoDB and TypeScript.
162 lines (161 loc) • 7.12 kB
JavaScript
import { DynamoDBToolboxError } from '../../errors/index.js';
import { isArray } from '../../utils/validation/isArray.js';
import { checkSchemaProps } from '../utils/checkSchemaProps.js';
import { hasDefinedDefault } from '../utils/hasDefinedDefault.js';
import { $computed, $discriminations_, $discriminators, $discriminators_ } from './constants.js';
export class AnyOfSchema {
constructor(elements, props) {
this.type = 'anyOf';
this.elements = elements;
this.props = props;
this[$discriminators_] = { [$computed]: false };
this[$discriminations_] = { [$computed]: false };
}
get checked() {
return Object.isFrozen(this.props);
}
check(path) {
if (this.checked) {
return;
}
checkSchemaProps(this.props, path);
if (!isArray(this.elements)) {
throw new DynamoDBToolboxError('schema.anyOf.invalidElements', {
message: `Invalid anyOf elements${path !== undefined ? ` at path '${path}'` : ''}: AnyOf elements must be an array.`,
path
});
}
if (this.elements.length === 0) {
throw new DynamoDBToolboxError('schema.anyOf.missingElements', {
message: `Invalid anyOf elements${path !== undefined ? ` at path '${path}'` : ''}: AnyOf attributes must have at least one element.`,
path
});
}
for (const element of this.elements) {
const { required, hidden, savedAs } = element.props;
if (required !== undefined && required !== 'atLeastOnce' && required !== 'always') {
throw new DynamoDBToolboxError('schema.anyOf.optionalElements', {
message: `Invalid anyOf elements${path !== undefined ? ` at path '${path}'` : ''}: AnyOf elements must be required.`,
path
});
}
if (hidden !== undefined && hidden !== false) {
throw new DynamoDBToolboxError('schema.anyOf.hiddenElements', {
message: `Invalid anyOf elements${path !== undefined ? ` at path '${path}'` : ''}: AnyOf elements cannot be hidden.`,
path
});
}
if (savedAs !== undefined) {
throw new DynamoDBToolboxError('schema.anyOf.savedAsElements', {
message: `Invalid anyOf elements${path !== undefined ? ` at path '${path}'` : ''}: AnyOf elements cannot be renamed (have savedAs prop).`,
path
});
}
if (hasDefinedDefault(element)) {
throw new DynamoDBToolboxError('schema.anyOf.defaultedElements', {
message: `Invalid anyOf elements${path !== undefined ? ` at path '${path}'` : ''}: AnyOf elements cannot have default or linked values.`,
path
});
}
}
const { discriminator } = this.props;
if (discriminator !== undefined) {
if (!(discriminator in this[$discriminators])) {
throw new DynamoDBToolboxError('schema.anyOf.invalidDiscriminator', {
message: `Invalid discriminator${path !== undefined ? ` at path '${path}'` : ''}: All elements must be map or anyOf schemas and discriminator must be the key of a string enum schema.`,
path,
payload: { discriminator }
});
}
}
this.elements.forEach((element, index) => {
element.check(`${path !== null && path !== void 0 ? path : ''}[${index}]`);
});
Object.freeze(this.props);
Object.freeze(this.elements);
}
get [$discriminators]() {
var _a;
if (!this[$discriminators_][$computed]) {
Object.assign(this[$discriminators_], (_a = this.elements.map(getDiscriminators).reduce(intersectDiscriminators, undefined)) !== null && _a !== void 0 ? _a : {}, { [$computed]: true });
}
return this[$discriminators_];
}
match(value) {
if (!this[$discriminations_][$computed]) {
const { discriminator } = this.props;
if (discriminator === undefined) {
return undefined;
}
for (const elementSchema of this.elements) {
Object.assign(this[$discriminations_], getDiscriminations(elementSchema, discriminator));
}
Object.assign(this[$discriminations_], { [$computed]: true });
}
return this[$discriminations_][value];
}
}
const getDiscriminators = (schema) => {
var _a;
switch (schema.type) {
case 'anyOf':
return schema[$discriminators];
case 'map': {
const discriminators = {};
for (const [attrName, attr] of Object.entries(schema.attributes)) {
if (attr.type === 'string' &&
attr.props.enum !== undefined &&
(attr.props.required === undefined || attr.props.required !== 'never') &&
attr.props.transform === undefined) {
discriminators[attrName] = (_a = attr.props.savedAs) !== null && _a !== void 0 ? _a : attrName;
}
}
return discriminators;
}
default:
return {};
}
};
const intersectDiscriminators = (discriminatorsA, discriminatorsB) => {
if (discriminatorsA === undefined) {
return discriminatorsB;
}
if (discriminatorsB === undefined) {
return discriminatorsA;
}
const [smallestDiscr, largestDiscr] = [discriminatorsA, discriminatorsB].sort((discA, discB) => Object.keys(discA).length > Object.keys(discB).length ? 1 : -1);
const intersectedDiscriminators = {};
for (const [attrName, attrSavedAs] of Object.entries(smallestDiscr)) {
if (attrName in largestDiscr && largestDiscr[attrName] === attrSavedAs) {
intersectedDiscriminators[attrName] = attrSavedAs;
}
}
return intersectedDiscriminators;
};
const getDiscriminations = (schema, discriminator) => {
var _a;
switch (schema.type) {
case 'anyOf': {
let discriminations = {};
for (const elementSchema of schema.elements) {
discriminations = {
...discriminations,
...getDiscriminations(elementSchema, discriminator)
};
}
return discriminations;
}
case 'map': {
const discriminations = {};
const discriminatorAttr = schema.attributes[discriminator];
if ((discriminatorAttr === null || discriminatorAttr === void 0 ? void 0 : discriminatorAttr.type) === 'string') {
for (const enumValue of (_a = discriminatorAttr.props.enum) !== null && _a !== void 0 ? _a : []) {
discriminations[enumValue] = schema;
}
}
return discriminations;
}
default:
return {};
}
};