Skip to content

Commit

Permalink
Add or operation
Browse files Browse the repository at this point in the history
  • Loading branch information
exoego committed Apr 16, 2024
1 parent 864c6ce commit 7b61de4
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 0 deletions.
45 changes: 45 additions & 0 deletions src/other.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,48 @@ export const optional = (innerValidator?: Validator<any, any>) => {
},
};
};

interface OrSchema extends Fn {
return: this["args"] extends [...any, infer prev, infer last]
? last extends { $outputType: infer T1 }
? prev extends { $outputType: infer T2 }
? T1 | T2
: T1 | prev
: prev extends { $outputType: infer T2 }
? last | T2
: last | prev
: never;
}

export const or = <T1, T2>(innerValidator: Validator<T1, any>) => {
const ctx = {
chain: null as Validator<T2, any> | null,
} satisfies { chain: Validator<T2, any> | null };

return {
name: "or" as const,
$inputType: "any" as unknown as T1 | T2,
$outputType: "any" as unknown as OrSchema,
processChain: (chain: Validator<T2, any> | null) => {
if (chain !== null) {
ctx.chain = chain;
}
return [];
},
parse: (arg: any) => {
if (arg === null) return arg;
const chain = ctx.chain;
if (chain === null) {
throw new Error(
`No inner validator. Make sure to do c.someValidator().or(c.otherValidator())`,
);
}

try {
return chain.parse(arg);
} catch (e) {
return innerValidator.parse(arg);
}
},
};
};
41 changes: 41 additions & 0 deletions tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
object,
nullable,
optional,
or,
coerce,
} from "../src";
import { Equal, Expect } from "./helpers.types";
Expand Down Expand Up @@ -43,6 +44,7 @@ describe("basic tests", () => {
nullable,
literal,
optional,
or,
set,
setNonEmpty,
setMin,
Expand Down Expand Up @@ -238,4 +240,43 @@ describe("basic tests", () => {

// TODO: test other coerce functions
});

describe("or", () => {
test("basic", () => {
const schema = c.string().or(c.number());
expect(schema.parse("yay")).toEqual("yay");
expect(schema.parse(42)).toEqual(42);
expect(() => schema.parse({ x: 42 })).toThrow();

type SchemaType = Infer<typeof schema>;
type _test = Expect<Equal<SchemaType, string | number>>;
});

test("literal", () => {
const schema = c.literal("a").or(c.literal(42));
expect(schema.parse("a")).toEqual("a");
expect(schema.parse(42)).toEqual(42);
expect(() => schema.parse("aaa")).toThrow();

type SchemaType = Infer<typeof schema>;
type _test = Expect<Equal<SchemaType, "a" | 42>>;
});

test("object", () => {
const schema = c.object({ x: number() }).or(c.object({ y: string() }));
// FIXME: .or should be chainable multiple times
// .or(c.string());
expect(schema.parse({ x: 42 })).toEqual({ x: 42 });
expect(schema.parse({ y: "foo" })).toEqual({ y: "foo" });
// expect(schema.parse("foo")).toEqual("foo");
expect(() => schema.parse({ x: "bar" })).toThrow();
expect(() => schema.parse({ y: 42 })).toThrow();

// strip keys in other schema
expect(schema.parse({ x: 42, y: "foo" })).toEqual({ x: 42 });

type SchemaType = Infer<typeof schema>;
type _test = Expect<Equal<SchemaType, { x: number } | { y: string }>>;
});
});
});

0 comments on commit 7b61de4

Please sign in to comment.