diff --git a/example/prototypes/discriminated-union.ts b/example/prototypes/discriminated-union.ts new file mode 100644 index 00000000..22da0f48 --- /dev/null +++ b/example/prototypes/discriminated-union.ts @@ -0,0 +1,96 @@ + +/*-------------------------------------------------------------------------- + +@sinclair/typebox/prototypes + +The MIT License (MIT) + +Copyright (c) 2017-2024 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +import { Static, Kind, TSchema, TObject, SchemaOptions, CreateType, TLiteral, TypeRegistry, ValueGuard, KindGuard, TUnion } from '@sinclair/typebox' +import { GetErrorFunction, SetErrorFunction } from 'src/errors/function' +import { Value } from '@sinclair/typebox/value' + +// ------------------------------------------------------------------ +// DiscriminatedUnionError +// ------------------------------------------------------------------ +const errorFunction = GetErrorFunction() + +// prettier-ignore +SetErrorFunction((parameter) => { + if (parameter.schema[Kind] !== 'DiscriminatedUnion') { + return errorFunction(parameter) + } + const union = parameter.schema as TDiscriminatedUnion + // Try generate error when value matches known discriminator literal + if (ValueGuard.IsObject(parameter.value) && union.discriminator in parameter.value) { + const variant = parameter.schema.anyOf.find((variant: TSchema) => union.discriminator in variant.properties + && (variant.properties[union.discriminator] as TLiteral).const === + (parameter.value as Record)[union.discriminator]) + if (KindGuard.IsSchema(variant)) { + const literal = variant.properties[union.discriminator] + return `Invalid value for DiscriminatedUnion variant '${literal.const}'` + } + } + // Return generic error containing possible discriminator types. + const options = union.anyOf.map(object => object.properties[union.discriminator].const) as string[] + return `Expected value of ${options.map(option => `'${option}'`).join(', ')} for DiscriminatedUnion` +}) + +// ------------------------------------------------------------------ +// TDiscriminatedUnionObject +// +// Constructs a base TObject type requiring 1 discriminator property +// ------------------------------------------------------------------ +// prettier-ignore +type TDiscriminatedUnionProperties = { + [_ in Discriminator]: TLiteral +} +// prettier-ignore +type TDiscriminatedUnionObject = TObject> + +// ------------------------------------------------------------------ +// DiscriminatedUnion +// ------------------------------------------------------------------ +// prettier-ignore +TypeRegistry.Set('DiscriminatedUnion', (schema: TDiscriminatedUnion, value) => { + return schema.anyOf.some(variant => Value.Check(variant, [], value)) +}) +// prettier-ignore +export interface TDiscriminatedUnion extends TSchema { + [Kind]: 'DiscriminatedUnion' + static: Static> + discriminator: Discriminator + anyOf: Types +} + +/** Creates a DiscriminatedUnion. */ +// prettier-ignore +export function DiscriminatedUnion[]>( + discriminator: Discriminator, types: [...Types], options?: SchemaOptions +): TDiscriminatedUnion { + return CreateType({ [Kind]: 'DiscriminatedUnion', anyOf: types, discriminator }, options) as never +} + + + diff --git a/example/prototypes/index.ts b/example/prototypes/index.ts index 179da98f..d5d9aeda 100644 --- a/example/prototypes/index.ts +++ b/example/prototypes/index.ts @@ -26,6 +26,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ +export * from './discriminated-union' export * from './from-schema' export * from './partial-deep' export * from './union-enum'