-
Notifications
You must be signed in to change notification settings - Fork 40
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
Add withLazyFallback type #105
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me. It worries me that @gcanti hasn't made any comments on this PR or merged it, since it's been here since September 2019.
@Frikki my fault, forgot to comment. @lauritzsh I'm sorry We could put |
No worries, that sounds good. Will the |
@lauritzsh two exports for now (I'll replace the deprecated |
All good, @gcanti. I hoped it was merely an oversight. 😄 |
I was looking at doing something similar, but passing the whole object under validation, however it doesn't seem it's doable. Would have been nice to be able to do something like: const UserCodec = t.type({
firstName: t.string,
lastName' t.string,
fullName: withObjectFallback(t.string, (i: t.InputOf<typeof UserCodec>) => i?.firstName + ' ' + i?.lastName)
}) However, there doesn't seem to be a way for a the codec at I'm not sure what a good approach would be here. Maybe a more loose codec first, chained into the full user codec? But it starts getting quite unwieldy. @gcanti If you find the time, would love to hear your thoughts. |
@VanTanev ideally decoders shouldn't have so much context, in order to be really composable they should be able to work in isolation, without depending on some specific context. p.s. |
@gcanti I guess the basic approach would be to have a more lax and a more strict codec, and chain them together. This is what I came up with, but it seems shaky at best. I kind of want to have a codec be the ultimate interface exposed, instead my own ad-hoc function, as my API interface literally takes a URL and a codec as inputs. const UserCodecBase = t.type({
firstName: t.string,
lastName: t.string,
fullName: t.union([t.undefined, t.string]),
})
const UserCodecRefined = t.type({
fullName: t.string,
})
const UserCodecCombined = t.type({
...UserCodecBase.props,
...UserCodecRefined.props,
})
export type User = t.TypeOf<typeof UserCodecCombined>
export const UserCodec = new t.Type(
UserCodecCombined.name,
(u): u is User => UserCodecCombined.is(u),
(u, c) =>
pipe(
UserCodecBase.validate(u, c),
E.map(user => {
user.fullName = user.fullName ?? user.firstName + ' ' + user.lastName
return user
}),
E.chain(user => UserCodecCombined.validate(user, c))
),
UserCodecCombined.encode,
) as t.TypeC<typeof UserCodecCombined.props> |
@VanTanev can the |
It can be different, but it's OK to fallback to a computed version when not present. The real use case I'm running into is more complex, but the idea is still that there is a property that can come with a distinct value, or a fallback should be computed from other properties on the object. When you say you can make an instance that breaks the scheme, is that because I didn't use the Ultimately, I'm looking for a way to build a codec that is |
@VanTanev I think I would not try to encode your complex default value behavior in your codec. Just use export const UserBase = t.type({
firstName: t.string,
lastName: t.string
})
export interface UserBase extends t.TypeOf<typeof UserBase> {}
export const UserWithMaybeFullName = t.intersection([UserBase, t.partial({ fullName: t.string })])
export interface UserWithFullName extends UserBase {
fullName: string
}
export function toUserWithFullName(user: UserWithMaybeFullName): UserWithFullName {
return { ...user, fullName: user.fullName && user.firstName + ' ' + user.lastName }
}
pipe(UserWithMaybeFullName.decode(input), E.map(toUserWithFullName)) |
Yeah, I'm aware of that approach, but we have API consumers setup that directly take an io-ts codec and output something like Ultimately, this isn't too far off from what |
You can also shorten your code a little bit with withValidate(
UserCodecBase,
(u, c) => pipe(
UserCodecBase.validate(u, c),
E.map(user => ({
...user,
fullName: user.fullName ?? user.firstName + ' ' + user.lastName
}))
)
).pipe(UserCodecCombined) |
This is a great approach, I completely forgot about |
I made a version of
withFallback
where you can supply a function that will be called. I am not sure whether its best to have just one function or two distinct. Saw it requested in #103 and could also use it in a project.