diff --git a/src/structs/utilities.ts b/src/structs/utilities.ts index 28c1c53b..cee9d300 100644 --- a/src/structs/utilities.ts +++ b/src/structs/utilities.ts @@ -1,6 +1,6 @@ import { Struct, Context, Validator } from '../struct' import { object, optional, type } from './types' -import { ObjectSchema, Assign, ObjectType, PartialObjectSchema } from '../utils' +import { ObjectSchema, Assign, ObjectType, PartialObjectSchema, OverrideSourceType } from '../utils' /** * Create a new struct that combines the properties properties from multiple @@ -59,6 +59,17 @@ export function assign(...Structs: Struct[]): any { return isType ? type(schema) : object(schema) } +/** + * Create a new struct based on target struct and overridden by source struct. + */ + +export function override>( + target: Struct, A>, + source: B, +): Struct>, Assign> { + return assign(target, type(source)) +} + /** * Define a new struct type with a custom validation function. */ diff --git a/src/utils.ts b/src/utils.ts index 92df9755..e2504e1a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -291,6 +291,15 @@ export type ObjectType = Simplify< Optionalize<{ [K in keyof S]: Infer }> > +/** + * A schema for source type of override utility. + */ + +export type OverrideSourceType = Partial< + Record +> & + ObjectSchema + /** * Omit properties from a type that extend from a specific type. */ diff --git a/test/validation/override/invalid-object.ts b/test/validation/override/invalid-object.ts new file mode 100644 index 00000000..1c045084 --- /dev/null +++ b/test/validation/override/invalid-object.ts @@ -0,0 +1,28 @@ +import { object, override, string, number } from '../../../src' + +const Target = object({ a: string() }) + +export const Struct = override(Target, { a: number(), b: number() }) + +export const data = { + a: 'invalid', + b: 2, + c: 5, +} + +export const failures = [ + { + value: 'invalid', + type: 'number', + refinement: undefined, + path: ['a'], + branch: [data, data.a], + }, + { + branch: [data, data.c], + path: ['c'], + refinement: undefined, + type: 'never', + value: 5, + }, +] diff --git a/test/validation/override/invalid-type.ts b/test/validation/override/invalid-type.ts new file mode 100644 index 00000000..afd7669c --- /dev/null +++ b/test/validation/override/invalid-type.ts @@ -0,0 +1,21 @@ +import { type, override, string, number } from '../../../src' + +const Target = type({ a: string() }) + +export const Struct = override(Target, { a: number(), b: number() }) + +export const data = { + a: 'invalid', + b: 2, + c: 5, +} + +export const failures = [ + { + value: 'invalid', + type: 'number', + refinement: undefined, + path: ['a'], + branch: [data, data.a], + }, +] diff --git a/test/validation/override/valid-object.ts b/test/validation/override/valid-object.ts new file mode 100644 index 00000000..c5e0ad47 --- /dev/null +++ b/test/validation/override/valid-object.ts @@ -0,0 +1,15 @@ +import { type, override, string, number } from '../../../src' + +const Target = type({ a: string() }) + +export const Struct = override(Target, { a: number(), b: number() }) + +export const data = { + a: 1, + b: 2, +} + +export const output = { + a: 1, + b: 2, +} diff --git a/test/validation/override/valid-type.ts b/test/validation/override/valid-type.ts new file mode 100644 index 00000000..85a9d132 --- /dev/null +++ b/test/validation/override/valid-type.ts @@ -0,0 +1,17 @@ +import { type, override, string, number } from '../../../src' + +const Target = type({ a: string() }) + +export const Struct = override(Target, { b: number() }) + +export const data = { + a: '1', + b: 2, + c: 3, +} + +export const output = { + a: '1', + b: 2, + c: 3, +}