Open gavacho opened 3 years ago
Just for clarification in your assertIs
function you use Type<T>
which is equivalent to Type<T, T>
but NumberFromString
is Type<number, string>
. So you shouldn't be able to use it in your assertIs
function?
Ah, so that's another reason why assertIs
isn't a viable solution. I think to accomplish what I want (getting a failure report for when is
fails) will require a change to io-ts?
Why not simply use decode as your assert function. I normally have a cast
function defined for unsafe casting where I know it is ok to throw.
import * as E from 'fp-ts/Either'
import { flow } from 'fp-ts/function'
import * as t from 'io-ts'
import { failure } from 'io-ts/PathReporter'
export function cast<O, A>(codec: t.Type<A, O>): (value: O) => A
export function cast<I, A>(codec: t.Decoder<I, A>): (value: I) => A
export function cast<I, A>(codec: t.Decoder<I, A>): (value: I) => A {
return flow(
codec.decode,
E.getOrElse<t.Errors, A>(errors => {
throw new Error(failure(errors).join('\n'))
})
)
}
Why not simply use decode as your assert function.
This is possible and it is what I'm doing today. The reason why I have opened this ticket is because that decode-function-which-throws is not (and can not be) a TypeScript Assert Function.
TypeScript Assert Functions allow us to do something that your cast
doesn't. Specifically, this is possible with a TypeScript Assert Function:
const response = await fetch(...);
assert(response.ok);
MyCodec.assert(response.body);
return response.body.something.typed;
If I wanted to accomplish the same thing using your cast
, I would be required to introduce a new variable like this:
const repsonse = await fetch(...);
assert(response.ok);
const body = cast(MyCodec)(response.body);
return body.something.typed;
In a test scenario which is hitting many endpoint, these extra variables add up! I can avoid them now by doing:
const response = await fetch(...);
assert(response.ok);
assert(MyCodec.is(response.body));
return response.body.something.typed;
But as I mentioned before, the downside with this approach is that I don't get good error messages when is
fails.
Since codecs provide a TypeScript User-Defined Type Guard (e.g. MyCodec.is
) it seems reasonable to want to have a TypeScript Assertion Function as well.
When it comes to assert
functions I am not the right person to argue about with them cause I never used them or ever had the need to use them.
But what you describe is normally a classic use case for io-ts
using the decode
function. In this case is more powerful or how do you handle something like the transformation of DateISOString to Date Objects?
When it comes to assert functions I am not the right person to argue about with them cause I never used them or ever had the need to use them.
It sounds like the way you use io-ts would not change if the is
method were removed from the Type interface. Because my request provides a very subtle (but valuable) benefit, it might be hard to identify.
But what you describe is normally a classic use case for io-ts using the decode function.
Yes. I use a function very similar to your cast
in my code. I understand that decode
can be used to accomplish something similar to what I'm requesting. I am requesting something that can not be accomplished with decode.
In this case is more powerful or how do you handle something like the transformation of DateISOString to Date Objects?
Yes, this case is more powerful.
Let's imagine a codec like:
const Point = t.strict({
x: NumberFromString,
y: NumberFromString,
});
And let's imagine two different data structures:
const numberPoint = { x: 100, y: 100 };
const stringPoint = { x: '200', y: '200' };
If we used decode
, it would pass stringPoint
and fail numberPoint
, right? But if we used is
then it would pass numberPoint
and fail stringPoint
. This demonstrates that decode
does not do the same thing as is
.
If you agree that is
does not do the same thing as decode
, then can you agree that it would be useful for users of is
to have detailed error messages (like the ones available when calling decode
)?
I don't think the additional error reporting from is
should be necessary because it should be used as a discriminator. Either we've obtained our decoded value by actually decoding or we are using a type system that should be relieving us of the need for detailed error on discrimination.
I don't think that arbitrary values requiring knowledge as to "why" something isn't something else should really exist if we are coding correctly. Certainly there may be use cases that I'm unaware of. But I don't think adding this functionality to is
makes it "more powerful" - it just makes it more cumbersome. Plus - more importantly - where would these alleged "errors" be reported?
Perhaps this is just a misunderstanding or misuse of is
leading to the idea that additional information is required. Use it as a discriminator for sum types. That is the purpose.
If validation of the "assert" notion is needed, it should be done at the point of decoding.
🚀 Feature request
Current Behavior
I use
is
to narrow my type like:The problem I have is that when there is a failure, I don't receive a detailed error like
decode
provides.I considered creating this function:
But because some codecs are decoders,
decode
isn't guaranteed to fail every timeis
fails:Desired Behavior
Would it be possible to add
assert
to the Type interface that would behave as a TypeScript Assert Function and also provide a detailed error messages the way decode does?Then I would be able to do: