-
Notifications
You must be signed in to change notification settings - Fork 90
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
Runtype definitions are not working as expected #294
Comments
Thanks, this kind of use case is exactly what I've been thinking recently. First of all, your examples are a bit wrong as it's not wrapped within const A: Runtype<A> = Record({
foo: String,
bar: String,
}) The problem is, the constraint that requires type A = {
foo: string
bar: string
}
const A: Runtype<A> = Record({
foo: String,
bar: String.Or(Undefined),
})
// Types of property 'bar' are incompatible.
// Type 'string | undefined' is not assignable to type 'string'.
// Type 'undefined' is not assignable to type 'string'. I guess what you really want is a dedicated API that statically ensures given runtype to be not only assignable to but also exactly type Exactly<T, U> = [T, U] extends [U, T] ? true : false
const Ensure: <T>() => <U>(
u: Exactly<Runtype<T>, Runtype<U>> extends true ? Runtype<U> : never,
) => Runtype<T> = () => u => u as any
type A = {
foo: string
bar: string | undefined
}
const A: Runtype<A> = Ensure<A>()(
Record({
foo: String,
bar: String,
}),
)
// Argument of type 'Record<{ foo: String; bar: String; }, false>' is not assignable to parameter of type 'never'. This is just a rough idea, we'd like to get more understandable error messages in real-world usage, but it works. By the way, could you elaborate on how you can write such constraint in |
Hi, I reported a similar case to Zod's repo and wrote an example on how to write such constraint in |
In type A = {
foo: string
bar: string | undefined
}
type B = {
foo: string
bar: string
}
type F<A> = (a: A) => void
const f: F<A> = (b: B): void => {}
// Types of property 'bar' are incompatible.
// Type 'string | undefined' is not assignable to type 'string'.
// Type 'undefined' is not assignable to type 'string'.(2322) So, the type safety of runtype definitions is probably achieved by having a phantom function signature in the export interface RuntypeBase<A = any> {
_phantom: (a: A) => never;
...
}; Just confirmed it works. The error message with the OP's example is like:
But I feel it quite hacky 🤣 |
So if I understood correctly, this works "by chance" on Assuming the hacky |
BTW, thanks for providing all that information regarding TypeScript types @yuhr like this:
I didn't know that! I agree that |
Yeah, I believe it wouldn't be something intended.
Definitely, we must cover this. I said hacky, but didn't mean it's an enough reason to stop taking that option (I see the current codebase of I just wonder which way provides better experience to users. On the other hand, I'm going to tinker around how we can make things more sophisticated. |
I would say that this
I also agree on that this is the solution which provides better experience to the users. And what I like the most of this approach is that the strict type checking is not optional if you define a Apart from that, me and @amonsosanz ended up using this temporal function to have strict type checking. I'm posting our final solution just in case it helps someone that is finding this stricness: export const StrictSchema: <T>() => <U>(
u: Exact<Runtype<T>, Runtype<U>> extends true
? Exact<Required<T>, Required<U>> extends true
? Runtype<U>
: never
: never
) => Runtype<T> =
() =>
<T>(u: unknown) =>
u as Runtype<T>; Of course, as you've said before, the drawback is the error message when the
Thank you a lot for your help, all the credits to you @yuhr. We just removed the |
Hi! I've found an issue when you try to create a
Runtype
from aTypeScript
type with an optional property in it.So, for example, imagine that I have this TS type:
This type is coming from the domain of my application, so I want to have it as a TS and not having it depending on an external library, so I'm able to change the runtime decoding at any time without affecting the rest of the app. I mean, I don't want to infer my domain from an external library doing:
So okay, no problem, I should be able to do:
And yeah, it works perfectly, no compilation errors as we expect. But what if I forgot the
Optional
like this?I don't get any compilation error, but the
bar
property is defined asstring | undefined
in myA
type, so it should take into account that it could beundefined
. What if I'm trying to decode a response from the API and it's returningundefined
? The app is going to break in runtime, so then what's the purpose of using a library to prevent this kind of errors?And I don't get any compilation error neither if I try to add more extra properties to the runtype definition like:
The same as before, the API is not going to return
extraPropNotInA
and when trying to decoding the response the app is going to break again! So, where is the strictness?For example, in
io-ts
I get a compilation error in both cases, like it should be in my opinion. It really guarantees that I'm not going to face a runtime error. But trying another libraries likezod
andruntypes
I've seen that all of them fail in this kind of scenarios. Is this behavior intended? And if it's, why? Thanks!The text was updated successfully, but these errors were encountered: