A
arktype•2mo ago
papy0977

Fail fast on union type

Hi i try to fail fast on union type as i know if there is an error , it's pointless to continue checking the union and get the right message and not the full list of all union message. For this i throw an error in the union branch and do a try catch on the type generated. Is there a clever/official way to do that:
function arkTry<T extends (...args: unknown[]) => unknown>(fn: T) {
return (...args: Parameters<T>): ReturnType<T> | ArkError => {
try {
return fn(...args) as ReturnType<T>;
} catch (e) {
return e as ArkError;
}
};
}

function arkValidOrThrow<T>(x: T): Exclude<T, ArkErrors | ArkError> | never {
if (x instanceof ArkErrors || x instanceof ArkError) {
throw new Error(x.message);
}
return x as Exclude<T, ArkErrors | ArkError>;
}
function arkTry<T extends (...args: unknown[]) => unknown>(fn: T) {
return (...args: Parameters<T>): ReturnType<T> | ArkError => {
try {
return fn(...args) as ReturnType<T>;
} catch (e) {
return e as ArkError;
}
};
}

function arkValidOrThrow<T>(x: T): Exclude<T, ArkErrors | ArkError> | never {
if (x instanceof ArkErrors || x instanceof ArkError) {
throw new Error(x.message);
}
return x as Exclude<T, ArkErrors | ArkError>;
}
32 Replies
papy0977
papy0977OP•2mo ago
export const $mathExpr = scope({
col: { _col: "string" },
var: { _var: "string" },
scalar: "number | col | var",
operand: "scalar | expr",
add: [
{ _add: "operand[]" },
":",
(data, ctx) => {
if (data._add.length >= 2) return true;
throw ctx.error("an array of 2 or more operands");
},
],
sub: [
{ _sub: "operand[]" },
":",
(data, ctx) => {
if (data._sub.length >= 2) return true;
throw ctx.error("an array of 2 or more operands");
},
],
mul: [
{ _mul: "operand[]" },
":",
(data, ctx) => {
if (data._mul.length >= 2) return true;
throw ctx.error("an array of 2 or more operands");
},
],
expr: "add | sub | mul",
}).export();

const $GQLMathExprSchema = $mathExpr.expr;

export const GQLMathExprSchema = arkTry($GQLMathExprSchema);
export type GQLMathExpr = typeof $GQLMathExprSchema.infer;

const foo = arkValidOrThrow(
GQLMathExprSchema({
_mul: [1,2,{_mul: [3, 4, { _sub: [5, 6] }, { _add: [10], _sub: [1, 2] }],},],
}),
);

console.log("_mul" in foo && foo._mul);
export const $mathExpr = scope({
col: { _col: "string" },
var: { _var: "string" },
scalar: "number | col | var",
operand: "scalar | expr",
add: [
{ _add: "operand[]" },
":",
(data, ctx) => {
if (data._add.length >= 2) return true;
throw ctx.error("an array of 2 or more operands");
},
],
sub: [
{ _sub: "operand[]" },
":",
(data, ctx) => {
if (data._sub.length >= 2) return true;
throw ctx.error("an array of 2 or more operands");
},
],
mul: [
{ _mul: "operand[]" },
":",
(data, ctx) => {
if (data._mul.length >= 2) return true;
throw ctx.error("an array of 2 or more operands");
},
],
expr: "add | sub | mul",
}).export();

const $GQLMathExprSchema = $mathExpr.expr;

export const GQLMathExprSchema = arkTry($GQLMathExprSchema);
export type GQLMathExpr = typeof $GQLMathExprSchema.infer;

const foo = arkValidOrThrow(
GQLMathExprSchema({
_mul: [1,2,{_mul: [3, 4, { _sub: [5, 6] }, { _add: [10], _sub: [1, 2] }],},],
}),
);

console.log("_mul" in foo && foo._mul);
ssalbdivad
ssalbdivad•2mo ago
Yes just do nothing because that already happens 😛
papy0977
papy0977OP•2mo ago
?
ssalbdivad
ssalbdivad•2mo ago
There is a property on Traversal we use internally to determine whether to failFast based on whether we're checking a union. All the existing validation logic is already written based on this
No description
papy0977
papy0977OP•2mo ago
in my case it'not working as i have a message like:
error: _add must be an array (was missing), _mul[2]._add must be an array (was missing), _mul[2]._col must be a string (was missing), _mul[2]._mul[3] must be _mul[2]._mul[3] must be an array of 2 or more operands (was {"_add":[10],"_sub":[1]}), _mul[2]._mul[3]._col must be a string (was missing), _mul[2]._mul[3]._mul must be an array (was missing) or _mul[2]._mul[3]._var must be a string (was missing) (was {"_add":[10],"_sub":[1]}), _mul[2]._sub must be an array (was missing), _mul[2]._var must be a string (was missing) or _sub must be an array (was missing)
error: _add must be an array (was missing), _mul[2]._add must be an array (was missing), _mul[2]._col must be a string (was missing), _mul[2]._mul[3] must be _mul[2]._mul[3] must be an array of 2 or more operands (was {"_add":[10],"_sub":[1]}), _mul[2]._mul[3]._col must be a string (was missing), _mul[2]._mul[3]._mul must be an array (was missing) or _mul[2]._mul[3]._var must be a string (was missing) (was {"_add":[10],"_sub":[1]}), _mul[2]._sub must be an array (was missing), _mul[2]._var must be a string (was missing) or _sub must be an array (was missing)
my error is lost in all the union
ssalbdivad
ssalbdivad•2mo ago
I'm confused what you mean by fail fast. ArkType moves on from a branch of a union after encountering the first error on that branch, but you can't skip the entire union because other branches could be valid? Btw you can do string[] > 2 Or operand[] >= 2 I guess
papy0977
papy0977OP•2mo ago
yes i want to skip the check of other branch as it's poinless if i have enter for example the add branch and there is not enough operand there is no point to continue
ssalbdivad
ssalbdivad•2mo ago
It sounds like you don't want a union then I think I get the idea you're basically saying you want to discriminate
papy0977
papy0977OP•2mo ago
yes i know for the syntax thank you, i just wanted to have more control 😉
ssalbdivad
ssalbdivad•2mo ago
I mean throwing directly is going to negate a lot of the benefits but I suppose whatever you find best Maybe you can get the behavior you want by declaring your objects as closed?
papy0977
papy0977OP•2mo ago
what's puzzle me is the error message i can't say at first sight what happens so that why i wanted to terminate early
ssalbdivad
ssalbdivad•2mo ago
We can't internally discriminate based on key presence yet though.
papy0977
papy0977OP•2mo ago
Ok thank you, i will keep it as this for the moment (or maybe it's not the way todo it as you say ?) and see how it's going. For the moment i put 3 operators, but when it will grows that will a hell of a chain or errors 😉 Thank you for your time.
ssalbdivad
ssalbdivad•2mo ago
Yeah I don't think this is the right approach
ssalbdivad
ssalbdivad•2mo ago
This issue would will be a built in way for what you want https://github.com/arktypeio/arktype/issues/786
GitHub
Discriminate based on key presence if keys are strict · Issue #786 ...
Would allow a union like the following to be discriminated: const discriminated = type({ a: "string", }) .or({ b: "string" }) .onUndeclaredKey("reject");
ssalbdivad
ssalbdivad•2mo ago
For now basically you want to be sure that the union can be discriminated, so some common key at which there are disjoint values. e.g. if there were a kind: "add", operands: [1, 2] or similar that would work Or you just write a single morph that handles all the discrmination logic for now as a workaround where you delegate to another type based on which keys are present
papy0977
papy0977OP•2mo ago
ok will see what i can achieve as i'm not a type system expert 😉
ssalbdivad
ssalbdivad•2mo ago
I get the idea more what you were asking for originally is more like wanting to match a branch of the union early (i.e. discriminate) then fail if the matching branch is invalid
papy0977
papy0977OP•2mo ago
yes because if a part of an expression is not valid then the whole is also not
ssalbdivad
ssalbdivad•2mo ago
Yeah makes sense, so really we just want that discrimination issue. I'm also working on pattern matching now which might be perfect for parsing logic like this: https://github.com/arktypeio/arktype/issues/802
GitHub
Investigate typed match expression · Issue #802 · arktypeio/arktype
I often hear about devs using other languages missing match expressions. I'd need to do some investigation around common syntax and use cases, but I could imagine a thin layer around AT providi...
ssalbdivad
ssalbdivad•2mo ago
Right, but you wouldn't want to fail from e.g. _add if the _add key wasn't present- you want to essentially know you're on the right branch (discriminate) then fail fast, which would happen anyways
papy0977
papy0977OP•2mo ago
yes it could be handy
ssalbdivad
ssalbdivad•2mo ago
That key presence thing has come up a few times, good to see more use cases for that will try and prioritize Unfortunately it is a lot less efficient than other discriminants because it can't be checked in constant time
papy0977
papy0977OP•2mo ago
can we use arktype as a parser validater, as arktype is already descending all branches for us ?
ssalbdivad
ssalbdivad•2mo ago
What do you mean?
papy0977
papy0977OP•2mo ago
like for example in my case i gave it an expression object and it validate and morph it into another shape. I don't know how describe. What i am doing is transforming a graphql AST into an SQL one
ssalbdivad
ssalbdivad•2mo ago
You can arbitrarily chain narrows and morphs together with other types, if that is what you mean. Using .to({ someOutput: "string" }) will be useful if you just morphed and want to pipe to another type directly. Match will be particularly helpful though because it is essentially an ordered union which is often very useful for something like parsing
papy0977
papy0977OP•2mo ago
Thank you very much, i think i will go to sleep for some few hours now.
ssalbdivad
ssalbdivad•2mo ago
Good luck! Excited to publish match soon so you can try that!
papy0977
papy0977OP•2mo ago
Hi, Following our chat, is there a way to intercept an error ? In my example the throw work because when it goes to narrow the branch is valid, but imagine i enter the branch _add, and there is an error on the operand i have no way to act here as the others branches (sub and mul) will be tried, but it pointless as i know it was an add branch.
ssalbdivad
ssalbdivad•2mo ago
No. It feels like a union is not really the right task for the job here. I'd either right the narrowing function up front then delegate to the appropriate branch or wait for pattern matching since that is essentially what it does (will try to release today)
papy0977
papy0977OP•2mo ago
Yep, i was expecting not to go the route of narrowing , just letting arktype do all the work of validating/branching 😉 Hope your release go well 🙂

Did you find this page helpful?