diff --git a/packages/entity/src/EntityFields.ts b/packages/entity/src/EntityFields.ts index 59a7a0ab..57aaf246 100644 --- a/packages/entity/src/EntityFields.ts +++ b/packages/entity/src/EntityFields.ts @@ -111,67 +111,67 @@ export abstract class EntityFieldDefinition { } /** - * Validates input value for a field of this type. Null is considered valid. This is used for things like: + * Validates input value for a field of this type. Null and undefined are considered valid by default. This is used for things like: * - EntityLoader.loadByFieldValue - to ensure the value being loaded by is a valid value * - EntityMutator.setField - to ensure the value being set is a valid value */ - public validateInputValueIfNotNull(value: T | null): boolean { - if (value === null) { + public validateInputValue(value: T | null | undefined): boolean { + if (value === null || value === undefined) { return true; } - return this.validateInputValue(value); + return this.validateInputValueInternal(value); } - protected abstract validateInputValue(value: T): boolean; + protected abstract validateInputValueInternal(value: T): boolean; } export class StringField extends EntityFieldDefinition { - protected validateInputValue(value: string): boolean { + protected validateInputValueInternal(value: string): boolean { return typeof value === 'string'; } } export class UUIDField extends StringField { - protected validateInputValue(value: string): boolean { + protected validateInputValueInternal(value: string): boolean { return validateUUID(value); } } export class DateField extends EntityFieldDefinition { - protected validateInputValue(value: Date): boolean { + protected validateInputValueInternal(value: Date): boolean { return value instanceof Date; } } export class BooleanField extends EntityFieldDefinition { - protected validateInputValue(value: boolean): boolean { + protected validateInputValueInternal(value: boolean): boolean { return typeof value === 'boolean'; } } export class NumberField extends EntityFieldDefinition { - protected validateInputValue(value: number): boolean { + protected validateInputValueInternal(value: number): boolean { return typeof value === 'number'; } } export class StringArrayField extends EntityFieldDefinition { - protected validateInputValue(value: string[]): boolean { + protected validateInputValueInternal(value: string[]): boolean { return Array.isArray(value) && value.every((subValue) => typeof subValue === 'string'); } } export class JSONObjectField extends EntityFieldDefinition { - protected validateInputValue(value: object): boolean { + protected validateInputValueInternal(value: object): boolean { return typeof value === 'object' && !Array.isArray(value); } } export class EnumField extends EntityFieldDefinition { - protected validateInputValue(value: string | number): boolean { + protected validateInputValueInternal(value: string | number): boolean { return typeof value === 'number' || typeof value === 'string'; } } export class JSONArrayField extends EntityFieldDefinition { - protected validateInputValue(value: any[]): boolean { + protected validateInputValueInternal(value: any[]): boolean { return Array.isArray(value); } } export class MaybeJSONArrayField extends EntityFieldDefinition { - protected validateInputValue(_value: any): boolean { + protected validateInputValueInternal(_value: any): boolean { return true; } } diff --git a/packages/entity/src/EntityLoader.ts b/packages/entity/src/EntityLoader.ts index 9e95958c..49190586 100644 --- a/packages/entity/src/EntityLoader.ts +++ b/packages/entity/src/EntityLoader.ts @@ -336,7 +336,7 @@ export default class EntityLoader< const fieldDefinition = this.entityConfiguration.schema.get(fieldName); invariant(fieldDefinition, `must have field definition for field = ${fieldName}`); for (const fieldValue of fieldValues) { - const isInputValid = fieldDefinition.validateInputValueIfNotNull(fieldValue); + const isInputValid = fieldDefinition.validateInputValue(fieldValue); if (!isInputValid) { throw new EntityInvalidFieldValueError(this.entityClass, fieldName, fieldValue); } diff --git a/packages/entity/src/EntityMutator.ts b/packages/entity/src/EntityMutator.ts index a7fdac87..b584364a 100644 --- a/packages/entity/src/EntityMutator.ts +++ b/packages/entity/src/EntityMutator.ts @@ -79,7 +79,7 @@ abstract class BaseMutator< const fieldValue = fields[fieldName]; const fieldDefinition = this.entityConfiguration.schema.get(fieldName); invariant(fieldDefinition, `must have field definition for field = ${fieldName}`); - const isInputValid = fieldDefinition.validateInputValueIfNotNull(fieldValue); + const isInputValid = fieldDefinition.validateInputValue(fieldValue); if (!isInputValid) { throw new EntityInvalidFieldValueError(this.entityClass, fieldName, fieldValue); } diff --git a/packages/entity/src/__tests__/EntityFields-test.ts b/packages/entity/src/__tests__/EntityFields-test.ts index 81e37449..1875f659 100644 --- a/packages/entity/src/__tests__/EntityFields-test.ts +++ b/packages/entity/src/__tests__/EntityFields-test.ts @@ -15,7 +15,7 @@ import { } from '../EntityFields'; class TestFieldDefinition extends EntityFieldDefinition { - protected validateInputValue(value: string): boolean { + protected validateInputValueInternal(value: string): boolean { return value === 'helloworld'; } } @@ -33,17 +33,22 @@ describe(EntityFieldDefinition, () => { test('validator returns true when value is null', () => { const fieldDefinition = new TestFieldDefinition({ columnName: 'wat', cache: true }); - expect(fieldDefinition.validateInputValueIfNotNull(null)).toBe(true); + expect(fieldDefinition.validateInputValue(null)).toBe(true); + }); + + test('validator returns true when value is undefined', () => { + const fieldDefinition = new TestFieldDefinition({ columnName: 'wat', cache: true }); + expect(fieldDefinition.validateInputValue(undefined)).toBe(true); }); test('validator returns false when value is invalid', () => { const fieldDefinition = new TestFieldDefinition({ columnName: 'wat', cache: true }); - expect(fieldDefinition.validateInputValueIfNotNull('nothelloworld')).toBe(false); + expect(fieldDefinition.validateInputValue('nothelloworld')).toBe(false); }); test('validator returns true when value is valid', () => { const fieldDefinition = new TestFieldDefinition({ columnName: 'wat', cache: true }); - expect(fieldDefinition.validateInputValueIfNotNull('helloworld')).toBe(true); + expect(fieldDefinition.validateInputValue('helloworld')).toBe(true); }); }); @@ -55,13 +60,13 @@ const describeFieldTestCase = ( describe(fieldDefinition.constructor.name, () => { if (validValues.length > 0) { test.each(validValues)(`${fieldDefinition.constructor.name}.valid %p`, (value) => { - expect(fieldDefinition.validateInputValueIfNotNull(value)).toBe(true); + expect(fieldDefinition.validateInputValue(value)).toBe(true); }); } if (invalidValues.length > 0) { test.each(invalidValues)(`${fieldDefinition.constructor.name}.invalid %p`, (value) => { - expect(fieldDefinition.validateInputValueIfNotNull(value)).toBe(false); + expect(fieldDefinition.validateInputValue(value)).toBe(false); }); } });