Skip to content

Commit

Permalink
feat(s.object): add passthrough (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyranet authored Mar 13, 2022
1 parent 56e9b19 commit ee9f6f3
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 2 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,10 @@ person.parse({

You can use the `.ignore` getter to reset an object schema to the default behaviour (ignoring unrecognized keys).

##### `.passthrough`

You can use the `.passthrough` getter to make the validator add the unrecognized properties the shape does not have, from the input.

#### Records

Record schemas are similar to objects, but validate `Record<string, T>` types, keep in mind this does not check for the keys, and cannot support validation for specific ones:
Expand Down
15 changes: 14 additions & 1 deletion src/validators/ObjectValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export class ObjectValidator<T extends NonNullObject> extends BaseValidator<T> {
this.handleStrategy = (value) => this.handleStrictStrategy(value);
break;
}
case ObjectValidatorStrategy.Passthrough:
this.handleStrategy = (value) => this.handlePassthroughStrategy(value);
break;
}
}

Expand All @@ -43,6 +46,10 @@ export class ObjectValidator<T extends NonNullObject> extends BaseValidator<T> {
return Reflect.construct(this.constructor, [this.shape, ObjectValidatorStrategy.Ignore, this.constraints]);
}

public get passthrough(): this {
return Reflect.construct(this.constructor, [this.shape, ObjectValidatorStrategy.Passthrough, this.constraints]);
}

public get partial(): ObjectValidator<{ [Key in keyof T]?: T[Key] }> {
const shape = Object.fromEntries(this.keys.map((key) => [key, this.shape[key].optional]));
return Reflect.construct(this.constructor, [shape, this.strategy, this.constraints]);
Expand Down Expand Up @@ -138,9 +145,15 @@ export class ObjectValidator<T extends NonNullObject> extends BaseValidator<T> {
? Result.ok(finalResult)
: Result.err(new CombinedPropertyError(errors));
}

private handlePassthroughStrategy(value: NonNullObject): Result<T, CombinedPropertyError> {
const result = this.handleIgnoreStrategy(value);
return result.isErr() ? result : Result.ok({ ...value, ...result.value } as T);
}
}

export const enum ObjectValidatorStrategy {
Ignore,
Strict
Strict,
Passthrough
}
21 changes: 20 additions & 1 deletion tests/validators/object.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ describe('ObjectValidator', () => {
});

describe('Ignore', () => {
const ignorePredicate = predicate.strict.ignore;
const ignorePredicate = predicate.ignore;

test('GIVEN matching keys and values THEN returns no errors', () => {
expect(ignorePredicate.parse({ username: 'Sapphire', password: 'helloworld', email: '[email protected]' })).toStrictEqual({
Expand All @@ -114,6 +114,25 @@ describe('ObjectValidator', () => {
});
});

describe('Passthrough', () => {
const passthroughPredicate = predicate.passthrough;

test('GIVEN matching keys and values THEN returns no errors', () => {
expect(passthroughPredicate.parse({ username: 'Sapphire', password: 'helloworld', email: '[email protected]' })).toStrictEqual({
email: '[email protected]',
username: 'Sapphire',
password: 'helloworld'
});
});

test('GIVEN missing keys THEN throws CombinedPropertyError with MissingPropertyError', () => {
expectError(
() => passthroughPredicate.parse({ username: 'Sapphire' }),
new CombinedPropertyError([['password', new MissingPropertyError('password')]])
);
});
});

describe('Partial', () => {
const partialPredicate = predicate.partial;

Expand Down

0 comments on commit ee9f6f3

Please sign in to comment.