From da4523849b9c318ee344d73721fc2cde55ea12a0 Mon Sep 17 00:00:00 2001 From: Yamiteru Date: Mon, 29 Apr 2024 19:30:37 +0200 Subject: [PATCH] feat: rework the remaining assertions --- src/assertions/array/index.ts | 4 ++-- src/assertions/boolean/index.ts | 2 +- src/assertions/email/index.ts | 2 +- src/assertions/expect/index.ts | 19 +++++++++++-------- src/assertions/isArray/index.ts | 6 +++--- src/assertions/isArray/types.d.ts | 14 ++++++++++++++ src/assertions/isObject/index.ts | 12 ++++++++---- src/assertions/isObject/types.d.ts | 15 +++++++++++++++ src/assertions/nullable/index.ts | 4 ++-- src/assertions/number/index.ts | 2 +- src/assertions/object/index.ts | 4 ++-- src/assertions/optional/index.ts | 4 ++-- src/assertions/regex/index.ts | 4 ++-- src/assertions/string/index.ts | 2 +- src/assertions/tuple/index.ts | 4 ++-- src/assertions/type/index.ts | 5 +++-- src/assertions/type/types.d.ts | 17 +++++++++++------ src/error.ts | 8 ++++++-- src/utils.ts | 23 +++++++++++++++++++++-- 19 files changed, 108 insertions(+), 43 deletions(-) create mode 100644 src/assertions/isArray/types.d.ts diff --git a/src/assertions/array/index.ts b/src/assertions/array/index.ts index 22e7fc6..e7a0e82 100644 --- a/src/assertions/array/index.ts +++ b/src/assertions/array/index.ts @@ -16,8 +16,8 @@ import type { Validate } from "./types"; * ``` */ export const array = <$Brand extends AnyBrand>(brand: $Brand) => - ((v: unknown) => { - isArray(v); + ((v: any) => { + (isArray as any)(v); for (let i = 0; i < v.length; ++i) { (brand as any)((v as any)[i]); diff --git a/src/assertions/boolean/index.ts b/src/assertions/boolean/index.ts index 123b4ac..ed9059f 100644 --- a/src/assertions/boolean/index.ts +++ b/src/assertions/boolean/index.ts @@ -10,4 +10,4 @@ import type { Validate } from "./types"; * boolean(true); // passes * ``` */ -export const boolean: Validate.Type.Boolean = type("boolean"); +export const boolean = type("boolean") as Validate.Type.Boolean; diff --git a/src/assertions/email/index.ts b/src/assertions/email/index.ts index 2e63044..55ccf5f 100644 --- a/src/assertions/email/index.ts +++ b/src/assertions/email/index.ts @@ -10,4 +10,4 @@ import type { Validate } from "./types"; * email("yamiteru@icloud.com"); // passes * ``` */ -export const email: Validate.Regex.Email = regex(/^\w+@.+\..+$/); +export const email = regex(/^\w+@.+\..+$/) as Validate.Regex.Email; diff --git a/src/assertions/expect/index.ts b/src/assertions/expect/index.ts index 88fd6f8..edb89ae 100644 --- a/src/assertions/expect/index.ts +++ b/src/assertions/expect/index.ts @@ -1,10 +1,11 @@ import type { Message } from "@assertions/expect/types"; -import type { Assertion } from "@the-minimal/types"; +import { error } from "@error"; +import type { AnyBrand } from "@the-minimal/types"; /** * Wraps assertion and throws an error with message if assertion fails. * - * @param assertion - Assertion to be checked. + * @param brand - Assertion to be checked. * @param message - Message to be used in error. * * @example @@ -15,12 +16,14 @@ import type { Assertion } from "@the-minimal/types"; * ); * ``` */ -export const expect = - <$Input>(assertion: Assertion<$Input>, message: Message): Assertion<$Input> => - (v) => { +export const expect = <$Brand extends AnyBrand>( + brand: $Brand, + message: Message, +) => + ((v: unknown) => { try { - assertion(v); + (brand as any)(v); } catch (e: any) { - throw Error(message(e, v)); + error(brand, message(e, v)); } - }; + }) as unknown as $Brand; diff --git a/src/assertions/isArray/index.ts b/src/assertions/isArray/index.ts index 713bd4a..e6f81a8 100644 --- a/src/assertions/isArray/index.ts +++ b/src/assertions/isArray/index.ts @@ -1,5 +1,5 @@ import { error } from "@error"; -import type { Assertion } from "@the-minimal/types"; +import type { Validate } from "./types"; /** * Checks that the value is an array. @@ -10,5 +10,5 @@ import type { Assertion } from "@the-minimal/types"; * isArray([]); // passes * ``` */ -export const isArray: Assertion> = ((v) => - Array.isArray(v) || error(isArray)) as Assertion>; +export const isArray = ((v: unknown) => + Array.isArray(v) || error(isArray)) as unknown as Validate.Type.Array; diff --git a/src/assertions/isArray/types.d.ts b/src/assertions/isArray/types.d.ts new file mode 100644 index 0000000..5950c55 --- /dev/null +++ b/src/assertions/isArray/types.d.ts @@ -0,0 +1,14 @@ +import type { Brand, None } from "@the-minimal/types"; + +export namespace Validate { + export namespace Type { + export type Array = Brand< + "array", + None, + { + input: unknown[]; + output: None; + } + >; + } +} diff --git a/src/assertions/isObject/index.ts b/src/assertions/isObject/index.ts index 1499703..a843a76 100644 --- a/src/assertions/isObject/index.ts +++ b/src/assertions/isObject/index.ts @@ -1,6 +1,8 @@ -import type { ObjectUnknown } from "@assertions/isObject/types"; +import type { Validate as AndValidate } from "@assertions/and/types"; +// TODO: this is fucking ugly +import type { Validate as IsObjectValidate } from "@assertions/isObject/types"; +import type { Validate as NotValueValidate } from "@assertions/notValue/types"; import { error } from "@error"; -import type { Assertion } from "@the-minimal/types"; /** * Checks that the value is of type object and is not null. @@ -12,6 +14,8 @@ import type { Assertion } from "@the-minimal/types"; * isObject({}); // passes * ``` */ -export const isObject: Assertion = ((v) => +export const isObject = ((v: unknown) => (v !== null && typeof v === "object") || - error(isObject)) as Assertion; + error(isObject)) as unknown as AndValidate.And< + [NotValueValidate.Value.Ne, IsObjectValidate.Type.Object] +>; diff --git a/src/assertions/isObject/types.d.ts b/src/assertions/isObject/types.d.ts index 6043024..478e6f5 100644 --- a/src/assertions/isObject/types.d.ts +++ b/src/assertions/isObject/types.d.ts @@ -1 +1,16 @@ +import type { Brand, None } from "@the-minimal/types"; + +export namespace Validate { + export namespace Type { + export type Object = Brand< + "object", + None, + { + input: ObjectUnknown; + output: None; + } + >; + } +} + export type ObjectUnknown = Record; diff --git a/src/assertions/nullable/index.ts b/src/assertions/nullable/index.ts index 8cb9f7b..2825ec5 100644 --- a/src/assertions/nullable/index.ts +++ b/src/assertions/nullable/index.ts @@ -1,5 +1,5 @@ import type { AnyBrand } from "@the-minimal/types"; -import type { General } from "./types"; +import type { Validate } from "./types"; /** * Checks if the assertion passes or if the value is null. @@ -17,4 +17,4 @@ import type { General } from "./types"; */ export const nullable = <$Brand extends AnyBrand>(brand: $Brand) => ((v: unknown) => - v === null || (brand as any)(v)) as unknown as General.Nullable<$Brand>; + v === null || (brand as any)(v)) as unknown as Validate.Nullable<$Brand>; diff --git a/src/assertions/number/index.ts b/src/assertions/number/index.ts index ded7be7..5f8b54a 100644 --- a/src/assertions/number/index.ts +++ b/src/assertions/number/index.ts @@ -10,4 +10,4 @@ import type { Validate } from "./types"; * number(1); // passes * ``` */ -export const number: Validate.Type.Number = type("number"); +export const number = type("number") as Validate.Type.Number; diff --git a/src/assertions/object/index.ts b/src/assertions/object/index.ts index ee8d26d..d631355 100644 --- a/src/assertions/object/index.ts +++ b/src/assertions/object/index.ts @@ -28,8 +28,8 @@ import type { General, ObjectSchema } from "@assertions/object/types"; export const object = <$Schema extends ObjectSchema>(schema: $Schema) => { const keys = Object.keys(schema); - return ((v: unknown) => { - isObject(v); + return ((v: any) => { + (isObject as any)(v); for (let i = 0; i < keys.length; ++i) { (schema[keys[i]] as any)(v[keys[i] as any]); diff --git a/src/assertions/optional/index.ts b/src/assertions/optional/index.ts index b7c5aac..5082478 100644 --- a/src/assertions/optional/index.ts +++ b/src/assertions/optional/index.ts @@ -1,5 +1,5 @@ import type { AnyBrand } from "@the-minimal/types"; -import type { General } from "./types"; +import type { Validate } from "./types"; /** * Checks if the assertion passes or if the value is undefined. @@ -18,4 +18,4 @@ import type { General } from "./types"; export const optional = <$Brand extends AnyBrand>(brand: $Brand) => ((v: unknown) => v === undefined || - (brand as any)(v)) as unknown as General.Optional<$Brand>; + (brand as any)(v)) as unknown as Validate.Optional<$Brand>; diff --git a/src/assertions/regex/index.ts b/src/assertions/regex/index.ts index 81b84b2..5bf0f2e 100644 --- a/src/assertions/regex/index.ts +++ b/src/assertions/regex/index.ts @@ -1,5 +1,5 @@ import { error } from "@error"; -import type { Validation } from "./types"; +import type { Validate } from "./types"; /** * Checks if value matches regex pattern. @@ -16,4 +16,4 @@ import type { Validation } from "./types"; */ export const regex = (pattern: RegExp) => ((v: string) => - pattern.test(v) || error(regex)) as unknown as Validation.Regex.Any; + pattern.test(v) || error(regex)) as unknown as Validate.Regex.Any; diff --git a/src/assertions/string/index.ts b/src/assertions/string/index.ts index 97970fe..171f57f 100644 --- a/src/assertions/string/index.ts +++ b/src/assertions/string/index.ts @@ -10,4 +10,4 @@ import type { Validate } from "./types"; * string("hello"); // passes * ``` */ -export const string: Validate.Type.String = type("string"); +export const string = type("string") as Validate.Type.String; diff --git a/src/assertions/tuple/index.ts b/src/assertions/tuple/index.ts index 2876647..8896dae 100644 --- a/src/assertions/tuple/index.ts +++ b/src/assertions/tuple/index.ts @@ -16,8 +16,8 @@ import type { TupleSchema, Validate } from "./types"; * ``` */ export const tuple = (assertions: $Schema) => - ((v: unknown) => { - isArray(v); + ((v: any) => { + (isArray as any)(v); for (let i = 0; i < assertions.length; ++i) { (assertions[i] as any)(v[i]); diff --git a/src/assertions/type/index.ts b/src/assertions/type/index.ts index 56591a2..e5df71d 100644 --- a/src/assertions/type/index.ts +++ b/src/assertions/type/index.ts @@ -1,5 +1,5 @@ import { error } from "@error"; -import type { Type } from "./types"; +import type { Validate } from "./types"; /** * Checks that the value is of the provided type. @@ -15,4 +15,5 @@ import type { Type } from "./types"; * ``` */ export const type = (input: string) => - ((v: unknown) => typeof v === input || error(type)) as unknown as Type.Any; + ((v: unknown) => + typeof v === input || error(type)) as unknown as Validate.Type.Any; diff --git a/src/assertions/type/types.d.ts b/src/assertions/type/types.d.ts index 40fbde0..21b56be 100644 --- a/src/assertions/type/types.d.ts +++ b/src/assertions/type/types.d.ts @@ -1,9 +1,14 @@ -import type {Brand} from "@the-minimal/types"; +import type { Brand } from "@the-minimal/types"; export namespace Validate { - export namespace Type { - export type Any = Brand<"type", any, { - input: any; - output: any; - }>; + export namespace Type { + export type Any = Brand< + "type", + any, + { + input: any; + output: any; + } + >; + } } diff --git a/src/error.ts b/src/error.ts index 9189240..839a870 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,3 +1,7 @@ -export const error = (assertion: unknown) => { - throw assertion; +export const error = (cause: unknown, message = "") => { + throw { + name: "Validation", + message, + cause, + }; }; diff --git a/src/utils.ts b/src/utils.ts index dc5f258..d52df51 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,9 +1,28 @@ import type { AnyBrand } from "@the-minimal/types"; -import type { InferInput } from "@types"; +import type { InferOutput } from "@types"; export function assert<$Brand extends AnyBrand>( brand: $Brand, value: unknown, -): asserts value is InferInput<$Brand> { +): asserts value is InferOutput<$Brand> { (brand as any)(value); } + +export function parse<$Brand extends AnyBrand>(brand: $Brand, value: unknown) { + (brand as any)(value); + + return value as InferOutput<$Brand>; +} + +export function is<$Brand extends AnyBrand>( + brand: $Brand, + value: unknown, +): value is InferOutput<$Brand> { + try { + (brand as any)(value); + + return true; + } catch { + return false; + } +}