-
Notifications
You must be signed in to change notification settings - Fork 328
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
Improve interactions between recursive and exact types. #390
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
exact
recursion
type.RecursiveType
doesn't satisfy theHasProps
type, even if the type it's wrapping does. This is easy to fix by adding aHasPropsRecursive
type toHasProps
and then extendinggetProps
toreturn getProps(codec.type)
for recursive codecs.But now we have another problem: calling
exact
on aRecursiveType
forces the lazy evaluation that makesRecursiveType
work. This causes issues if you're trying to have theexact
be part of the recursion. For example, this fails because the function runs beforeT
is defined:I fixed this by having
exact
delay its evaluation ofprops
until forced by someone callingvalidate
orencode
on 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
@since
for the new types.