Skip to content

Commit

Permalink
Fix discriminatedUnion with intersections
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexGalays committed Jul 19, 2021
1 parent 434827e commit 3942f88
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 7 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "idonttrustlikethat",
"version": "2.0.3",
"version": "2.0.4",
"sideEffects": false,
"description": "Validation for TypeScript",
"license": "MIT",
Expand Down
33 changes: 28 additions & 5 deletions src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class Validator<T> {
) {}

// Phantom type
T: T = (undefined as any) as T
T: T = undefined as any as T

/**
* Validate any value.
Expand Down Expand Up @@ -44,7 +44,7 @@ export class Validator<T> {
/**
* Chains this validator with another one, in series.
* The resulting value of the first validator will be the input of the second.
*
*
* ```ts
* declare const stringToInt: Validator<number>
* declare const intToDate: Validator<Date>
Expand Down Expand Up @@ -86,7 +86,7 @@ export class Validator<T> {
*/
tagged<TAG extends number>(this: Validator<number>): Validator<TAG>
tagged<TAG>(): Validator<TAG> {
return (this as {}) as Validator<TAG>
return this as {} as Validator<TAG>
}

/**
Expand All @@ -108,7 +108,9 @@ export class Validator<T> {
*/
default<D>(defaultValue: D): Validator<NonNullable<T> | D>
default<D>(defaultValue: D): Validator<unknown> {
return this.nullable().map(v => (v === null || v === undefined ? defaultValue : v))
return this.nullable().map(v =>
v === null || v === undefined ? defaultValue : v
)
}
}

Expand Down Expand Up @@ -407,11 +409,23 @@ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
? I
: never

// Just like ObjectValidator... but with one extra step. The compiler can't make ObjectValidator work here.
type IntersectionOfObjectsResult<VS extends ObjectValidator<any>[]> = Validator<
Id<UnionToIntersection<VS[number]['T']>>
> & {
props: Id<UnionToIntersection<VS[number]['props']>>
}
// Special signature for when all validators are object validators: we want the output to be compatible with ObjectValidator too.
export function intersection<VS extends ObjectValidator<any>[]>(
...vs: VS
): IntersectionOfObjectsResult<VS>
export function intersection<VS extends AnyValidator[]>(
...vs: VS
): Validator<Id<UnionToIntersection<VS[number]['T']>>>
export function intersection(...validators: any[]): any {
return new Validator((v, config, p) => {
const allObjectValidators = validators.every(v => Boolean(v.props))

const validator = new Validator((v, config, p) => {
let result: any = {}

for (let i = 0; i < validators.length; i++) {
Expand All @@ -426,6 +440,15 @@ export function intersection(...validators: any[]): any {

return Ok(result)
})

if (allObjectValidators) {
;(validator as any).props = validators.reduce((acc, v) => {
Object.assign(acc, v.props)
return acc
}, {})
}

return validator
}

//--------------------------------------
Expand Down
32 changes: 31 additions & 1 deletion test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ import { lift } from 'space-lift'

import * as v from '../commonjs/validation'
import { Ok, Err } from '../commonjs/validation'
import { nonEmpty } from '../src/validation'
import {
discriminatedUnion,
intersection,
literal,
nonEmpty,
number,
object,
string
} from '../src/validation'

const showErrorMessages = true

Expand Down Expand Up @@ -386,6 +394,28 @@ describe('validation core', () => {
printErrorMessage(notOkValidation4)
})

it('can validate a discriminated union made of intersection types', () => {
const a = object({ type: literal('a'), a: number })
const b = object({ b: string })
const ab = intersection(a, b)

const c = object({ type: literal('c'), c: number })
const d = object({ d: string })
const cd = intersection(c, d)

const validator = discriminatedUnion('type', ab, cd)

const notOKValidation = validator.validate({ type: 'c', c: 10 })
const okValidation = validator.validate({ type: 'c', c: 10, d: 'dd' })

expect(notOKValidation.ok).toBe(false)
expect(okValidation.ok && okValidation.value).toEqual({
type: 'c',
c: 10,
d: 'dd'
})
})

it('can validate a literal value', () => {
const literalStr = v.literal('hello')

Expand Down

0 comments on commit 3942f88

Please sign in to comment.