Skip to content

Commit 4ac324b

Browse files
committed
fix: getErrors type inference
1 parent 6b57603 commit 4ac324b

2 files changed

Lines changed: 46 additions & 2 deletions

File tree

packages/experimental/src/error.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,15 @@ export class ControlFlow extends Error {
170170
}
171171

172172
type ErrorsOf<F extends PublicFn<any, any, any, any, any, any, any, any>> =
173-
F extends { $schema: { errors: infer E } } ? E : never;
173+
// `PublicFn` carries the declared error map in its `Er` type parameter.
174+
// Inferring from the generic is cheaper/steadier than pattern-matching the
175+
// optional `$schema` object, and avoids declaration-size blowups.
176+
F extends PublicFn<any, any, any, any, any, infer Er, any, any, any>
177+
? Er
178+
: never;
174179

175180
export const getErrors = <
176181
F extends PublicFn<any, any, any, any, any, any, any, any>,
177182
>(
178183
fn: F,
179-
): ErrorsOf<F> => fn.$schema?.errors;
184+
): ErrorsOf<F> | undefined => fn.$schema?.errors;

packages/experimental/src/fn.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, expect, expectTypeOf, it } from "vitest";
22
import {
33
FnError,
4+
getErrors,
45
memoryAdapter,
56
UnexpectedError,
67
ValidationError,
@@ -1149,3 +1150,41 @@ describe("multi-issue validation", () => {
11491150
}
11501151
});
11511152
});
1153+
1154+
describe("getErrors", () => {
1155+
it("returns undefined when fn declares no errors", () => {
1156+
const f = v.fn(
1157+
"ge.no_errors",
1158+
{ input: { n: v.number() } },
1159+
(c) => c.input.n,
1160+
);
1161+
expect(getErrors(f)).toBeUndefined();
1162+
});
1163+
1164+
it("returns the declared error map at runtime", () => {
1165+
const f = v.fn(
1166+
"ge.with_errors",
1167+
{ errors: { not_found: v.object({ id: v.string() }), forbidden: {} } },
1168+
() => "ok",
1169+
);
1170+
const errs = getErrors(f);
1171+
expect(errs).toBeDefined();
1172+
expect(errs).toHaveProperty("not_found");
1173+
expect(errs).toHaveProperty("forbidden");
1174+
});
1175+
1176+
it("infers the error-map type from the generic parameter", () => {
1177+
const conflict = v.object({ email: v.string() });
1178+
const f = v.fn("ge.typed", { errors: { conflict } }, () => "ok");
1179+
// Type-level: getErrors should return the declared map or undefined.
1180+
expectTypeOf(getErrors(f)).toMatchTypeOf<
1181+
{ conflict: typeof conflict } | undefined
1182+
>();
1183+
});
1184+
1185+
it("returns undefined for a fn that omitted the errors key", () => {
1186+
const bare = v.fn(() => "bare");
1187+
// $schema is always present from defineFn, but without an errors key.
1188+
expect(getErrors(bare)).toBeUndefined();
1189+
});
1190+
});

0 commit comments

Comments
 (0)