Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow undefined value for all fields #114

Merged
merged 1 commit into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions packages/entity/src/EntityFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,67 +111,67 @@ export abstract class EntityFieldDefinition<T> {
}

/**
* 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<string> {
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<Date> {
protected validateInputValue(value: Date): boolean {
protected validateInputValueInternal(value: Date): boolean {
return value instanceof Date;
}
}
export class BooleanField extends EntityFieldDefinition<boolean> {
protected validateInputValue(value: boolean): boolean {
protected validateInputValueInternal(value: boolean): boolean {
return typeof value === 'boolean';
}
}
export class NumberField extends EntityFieldDefinition<number> {
protected validateInputValue(value: number): boolean {
protected validateInputValueInternal(value: number): boolean {
return typeof value === 'number';
}
}
export class StringArrayField extends EntityFieldDefinition<string[]> {
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<object> {
protected validateInputValue(value: object): boolean {
protected validateInputValueInternal(value: object): boolean {
return typeof value === 'object' && !Array.isArray(value);
}
}
export class EnumField extends EntityFieldDefinition<string | number> {
protected validateInputValue(value: string | number): boolean {
protected validateInputValueInternal(value: string | number): boolean {
return typeof value === 'number' || typeof value === 'string';
}
}
export class JSONArrayField extends EntityFieldDefinition<any[]> {
protected validateInputValue(value: any[]): boolean {
protected validateInputValueInternal(value: any[]): boolean {
return Array.isArray(value);
}
}
export class MaybeJSONArrayField extends EntityFieldDefinition<any | any[]> {
protected validateInputValue(_value: any): boolean {
protected validateInputValueInternal(_value: any): boolean {
return true;
}
}
2 changes: 1 addition & 1 deletion packages/entity/src/EntityLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/entity/src/EntityMutator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
17 changes: 11 additions & 6 deletions packages/entity/src/__tests__/EntityFields-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
} from '../EntityFields';

class TestFieldDefinition extends EntityFieldDefinition<string> {
protected validateInputValue(value: string): boolean {
protected validateInputValueInternal(value: string): boolean {
return value === 'helloworld';
}
}
Expand All @@ -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);
});
});

Expand All @@ -55,13 +60,13 @@ const describeFieldTestCase = <T>(
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);
});
}
});
Expand Down