-
Notifications
You must be signed in to change notification settings - Fork 320
Improve interactions between recursive and exact types. #390
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
base: master
Are you sure you want to change the base?
Conversation
Add HasProps interfaces for recursive and exact types over types that have props. Stop exact from immediately forcing evaluation of recursive types.
What about it('should strip additional properties (recursive)', () => {
interface P {
rec?: P
}
const T: t.Type<P> = t.recursion('T', () => t.exact(t.partial({ rec: T })))
assertSuccess(T.decode({ a: 1, rec: { b: 2, rec: {} } }), { rec: { rec: {} } })
})
it('should strip additional properties (mutually recursive)', () => {
interface P1 {
a?: P2
}
interface P2 {
b?: P1
}
const T1: t.Type<P1> = t.recursion('T1', () => t.exact(t.partial({ a: T2 })))
const T2: t.Type<P2> = t.recursion('T2', () => t.exact(t.partial({ b: T1 })))
assertSuccess(T1.decode({ x: 1, a: { y: 2, b: { z: 3, a: {} } } }), { a: { b: { a: {} } } })
}) |
|
Sorry it took me so long to respond, this required some investigation on my part 😅. Putting the I'm doing some code generation to create io-ts codecs from an OpenAPI spec. My template to generate a codec looks like this (types removed for readability): const codec = t.recursion('', () => t.intersection([
supertype,
t.type({ ... required properties ... }),
t.partial({ ... optional properties ... }),
]));where This all works fine, but now I want my codecs to strip any unknown keys. It's clear that I need to use const codec = t.intersection([t.exact(t.type({})), t.exact(t.partial({a: t.number}))])
codec.decode({ a: 1 }) // => { }
codec.decode({ a: 1, b: 2 }) // => { a: 1 }
// Adding b to the input stops it from stripping the a?I think I ran into some other weird cases, but it's difficult to extract them into reasonable examples. Anyway, this convinced me that I should just avoid taking an To avoid taking an const inexactCodec = t.recursion('', () => t.intersection([
inexactSupertype,
t.type({ ... required properties ... }),
t.partial({ ... optional properties ... }),
]));
const exactCodec = t.recursion('', () => t.exact(t.intersection([
inexactSupertype,
t.type({ ... required properties ... }),
t.partial({ ... optional properties ... }),
])));Which has some code duplication that I can't figure out a way to resolve, since it's stuck inside the const inexactCodec = t.recursion('', () => t.intersection([
inexactSupertype,
t.type({ ... required properties ... }),
t.partial({ ... optional properties ... }),
]));
const exactCodec = t.exact(inexactCodec)So, that's why I made the changes here. Now that I've written this up, I think that if I had figured out / fixed the interactions between I think this pullreq does enable something that there wasn't a way to do before, although the unit tests I added were already possible to do by moving the |
|
@GriffinSchneider this looks like a bug const codec = t.intersection([t.exact(t.type({})), t.exact(t.partial({a: t.number}))])
codec.decode({ a: 1 }) // => { }
codec.decode({ a: 1, b: 2 }) // => { a: 1 }
// Adding b to the input stops it from stripping the a?I'll open an issue |
Currently, you can't create an
exactrecursiontype.RecursiveTypedoesn't satisfy theHasPropstype, even if the type it's wrapping does. This is easy to fix by adding aHasPropsRecursivetype toHasPropsand then extendinggetPropstoreturn getProps(codec.type)for recursive codecs.But now we have another problem: calling
exacton aRecursiveTypeforces the lazy evaluation that makesRecursiveTypework. This causes issues if you're trying to have theexactbe part of the recursion. For example, this fails because the function runs beforeTis defined:I fixed this by having
exactdelay its evaluation ofpropsuntil forced by someone callingvalidateorencodeon the result. I added some tests with a few more examples of things that didn't work before this pullreq.Also, I bumped the version to 2.1.0 since I needed an
@sincefor the new types.