diff --git a/.github/workflows/auto-deprecate.yml b/.github/workflows/auto-deprecate.yml index 10661855..3bc1ea0d 100644 --- a/.github/workflows/auto-deprecate.yml +++ b/.github/workflows/auto-deprecate.yml @@ -17,7 +17,7 @@ jobs: with: node-version: 20 cache: yarn - registry-url: https://registry.npmjs.org/ + registry-url: https://registry.yarnpkg.org/ - name: Install Dependencies run: yarn --immutable - name: Deprecate versions diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 08811e1e..efd946dd 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -20,7 +20,7 @@ jobs: with: node-version: 20 cache: yarn - registry-url: https://registry.npmjs.org/ + registry-url: https://registry.yarnpkg.org/ - name: Install Dependencies run: yarn --immutable - name: Run ESLint @@ -37,7 +37,7 @@ jobs: with: node-version: 20 cache: yarn - registry-url: https://registry.npmjs.org/ + registry-url: https://registry.yarnpkg.org/ - name: Install Dependencies run: yarn --immutable - name: Generate Documentation @@ -60,11 +60,13 @@ jobs: with: node-version: ${{ matrix.node }} cache: yarn - registry-url: https://registry.npmjs.org/ + registry-url: https://registry.yarnpkg.org/ - name: Install Dependencies run: yarn --immutable - - name: Typecheck And Build Code - run: yarn typecheck && yarn build + - name: Typecheck Code + run: yarn typecheck + - name: Build Code + run: yarn build - name: Run tests run: yarn test - name: Codecov Upload ${{ matrix.node }} diff --git a/.github/workflows/deprecate-on-merge.yml b/.github/workflows/deprecate-on-merge.yml index 05aef98f..e49e92f5 100644 --- a/.github/workflows/deprecate-on-merge.yml +++ b/.github/workflows/deprecate-on-merge.yml @@ -17,7 +17,7 @@ jobs: with: node-version: 20 cache: yarn - registry-url: https://registry.npmjs.org/ + registry-url: https://registry.yarnpkg.org/ - name: Install Dependencies run: yarn --immutable - name: Deprecate versions diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 7415562c..e074f10a 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -29,7 +29,7 @@ jobs: with: node-version: 20 cache: yarn - registry-url: https://registry.npmjs.org/ + registry-url: https://registry.yarnpkg.org/ - name: Install Dependencies run: yarn --immutable - name: Build Documentation diff --git a/.prettierrc.mjs b/.prettierrc.mjs index 056197df..acebb9b9 100644 --- a/.prettierrc.mjs +++ b/.prettierrc.mjs @@ -5,7 +5,7 @@ export default { overrides: [ ...sapphirePrettierConfig.overrides, { - files: ['README.md'], + files: ['README.md', 'UPGRADING-v3-v4.md'], options: { tabWidth: 2, useTabs: false, diff --git a/.vscode/settings.json b/.vscode/settings.json index d2b0922b..a7d5da69 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,7 +13,7 @@ "**/dist/": true, "**/.git/": true }, - "cSpell.words": ["bortzmeyer", "fhqwhgads", "manisharaan", "midichlorians", "Pallas", "Plagueis"], + "cSpell.words": ["bortzmeyer", "fhqwhgads", "Jsonified", "manisharaan", "midichlorians", "Pallas", "Plagueis"], "sonarlint.connectedMode.project": { "connectionId": "https-sonarqube-sapphirejs-dev", "projectKey": "sapphiredev_shapeshift_12fd031b-e1ff-436c-8ccb-4fb016c525c9" diff --git a/README.md b/README.md index c9f113f4..07bf264b 100644 --- a/README.md +++ b/README.md @@ -16,59 +16,58 @@ Blazing fast input validation and transformation ⚡ ## Table of Contents -- [@sapphire/shapeshift](#sapphireshapeshift) - - [Table of Contents](#table-of-contents) - - [Description](#description) - - [Features](#features) - - [Usage](#usage) - - [Basic usage](#basic-usage) - - [Defining validations](#defining-validations) - - [Primitives](#primitives) - - [Literals](#literals) - - [Strings](#strings) - - [Numbers](#numbers) - - [BigInts](#bigints) - - [Booleans](#booleans) - - [Arrays](#arrays) - - [Tuples](#tuples) - - [Unions](#unions) - - [Enums](#enums) - - [Maps](#maps) - - [Sets](#sets) - - [Instances](#instances) - - [Records](#records) - - [Functions // TODO](#functions--todo) - - [TypedArray](#typedarray) - - [Defining schemas (objects)](#defining-schemas-objects) - - [Utility types for TypeScript](#utility-types-for-typescript) - - [Extracting an interface from a schema](#extracting-an-interface-from-a-schema) - - [Defining the structure of a schema through an interface](#defining-the-structure-of-a-schema-through-an-interface) - - [`.extend`:](#extend) - - [`.pick` / `.omit`:](#pick--omit) - - [`.partial`](#partial) - - [`.required`](#required) - - [Handling unrecognized keys](#handling-unrecognized-keys) - - [`.strict`](#strict) - - [`.ignore`](#ignore) - - [`.passthrough`](#passthrough) - - [BaseValidator: methods and properties](#basevalidator-methods-and-properties) - - [`.run`](#rundata-unknown-resultt-error-given-a-validation-you-can-call-this-method-to-check-whether-or-not-the) - - [`.parse`](#parsedata-unknown-t-given-a-validations-you-can-call-this-method-to-check-whether-or-not-the-input-is-valid) - - [`.transform`](#transformrvalue-t--r-nopvalidatorr-adds-a-constraint-that-modifies-the-input) - - [`.reshape`](#reshapervalue-t--resultr-error--iconstraint-nopvalidatorr-adds-a-constraint-able-to-both-validate) - - [`.default`](#defaultvalue-t----t-transform-undefined-into-the-given-value-or-the-callbacks-returned-value) - - [`.optional`](#optional-a-convenience-method-that-returns-a-union-of-the-type-with-sundefined) - - [`.nullable`](#nullable-a-convenience-method-that-returns-a-union-of-the-type-with-snullable) - - [`.nullish`](#nullish-a-convenience-method-that-returns-a-union-of-the-type-with-snullish) - - [`.array`](#array-a-convenience-method-that-returns-an-arrayvalidator-with-the-type) - - [`.or`](#or-a-convenience-method-that-returns-an-unionvalidator-with-the-type-this-method-is-also-overridden-in) - - [`.when`](#when-adjust-the-schema-based-on-a-sibling-or-sinbling-children-fields) - - [Available options for providing `is`](#available-options-for-providing-is) - - [Resolving of the `key` (first) parameter](#resolving-of-the-key-first-parameter) - - [Examples](#examples) - - [Enabling and disabling validation](#enabling-and-disabling-validation) - - [Buy us some doughnuts](#buy-us-some-doughnuts) - - [Contributors](#contributors) +- [Table of Contents](#table-of-contents) +- [Description](#description) +- [Features](#features) +- [Usage](#usage) + - [Basic usage](#basic-usage) + - [Defining validations](#defining-validations) + - [Primitives](#primitives) + - [Literals](#literals) + - [Strings](#strings) + - [Numbers](#numbers) + - [BigInts](#bigints) + - [Booleans](#booleans) + - [Arrays](#arrays) + - [Tuples](#tuples) + - [Unions](#unions) + - [Enums](#enums) + - [Maps](#maps) + - [Sets](#sets) + - [Instances](#instances) + - [Records](#records) + + - [TypedArray](#typedarray) + - [Defining schemas (objects)](#defining-schemas-objects) + - [Utility types for TypeScript](#utility-types-for-typescript) + - [Extracting an interface from a schema](#extracting-an-interface-from-a-schema) + - [Defining the structure of a schema through an interface](#defining-the-structure-of-a-schema-through-an-interface) + - [`.extend`:](#extend) + - [`.pick` / `.omit`:](#pick--omit) + - [`.partial`](#partial) + - [`.required`](#required) + - [Handling unrecognized keys](#handling-unrecognized-keys) + - [`.strict`](#strict) + - [`.ignore`](#ignore) + - [`.passthrough`](#passthrough) + - [BaseValidator: methods and properties](#basevalidator-methods-and-properties) + - [`.run(data: unknown): Result`: given a validation, you can call this method to check whether or not the](#rundata-unknown-result-given-a-validation-you-can-call-this-method-to-check-whether-or-not-the) + - [`.parse(data: unknown): T`: given a validations, you can call this method to check whether or not the input is valid.](#parsedata-unknown-t-given-a-validations-you-can-call-this-method-to-check-whether-or-not-the-input-is-valid) + - [`.transform((value: T) => R): NopValidator`: adds a constraint that modifies the input:](#transformvalue-t--r-nopvalidator-adds-a-constraint-that-modifies-the-input) + - [`.reshape((value: T) => Result | IConstraint): NopValidator`: adds a constraint able to both validate](#reshapevalue-t--result--iconstraint-nopvalidator-adds-a-constraint-able-to-both-validate) + - [`.default(value: T | (() => T))`: transform `undefined` into the given value or the callback's returned value:](#defaultvalue-t----t-transform-undefined-into-the-given-value-or-the-callbacks-returned-value) + - [`.optional`: a convenience method that returns a union of the type with `s.undefined()`.](#optional-a-convenience-method-that-returns-a-union-of-the-type-with-sundefined) + - [`.nullable`: a convenience method that returns a union of the type with `s.nullable()`.](#nullable-a-convenience-method-that-returns-a-union-of-the-type-with-snullable) + - [`.nullish`: a convenience method that returns a union of the type with `s.nullish()`.](#nullish-a-convenience-method-that-returns-a-union-of-the-type-with-snullish) + - [`.array`: a convenience method that returns an ArrayValidator with the type.](#array-a-convenience-method-that-returns-an-arrayvalidator-with-the-type) + - [`.or`: a convenience method that returns an UnionValidator with the type. This method is also overridden in](#or-a-convenience-method-that-returns-an-unionvalidator-with-the-type-this-method-is-also-overridden-in) + - [`.when`: Adjust the schema based on a sibling or sinbling children fields.](#when-adjust-the-schema-based-on-a-sibling-or-sinbling-children-fields) + - [Available options for providing `is`](#available-options-for-providing-is) + - [Resolving of the `key` (first) parameter](#resolving-of-the-key-first-parameter) + - [Examples](#examples) + - [Enabling and disabling validation](#enabling-and-disabling-validation) +- [Buy us some doughnuts](#buy-us-some-doughnuts) +- [Contributors](#contributors) ## Description @@ -84,7 +83,7 @@ A very fast and lightweight input validation and transformation library for Java - TypeScript friendly - Offers CJS, ESM and UMD builds -- API similar to [`zod`] +- API similar to [`zod`] and [`yup`] - Faster than ⚡ ## Usage @@ -102,7 +101,7 @@ Creating a simple string validation ```typescript import { s } from '@sapphire/shapeshift'; -const myStringValidation = s.string; +const myStringValidation = s.string(); // Parse myStringValidation.parse('sapphire'); // => returns 'sapphire' @@ -115,7 +114,7 @@ Creating an object schema import { s } from '@sapphire/shapeshift'; const user = s.object({ - username: s.string + username: s.string() }); user.parse({ username: 'Sapphire' }); @@ -133,23 +132,23 @@ user.parse({ username: 'Sapphire' }); import { s } from '@sapphire/shapeshift'; // Primitives -s.string; -s.number; -s.bigint; -s.boolean; -s.date; +s.string(); +s.number(); +s.bigint(); +s.boolean(); +s.date(); // Empty Types -s.undefined; -s.null; -s.nullish; // Accepts undefined | null +s.undefined(); +s.null(); +s.nullish(); // Accepts undefined | null // Catch-all Types -s.any; -s.unknown; +s.any(); +s.unknown(); // Never Type -s.never; +s.never(); ``` #### Literals @@ -161,7 +160,7 @@ s.literal('sapphire'); s.literal(12); s.literal(420n); s.literal(true); -s.literal(new Date(1639278160000)); // s.date.equal(1639278160000); +s.literal(new Date(1639278160000)); // s.date().equal(1639278160000); ``` #### Strings @@ -171,20 +170,20 @@ s.literal(new Date(1639278160000)); // s.date.equal(1639278160000); Shapeshift includes a handful of string-specific validations: ```typescript -s.string.lengthLessThan(5); -s.string.lengthLessThanOrEqual(5); -s.string.lengthGreaterThan(5); -s.string.lengthGreaterThanOrEqual(5); -s.string.lengthEqual(5); -s.string.lengthNotEqual(5); -s.string.email; -s.string.url(); -s.string.uuid(); -s.string.regex(regex); -s.string.ip(); -s.string.ipv4; -s.string.ipv6; -s.string.phone(); +s.string().lengthLessThan(5); +s.string().lengthLessThanOrEqual(5); +s.string().lengthGreaterThan(5); +s.string().lengthGreaterThanOrEqual(5); +s.string().lengthEqual(5); +s.string().lengthNotEqual(5); +s.string().email(); +s.string().url(); +s.string().uuid(); +s.string().regex(regex); +s.string().ip(); +s.string().ipv4(); +s.string().ipv6(); +s.string().phone(); ``` #### Numbers @@ -194,37 +193,37 @@ s.string.phone(); Shapeshift includes a handful of number-specific validations: ```typescript -s.number.greaterThan(5); // > 5 -s.number.greaterThanOrEqual(5); // >= 5 -s.number.lessThan(5); // < 5 -s.number.lessThanOrEqual(5); // <= 5 -s.number.equal(5); // === 5 -s.number.notEqual(5); // !== 5 +s.number().greaterThan(5); // > 5 +s.number().greaterThanOrEqual(5); // >= 5 +s.number().lessThan(5); // < 5 +s.number().lessThanOrEqual(5); // <= 5 +s.number().equal(5); // === 5 +s.number().notEqual(5); // !== 5 -s.number.equal(NaN); // special case: Number.isNaN -s.number.notEqual(NaN); // special case: !Number.isNaN +s.number().equal(NaN); // special case: Number.isNaN +s.number().notEqual(NaN); // special case: !Number.isNaN -s.number.int; // value must be an integer -s.number.safeInt; // value must be a safe integer -s.number.finite; // value must be finite +s.number().int(); // value must be an integer +s.number().safeInt(); // value must be a safe integer +s.number().finite(); // value must be finite -s.number.positive; // .greaterThanOrEqual(0) -s.number.negative; // .lessThan(0) +s.number().positive(); // .greaterThanOrEqual(0) +s.number().negative(); // .lessThan(0) -s.number.divisibleBy(5); // Divisible by 5 +s.number().divisibleBy(5); // Divisible by 5 ``` And transformations: ```typescript -s.number.abs; // Transforms the number to an absolute number -s.number.sign; // Gets the number's sign +s.number().abs(); // Transforms the number to an absolute number +s.number().sign(); // Gets the number's sign -s.number.trunc; // Transforms the number to the result of `Math.trunc` -s.number.floor; // Transforms the number to the result of `Math.floor` -s.number.fround; // Transforms the number to the result of `Math.fround` -s.number.round; // Transforms the number to the result of `Math.round` -s.number.ceil; // Transforms the number to the result of `Math.ceil` +s.number().trunc(); // Transforms the number to the result of `Math.trunc` +s.number().floor(); // Transforms the number to the result of `Math.floor` +s.number().fround(); // Transforms the number to the result of `Math.fround` +s.number().round(); // Transforms the number to the result of `Math.round` +s.number().ceil(); // Transforms the number to the result of `Math.ceil` ``` #### BigInts @@ -234,26 +233,26 @@ s.number.ceil; // Transforms the number to the result of `Math.ceil` Shapeshift includes a handful of number-specific validations: ```typescript -s.bigint.greaterThan(5n); // > 5n -s.bigint.greaterThanOrEqual(5n); // >= 5n -s.bigint.lessThan(5n); // < 5n -s.bigint.lessThanOrEqual(5n); // <= 5n -s.bigint.equal(5n); // === 5n -s.bigint.notEqual(5n); // !== 5n +s.bigint().greaterThan(5n); // > 5n +s.bigint().greaterThanOrEqual(5n); // >= 5n +s.bigint().lessThan(5n); // < 5n +s.bigint().lessThanOrEqual(5n); // <= 5n +s.bigint().equal(5n); // === 5n +s.bigint().notEqual(5n); // !== 5n -s.bigint.positive; // .greaterThanOrEqual(0n) -s.bigint.negative; // .lessThan(0n) +s.bigint().positive(); // .greaterThanOrEqual(0n) +s.bigint().negative(); // .lessThan(0n) -s.bigint.divisibleBy(5n); // Divisible by 5n +s.bigint().divisibleBy(5n); // Divisible by 5n ``` And transformations: ```typescript -s.bigint.abs; // Transforms the bigint to an absolute bigint +s.bigint().abs(); // Transforms the bigint to an absolute bigint -s.bigint.intN(5); // Clamps to a bigint to a signed bigint with 5 digits, see BigInt.asIntN -s.bigint.uintN(5); // Clamps to a bigint to an unsigned bigint with 5 digits, see BigInt.asUintN +s.bigint().intN(5); // Clamps to a bigint to a signed bigint with 5 digits, see BigInt.asIntN +s.bigint().uintN(5); // Clamps to a bigint to an unsigned bigint with 5 digits, see BigInt.asUintN ``` #### Booleans @@ -263,14 +262,14 @@ s.bigint.uintN(5); // Clamps to a bigint to an unsigned bigint with 5 digits, se Shapeshift includes a few boolean-specific validations: ```typescript -s.boolean.true; // value must be true -s.boolean.false; // value must be false +s.boolean().true(); // value must be true +s.boolean().false(); // value must be false -s.boolean.equal(true); // s.boolean.true -s.boolean.equal(false); // s.boolean.false +s.boolean().equal(true); // s.boolean().true() +s.boolean().equal(false); // s.boolean().false() -s.boolean.notEqual(true); // s.boolean.false -s.boolean.notEqual(false); // s.boolean.true +s.boolean().notEqual(true); // s.boolean().false() +s.boolean().notEqual(false); // s.boolean().true() ``` #### Arrays @@ -278,27 +277,27 @@ s.boolean.notEqual(false); // s.boolean.true [Back to top][toc] ```typescript -const stringArray = s.array(s.string); -const stringArray = s.string.array; +const stringArray = s.array(s.string()); +const stringArray = s.string().array(); ``` Shapeshift includes a handful of array-specific validations: ```typescript -s.string.array.lengthLessThan(5); // Must have less than 5 elements -s.string.array.lengthLessThanOrEqual(5); // Must have 5 or less elements -s.string.array.lengthGreaterThan(5); // Must have more than 5 elements -s.string.array.lengthGreaterThanOrEqual(5); // Must have 5 or more elements -s.string.array.lengthEqual(5); // Must have exactly 5 elements -s.string.array.lengthNotEqual(5); // Must not have exactly 5 elements -s.string.array.lengthRange(0, 4); // Must have at least 0 elements and less than 4 elements (in math, that is [0, 4)) -s.string.array.lengthRangeInclusive(0, 4); // Must have at least 0 elements and at most 4 elements (in math, that is [0, 4]) -s.string.array.lengthRangeExclusive(0, 4); // Must have more than 0 element and less than 4 elements (in math, that is (0, 4)) -s.string.array.unique; // All elements must be unique. Deep equality is used to check for uniqueness. +s.string().array().lengthLessThan(5); // Must have less than 5 elements +s.string().array().lengthLessThanOrEqual(5); // Must have 5 or less elements +s.string().array().lengthGreaterThan(5); // Must have more than 5 elements +s.string().array().lengthGreaterThanOrEqual(5); // Must have 5 or more elements +s.string().array().lengthEqual(5); // Must have exactly 5 elements +s.string().array().lengthNotEqual(5); // Must not have exactly 5 elements +s.string().array().lengthRange(0, 4); // Must have at least 0 elements and less than 4 elements (in math, that is [0, 4)) +s.string().array().lengthRangeInclusive(0, 4); // Must have at least 0 elements and at most 4 elements (in math, that is [0, 4]) +s.string().array().lengthRangeExclusive(0, 4); // Must have more than 0 element and less than 4 elements (in math, that is (0, 4)) +s.string().array().unique(); // All elements must be unique. Deep equality is used to check for uniqueness. ``` > **Note**: All `.length` methods define tuple types with the given amount of elements. For example, -> `s.string.array.lengthGreaterThanOrEqual(2)`'s inferred type is `[string, string, ...string[]]` +> `s.string().array().lengthGreaterThanOrEqual(2)`'s inferred type is `[string, string, ...string[]]` #### Tuples @@ -308,9 +307,9 @@ Unlike arrays, tuples have a fixed number of elements and each element can have ```typescript const dish = s.tuple([ - s.string, // Dish's name - s.number.int, // Table's number - s.date // Date the dish was ready for delivery + s.string(), // Dish's name + s.number().int(), // Table's number + s.date() // Date the dish was ready for delivery ]); dish.parse(['Iberian ham', 10, new Date()]); @@ -323,7 +322,7 @@ dish.parse(['Iberian ham', 10, new Date()]); Shapeshift includes a built-in method for composing OR types: ```typescript -const stringOrNumber = s.union(s.string, s.number); +const stringOrNumber = s.union([s.string(), s.number()]); stringOrNumber.parse('Sapphire'); // => 'Sapphire' stringOrNumber.parse(42); // => 42 @@ -334,11 +333,11 @@ stringOrNumber.parse({}); // => throws CombinedError [Back to top][toc] -Enums are a convenience method that aliases `s.union(s.literal(a), s.literal(b), ...)`: +Enums are a convenience method that aliases `s.union([s.literal(a), s.literal(b), ...])`: ```typescript -s.enum('Red', 'Green', 'Blue'); -// s.union(s.literal('Red'), s.literal('Green'), s.literal('Blue')); +s.enum(['Red', 'Green', 'Blue']); +// s.union([s.literal('Red'), s.literal('Green'), s.literal('Blue')]); ``` #### Maps @@ -346,7 +345,7 @@ s.enum('Red', 'Green', 'Blue'); [Back to top][toc] ```typescript -const map = s.map(s.string, s.number); +const map = s.map(s.string(), s.number()); // Map ``` @@ -355,7 +354,7 @@ const map = s.map(s.string, s.number); [Back to top][toc] ```typescript -const set = s.set(s.number); +const set = s.set(s.number()); // Set ``` @@ -384,7 +383,7 @@ Record validations are similar to objects, but validate `Record` type the keys, and cannot support validation for specific ones: ```typescript -const tags = s.record(s.string); +const tags = s.record(s.string()); tags.parse({ foo: 'bar', hello: 'world' }); // => { foo: 'bar', hello: 'world' } tags.parse({ foo: 42 }); // => throws CombinedError @@ -393,7 +392,7 @@ tags.parse('Hello'); // => throws ValidateError --- -_**Function validation is not yet implemented and will be made available starting v2.1.0**_ + #### TypedArray @@ -423,17 +422,17 @@ s.function([s.string, s.number], s.string); // (arg0: string, arg1: number) => s ```ts const typedArray = s.typedArray(); -const int16Array = s.int16Array; -const uint16Array = s.uint16Array; -const uint8ClampedArray = s.uint8ClampedArray; -const int16Array = s.int16Array; -const uint16Array = s.uint16Array; -const int32Array = s.int32Array; -const uint32Array = s.uint32Array; -const float32Array = s.float32Array; -const float64Array = s.float64Array; -const bigInt64Array = s.bigInt64Array; -const bigUint64Array = s.bigUint64Array; +const int16Array = s.int16Array(); +const uint16Array = s.uint16Array(); +const uint8ClampedArray = s.uint8ClampedArray(); +const int16Array = s.int16Array(); +const uint16Array = s.uint16Array(); +const int32Array = s.int32Array(); +const uint32Array = s.uint32Array(); +const float32Array = s.float32Array(); +const float64Array = s.float64Array(); +const bigInt64Array = s.bigInt64Array(); +const bigUint64Array = s.bigUint64Array(); ``` Shapeshift includes a handful of validations specific to typed arrays. @@ -463,8 +462,8 @@ Note that all of these methods have analogous methods for working with the typed ```typescript // Properties are required by default: const animal = s.object({ - name: s.string, - age: s.number + name: s.string(), + age: s.number() }); ``` @@ -485,11 +484,11 @@ You can use the `InferType` type to extract the interface from a schema, for exa import { InferType, s } from '@sapphire/shapeshift'; const schema = s.object({ - foo: s.string, - bar: s.number, - baz: s.boolean, - qux: s.bigint, - quux: s.date + foo: s.string(), + bar: s.number(), + baz: s.boolean(), + qux: s.bigint(), + quux: s.date() }); type Inferredtype = InferType; @@ -541,19 +540,19 @@ type RecipeSchemaType = SchemaOf; // Expected Type: ObjectValidator const instructionSchema: InstructionSchemaType = s.object({ - instructionId: s.string.optional, - message: s.string + instructionId: s.string().optional(), + message: s.string() }); const ingredientSchema: IngredientSchemaType = s.object({ - ingredientId: s.string.optional, - name: s.string + ingredientId: s.string().optional(), + name: s.string() }); const recipeSchema: RecipeSchemaType = s.object({ - recipeId: s.string.optional, - title: s.string, - description: s.string, + recipeId: s.string().optional(), + title: s.string(), + description: s.string(), instructions: s.array(instructionSchema), ingredients: s.array(ingredientSchema) }); @@ -568,17 +567,17 @@ validator with the merged properties: ```typescript const animal = s.object({ - name: s.string.optional, - age: s.number + name: s.string().optional(), + age: s.number() }); const pet = animal.extend({ - owner: s.string.nullish + owner: s.string().nullish() }); const pet = animal.extend( s.object({ - owner: s.string.nullish + owner: s.string().nullish() }) ); ``` @@ -595,16 +594,16 @@ that return a modifier version: ```typescript const pkg = s.object({ - name: s.string, - description: s.string, - dependencies: s.string.array + name: s.string(), + description: s.string(), + dependencies: s.string().array() }); const justTheName = pkg.pick(['name']); -// s.object({ name: s.string }); +// s.object({ name: s.string() }); const noDependencies = pkg.omit(['dependencies']); -// s.object({ name: s.string, description: s.string }); +// s.object({ name: s.string(), description: s.string() }); ``` #### `.partial` @@ -616,8 +615,8 @@ all properties optional: ```typescript const user = s.object({ - username: s.string, - password: s.string + username: s.string(), + password: s.string() }).partial; ``` @@ -625,8 +624,8 @@ Which is the same as doing: ```typescript const user = s.object({ - username: s.string.optional, - password: s.string.optional + username: s.string().optional(), + password: s.string().optional() }); ``` @@ -641,8 +640,8 @@ all properties required: ```typescript const user = s.object({ - username: s.string.optional, - password: s.string.optional + username: s.string().optional(), + password: s.string().optional() }).required; ``` @@ -650,8 +649,8 @@ Which is the same as doing: ```typescript const user = s.object({ - username: s.string, - password: s.string + username: s.string(), + password: s.string() }); ``` @@ -665,7 +664,7 @@ By default, Shapeshift will not include keys that are not defined by the schema ```typescript const person = s.object({ - framework: s.string + framework: s.string() }); person.parse({ @@ -683,7 +682,7 @@ You can disallow unknown keys with `.strict`. If the input includes any unknown ```typescript const person = s.object({ - framework: s.string + framework: s.string() }).strict; person.parse({ @@ -729,7 +728,7 @@ All validations in Shapeshift contain certain methods. ```typescript import { s } from '@sapphire/shapeshift'; -const getLength = s.string.transform((value) => value.length); +const getLength = s.string().transform((value) => value.length); getLength.parse('Hello There'); // => 11 ``` @@ -742,7 +741,7 @@ getLength.parse('Hello There'); // => 11 ```typescript import { s, Result } from '@sapphire/shapeshift'; -const getLength = s.string.reshape((value) => Result.ok(value.length)); +const getLength = s.string().reshape((value) => Result.ok(value.length)); getLength.parse('Hello There'); // => 11 ``` @@ -752,13 +751,13 @@ getLength.parse('Hello There'); // => 11 - #### `.default(value: T | (() => T))`: transform `undefined` into the given value or the callback's returned value: ```typescript -const name = s.string.default('Sapphire'); +const name = s.string().default('Sapphire'); name.parse('Hello'); // => 'Hello' name.parse(undefined); // => 'Sapphire' ``` ```typescript -const number = s.number.default(Math.random); +const number = s.number().default(Math.random); number.parse(12); // => 12 number.parse(undefined); // => 0.989911985608602 number.parse(undefined); // => 0.3224350185068794 @@ -766,39 +765,39 @@ number.parse(undefined); // => 0.3224350185068794 > :warning: The default values are not validated. -- #### `.optional`: a convenience method that returns a union of the type with `s.undefined`. +- #### `.optional`: a convenience method that returns a union of the type with `s.undefined()`. ```typescript -s.string.optional; // s.union(s.string, s.undefined) +s.string().optional(); // s.union(s.string(), s.undefined()) ``` -- #### `.nullable`: a convenience method that returns a union of the type with `s.nullable`. +- #### `.nullable`: a convenience method that returns a union of the type with `s.nullable()`. ```typescript -s.string.nullable; // s.union(s.string, s.nullable) +s.string().nullable(); // s.union(s.string(), s.nullable()) ``` -- #### `.nullish`: a convenience method that returns a union of the type with `s.nullish`. +- #### `.nullish`: a convenience method that returns a union of the type with `s.nullish()`. ```typescript -s.string.nullish; // s.union(s.string, s.nullish) +s.string().nullish(); // s.union(s.string(), s.nullish()) ``` - #### `.array`: a convenience method that returns an ArrayValidator with the type. ```typescript -s.string.array; // s.array(s.string) +s.string().array(); // s.array(s.string()) ``` - #### `.or`: a convenience method that returns an UnionValidator with the type. This method is also overridden in UnionValidator to just append one more entry. ```typescript -s.string.or(s.number); -// => s.union(s.string, s.number) +s.string().or(s.number()); +// => s.union(s.string(), s.number()) -s.object({ name: s.string }).or(s.string, s.number); -// => s.union(s.object({ name: s.string }), s.string, s.number) +s.object({ name: s.string() }).or(s.string(), s.number()); +// => s.union(s.object({ name: s.string() }), s.string(), s.number()) ``` - #### `.when`: Adjust the schema based on a sibling or sinbling children fields. @@ -836,8 +835,8 @@ Let's start with a basic example: ```typescript const whenPredicate = s.object({ - booleanLike: s.boolean, - numberLike: s.number.when('booleanLike', { + booleanLike: s.boolean(), + numberLike: s.number().when('booleanLike', { then: (schema) => schema.greaterThanOrEqual(5), otherwise: (schema) => schema.lessThanOrEqual(5) }) @@ -847,7 +846,7 @@ whenPredicate.parse({ booleanLike: true, numberLike: 6 }); // => { booleanLike: true, numberLike: 6 } whenPredicate.parse({ booleanLike: true, numberLike: 4 }); -// => ExpectedConstraintError('s.number.greaterThanOrEqual', 'Invalid number value', 4, 'expected >= 5') +// => ExpectedConstraintError('s.number().greaterThanOrEqual', 'Invalid number value', 4, 'expected >= 5') whenPredicate.parse({ booleanLike: false, numberLike: 4 }); // => { booleanLike: false, numberLike: 4 } @@ -857,9 +856,9 @@ The provided key can also be an array of sibling children: ```typescript const whenPredicate = s.object({ - booleanLike: s.boolean, - stringLike: s.string, - numberLike: s.number.when(['booleanLike', 'stringLike'], { + booleanLike: s.boolean(), + stringLike: s.string(), + numberLike: s.number().when(['booleanLike', 'stringLike'], { is: ([booleanLikeValue, stringLikeValue]) => booleanLikeValue === true && stringLikeValue === 'foobar', then: (schema) => schema.greaterThanOrEqual(5), otherwise: (schema) => schema.lessThanOrEqual(5) @@ -870,10 +869,10 @@ whenPredicate.parse({ booleanLike: true, stringLike: 'foobar', numberLike: 6 }); // => { booleanLike: true, numberLike: 6 } whenPredicate.parse({ booleanLike: true, stringLike: 'barfoo', numberLike: 4 }); -// => ExpectedConstraintError('s.number.greaterThanOrEqual', 'Invalid number value', 4, 'expected >= 5') +// => ExpectedConstraintError('s.number().greaterThanOrEqual', 'Invalid number value', 4, 'expected >= 5') whenPredicate.parse({ booleanLike: false, stringLike: 'foobar' numberLike: 4 }); -// => ExpectedConstraintError('s.number.greaterThanOrEqual', 'Invalid number value', 4, 'expected >= 5') +// => ExpectedConstraintError('s.number().greaterThanOrEqual', 'Invalid number value', 4, 'expected >= 5') ``` ### Enabling and disabling validation @@ -896,7 +895,7 @@ setGlobalValidationEnabled(false); ```typescript import { s } from '@sapphire/shapeshift'; -const predicate = s.string.lengthGreaterThan(5).setValidationEnabled(false); +const predicate = s.string().lengthGreaterThan(5).setValidationEnabled(false); ``` ## Buy us some doughnuts @@ -930,5 +929,6 @@ Thank you to all the people who already contributed to Sapphire! [contributing]: https://github.com/sapphiredev/.github/blob/main/.github/CONTRIBUTING.md [`zod`]: https://github.com/colinhacks/zod +[`yup`]: https://github.com/jquense/yup [documentation]: https://www.sapphirejs.dev/docs/Documentation/api-shapeshift/ [toc]: #table-of-contents diff --git a/UPGRADING-v3-v4.md b/UPGRADING-v3-v4.md new file mode 100644 index 00000000..dbcd3cda --- /dev/null +++ b/UPGRADING-v3-v4.md @@ -0,0 +1,211 @@ +# Migration guide for @sapphire/shapeshift v3.x to v4.x + +## Introduction + +In version 4.x of @sapphire/shapeshift we have changed the entire API to support supplying custom messages for your +errors in order to allow for better localization and customization of the error messages. This guide will help you +migrate your code from v3.x to v4.x. + +Before we start we should note that with the amount of changes that have been made it is entirely possible that we have +forgot to mention something in this guide. We therefore strongly recommend the usage of some form of type checking (i.e. +through TypeScript) for your code base to ensure that all your code is compliant and correctly migrated. If you find +that we have failed to mention anything, we would also greatly appreciate a Pull Request to update this document. + +## Function changes + +In general the biggest change between v3.x and v4.x is that where before you would write validators like this: + +```typescript +import { s } from '@sapphire/shapeshift'; + +const validator = s.string; +``` + +Wherein `string` would be a getter for our `StringValidator` class, you would now write validators like this: + +```typescript +import { s } from '@sapphire/shapeshift'; + +const validator = s.string(); +``` + +This is to allow for passing arguments to the method, currently only a custom message but potentially more in the +future. These kind of methods accept a single parameter which is an object with a `message` key. This message represents +the custom message that will be available on the error object when the validation fails. + +An example of this would be: + +```typescript +import { s } from '@sapphire/shapeshift'; + +const validator = s.string({ message: 'This is a custom message' }); +``` + +For a (non-exhaustive) list of all the methods there we changed please see [List of function changes][lofc] below. + +## Type only changes + +Three types were removed that were previously marked as `@deprecated` in v3.x. We took the oppertunity of the major +release to remove these deprecated types. For a list of all the types that were removed please see [List of Type Only +changes][lotc] below. + +--- + +## References + +### List of function changes + +- `s.any` is now `s.any()` to allow for custom options as argument. +- `s.array(T).lengthEqual` is now `s.array(T).lengthEqual()` to allow for custom options as argument. +- `s.array(T).lengthGreaterThan` is now `s.array(T).lengthGreaterThan()` to allow for custom options as argument. +- `s.array(T).lengthGreaterThanOrEqual` is now `s.array(T).lengthGreaterThanOrEqual()` to allow for custom options as + argument. +- `s.array(T).lengthLessThan` is now `s.array(T).lengthLessThan()` to allow for custom options as argument. +- `s.array(T).lengthLessThanOrEqual` is now `s.array(T).lengthLessThanOrEqual()` to allow for custom options as + argument. +- `s.array(T).lengthNotEqual` is now `s.array(T).lengthNotEqual()` to allow for custom options as argument. +- `s.array(T).lengthRange` is now `s.array(T).lengthRange()` to allow for custom options as argument. +- `s.array(T).lengthRangeExclusive` is now `s.array(T).lengthRangeExclusive()` to allow for custom options as argument. +- `s.array(T).lengthRangeInclusive` is now `s.array(T).lengthRangeInclusive()` to allow for custom options as argument. +- `s.array(T).unique` is now `s.array(T).unique()` to allow for custom options as argument. +- `s.array` is now `s.array()` to allow for custom options as argument. +- `s.bigint.divisibleBy` is now `s.bigint().divisibleBy()` to allow for custom options as argument. +- `s.bigint.equal` is now `s.bigint().equal()` to allow for custom options as argument. +- `s.bigint.greaterThan` is now `s.bigint().greaterThan()` to allow for custom options as argument. +- `s.bigint.greaterThanOrEqual` is now `s.bigint().greaterThanOrEqual()` to allow for custom options as argument. +- `s.bigint.lessThan` is now `s.bigint().lessThan()` to allow for custom options as argument. +- `s.bigint.lessThanOrEqual` is now `s.bigint().lessThanOrEqual()` to allow for custom options as argument. +- `s.bigint.notEqual` is now `s.bigint().notEqual()` to allow for custom options as argument. +- `s.bigint().abs` is now `s.bigint().abs()` to allow for custom options as second argument. +- `s.bigint().negative` is now `s.bigint().negative()` to allow for custom options as second argument. +- `s.bigint().positive` is now `s.bigint().positive()` to allow for custom options as second argument. +- `s.bigint` is now `s.bigint()` to allow for custom options as argument. +- `s.bigInt64Array` is now `s.bigInt64Array()` to allow for custom options as argument. +- `s.bigUint64Array` is now `s.bigUint64Array()` to allow for custom options as argument. +- `s.boolean.false` is now `s.boolean().false()` to allow for custom options as second argument. +- `s.boolean.true` is now `s.boolean().true()` to allow for custom options as second argument. +- `s.boolean` is now `s.boolean()` to allow for custom options as argument. +- `s.default(...)` now gets a second parameter to allow for custom options as argument. +- `s.default(...).default(...)` now gets a second parameter to allow for custom options as argument. +- `s.date.equal` is now `s.date().equal()` to allow for custom options as argument. +- `s.date.greaterThan` is now `s.date().greaterThan()` to allow for custom options as argument. +- `s.date.greaterThanOrEqual` is now `s.date().greaterThanOrEqual()` to allow for custom options as argument. +- `s.date.invalid` is now `s.date().invalid()` to allow for custom options as argument. +- `s.date.lessThan` is now `s.date().lessThan()` to allow for custom options as argument. +- `s.date.lessThanOrEqual` is now `s.date().lessThanOrEqual()` to allow for custom options as argument. +- `s.date.notEqual` is now `s.date().notEqual()` to allow for custom options as argument. +- `s.date.valid` is now `s.date().valid()` to allow for custom options as argument. +- `s.date` is now `s.date()` to allow for custom options as argument. +- `s.enum(1, 2, 3)` is now `s.enum([1, 2, 3])` to allow for custom options as second argument. +- `s.float32Array` is now `s.float32Array()` to allow for custom options as argument. +- `s.float64Array` is now `s.float64Array()` to allow for custom options as argument. +- `s.int16Array` is now `s.int16Array()` to allow for custom options as argument. +- `s.int32Array` is now `s.int32Array()` to allow for custom options as argument. +- `s.int8Array` is now `s.int8Array()` to allow for custom options as argument. +- `s.never` is now `s.never()` to allow for custom options as argument. +- `s.null` is now `s.null()` to allow for custom options as argument. +- `s.nullable` is now `s.nullable()` to allow for custom options as argument. +- `s.nullish` is now `s.nullish()` to allow for custom options as argument. +- `s.nullish` is now `s.nullish()` to allow for custom options as argument. +- `s.number.abs` is now `s.number().abs()` to allow for custom options as argument. +- `s.number.ceil` is now `s.number().ceil()` to allow for custom options as argument. +- `s.number.divisibleBy` is now `s.number().divisibleBy()` to allow for custom options as argument. +- `s.number.equal` is now `s.number().equal()` to allow for custom options as argument. +- `s.number.finite` is now `s.number().finite()` to allow for custom options as argument. +- `s.number.floor` is now `s.number().floor()` to allow for custom options as argument. +- `s.number.fround` is now `s.number().fround()` to allow for custom options as argument. +- `s.number.greaterThan` is now `s.number().greaterThan()` to allow for custom options as argument. +- `s.number.greaterThanOrEqual` is now `s.number().greaterThanOrEqual()` to allow for custom options as argument. +- `s.number.int` is now `s.number().int()` to allow for custom options as argument. +- `s.number.lessThan` is now `s.number().lessThan()` to allow for custom options as argument. +- `s.number.lessThanOrEqual` is now `s.number().lessThanOrEqual()` to allow for custom options as argument. +- `s.number.negative` is now `s.number().negative()` to allow for custom options as argument. +- `s.number.notEqual` is now `s.number().notEqual()` to allow for custom options as argument. +- `s.number.positive` is now `s.number().positive()` to allow for custom options as argument. +- `s.number.round` is now `s.number().round()` to allow for custom options as argument. +- `s.number.safeInt` is now `s.number().safeInt()` to allow for custom options as argument. +- `s.number.sign` is now `s.number().sign()` to allow for custom options as argument. +- `s.number.trunc` is now `s.number().trunc()` to allow for custom options as argument. +- `s.number` is now `s.number()` to allow for custom options as argument. +- `s.object.ignore` is now `s.object().ignore()` to allow for custom options as argument. +- `s.object.partial` is now `s.object().partial()` to allow for custom options as argument. +- `s.object.passthrough` is now `s.object().passthrough()` to allow for custom options as argument. +- `s.object.required` is now `s.object().required()` to allow for custom options as argument. +- `s.object.strict` is now `s.object().strict()` to allow for custom options as argument. +- `s.optional` is now `s.optional()` to allow for custom options as argument. +- `s.required(...)` now gets a second parameter to allow for custom options as argument. +- `s.set` is now `s.set()` to allow for custom options as argument. +- `s.string.date` is now `s.string().date()` to allow for custom options as argument. +- `s.string.email` is now `s.string().email()` to allow for custom options as argument. +- `s.string.ipv4` is now `s.string().ipv4()` to allow for custom options as argument. +- `s.string.ipv6` is now `s.string().ipv6()` to allow for custom options as argument. +- `s.string().ip` is now `s.string().ip()` to allow for custom options as argument. +- `s.string().lengthEqual` is now `s.string().lengthEqual()` to allow for custom options as argument. +- `s.string().lengthGreaterThan` is now `s.string().lengthGreaterThan()` to allow for custom options as argument. +- `s.string().lengthGreaterThanOrEqual` is now `s.string().lengthGreaterThanOrEqual()` to allow for custom options as + argument. +- `s.string().lengthLessThan` is now `s.string().lengthLessThan()` to allow for custom options as argument. +- `s.string().lengthLessThanOrEqual` is now `s.string().lengthLessThanOrEqual()` to allow for custom options as + argument. +- `s.string().lengthNotEqual` is now `s.string().lengthNotEqual()` to allow for custom options as argument. +- `s.string().phone` is now `s.string().phone()` to allow for custom options as argument. +- `s.string().regex` is now `s.string().regex()` to allow for custom options as argument. +- `s.string().url` is now `s.string().url()` to allow for custom options as argument. +- `s.string` is now `s.string()` to allow for custom options as argument. +- `s.tuple(1, 2, 3)` is now `s.tuple([1, 2, 3])` to allow for custom options as second argument. +- `s.typedArray(T).byteLengthEqual` is now `s.typedArray(T).byteLengthEqual()` to allow for custom options as argument. +- `s.typedArray(T).byteLengthGreaterThan` is now `s.typedArray(T).byteLengthGreaterThan()` to allow for custom options + as argument. +- `s.typedArray(T).byteLengthGreaterThanOrEqual` is now `s.typedArray(T).byteLengthGreaterThanOrEqual()` to allow for + custom options as argument. +- `s.typedArray(T).byteLengthLessThan` is now `s.typedArray(T).byteLengthLessThan()` to allow for custom options as + argument. +- `s.typedArray(T).byteLengthLessThanOrEqual` is now `s.typedArray(T).byteLengthLessThanOrEqual()` to allow for custom + options as argument. +- `s.typedArray(T).byteLengthNotEqual` is now `s.typedArray(T).byteLengthNotEqual()` to allow for custom options as + argument. +- `s.typedArray(T).byteLengthRange` is now `s.typedArray(T).byteLengthRange()` to allow for custom options as argument. +- `s.typedArray(T).byteLengthRangeExclusive` is now `s.typedArray(T).byteLengthRangeExclusive()` to allow for custom + options as argument. +- `s.typedArray(T).byteLengthRangeInclusive` is now `s.typedArray(T).byteLengthRangeInclusive()` to allow for custom + options as argument. +- `s.typedArray(T).lengthEqual` is now `s.typedArray(T).lengthEqual()` to allow for custom options as argument. +- `s.typedArray(T).lengthGreaterThan` is now `s.typedArray(T).lengthGreaterThan()` to allow for custom options as + argument. +- `s.typedArray(T).lengthGreaterThanOrEqual` is now `s.typedArray(T).lengthGreaterThanOrEqual()` to allow for custom + options as argument. +- `s.typedArray(T).lengthLessThan` is now `s.typedArray(T).lengthLessThan()` to allow for custom options as argument. +- `s.typedArray(T).lengthLessThanOrEqual` is now `s.typedArray(T).lengthLessThanOrEqual()` to allow for custom options + as argument. +- `s.typedArray(T).lengthNotEqual` is now `s.typedArray(T).lengthNotEqual()` to allow for custom options as argument. +- `s.typedArray(T).lengthRange` is now `s.typedArray(T).lengthRange()` to allow for custom options as argument. +- `s.typedArray(T).lengthRangeExclusive` is now `s.typedArray(T).lengthRangeExclusive()` to allow for custom options as + argument. +- `s.typedArray(T).lengthRangeInclusive` is now `s.typedArray(T).lengthRangeInclusive()` to allow for custom options as + argument. +- `s.uint16Array` is now `s.uint16Array()` to allow for custom options as argument. +- `s.uint32Array` is now `s.uint32Array()` to allow for custom options as argument. +- `s.uint8Array` is now `s.uint8Array()` to allow for custom options as argument. +- `s.uint8ClampedArray` is now `s.uint8ClampedArray()` to allow for custom options as argument. +- `s.undefined` is now `s.undefined()` to allow for custom options as argument. +- `s.union(1, 2, 3).required` is now `s.union(1, 2, 3).required()` to allow for custom options as argument. +- `s.union(1, 2, 3)` is now `s.union([1, 2, 3])` to allow for custom options as second argument. +- `s.unknown` is now `s.unknown()` to allow for custom options as argument. +- `uniqueArray` is now a function (instead of a constant) to allow for custom options as argument. +- `dateInvalid` is now a function (instead of a constant) to allow for custom options as argument. +- `dateValid` is now a function (instead of a constant) to allow for custom options as argument. +- `numberFinite` is now a function (instead of a constant) to allow for custom options as argument. +- `numberInt` is now a function (instead of a constant) to allow for custom options as argument. +- `numberNaN` is now a function (instead of a constant) to allow for custom options as argument. +- `numberNotNaN` is now a function (instead of a constant) to allow for custom options as argument. +- `numberSafeInt` is now a function (instead of a constant) to allow for custom options as argument. + +## List of Type Only changes + +- `PickDefined` utility type has been removed. +- `PickUndefinedMakeOptional` utility type has been removed. +- `NonNullObject` utility type has been removed. + +[lofc]: #list-of-function-changes +[lotc]: #list-of-type-only-changes +[lovcf]: #list-of-validators-that-were-changed-to-functions diff --git a/package.json b/package.json index 090df38a..8cffaccf 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@sapphire/ts-config": "^5.0.1", "@types/jsdom": "^21.1.6", "@types/lodash": "^4.17.4", - "@types/node": "^20.11.5", + "@types/node": "^20.12.12", "@typescript-eslint/eslint-plugin": "^7.9.0", "@typescript-eslint/parser": "^7.9.0", "@vitest/coverage-v8": "^1.6.0", @@ -73,7 +73,8 @@ "url": "git+https://github.com/sapphiredev/shapeshift.git" }, "files": [ - "dist/" + "dist/", + "UPGRADING-v3-v4.md" ], "engines": { "node": ">=v16" diff --git a/src/constraints/ArrayConstraints.ts b/src/constraints/ArrayConstraints.ts index c20799f8..1867be3e 100644 --- a/src/constraints/ArrayConstraints.ts +++ b/src/constraints/ArrayConstraints.ts @@ -1,5 +1,6 @@ import { ExpectedConstraintError } from '../lib/errors/ExpectedConstraintError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import type { IConstraint } from './base/IConstraint'; import { isUnique } from './util/isUnique'; import { equal, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, notEqual, type Comparator } from './util/operators'; @@ -15,85 +16,104 @@ export type ArrayConstraintName = `s.array(T).${ | 'NotEqual' | 'Range' | 'RangeInclusive' - | 'RangeExclusive'}`}`; + | 'RangeExclusive'}`}()`; -function arrayLengthComparator(comparator: Comparator, name: ArrayConstraintName, expected: string, length: number): IConstraint { +function arrayLengthComparator( + comparator: Comparator, + name: ArrayConstraintName, + expected: string, + length: number, + options?: ValidatorOptions +): IConstraint { return { run(input: T[]) { return comparator(input.length, length) // ? Result.ok(input) - : Result.err(new ExpectedConstraintError(name, 'Invalid Array length', input, expected)); + : Result.err(new ExpectedConstraintError(name, options?.message ?? 'Invalid Array length', input, expected)); } }; } -export function arrayLengthLessThan(value: number): IConstraint { +export function arrayLengthLessThan(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length < ${value}`; - return arrayLengthComparator(lessThan, 's.array(T).lengthLessThan', expected, value); + return arrayLengthComparator(lessThan, 's.array(T).lengthLessThan()', expected, value, options); } -export function arrayLengthLessThanOrEqual(value: number): IConstraint { +export function arrayLengthLessThanOrEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length <= ${value}`; - return arrayLengthComparator(lessThanOrEqual, 's.array(T).lengthLessThanOrEqual', expected, value); + return arrayLengthComparator(lessThanOrEqual, 's.array(T).lengthLessThanOrEqual()', expected, value, options); } -export function arrayLengthGreaterThan(value: number): IConstraint { +export function arrayLengthGreaterThan(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length > ${value}`; - return arrayLengthComparator(greaterThan, 's.array(T).lengthGreaterThan', expected, value); + return arrayLengthComparator(greaterThan, 's.array(T).lengthGreaterThan()', expected, value, options); } -export function arrayLengthGreaterThanOrEqual(value: number): IConstraint { +export function arrayLengthGreaterThanOrEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length >= ${value}`; - return arrayLengthComparator(greaterThanOrEqual, 's.array(T).lengthGreaterThanOrEqual', expected, value); + return arrayLengthComparator(greaterThanOrEqual, 's.array(T).lengthGreaterThanOrEqual()', expected, value, options); } -export function arrayLengthEqual(value: number): IConstraint { +export function arrayLengthEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length === ${value}`; - return arrayLengthComparator(equal, 's.array(T).lengthEqual', expected, value); + return arrayLengthComparator(equal, 's.array(T).lengthEqual()', expected, value, options); } -export function arrayLengthNotEqual(value: number): IConstraint { +export function arrayLengthNotEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length !== ${value}`; - return arrayLengthComparator(notEqual, 's.array(T).lengthNotEqual', expected, value); + return arrayLengthComparator(notEqual, 's.array(T).lengthNotEqual()', expected, value, options); } -export function arrayLengthRange(start: number, endBefore: number): IConstraint { +export function arrayLengthRange(start: number, endBefore: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length >= ${start} && expected.length < ${endBefore}`; return { run(input: T[]) { return input.length >= start && input.length < endBefore // ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.array(T).lengthRange', 'Invalid Array length', input, expected)); + : Result.err(new ExpectedConstraintError('s.array(T).lengthRange()', options?.message ?? 'Invalid Array length', input, expected)); } }; } -export function arrayLengthRangeInclusive(start: number, end: number): IConstraint { +export function arrayLengthRangeInclusive(start: number, end: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length >= ${start} && expected.length <= ${end}`; return { run(input: T[]) { return input.length >= start && input.length <= end // ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.array(T).lengthRangeInclusive', 'Invalid Array length', input, expected)); + : Result.err( + new ExpectedConstraintError('s.array(T).lengthRangeInclusive()', options?.message ?? 'Invalid Array length', input, expected) + ); } }; } -export function arrayLengthRangeExclusive(startAfter: number, endBefore: number): IConstraint { +export function arrayLengthRangeExclusive(startAfter: number, endBefore: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length > ${startAfter} && expected.length < ${endBefore}`; return { run(input: T[]) { return input.length > startAfter && input.length < endBefore // ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.array(T).lengthRangeExclusive', 'Invalid Array length', input, expected)); + : Result.err( + new ExpectedConstraintError('s.array(T).lengthRangeExclusive()', options?.message ?? 'Invalid Array length', input, expected) + ); } }; } -export const uniqueArray: IConstraint = { - run(input: unknown[]) { - return isUnique(input) // - ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.array(T).unique', 'Array values are not unique', input, 'Expected all values to be unique')); - } -}; +export function uniqueArray(options?: ValidatorOptions): IConstraint { + return { + run(input: unknown[]) { + return isUnique(input) // + ? Result.ok(input) + : Result.err( + new ExpectedConstraintError( + 's.array(T).unique()', + options?.message ?? 'Array values are not unique', + input, + 'Expected all values to be unique' + ) + ); + } + }; +} diff --git a/src/constraints/BigIntConstraints.ts b/src/constraints/BigIntConstraints.ts index dc5ec33f..23aace7d 100644 --- a/src/constraints/BigIntConstraints.ts +++ b/src/constraints/BigIntConstraints.ts @@ -1,64 +1,71 @@ import { ExpectedConstraintError } from '../lib/errors/ExpectedConstraintError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import type { IConstraint } from './base/IConstraint'; import { equal, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, notEqual, type Comparator } from './util/operators'; -export type BigIntConstraintName = `s.bigint.${ +export type BigIntConstraintName = `s.bigint().${ | 'lessThan' | 'lessThanOrEqual' | 'greaterThan' | 'greaterThanOrEqual' | 'equal' | 'notEqual' - | 'divisibleBy'}`; + | 'divisibleBy'}()`; -function bigintComparator(comparator: Comparator, name: BigIntConstraintName, expected: string, number: bigint): IConstraint { +function bigintComparator( + comparator: Comparator, + name: BigIntConstraintName, + expected: string, + number: bigint, + options?: ValidatorOptions +): IConstraint { return { run(input: bigint) { return comparator(input, number) // ? Result.ok(input) - : Result.err(new ExpectedConstraintError(name, 'Invalid bigint value', input, expected)); + : Result.err(new ExpectedConstraintError(name, options?.message ?? 'Invalid bigint value', input, expected)); } }; } -export function bigintLessThan(value: bigint): IConstraint { +export function bigintLessThan(value: bigint, options?: ValidatorOptions): IConstraint { const expected = `expected < ${value}n`; - return bigintComparator(lessThan, 's.bigint.lessThan', expected, value); + return bigintComparator(lessThan, 's.bigint().lessThan()', expected, value, options); } -export function bigintLessThanOrEqual(value: bigint): IConstraint { +export function bigintLessThanOrEqual(value: bigint, options?: ValidatorOptions): IConstraint { const expected = `expected <= ${value}n`; - return bigintComparator(lessThanOrEqual, 's.bigint.lessThanOrEqual', expected, value); + return bigintComparator(lessThanOrEqual, 's.bigint().lessThanOrEqual()', expected, value, options); } -export function bigintGreaterThan(value: bigint): IConstraint { +export function bigintGreaterThan(value: bigint, options?: ValidatorOptions): IConstraint { const expected = `expected > ${value}n`; - return bigintComparator(greaterThan, 's.bigint.greaterThan', expected, value); + return bigintComparator(greaterThan, 's.bigint().greaterThan()', expected, value, options); } -export function bigintGreaterThanOrEqual(value: bigint): IConstraint { +export function bigintGreaterThanOrEqual(value: bigint, options?: ValidatorOptions): IConstraint { const expected = `expected >= ${value}n`; - return bigintComparator(greaterThanOrEqual, 's.bigint.greaterThanOrEqual', expected, value); + return bigintComparator(greaterThanOrEqual, 's.bigint().greaterThanOrEqual()', expected, value, options); } -export function bigintEqual(value: bigint): IConstraint { +export function bigintEqual(value: bigint, options?: ValidatorOptions): IConstraint { const expected = `expected === ${value}n`; - return bigintComparator(equal, 's.bigint.equal', expected, value); + return bigintComparator(equal, 's.bigint().equal()', expected, value, options); } -export function bigintNotEqual(value: bigint): IConstraint { +export function bigintNotEqual(value: bigint, options?: ValidatorOptions): IConstraint { const expected = `expected !== ${value}n`; - return bigintComparator(notEqual, 's.bigint.notEqual', expected, value); + return bigintComparator(notEqual, 's.bigint().notEqual()', expected, value, options); } -export function bigintDivisibleBy(divider: bigint): IConstraint { +export function bigintDivisibleBy(divider: bigint, options?: ValidatorOptions): IConstraint { const expected = `expected % ${divider}n === 0n`; return { run(input: bigint) { return input % divider === 0n // ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.bigint.divisibleBy', 'BigInt is not divisible', input, expected)); + : Result.err(new ExpectedConstraintError('s.bigint().divisibleBy()', options?.message ?? 'BigInt is not divisible', input, expected)); } }; } diff --git a/src/constraints/BooleanConstraints.ts b/src/constraints/BooleanConstraints.ts index 80264883..47247840 100644 --- a/src/constraints/BooleanConstraints.ts +++ b/src/constraints/BooleanConstraints.ts @@ -1,21 +1,26 @@ import { ExpectedConstraintError } from '../lib/errors/ExpectedConstraintError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import type { IConstraint } from './base/IConstraint'; -export type BooleanConstraintName = `s.boolean.${boolean}`; +export type BooleanConstraintName = `s.boolean().${boolean}()`; -export const booleanTrue: IConstraint = { - run(input: boolean) { - return input // - ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.boolean.true', 'Invalid boolean value', input, 'true')); - } -}; +export function booleanTrue(options?: ValidatorOptions): IConstraint { + return { + run(input: boolean) { + return input // + ? Result.ok(input) + : Result.err(new ExpectedConstraintError('s.boolean().true()', options?.message ?? 'Invalid boolean value', input, 'true')); + } + }; +} -export const booleanFalse: IConstraint = { - run(input: boolean) { - return input // - ? Result.err(new ExpectedConstraintError('s.boolean.false', 'Invalid boolean value', input, 'false')) - : Result.ok(input); - } -}; +export function booleanFalse(options?: ValidatorOptions): IConstraint { + return { + run(input: boolean) { + return input // + ? Result.err(new ExpectedConstraintError('s.boolean().false()', options?.message ?? 'Invalid boolean value', input, 'false')) + : Result.ok(input); + } + }; +} diff --git a/src/constraints/DateConstraints.ts b/src/constraints/DateConstraints.ts index f2c2f3d8..ecc65f24 100644 --- a/src/constraints/DateConstraints.ts +++ b/src/constraints/DateConstraints.ts @@ -1,9 +1,10 @@ import { ExpectedConstraintError } from '../lib/errors/ExpectedConstraintError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import type { IConstraint } from './base/IConstraint'; import { equal, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, notEqual, type Comparator } from './util/operators'; -export type DateConstraintName = `s.date.${ +export type DateConstraintName = `s.date().${ | 'lessThan' | 'lessThanOrEqual' | 'greaterThan' @@ -11,60 +12,70 @@ export type DateConstraintName = `s.date.${ | 'equal' | 'notEqual' | 'valid' - | 'invalid'}`; + | 'invalid'}()`; -function dateComparator(comparator: Comparator, name: DateConstraintName, expected: string, number: number): IConstraint { +function dateComparator( + comparator: Comparator, + name: DateConstraintName, + expected: string, + number: number, + options?: ValidatorOptions +): IConstraint { return { run(input: Date) { return comparator(input.getTime(), number) // ? Result.ok(input) - : Result.err(new ExpectedConstraintError(name, 'Invalid Date value', input, expected)); + : Result.err(new ExpectedConstraintError(name, options?.message ?? 'Invalid Date value', input, expected)); } }; } -export function dateLessThan(value: Date): IConstraint { +export function dateLessThan(value: Date, options?: ValidatorOptions): IConstraint { const expected = `expected < ${value.toISOString()}`; - return dateComparator(lessThan, 's.date.lessThan', expected, value.getTime()); + return dateComparator(lessThan, 's.date().lessThan()', expected, value.getTime(), options); } -export function dateLessThanOrEqual(value: Date): IConstraint { +export function dateLessThanOrEqual(value: Date, options?: ValidatorOptions): IConstraint { const expected = `expected <= ${value.toISOString()}`; - return dateComparator(lessThanOrEqual, 's.date.lessThanOrEqual', expected, value.getTime()); + return dateComparator(lessThanOrEqual, 's.date().lessThanOrEqual()', expected, value.getTime(), options); } -export function dateGreaterThan(value: Date): IConstraint { +export function dateGreaterThan(value: Date, options?: ValidatorOptions): IConstraint { const expected = `expected > ${value.toISOString()}`; - return dateComparator(greaterThan, 's.date.greaterThan', expected, value.getTime()); + return dateComparator(greaterThan, 's.date().greaterThan()', expected, value.getTime(), options); } -export function dateGreaterThanOrEqual(value: Date): IConstraint { +export function dateGreaterThanOrEqual(value: Date, options?: ValidatorOptions): IConstraint { const expected = `expected >= ${value.toISOString()}`; - return dateComparator(greaterThanOrEqual, 's.date.greaterThanOrEqual', expected, value.getTime()); + return dateComparator(greaterThanOrEqual, 's.date().greaterThanOrEqual()', expected, value.getTime(), options); } -export function dateEqual(value: Date): IConstraint { +export function dateEqual(value: Date, options?: ValidatorOptions): IConstraint { const expected = `expected === ${value.toISOString()}`; - return dateComparator(equal, 's.date.equal', expected, value.getTime()); + return dateComparator(equal, 's.date().equal()', expected, value.getTime(), options); } -export function dateNotEqual(value: Date): IConstraint { +export function dateNotEqual(value: Date, options?: ValidatorOptions): IConstraint { const expected = `expected !== ${value.toISOString()}`; - return dateComparator(notEqual, 's.date.notEqual', expected, value.getTime()); + return dateComparator(notEqual, 's.date().notEqual()', expected, value.getTime(), options); } -export const dateInvalid: IConstraint = { - run(input: Date) { - return Number.isNaN(input.getTime()) // - ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.date.invalid', 'Invalid Date value', input, 'expected === NaN')); - } -}; +export function dateInvalid(options?: ValidatorOptions): IConstraint { + return { + run(input: Date) { + return Number.isNaN(input.getTime()) // + ? Result.ok(input) + : Result.err(new ExpectedConstraintError('s.date().invalid()', options?.message ?? 'Invalid Date value', input, 'expected === NaN')); + } + }; +} -export const dateValid: IConstraint = { - run(input: Date) { - return Number.isNaN(input.getTime()) // - ? Result.err(new ExpectedConstraintError('s.date.valid', 'Invalid Date value', input, 'expected !== NaN')) - : Result.ok(input); - } -}; +export function dateValid(options?: ValidatorOptions): IConstraint { + return { + run(input: Date) { + return Number.isNaN(input.getTime()) // + ? Result.err(new ExpectedConstraintError('s.date().valid()', options?.message ?? 'Invalid Date value', input, 'expected !== NaN')) + : Result.ok(input); + } + }; +} diff --git a/src/constraints/NumberConstraints.ts b/src/constraints/NumberConstraints.ts index 593ff0cb..79918ff5 100644 --- a/src/constraints/NumberConstraints.ts +++ b/src/constraints/NumberConstraints.ts @@ -1,118 +1,152 @@ import { ExpectedConstraintError } from '../lib/errors/ExpectedConstraintError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; + import type { IConstraint } from './base/IConstraint'; import { equal, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, notEqual, type Comparator } from './util/operators'; -export type NumberConstraintName = `s.number.${ +export type NumberConstraintName = `s.number().${ | 'lessThan' | 'lessThanOrEqual' | 'greaterThan' | 'greaterThanOrEqual' | 'equal' - | 'equal(NaN)' + | 'equal' + | 'notEqual' | 'notEqual' - | 'notEqual(NaN)' | 'int' | 'safeInt' | 'finite' - | 'divisibleBy'}`; + | 'divisibleBy'}(${string})`; -function numberComparator(comparator: Comparator, name: NumberConstraintName, expected: string, number: number): IConstraint { +function numberComparator( + comparator: Comparator, + name: NumberConstraintName, + expected: string, + number: number, + options?: ValidatorOptions +): IConstraint { return { run(input: number) { return comparator(input, number) // ? Result.ok(input) - : Result.err(new ExpectedConstraintError(name, 'Invalid number value', input, expected)); + : Result.err(new ExpectedConstraintError(name, options?.message ?? 'Invalid number value', input, expected)); } }; } -export function numberLessThan(value: number): IConstraint { +export function numberLessThan(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected < ${value}`; - return numberComparator(lessThan, 's.number.lessThan', expected, value); + return numberComparator(lessThan, 's.number().lessThan()', expected, value, options); } -export function numberLessThanOrEqual(value: number): IConstraint { +export function numberLessThanOrEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected <= ${value}`; - return numberComparator(lessThanOrEqual, 's.number.lessThanOrEqual', expected, value); + return numberComparator(lessThanOrEqual, 's.number().lessThanOrEqual()', expected, value, options); } -export function numberGreaterThan(value: number): IConstraint { +export function numberGreaterThan(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected > ${value}`; - return numberComparator(greaterThan, 's.number.greaterThan', expected, value); + return numberComparator(greaterThan, 's.number().greaterThan()', expected, value, options); } -export function numberGreaterThanOrEqual(value: number): IConstraint { +export function numberGreaterThanOrEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected >= ${value}`; - return numberComparator(greaterThanOrEqual, 's.number.greaterThanOrEqual', expected, value); + return numberComparator(greaterThanOrEqual, 's.number().greaterThanOrEqual()', expected, value, options); } -export function numberEqual(value: number): IConstraint { +export function numberEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected === ${value}`; - return numberComparator(equal, 's.number.equal', expected, value); + return numberComparator(equal, 's.number().equal()', expected, value, options); } -export function numberNotEqual(value: number): IConstraint { +export function numberNotEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected !== ${value}`; - return numberComparator(notEqual, 's.number.notEqual', expected, value); + return numberComparator(notEqual, 's.number().notEqual()', expected, value, options); } -export const numberInt: IConstraint = { - run(input: number) { - return Number.isInteger(input) // - ? Result.ok(input) - : Result.err( - new ExpectedConstraintError('s.number.int', 'Given value is not an integer', input, 'Number.isInteger(expected) to be true') - ); - } -}; +export function numberInt(options?: ValidatorOptions): IConstraint { + return { + run(input: number) { + return Number.isInteger(input) // + ? Result.ok(input) + : Result.err( + new ExpectedConstraintError( + 's.number().int()', + options?.message ?? 'Given value is not an integer', + input, + 'Number.isInteger(expected) to be true' + ) + ); + } + }; +} -export const numberSafeInt: IConstraint = { - run(input: number) { - return Number.isSafeInteger(input) // - ? Result.ok(input) - : Result.err( - new ExpectedConstraintError( - 's.number.safeInt', - 'Given value is not a safe integer', - input, - 'Number.isSafeInteger(expected) to be true' - ) - ); - } -}; +export function numberSafeInt(options?: ValidatorOptions): IConstraint { + return { + run(input: number) { + return Number.isSafeInteger(input) // + ? Result.ok(input) + : Result.err( + new ExpectedConstraintError( + 's.number().safeInt()', + options?.message ?? 'Given value is not a safe integer', + input, + 'Number.isSafeInteger(expected) to be true' + ) + ); + } + }; +} -export const numberFinite: IConstraint = { - run(input: number) { - return Number.isFinite(input) // - ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.number.finite', 'Given value is not finite', input, 'Number.isFinite(expected) to be true')); - } -}; +export function numberFinite(options?: ValidatorOptions): IConstraint { + return { + run(input: number) { + return Number.isFinite(input) // + ? Result.ok(input) + : Result.err( + new ExpectedConstraintError( + 's.number().finite()', + options?.message ?? 'Given value is not finite', + input, + 'Number.isFinite(expected) to be true' + ) + ); + } + }; +} -export const numberNaN: IConstraint = { - run(input: number) { - return Number.isNaN(input) // - ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.number.equal(NaN)', 'Invalid number value', input, 'expected === NaN')); - } -}; +export function numberNaN(options?: ValidatorOptions): IConstraint { + return { + run(input: number) { + return Number.isNaN(input) // + ? Result.ok(input) + : Result.err( + new ExpectedConstraintError('s.number().equal(NaN)', options?.message ?? 'Invalid number value', input, 'expected === NaN') + ); + } + }; +} -export const numberNotNaN: IConstraint = { - run(input: number) { - return Number.isNaN(input) // - ? Result.err(new ExpectedConstraintError('s.number.notEqual(NaN)', 'Invalid number value', input, 'expected !== NaN')) - : Result.ok(input); - } -}; +export function numberNotNaN(options?: ValidatorOptions): IConstraint { + return { + run(input: number) { + return Number.isNaN(input) // + ? Result.err( + new ExpectedConstraintError('s.number().notEqual(NaN)', options?.message ?? 'Invalid number value', input, 'expected !== NaN') + ) + : Result.ok(input); + } + }; +} -export function numberDivisibleBy(divider: number): IConstraint { +export function numberDivisibleBy(divider: number, options?: ValidatorOptions): IConstraint { const expected = `expected % ${divider} === 0`; return { run(input: number) { return input % divider === 0 // ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.number.divisibleBy', 'Number is not divisible', input, expected)); + : Result.err(new ExpectedConstraintError('s.number().divisibleBy()', options?.message ?? 'Number is not divisible', input, expected)); } }; } diff --git a/src/constraints/ObjectConstrains.ts b/src/constraints/ObjectConstrains.ts index 9761b7ef..a0cc7e4d 100644 --- a/src/constraints/ObjectConstrains.ts +++ b/src/constraints/ObjectConstrains.ts @@ -1,8 +1,9 @@ import get from 'lodash/get.js'; import { ExpectedConstraintError } from '../lib/errors/ExpectedConstraintError'; import { Result } from '../lib/Result'; -import type { BaseValidator } from '../type-exports'; -import type { IConstraint } from './type-exports'; +import type { ValidatorOptions } from '../lib/util-types'; +import type { BaseValidator } from '../validators/BaseValidator'; +import type { IConstraint } from './base/IConstraint'; export type ObjectConstraintName = `s.object(T.when)`; @@ -17,12 +18,20 @@ export interface WhenOptions, Key extends WhenKey> export function whenConstraint, I, Key extends WhenKey>( key: Key, options: WhenOptions, - validator: T + validator: T, + validatorOptions?: ValidatorOptions ): IConstraint { return { run(input: I, parent?: any) { if (!parent) { - return Result.err(new ExpectedConstraintError('s.object(T.when)', 'Validator has no parent', parent, 'Validator to have a parent')); + return Result.err( + new ExpectedConstraintError( + 's.object(T.when)', + validatorOptions?.message ?? 'Validator has no parent', + parent, + 'Validator to have a parent' + ) + ); } const isKeyArray = Array.isArray(key); diff --git a/src/constraints/StringConstraints.ts b/src/constraints/StringConstraints.ts index 57151b36..34e01168 100644 --- a/src/constraints/StringConstraints.ts +++ b/src/constraints/StringConstraints.ts @@ -1,5 +1,6 @@ import { ExpectedConstraintError } from '../lib/errors/ExpectedConstraintError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import type { IConstraint } from './base/IConstraint'; import { validateEmail } from './util/emailValidator'; import { isIP, isIPv4, isIPv6 } from './util/net'; @@ -7,7 +8,7 @@ import { equal, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, notE import { validatePhoneNumber } from './util/phoneValidator'; import { createUrlValidators } from './util/urlValidators'; -export type StringConstraintName = `s.string.${ +export type StringConstraintName = `s.string().${ | `length${'LessThan' | 'LessThanOrEqual' | 'GreaterThan' | 'GreaterThanOrEqual' | 'Equal' | 'NotEqual'}` | 'regex' | 'url' @@ -15,7 +16,7 @@ export type StringConstraintName = `s.string.${ | 'email' | `ip${'v4' | 'v6' | ''}` | 'date' - | 'phone'}`; + | 'phone'}()`; export type StringProtocol = `${string}:`; @@ -33,75 +34,90 @@ export interface StringUuidOptions { nullable?: boolean; } -function stringLengthComparator(comparator: Comparator, name: StringConstraintName, expected: string, length: number): IConstraint { +function stringLengthComparator( + comparator: Comparator, + name: StringConstraintName, + expected: string, + length: number, + options?: ValidatorOptions +): IConstraint { return { run(input: string) { return comparator(input.length, length) // ? Result.ok(input) - : Result.err(new ExpectedConstraintError(name, 'Invalid string length', input, expected)); + : Result.err(new ExpectedConstraintError(name, options?.message ?? 'Invalid string length', input, expected)); } }; } -export function stringLengthLessThan(length: number): IConstraint { +export function stringLengthLessThan(length: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length < ${length}`; - return stringLengthComparator(lessThan, 's.string.lengthLessThan', expected, length); + return stringLengthComparator(lessThan, 's.string().lengthLessThan()', expected, length, options); } -export function stringLengthLessThanOrEqual(length: number): IConstraint { +export function stringLengthLessThanOrEqual(length: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length <= ${length}`; - return stringLengthComparator(lessThanOrEqual, 's.string.lengthLessThanOrEqual', expected, length); + return stringLengthComparator(lessThanOrEqual, 's.string().lengthLessThanOrEqual()', expected, length, options); } -export function stringLengthGreaterThan(length: number): IConstraint { +export function stringLengthGreaterThan(length: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length > ${length}`; - return stringLengthComparator(greaterThan, 's.string.lengthGreaterThan', expected, length); + return stringLengthComparator(greaterThan, 's.string().lengthGreaterThan()', expected, length, options); } -export function stringLengthGreaterThanOrEqual(length: number): IConstraint { +export function stringLengthGreaterThanOrEqual(length: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length >= ${length}`; - return stringLengthComparator(greaterThanOrEqual, 's.string.lengthGreaterThanOrEqual', expected, length); + return stringLengthComparator(greaterThanOrEqual, 's.string().lengthGreaterThanOrEqual()', expected, length, options); } -export function stringLengthEqual(length: number): IConstraint { +export function stringLengthEqual(length: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length === ${length}`; - return stringLengthComparator(equal, 's.string.lengthEqual', expected, length); + return stringLengthComparator(equal, 's.string().lengthEqual()', expected, length, options); } -export function stringLengthNotEqual(length: number): IConstraint { +export function stringLengthNotEqual(length: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length !== ${length}`; - return stringLengthComparator(notEqual, 's.string.lengthNotEqual', expected, length); + return stringLengthComparator(notEqual, 's.string().lengthNotEqual()', expected, length, options); } -export function stringEmail(): IConstraint { +export function stringEmail(options?: ValidatorOptions): IConstraint { return { run(input: string) { return validateEmail(input) ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.string.email', 'Invalid email address', input, 'expected to be an email address')); + : Result.err( + new ExpectedConstraintError( + 's.string().email()', + options?.message ?? 'Invalid email address', + input, + 'expected to be an email address' + ) + ); } }; } -function stringRegexValidator(type: StringConstraintName, expected: string, regex: RegExp): IConstraint { +function stringRegexValidator(type: StringConstraintName, expected: string, regex: RegExp, options?: ValidatorOptions): IConstraint { return { run(input: string) { return regex.test(input) // ? Result.ok(input) - : Result.err(new ExpectedConstraintError(type, 'Invalid string format', input, expected)); + : Result.err(new ExpectedConstraintError(type, options?.message ?? 'Invalid string format', input, expected)); } }; } -export function stringUrl(options?: UrlOptions): IConstraint { - const validatorFn = createUrlValidators(options); +export function stringUrl(options?: UrlOptions, validatorOptions?: ValidatorOptions): IConstraint { + const validatorFn = createUrlValidators(options, validatorOptions); return { run(input: string) { let url: URL; try { url = new URL(input); } catch { - return Result.err(new ExpectedConstraintError('s.string.url', 'Invalid URL', input, 'expected to match a URL')); + return Result.err( + new ExpectedConstraintError('s.string().url()', validatorOptions?.message ?? 'Invalid URL', input, 'expected to match a URL') + ); } const validatorFnResult = validatorFn(input, url); @@ -111,25 +127,27 @@ export function stringUrl(options?: UrlOptions): IConstraint { }; } -export function stringIp(version?: 4 | 6): IConstraint { +export function stringIp(version?: 4 | 6, options?: ValidatorOptions): IConstraint { const ipVersion = version ? (`v${version}` as const) : ''; const validatorFn = version === 4 ? isIPv4 : version === 6 ? isIPv6 : isIP; - const name = `s.string.ip${ipVersion}` as const; + const name = `s.string().ip${ipVersion}()` as const; const message = `Invalid IP${ipVersion} address`; const expected = `expected to be an IP${ipVersion} address`; return { run(input: string) { - return validatorFn(input) ? Result.ok(input) : Result.err(new ExpectedConstraintError(name, message, input, expected)); + return validatorFn(input) + ? Result.ok(input) + : Result.err(new ExpectedConstraintError(name, options?.message ?? message, input, expected)); } }; } -export function stringRegex(regex: RegExp) { - return stringRegexValidator('s.string.regex', `expected ${regex}.test(expected) to be true`, regex); +export function stringRegex(regex: RegExp, options?: ValidatorOptions) { + return stringRegexValidator('s.string().regex()', `expected ${regex}.test(expected) to be true`, regex, options); } -export function stringUuid({ version = 4, nullable = false }: StringUuidOptions = {}) { +export function stringUuid({ version = 4, nullable = false }: StringUuidOptions = {}, options?: ValidatorOptions) { version ??= '1-5'; const regex = new RegExp( `^(?:[0-9A-F]{8}-[0-9A-F]{4}-[${version}][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}${ @@ -138,10 +156,10 @@ export function stringUuid({ version = 4, nullable = false }: StringUuidOptions 'i' ); const expected = `expected to match UUID${typeof version === 'number' ? `v${version}` : ` in range of ${version}`}`; - return stringRegexValidator('s.string.uuid', expected, regex); + return stringRegexValidator('s.string().uuid()', expected, regex, options); } -export function stringDate(): IConstraint { +export function stringDate(options?: ValidatorOptions): IConstraint { return { run(input: string) { const time = Date.parse(input); @@ -149,8 +167,8 @@ export function stringDate(): IConstraint { return Number.isNaN(time) ? Result.err( new ExpectedConstraintError( - 's.string.date', - 'Invalid date string', + 's.string().date()', + options?.message ?? 'Invalid date string', input, 'expected to be a valid date string (in the ISO 8601 or ECMA-262 format)' ) @@ -160,12 +178,19 @@ export function stringDate(): IConstraint { }; } -export function stringPhone(): IConstraint { +export function stringPhone(options?: ValidatorOptions): IConstraint { return { run(input: string) { return validatePhoneNumber(input) ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.string.phone', 'Invalid phone number', input, 'expected to be a phone number')); + : Result.err( + new ExpectedConstraintError( + 's.string().phone()', + options?.message ?? 'Invalid phone number', + input, + 'expected to be a phone number' + ) + ); } }; } diff --git a/src/constraints/TypedArrayLengthConstraints.ts b/src/constraints/TypedArrayLengthConstraints.ts index 4ee3e9e1..20153af3 100644 --- a/src/constraints/TypedArrayLengthConstraints.ts +++ b/src/constraints/TypedArrayLengthConstraints.ts @@ -1,5 +1,6 @@ import { ExpectedConstraintError } from '../lib/errors/ExpectedConstraintError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import type { IConstraint } from './base/IConstraint'; import { equal, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, notEqual, type Comparator } from './util/operators'; import type { TypedArray } from './util/typedArray'; @@ -13,85 +14,107 @@ export type TypedArrayConstraintName = `s.typedArray(T).${'byteLength' | 'length | 'NotEqual' | 'Range' | 'RangeInclusive' - | 'RangeExclusive'}`; + | 'RangeExclusive'}()`; function typedArrayByteLengthComparator( comparator: Comparator, name: TypedArrayConstraintName, expected: string, - length: number + length: number, + options?: ValidatorOptions ): IConstraint { return { run(input: T) { return comparator(input.byteLength, length) // ? Result.ok(input) - : Result.err(new ExpectedConstraintError(name, 'Invalid Typed Array byte length', input, expected)); + : Result.err(new ExpectedConstraintError(name, options?.message ?? 'Invalid Typed Array byte length', input, expected)); } }; } -export function typedArrayByteLengthLessThan(value: number): IConstraint { +export function typedArrayByteLengthLessThan(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.byteLength < ${value}`; - return typedArrayByteLengthComparator(lessThan, 's.typedArray(T).byteLengthLessThan', expected, value); + return typedArrayByteLengthComparator(lessThan, 's.typedArray(T).byteLengthLessThan()', expected, value, options); } -export function typedArrayByteLengthLessThanOrEqual(value: number): IConstraint { +export function typedArrayByteLengthLessThanOrEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.byteLength <= ${value}`; - return typedArrayByteLengthComparator(lessThanOrEqual, 's.typedArray(T).byteLengthLessThanOrEqual', expected, value); + return typedArrayByteLengthComparator(lessThanOrEqual, 's.typedArray(T).byteLengthLessThanOrEqual()', expected, value, options); } -export function typedArrayByteLengthGreaterThan(value: number): IConstraint { +export function typedArrayByteLengthGreaterThan(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.byteLength > ${value}`; - return typedArrayByteLengthComparator(greaterThan, 's.typedArray(T).byteLengthGreaterThan', expected, value); + return typedArrayByteLengthComparator(greaterThan, 's.typedArray(T).byteLengthGreaterThan()', expected, value, options); } -export function typedArrayByteLengthGreaterThanOrEqual(value: number): IConstraint { +export function typedArrayByteLengthGreaterThanOrEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.byteLength >= ${value}`; - return typedArrayByteLengthComparator(greaterThanOrEqual, 's.typedArray(T).byteLengthGreaterThanOrEqual', expected, value); + return typedArrayByteLengthComparator(greaterThanOrEqual, 's.typedArray(T).byteLengthGreaterThanOrEqual()', expected, value, options); } -export function typedArrayByteLengthEqual(value: number): IConstraint { +export function typedArrayByteLengthEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.byteLength === ${value}`; - return typedArrayByteLengthComparator(equal, 's.typedArray(T).byteLengthEqual', expected, value); + return typedArrayByteLengthComparator(equal, 's.typedArray(T).byteLengthEqual()', expected, value, options); } -export function typedArrayByteLengthNotEqual(value: number): IConstraint { +export function typedArrayByteLengthNotEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.byteLength !== ${value}`; - return typedArrayByteLengthComparator(notEqual, 's.typedArray(T).byteLengthNotEqual', expected, value); + return typedArrayByteLengthComparator(notEqual, 's.typedArray(T).byteLengthNotEqual()', expected, value, options); } -export function typedArrayByteLengthRange(start: number, endBefore: number): IConstraint { +export function typedArrayByteLengthRange(start: number, endBefore: number, options?: ValidatorOptions): IConstraint { const expected = `expected.byteLength >= ${start} && expected.byteLength < ${endBefore}`; return { run(input: T) { return input.byteLength >= start && input.byteLength < endBefore // ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.typedArray(T).byteLengthRange', 'Invalid Typed Array byte length', input, expected)); + : Result.err( + new ExpectedConstraintError( + 's.typedArray(T).byteLengthRange()', + options?.message ?? 'Invalid Typed Array byte length', + input, + expected + ) + ); } }; } -export function typedArrayByteLengthRangeInclusive(start: number, end: number) { +export function typedArrayByteLengthRangeInclusive(start: number, end: number, options?: ValidatorOptions) { const expected = `expected.byteLength >= ${start} && expected.byteLength <= ${end}`; return { run(input: T) { return input.byteLength >= start && input.byteLength <= end // ? Result.ok(input) : Result.err( - new ExpectedConstraintError('s.typedArray(T).byteLengthRangeInclusive', 'Invalid Typed Array byte length', input, expected) + new ExpectedConstraintError( + 's.typedArray(T).byteLengthRangeInclusive()', + options?.message ?? 'Invalid Typed Array byte length', + input, + expected + ) ); } }; } -export function typedArrayByteLengthRangeExclusive(startAfter: number, endBefore: number): IConstraint { +export function typedArrayByteLengthRangeExclusive( + startAfter: number, + endBefore: number, + options?: ValidatorOptions +): IConstraint { const expected = `expected.byteLength > ${startAfter} && expected.byteLength < ${endBefore}`; return { run(input: T) { return input.byteLength > startAfter && input.byteLength < endBefore // ? Result.ok(input) : Result.err( - new ExpectedConstraintError('s.typedArray(T).byteLengthRangeExclusive', 'Invalid Typed Array byte length', input, expected) + new ExpectedConstraintError( + 's.typedArray(T).byteLengthRangeExclusive()', + options?.message ?? 'Invalid Typed Array byte length', + input, + expected + ) ); } }; @@ -101,76 +124,102 @@ function typedArrayLengthComparator( comparator: Comparator, name: TypedArrayConstraintName, expected: string, - length: number + length: number, + options?: ValidatorOptions ): IConstraint { return { run(input: T) { return comparator(input.length, length) // ? Result.ok(input) - : Result.err(new ExpectedConstraintError(name, 'Invalid Typed Array length', input, expected)); + : Result.err(new ExpectedConstraintError(name, options?.message ?? 'Invalid Typed Array length', input, expected)); } }; } -export function typedArrayLengthLessThan(value: number): IConstraint { +export function typedArrayLengthLessThan(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length < ${value}`; - return typedArrayLengthComparator(lessThan, 's.typedArray(T).lengthLessThan', expected, value); + return typedArrayLengthComparator(lessThan, 's.typedArray(T).lengthLessThan()', expected, value, options); } -export function typedArrayLengthLessThanOrEqual(value: number): IConstraint { +export function typedArrayLengthLessThanOrEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length <= ${value}`; - return typedArrayLengthComparator(lessThanOrEqual, 's.typedArray(T).lengthLessThanOrEqual', expected, value); + return typedArrayLengthComparator(lessThanOrEqual, 's.typedArray(T).lengthLessThanOrEqual()', expected, value, options); } -export function typedArrayLengthGreaterThan(value: number): IConstraint { +export function typedArrayLengthGreaterThan(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length > ${value}`; - return typedArrayLengthComparator(greaterThan, 's.typedArray(T).lengthGreaterThan', expected, value); + return typedArrayLengthComparator(greaterThan, 's.typedArray(T).lengthGreaterThan()', expected, value, options); } -export function typedArrayLengthGreaterThanOrEqual(value: number): IConstraint { +export function typedArrayLengthGreaterThanOrEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length >= ${value}`; - return typedArrayLengthComparator(greaterThanOrEqual, 's.typedArray(T).lengthGreaterThanOrEqual', expected, value); + return typedArrayLengthComparator(greaterThanOrEqual, 's.typedArray(T).lengthGreaterThanOrEqual()', expected, value, options); } -export function typedArrayLengthEqual(value: number): IConstraint { +export function typedArrayLengthEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length === ${value}`; - return typedArrayLengthComparator(equal, 's.typedArray(T).lengthEqual', expected, value); + return typedArrayLengthComparator(equal, 's.typedArray(T).lengthEqual()', expected, value, options); } -export function typedArrayLengthNotEqual(value: number): IConstraint { +export function typedArrayLengthNotEqual(value: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length !== ${value}`; - return typedArrayLengthComparator(notEqual, 's.typedArray(T).lengthNotEqual', expected, value); + return typedArrayLengthComparator(notEqual, 's.typedArray(T).lengthNotEqual()', expected, value, options); } -export function typedArrayLengthRange(start: number, endBefore: number): IConstraint { +export function typedArrayLengthRange(start: number, endBefore: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length >= ${start} && expected.length < ${endBefore}`; return { run(input: T) { return input.length >= start && input.length < endBefore // ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.typedArray(T).lengthRange', 'Invalid Typed Array length', input, expected)); + : Result.err( + new ExpectedConstraintError( + 's.typedArray(T).lengthRange()', + options?.message ?? 'Invalid Typed Array length', + input, + expected + ) + ); } }; } -export function typedArrayLengthRangeInclusive(start: number, end: number): IConstraint { +export function typedArrayLengthRangeInclusive(start: number, end: number, options?: ValidatorOptions): IConstraint { const expected = `expected.length >= ${start} && expected.length <= ${end}`; return { run(input: T) { return input.length >= start && input.length <= end // ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.typedArray(T).lengthRangeInclusive', 'Invalid Typed Array length', input, expected)); + : Result.err( + new ExpectedConstraintError( + 's.typedArray(T).lengthRangeInclusive()', + options?.message ?? 'Invalid Typed Array length', + input, + expected + ) + ); } }; } -export function typedArrayLengthRangeExclusive(startAfter: number, endBefore: number): IConstraint { +export function typedArrayLengthRangeExclusive( + startAfter: number, + endBefore: number, + options?: ValidatorOptions +): IConstraint { const expected = `expected.length > ${startAfter} && expected.length < ${endBefore}`; return { run(input: T) { return input.length > startAfter && input.length < endBefore // ? Result.ok(input) - : Result.err(new ExpectedConstraintError('s.typedArray(T).lengthRangeExclusive', 'Invalid Typed Array length', input, expected)); + : Result.err( + new ExpectedConstraintError( + 's.typedArray(T).lengthRangeExclusive()', + options?.message ?? 'Invalid Typed Array length', + input, + expected + ) + ); } }; } diff --git a/src/constraints/util/urlValidators.ts b/src/constraints/util/urlValidators.ts index c22978cb..7426531f 100644 --- a/src/constraints/util/urlValidators.ts +++ b/src/constraints/util/urlValidators.ts @@ -1,4 +1,5 @@ import { MultiplePossibilitiesConstraintError } from '../../lib/errors/MultiplePossibilitiesConstraintError'; +import type { ValidatorOptions } from '../../lib/util-types'; import { combinedErrorFn, type ErrorFn } from './common/combinedResultFn'; export type StringProtocol = `${string}:`; @@ -10,25 +11,25 @@ export interface UrlOptions { allowedDomains?: StringDomain[]; } -export function createUrlValidators(options?: UrlOptions) { +export function createUrlValidators(options?: UrlOptions, validatorOptions?: ValidatorOptions) { const fns: ErrorFn<[input: string, url: URL], MultiplePossibilitiesConstraintError>[] = []; - if (options?.allowedProtocols?.length) fns.push(allowedProtocolsFn(options.allowedProtocols)); - if (options?.allowedDomains?.length) fns.push(allowedDomainsFn(options.allowedDomains)); + if (options?.allowedProtocols?.length) fns.push(allowedProtocolsFn(options.allowedProtocols, validatorOptions)); + if (options?.allowedDomains?.length) fns.push(allowedDomainsFn(options.allowedDomains, validatorOptions)); return combinedErrorFn(...fns); } -function allowedProtocolsFn(allowedProtocols: StringProtocol[]) { +function allowedProtocolsFn(allowedProtocols: StringProtocol[], options?: ValidatorOptions) { return (input: string, url: URL) => allowedProtocols.includes(url.protocol as StringProtocol) ? null - : new MultiplePossibilitiesConstraintError('s.string.url', 'Invalid URL protocol', input, allowedProtocols); + : new MultiplePossibilitiesConstraintError('s.string().url()', options?.message ?? 'Invalid URL protocol', input, allowedProtocols); } -function allowedDomainsFn(allowedDomains: StringDomain[]) { +function allowedDomainsFn(allowedDomains: StringDomain[], options?: ValidatorOptions) { return (input: string, url: URL) => allowedDomains.includes(url.hostname as StringDomain) ? null - : new MultiplePossibilitiesConstraintError('s.string.url', 'Invalid URL domain', input, allowedDomains); + : new MultiplePossibilitiesConstraintError('s.string().url()', options?.message ?? 'Invalid URL domain', input, allowedDomains); } diff --git a/src/lib/Shapes.ts b/src/lib/Shapes.ts index 409b151e..b6daca73 100644 --- a/src/lib/Shapes.ts +++ b/src/lib/Shapes.ts @@ -1,5 +1,5 @@ import type { TypedArray, TypedArrayName } from '../constraints/util/typedArray'; -import type { Unwrap, UnwrapTuple } from '../lib/util-types'; +import type { Unwrap, UnwrapTuple, ValidatorOptions } from '../lib/util-types'; import { ArrayValidator, BaseValidator, @@ -13,6 +13,7 @@ import { NullishValidator, NumberValidator, ObjectValidator, + ObjectValidatorStrategy, PassthroughValidator, RecordValidator, SetValidator, @@ -26,146 +27,152 @@ import { TypedArrayValidator } from '../validators/TypedArrayValidator'; import type { Constructor, MappedObjectValidator } from './util-types'; export class Shapes { - public get string() { - return new StringValidator(); + public string(options?: ValidatorOptions) { + return new StringValidator(options); } - public get number() { - return new NumberValidator(); + public number(options?: ValidatorOptions) { + return new NumberValidator(options); } - public get bigint() { - return new BigIntValidator(); + public bigint(options?: ValidatorOptions) { + return new BigIntValidator(options); } - public get boolean() { - return new BooleanValidator(); + public boolean(options?: ValidatorOptions) { + return new BooleanValidator(options); } - public get date() { - return new DateValidator(); + public date(options?: ValidatorOptions) { + return new DateValidator(options); } - public object(shape: MappedObjectValidator) { - return new ObjectValidator(shape); + public object(shape: MappedObjectValidator, options?: ValidatorOptions) { + return new ObjectValidator(shape, ObjectValidatorStrategy.Ignore, options); } - public get undefined() { - return this.literal(undefined); + public undefined(options?: ValidatorOptions) { + return this.literal(undefined, { equalsOptions: options }); } - public get null() { - return this.literal(null); + public null(options?: ValidatorOptions) { + return this.literal(null, { equalsOptions: options }); } - public get nullish() { - return new NullishValidator(); + public nullish(options?: ValidatorOptions) { + return new NullishValidator(options); } - public get any() { - return new PassthroughValidator(); + public any(options?: ValidatorOptions) { + return new PassthroughValidator(options); } - public get unknown() { - return new PassthroughValidator(); + public unknown(options?: ValidatorOptions) { + return new PassthroughValidator(options); } - public get never() { - return new NeverValidator(); + public never(options?: ValidatorOptions) { + return new NeverValidator(options); } - public enum(...values: readonly T[]) { - return this.union(...values.map((value) => this.literal(value))); + public enum(values: readonly T[], options?: ValidatorOptions) { + return this.union( + values.map((value) => this.literal(value, { equalsOptions: options })), + options + ); } - public nativeEnum(enumShape: T): NativeEnumValidator { - return new NativeEnumValidator(enumShape); + public nativeEnum(enumShape: T, options?: ValidatorOptions): NativeEnumValidator { + return new NativeEnumValidator(enumShape, options); } - public literal(value: T): BaseValidator { - if (value instanceof Date) return this.date.equal(value) as unknown as BaseValidator; - return new LiteralValidator(value); + public literal(value: T, options?: { dateOptions?: ValidatorOptions; equalsOptions?: ValidatorOptions }): BaseValidator { + if (value instanceof Date) { + return this.date(options?.dateOptions).equal(value, options?.equalsOptions) as unknown as BaseValidator; + } + + return new LiteralValidator(value, options?.equalsOptions); } - public instance(expected: Constructor): InstanceValidator { - return new InstanceValidator(expected); + public instance(expected: Constructor, options?: ValidatorOptions): InstanceValidator { + return new InstanceValidator(expected, options); } - public union[]]>(...validators: [...T]): UnionValidator> { - return new UnionValidator(validators); + public union[]>(validators: T, options?: ValidatorOptions): UnionValidator> { + return new UnionValidator(validators, options); } - public array(validator: BaseValidator): ArrayValidator; - public array(validator: BaseValidator): ArrayValidator; - public array(validator: BaseValidator) { - return new ArrayValidator(validator); + public array(validator: BaseValidator, options?: ValidatorOptions): ArrayValidator; + public array(validator: BaseValidator, options?: ValidatorOptions): ArrayValidator; + public array(validator: BaseValidator, options?: ValidatorOptions) { + return new ArrayValidator(validator, options); } - public typedArray(type: TypedArrayName = 'TypedArray') { - return new TypedArrayValidator(type); + public typedArray(type: TypedArrayName = 'TypedArray', options?: ValidatorOptions) { + return new TypedArrayValidator(type, options); } - public get int8Array() { - return this.typedArray('Int8Array'); + public int8Array(options?: ValidatorOptions) { + return this.typedArray('Int8Array', options); } - public get uint8Array() { - return this.typedArray('Uint8Array'); + public uint8Array(options?: ValidatorOptions) { + return this.typedArray('Uint8Array', options); } - public get uint8ClampedArray() { - return this.typedArray('Uint8ClampedArray'); + public uint8ClampedArray(options?: ValidatorOptions) { + return this.typedArray('Uint8ClampedArray', options); } - public get int16Array() { - return this.typedArray('Int16Array'); + public int16Array(options?: ValidatorOptions) { + return this.typedArray('Int16Array', options); } - public get uint16Array() { - return this.typedArray('Uint16Array'); + public uint16Array(options?: ValidatorOptions) { + return this.typedArray('Uint16Array', options); } - public get int32Array() { - return this.typedArray('Int32Array'); + public int32Array(options?: ValidatorOptions) { + return this.typedArray('Int32Array', options); } - public get uint32Array() { - return this.typedArray('Uint32Array'); + public uint32Array(options?: ValidatorOptions) { + return this.typedArray('Uint32Array', options); } - public get float32Array() { - return this.typedArray('Float32Array'); + public float32Array(options?: ValidatorOptions) { + return this.typedArray('Float32Array', options); } - public get float64Array() { - return this.typedArray('Float64Array'); + public float64Array(options?: ValidatorOptions) { + return this.typedArray('Float64Array', options); } - public get bigInt64Array() { - return this.typedArray('BigInt64Array'); + public bigInt64Array(options?: ValidatorOptions) { + return this.typedArray('BigInt64Array', options); } - public get bigUint64Array() { - return this.typedArray('BigUint64Array'); + public bigUint64Array(options?: ValidatorOptions) { + return this.typedArray('BigUint64Array', options); } - public tuple[]]>(validators: [...T]): TupleValidator> { - return new TupleValidator(validators); + public tuple[]]>(validators: [...T], options?: ValidatorOptions): TupleValidator> { + return new TupleValidator(validators, options); } - public set(validator: BaseValidator) { - return new SetValidator(validator); + public set(validator: BaseValidator, options?: ValidatorOptions) { + return new SetValidator(validator, options); } - public record(validator: BaseValidator) { - return new RecordValidator(validator); + public record(validator: BaseValidator, options?: ValidatorOptions) { + return new RecordValidator(validator, options); } - public map(keyValidator: BaseValidator, valueValidator: BaseValidator) { - return new MapValidator(keyValidator, valueValidator); + public map(keyValidator: BaseValidator, valueValidator: BaseValidator, options?: ValidatorOptions) { + return new MapValidator(keyValidator, valueValidator, options); } - public lazy>(validator: (value: unknown) => T) { - return new LazyValidator(validator); + public lazy>(validator: (value: unknown) => T, options?: ValidatorOptions) { + return new LazyValidator(validator, options); } } diff --git a/src/lib/errors/BaseConstraintError.ts b/src/lib/errors/BaseConstraintError.ts index b2ddfe5d..2c907eae 100644 --- a/src/lib/errors/BaseConstraintError.ts +++ b/src/lib/errors/BaseConstraintError.ts @@ -9,6 +9,7 @@ import type { TypedArrayConstraintName } from '../../constraints/type-exports'; import { BaseError } from './BaseError'; +import type { BaseConstraintErrorJsonified } from './error-types'; export type ConstraintErrorNames = | TypedArrayConstraintName @@ -29,4 +30,13 @@ export abstract class BaseConstraintError extends BaseError { this.constraint = constraint; this.given = given; } + + public override toJSON(): BaseConstraintErrorJsonified { + return { + name: this.name, + constraint: this.constraint, + given: this.given, + message: this.message + }; + } } diff --git a/src/lib/errors/BaseError.ts b/src/lib/errors/BaseError.ts index 7a331955..355daad4 100644 --- a/src/lib/errors/BaseError.ts +++ b/src/lib/errors/BaseError.ts @@ -1,9 +1,17 @@ import type { InspectOptionsStylized } from 'util'; +import type { BaseErrorJsonified } from './error-types'; export const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom'); export const customInspectSymbolStackLess = Symbol.for('nodejs.util.inspect.custom.stack-less'); export abstract class BaseError extends Error { + public toJSON(): BaseErrorJsonified { + return { + name: this.name, + message: this.message + }; + } + protected [customInspectSymbol](depth: number, options: InspectOptionsStylized) { return `${this[customInspectSymbolStackLess](depth, options)}\n${this.stack!.slice(this.stack!.indexOf('\n'))}`; } diff --git a/src/lib/errors/CombinedError.ts b/src/lib/errors/CombinedError.ts index 5e359cf8..f58dd415 100644 --- a/src/lib/errors/CombinedError.ts +++ b/src/lib/errors/CombinedError.ts @@ -1,11 +1,12 @@ import type { InspectOptionsStylized } from 'util'; +import type { ValidatorOptions } from '../util-types'; import { BaseError, customInspectSymbolStackLess } from './BaseError'; export class CombinedError extends BaseError { public readonly errors: readonly BaseError[]; - public constructor(errors: readonly BaseError[]) { - super('Received one or more errors'); + public constructor(errors: readonly BaseError[], validatorOptions?: ValidatorOptions) { + super(validatorOptions?.message ?? 'Received one or more errors'); this.errors = errors; } diff --git a/src/lib/errors/CombinedPropertyError.ts b/src/lib/errors/CombinedPropertyError.ts index 940b2456..11e99003 100644 --- a/src/lib/errors/CombinedPropertyError.ts +++ b/src/lib/errors/CombinedPropertyError.ts @@ -1,11 +1,12 @@ import type { InspectOptionsStylized } from 'util'; +import type { ValidatorOptions } from '../util-types'; import { BaseError, customInspectSymbolStackLess } from './BaseError'; export class CombinedPropertyError extends BaseError { public readonly errors: [PropertyKey, BaseError][]; - public constructor(errors: [PropertyKey, BaseError][]) { - super('Received one or more errors'); + public constructor(errors: [PropertyKey, BaseError][], validatorOptions?: ValidatorOptions) { + super(validatorOptions?.message ?? 'Received one or more errors'); this.errors = errors; } diff --git a/src/lib/errors/ExpectedConstraintError.ts b/src/lib/errors/ExpectedConstraintError.ts index 093c09ce..feae2ceb 100644 --- a/src/lib/errors/ExpectedConstraintError.ts +++ b/src/lib/errors/ExpectedConstraintError.ts @@ -1,6 +1,7 @@ import { inspect, type InspectOptionsStylized } from 'util'; -import { customInspectSymbolStackLess } from './BaseError'; import { BaseConstraintError, type ConstraintErrorNames } from './BaseConstraintError'; +import { customInspectSymbolStackLess } from './BaseError'; +import type { ExpectedConstraintErrorJsonified } from './error-types'; export class ExpectedConstraintError extends BaseConstraintError { public readonly expected: string; @@ -10,12 +11,13 @@ export class ExpectedConstraintError extends BaseConstraintError this.expected = expected; } - public toJSON() { + public override toJSON(): ExpectedConstraintErrorJsonified { return { name: this.name, constraint: this.constraint, given: this.given, - expected: this.expected + expected: this.expected, + message: this.message }; } diff --git a/src/lib/errors/ExpectedValidationError.ts b/src/lib/errors/ExpectedValidationError.ts index c2e914d6..a7ee581d 100644 --- a/src/lib/errors/ExpectedValidationError.ts +++ b/src/lib/errors/ExpectedValidationError.ts @@ -1,5 +1,6 @@ import { inspect, type InspectOptionsStylized } from 'util'; import { customInspectSymbolStackLess } from './BaseError'; +import type { ExpectedValidationErrorJsonified } from './error-types'; import { ValidationError } from './ValidationError'; export class ExpectedValidationError extends ValidationError { @@ -10,12 +11,13 @@ export class ExpectedValidationError extends ValidationError { this.expected = expected; } - public override toJSON() { + public override toJSON(): ExpectedValidationErrorJsonified { return { name: this.name, validator: this.validator, given: this.given, - expected: this.expected + expected: this.expected, + message: this.message }; } diff --git a/src/lib/errors/MissingPropertyError.ts b/src/lib/errors/MissingPropertyError.ts index 44ed99e8..dee6c0be 100644 --- a/src/lib/errors/MissingPropertyError.ts +++ b/src/lib/errors/MissingPropertyError.ts @@ -1,17 +1,20 @@ import type { InspectOptionsStylized } from 'util'; +import type { ValidatorOptions } from '../util-types'; import { BaseError, customInspectSymbolStackLess } from './BaseError'; +import type { MissingPropertyErrorJsonified } from './error-types'; export class MissingPropertyError extends BaseError { public readonly property: PropertyKey; - public constructor(property: PropertyKey) { - super('A required property is missing'); + public constructor(property: PropertyKey, validatorOptions?: ValidatorOptions) { + super(validatorOptions?.message ?? 'A required property is missing'); this.property = property; } - public toJSON() { + public override toJSON(): MissingPropertyErrorJsonified { return { name: this.name, + message: this.message, property: this.property }; } diff --git a/src/lib/errors/MultiplePossibilitiesConstraintError.ts b/src/lib/errors/MultiplePossibilitiesConstraintError.ts index c86be97e..c3bb386e 100644 --- a/src/lib/errors/MultiplePossibilitiesConstraintError.ts +++ b/src/lib/errors/MultiplePossibilitiesConstraintError.ts @@ -1,6 +1,7 @@ import { inspect, type InspectOptionsStylized } from 'util'; -import { customInspectSymbolStackLess } from './BaseError'; import { BaseConstraintError, type ConstraintErrorNames } from './BaseConstraintError'; +import { customInspectSymbolStackLess } from './BaseError'; +import type { MultiplePossibilitiesConstraintErrorJsonified } from './error-types'; export class MultiplePossibilitiesConstraintError extends BaseConstraintError { public readonly expected: readonly string[]; @@ -10,9 +11,10 @@ export class MultiplePossibilitiesConstraintError extends BaseConst this.expected = expected; } - public toJSON() { + public override toJSON(): MultiplePossibilitiesConstraintErrorJsonified { return { name: this.name, + message: this.message, constraint: this.constraint, given: this.given, expected: this.expected diff --git a/src/lib/errors/UnknownEnumValueError.ts b/src/lib/errors/UnknownEnumValueError.ts index c516cea1..602e5558 100644 --- a/src/lib/errors/UnknownEnumValueError.ts +++ b/src/lib/errors/UnknownEnumValueError.ts @@ -1,22 +1,30 @@ import type { InspectOptionsStylized } from 'util'; +import type { ValidatorOptions } from '../util-types'; import { BaseError, customInspectSymbolStackLess } from './BaseError'; +import type { UnknownEnumValueErrorJsonified } from './error-types'; export class UnknownEnumValueError extends BaseError { public readonly value: string | number; public readonly enumKeys: string[]; public readonly enumMappings: Map; - public constructor(value: string | number, keys: string[], enumMappings: Map) { - super('Expected the value to be one of the following enum values:'); + public constructor( + value: string | number, + keys: string[], + enumMappings: Map, + validatorOptions?: ValidatorOptions + ) { + super(validatorOptions?.message ?? 'Expected the value to be one of the following enum values:'); this.value = value; this.enumKeys = keys; this.enumMappings = enumMappings; } - public toJSON() { + public override toJSON(): UnknownEnumValueErrorJsonified { return { name: this.name, + message: this.message, value: this.value, enumKeys: this.enumKeys, enumMappings: [...this.enumMappings.entries()] diff --git a/src/lib/errors/UnknownPropertyError.ts b/src/lib/errors/UnknownPropertyError.ts index 3fc06f96..914f7eea 100644 --- a/src/lib/errors/UnknownPropertyError.ts +++ b/src/lib/errors/UnknownPropertyError.ts @@ -1,20 +1,23 @@ import { inspect, type InspectOptionsStylized } from 'util'; +import type { ValidatorOptions } from '../util-types'; import { BaseError, customInspectSymbolStackLess } from './BaseError'; +import type { UnknownEnumKeyErrorJsonified } from './error-types'; export class UnknownPropertyError extends BaseError { public readonly property: PropertyKey; public readonly value: unknown; - public constructor(property: PropertyKey, value: unknown) { - super('Received unexpected property'); + public constructor(property: PropertyKey, value: unknown, options?: ValidatorOptions) { + super(options?.message ?? 'Received unexpected property'); this.property = property; this.value = value; } - public toJSON() { + public override toJSON(): UnknownEnumKeyErrorJsonified { return { name: this.name, + message: this.message, property: this.property, value: this.value }; diff --git a/src/lib/errors/ValidationError.ts b/src/lib/errors/ValidationError.ts index 5df0ecba..f5c1b7f4 100644 --- a/src/lib/errors/ValidationError.ts +++ b/src/lib/errors/ValidationError.ts @@ -1,5 +1,6 @@ import { inspect, type InspectOptionsStylized } from 'util'; import { BaseError, customInspectSymbolStackLess } from './BaseError'; +import type { ValidationErrorJsonified } from './error-types'; export class ValidationError extends BaseError { public readonly validator: string; @@ -12,9 +13,10 @@ export class ValidationError extends BaseError { this.given = given; } - public toJSON() { + public override toJSON(): ValidationErrorJsonified { return { name: this.name, + message: 'Unknown validation error occurred.', validator: this.validator, given: this.given }; diff --git a/src/lib/errors/error-types.ts b/src/lib/errors/error-types.ts new file mode 100644 index 00000000..52f01709 --- /dev/null +++ b/src/lib/errors/error-types.ts @@ -0,0 +1,43 @@ +import type { ConstraintErrorNames } from './BaseConstraintError'; + +export interface BaseErrorJsonified { + name: string; + message: string; +} + +export interface BaseConstraintErrorJsonified extends BaseErrorJsonified { + constraint: ConstraintErrorNames; + given: T; +} + +export interface ExpectedConstraintErrorJsonified extends BaseConstraintErrorJsonified { + expected: string; +} + +export interface ValidationErrorJsonified extends BaseErrorJsonified { + validator: string; + given: unknown; +} + +export interface ExpectedValidationErrorJsonified extends ValidationErrorJsonified { + expected: T; +} + +export interface MissingPropertyErrorJsonified extends BaseErrorJsonified { + property: PropertyKey; +} + +export interface MultiplePossibilitiesConstraintErrorJsonified extends BaseConstraintErrorJsonified { + expected: readonly string[]; +} + +export interface UnknownEnumValueErrorJsonified extends BaseErrorJsonified { + value: string | number; + enumKeys: string[]; + enumMappings: readonly (readonly [string | number, string | number])[]; +} + +export interface UnknownEnumKeyErrorJsonified extends BaseErrorJsonified { + property: PropertyKey; + value: unknown; +} diff --git a/src/lib/util-types.ts b/src/lib/util-types.ts index ccfde5a1..8ce9c4f5 100644 --- a/src/lib/util-types.ts +++ b/src/lib/util-types.ts @@ -7,22 +7,15 @@ export type Constructor = (new (...args: readonly any[]) => T) | (abstract ne export type Type = V extends BaseValidator ? T : never; /** - * @deprecated Use `object` instead. + * Additional options to pass to the validator. + * Right now this only supports a custom error message, but we provide an option for future expansion. */ -// eslint-disable-next-line @typescript-eslint/ban-types -export type NonNullObject = {} & object; - -/** - * @deprecated This type is no longer used and will be removed in the next major version. - */ -export type PickDefined = { [K in keyof T as undefined extends T[K] ? never : K]: T[K] }; - -/** - * @deprecated This type is no longer used and will be removed in the next major version. - */ -export type PickUndefinedMakeOptional = { - [K in keyof T as undefined extends T[K] ? K : never]+?: Exclude; -}; +export interface ValidatorOptions { + /** + * The custom message to throw when this validation fails. + */ + message?: string; +} type FilterDefinedKeys = Exclude< { diff --git a/src/type-exports.ts b/src/type-exports.ts index d8dbd339..1e65b582 100644 --- a/src/type-exports.ts +++ b/src/type-exports.ts @@ -1,51 +1,32 @@ export type { ArrayConstraintName, arrayLengthEqual, - arrayLengthGreaterThanOrEqual, arrayLengthGreaterThan, - arrayLengthLessThanOrEqual, + arrayLengthGreaterThanOrEqual, arrayLengthLessThan, + arrayLengthLessThanOrEqual, arrayLengthNotEqual, arrayLengthRange, arrayLengthRangeExclusive, arrayLengthRangeInclusive, - TypedArrayConstraintName, - typedArrayByteLengthEqual, - typedArrayByteLengthGreaterThanOrEqual, - typedArrayByteLengthGreaterThan, - typedArrayByteLengthLessThanOrEqual, - typedArrayByteLengthLessThan, - typedArrayByteLengthNotEqual, - typedArrayByteLengthRange, - typedArrayByteLengthRangeExclusive, - typedArrayByteLengthRangeInclusive, - typedArrayLengthEqual, - typedArrayLengthGreaterThanOrEqual, - typedArrayLengthGreaterThan, - typedArrayLengthLessThanOrEqual, - typedArrayLengthLessThan, - typedArrayLengthNotEqual, - typedArrayLengthRange, - typedArrayLengthRangeExclusive, - typedArrayLengthRangeInclusive, BigIntConstraintName, bigintDivisibleBy, bigintEqual, - bigintGreaterThanOrEqual, bigintGreaterThan, - bigintLessThanOrEqual, + bigintGreaterThanOrEqual, bigintLessThan, + bigintLessThanOrEqual, bigintNotEqual, BooleanConstraintName, booleanFalse, booleanTrue, DateConstraintName, dateEqual, - dateGreaterThanOrEqual, dateGreaterThan, + dateGreaterThanOrEqual, dateInvalid, - dateLessThanOrEqual, dateLessThan, + dateLessThanOrEqual, dateNotEqual, dateValid, IConstraint, @@ -53,11 +34,11 @@ export type { numberDivisibleBy, numberEqual, numberFinite, - numberGreaterThanOrEqual, numberGreaterThan, + numberGreaterThanOrEqual, numberInt, - numberLessThanOrEqual, numberLessThan, + numberLessThanOrEqual, numberNaN, numberNotEqual, numberNotNaN, @@ -67,16 +48,35 @@ export type { stringEmail, stringIp, stringLengthEqual, - stringLengthGreaterThanOrEqual, stringLengthGreaterThan, - stringLengthLessThanOrEqual, + stringLengthGreaterThanOrEqual, stringLengthLessThan, + stringLengthLessThanOrEqual, stringLengthNotEqual, StringProtocol, stringRegex, stringUrl, stringUuid, StringUuidOptions, + typedArrayByteLengthEqual, + typedArrayByteLengthGreaterThan, + typedArrayByteLengthGreaterThanOrEqual, + typedArrayByteLengthLessThan, + typedArrayByteLengthLessThanOrEqual, + typedArrayByteLengthNotEqual, + typedArrayByteLengthRange, + typedArrayByteLengthRangeExclusive, + typedArrayByteLengthRangeInclusive, + TypedArrayConstraintName, + typedArrayLengthEqual, + typedArrayLengthGreaterThan, + typedArrayLengthGreaterThanOrEqual, + typedArrayLengthLessThan, + typedArrayLengthLessThanOrEqual, + typedArrayLengthNotEqual, + typedArrayLengthRange, + typedArrayLengthRangeExclusive, + typedArrayLengthRangeInclusive, UrlOptions, UUIDVersion } from './constraints/type-exports'; @@ -93,6 +93,7 @@ export type { ValidationError } from './lib/errors/ValidationError'; export type { Shapes } from './lib/Shapes'; // export * from './lib/util-types'; +export * from './lib/errors/error-types'; // export type { ArrayValidator } from './validators/ArrayValidator'; export type { BaseValidator, ValidatorError } from './validators/BaseValidator'; diff --git a/src/validators/ArrayValidator.ts b/src/validators/ArrayValidator.ts index 79938313..e9ab3700 100644 --- a/src/validators/ArrayValidator.ts +++ b/src/validators/ArrayValidator.ts @@ -15,73 +15,88 @@ import type { BaseError } from '../lib/errors/BaseError'; import { CombinedPropertyError } from '../lib/errors/CombinedPropertyError'; import { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; -import type { ExpandSmallerTuples, Tuple, UnshiftTuple } from '../lib/util-types'; +import type { ExpandSmallerTuples, Tuple, UnshiftTuple, ValidatorOptions } from '../lib/util-types'; import { BaseValidator } from './imports'; export class ArrayValidator extends BaseValidator { private readonly validator: BaseValidator; - public constructor(validator: BaseValidator, constraints: readonly IConstraint[] = []) { - super(constraints); + public constructor(validator: BaseValidator, validatorOptions: ValidatorOptions = {}, constraints: readonly IConstraint[] = []) { + super(validatorOptions, constraints); this.validator = validator; } - public lengthLessThan(length: N): ArrayValidator]>>> { - return this.addConstraint(arrayLengthLessThan(length) as IConstraint) as any; + public lengthLessThan( + length: N, + options: ValidatorOptions = this.validatorOptions + ): ArrayValidator]>>> { + return this.addConstraint(arrayLengthLessThan(length, options) as IConstraint) as any; } - public lengthLessThanOrEqual(length: N): ArrayValidator]>> { - return this.addConstraint(arrayLengthLessThanOrEqual(length) as IConstraint) as any; + public lengthLessThanOrEqual( + length: N, + options: ValidatorOptions = this.validatorOptions + ): ArrayValidator]>> { + return this.addConstraint(arrayLengthLessThanOrEqual(length, options) as IConstraint) as any; } - public lengthGreaterThan(length: N): ArrayValidator<[...Tuple, I, ...T]> { - return this.addConstraint(arrayLengthGreaterThan(length) as IConstraint) as any; + public lengthGreaterThan( + length: N, + options: ValidatorOptions = this.validatorOptions + ): ArrayValidator<[...Tuple, I, ...T]> { + return this.addConstraint(arrayLengthGreaterThan(length, options) as IConstraint) as any; } - public lengthGreaterThanOrEqual(length: N): ArrayValidator<[...Tuple, ...T]> { - return this.addConstraint(arrayLengthGreaterThanOrEqual(length) as IConstraint) as any; + public lengthGreaterThanOrEqual( + length: N, + options: ValidatorOptions = this.validatorOptions + ): ArrayValidator<[...Tuple, ...T]> { + return this.addConstraint(arrayLengthGreaterThanOrEqual(length, options) as IConstraint) as any; } - public lengthEqual(length: N): ArrayValidator<[...Tuple]> { - return this.addConstraint(arrayLengthEqual(length) as IConstraint) as any; + public lengthEqual(length: N, options: ValidatorOptions = this.validatorOptions): ArrayValidator<[...Tuple]> { + return this.addConstraint(arrayLengthEqual(length, options) as IConstraint) as any; } - public lengthNotEqual(length: number): ArrayValidator<[...T]> { - return this.addConstraint(arrayLengthNotEqual(length) as IConstraint) as any; + public lengthNotEqual(length: N, options: ValidatorOptions = this.validatorOptions): ArrayValidator<[...Tuple]> { + return this.addConstraint(arrayLengthNotEqual(length, options) as IConstraint) as any; } public lengthRange( start: S, - endBefore: E + endBefore: E, + options: ValidatorOptions = this.validatorOptions ): ArrayValidator]>>, ExpandSmallerTuples]>>>> { - return this.addConstraint(arrayLengthRange(start, endBefore) as IConstraint) as any; + return this.addConstraint(arrayLengthRange(start, endBefore, options) as IConstraint) as any; } public lengthRangeInclusive( startAt: S, - endAt: E + endAt: E, + options: ValidatorOptions = this.validatorOptions ): ArrayValidator]>, ExpandSmallerTuples]>>>> { - return this.addConstraint(arrayLengthRangeInclusive(startAt, endAt) as IConstraint) as any; + return this.addConstraint(arrayLengthRangeInclusive(startAt, endAt, options) as IConstraint) as any; } public lengthRangeExclusive( startAfter: S, - endBefore: E + endBefore: E, + options: ValidatorOptions = this.validatorOptions ): ArrayValidator]>>, ExpandSmallerTuples<[...Tuple]>>> { - return this.addConstraint(arrayLengthRangeExclusive(startAfter, endBefore) as IConstraint) as any; + return this.addConstraint(arrayLengthRangeExclusive(startAfter, endBefore, options) as IConstraint) as any; } - public get unique(): this { - return this.addConstraint(uniqueArray as IConstraint); + public unique(options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(uniqueArray(options) as IConstraint); } protected override clone(): this { - return Reflect.construct(this.constructor, [this.validator, this.constraints]); + return Reflect.construct(this.constructor, [this.validator, this.validatorOptions, this.constraints]); } protected handle(values: unknown): Result { if (!Array.isArray(values)) { - return Result.err(new ValidationError('s.array(T)', 'Expected an array', values)); + return Result.err(new ValidationError('s.array(T)', this.validatorOptions.message ?? 'Expected an array', values)); } if (!this.shouldRunConstraints) { @@ -99,6 +114,6 @@ export class ArrayValidator extends BaseVali return errors.length === 0 // ? Result.ok(transformed) - : Result.err(new CombinedPropertyError(errors)); + : Result.err(new CombinedPropertyError(errors, this.validatorOptions)); } } diff --git a/src/validators/BaseValidator.ts b/src/validators/BaseValidator.ts index 68219add..4f64cf96 100644 --- a/src/validators/BaseValidator.ts +++ b/src/validators/BaseValidator.ts @@ -1,24 +1,27 @@ -import { getGlobalValidationEnabled } from '../lib/configs'; -import { Result } from '../lib/Result'; -import { ArrayValidator, DefaultValidator, LiteralValidator, NullishValidator, SetValidator, UnionValidator } from './imports'; -import { getValue } from './util/getValue'; +import type { IConstraint } from '../constraints/base/IConstraint'; import { whenConstraint, type WhenKey, type WhenOptions } from '../constraints/ObjectConstrains'; +import { getGlobalValidationEnabled } from '../lib/configs'; +import type { BaseConstraintError } from '../lib/errors/BaseConstraintError'; +import type { BaseError } from '../lib/errors/BaseError'; import type { CombinedError } from '../lib/errors/CombinedError'; import type { CombinedPropertyError } from '../lib/errors/CombinedPropertyError'; import type { UnknownEnumValueError } from '../lib/errors/UnknownEnumValueError'; import type { ValidationError } from '../lib/errors/ValidationError'; -import type { BaseConstraintError, InferResultType } from '../type-exports'; -import type { IConstraint } from '../constraints/base/IConstraint'; -import type { BaseError } from '../lib/errors/BaseError'; +import { Result } from '../lib/Result'; +import type { InferResultType, ValidatorOptions } from '../lib/util-types'; +import { ArrayValidator, DefaultValidator, LiteralValidator, NullishValidator, SetValidator, UnionValidator } from './imports'; +import { getValue } from './util/getValue'; export abstract class BaseValidator { public description?: string; + protected validatorOptions: ValidatorOptions; protected parent?: object; protected constraints: readonly IConstraint[] = []; protected isValidationEnabled: boolean | (() => boolean) | null = null; - public constructor(constraints: readonly IConstraint[] = []) { + public constructor(validatorOptions: ValidatorOptions = {}, constraints: readonly IConstraint[] = []) { this.constraints = constraints; + this.validatorOptions = validatorOptions; } public setParent(parent: object): this { @@ -26,48 +29,68 @@ export abstract class BaseValidator { return this; } - public get optional(): UnionValidator { - return new UnionValidator([new LiteralValidator(undefined), this.clone()]); + public optional(options: ValidatorOptions = this.validatorOptions): UnionValidator { + return new UnionValidator([new LiteralValidator(undefined, options), this.clone()], options); } - public get nullable(): UnionValidator { - return new UnionValidator([new LiteralValidator(null), this.clone()]); + public nullable(options: ValidatorOptions = this.validatorOptions): UnionValidator { + return new UnionValidator([new LiteralValidator(null, options), this.clone()], options); } - public get nullish(): UnionValidator { - return new UnionValidator([new NullishValidator(), this.clone()]); + public nullish(options: ValidatorOptions = this.validatorOptions): UnionValidator { + return new UnionValidator([new NullishValidator(options), this.clone()], options); } - public get array(): ArrayValidator { - return new ArrayValidator(this.clone()); + public array(options: ValidatorOptions = this.validatorOptions): ArrayValidator { + return new ArrayValidator(this.clone(), options); } - public get set(): SetValidator { - return new SetValidator(this.clone()); + public set(options: ValidatorOptions = this.validatorOptions): SetValidator { + return new SetValidator(this.clone(), options); } public or(...predicates: readonly BaseValidator[]): UnionValidator { - return new UnionValidator([this.clone(), ...predicates]); + return new UnionValidator([this.clone(), ...predicates], this.validatorOptions); } - public transform(cb: (value: T) => T): this; - public transform(cb: (value: T) => O): BaseValidator; - public transform(cb: (value: T) => O): BaseValidator { - return this.addConstraint({ run: (input) => Result.ok(cb(input) as unknown as T) }) as unknown as BaseValidator; + public transform(cb: (value: T) => T, options?: ValidatorOptions): this; + public transform(cb: (value: T) => O, options?: ValidatorOptions): BaseValidator; + public transform(cb: (value: T) => O, options: ValidatorOptions = this.validatorOptions): BaseValidator { + return this.addConstraint( + { + run: (input) => Result.ok(cb(input) as unknown as T) + }, + options + ) as unknown as BaseValidator; } - public reshape(cb: (input: T) => Result): this; - public reshape, O = InferResultType>(cb: (input: T) => R): BaseValidator; - public reshape, O = InferResultType>(cb: (input: T) => R): BaseValidator { - return this.addConstraint({ run: cb as unknown as (input: T) => Result> }) as unknown as BaseValidator; + public reshape(cb: (input: T) => Result, options?: ValidatorOptions): this; + public reshape, O = InferResultType>(cb: (input: T) => R, options?: ValidatorOptions): BaseValidator; + public reshape, O = InferResultType>( + cb: (input: T) => R, + options: ValidatorOptions = this.validatorOptions + ): BaseValidator { + return this.addConstraint( + { + run: cb as unknown as (input: T) => Result> + }, + options + ) as unknown as BaseValidator; } - public default(value: Exclude | (() => Exclude)): DefaultValidator> { - return new DefaultValidator(this.clone() as unknown as BaseValidator>, value); + public default( + value: Exclude | (() => Exclude), + options: ValidatorOptions = this.validatorOptions + ): DefaultValidator> { + return new DefaultValidator(this.clone() as unknown as BaseValidator>, value, options); } - public when = this>(key: Key, options: WhenOptions): this { - return this.addConstraint(whenConstraint(key, options, this as unknown as This)); + public when = this>( + key: Key, + options: WhenOptions, + validatorOptions?: ValidatorOptions + ): this { + return this.addConstraint(whenConstraint(key, options, this as unknown as This, validatorOptions)); } public describe(description: string): this { @@ -122,15 +145,16 @@ export abstract class BaseValidator { } protected clone(): this { - const clone: this = Reflect.construct(this.constructor, [this.constraints]); + const clone: this = Reflect.construct(this.constructor, [this.validatorOptions, this.constraints]); clone.isValidationEnabled = this.isValidationEnabled; return clone; } protected abstract handle(value: unknown): Result; - protected addConstraint(constraint: IConstraint): this { + protected addConstraint(constraint: IConstraint, validatorOptions: ValidatorOptions = this.validatorOptions): this { const clone = this.clone(); + clone.validatorOptions = validatorOptions; clone.constraints = clone.constraints.concat(constraint); return clone; } diff --git a/src/validators/BigIntValidator.ts b/src/validators/BigIntValidator.ts index 8db66a80..76416a2c 100644 --- a/src/validators/BigIntValidator.ts +++ b/src/validators/BigIntValidator.ts @@ -10,60 +10,61 @@ import { } from '../constraints/BigIntConstraints'; import { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import { BaseValidator } from './imports'; export class BigIntValidator extends BaseValidator { - public lessThan(number: bigint): this { - return this.addConstraint(bigintLessThan(number) as IConstraint); + public lessThan(number: bigint, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(bigintLessThan(number, options) as IConstraint); } - public lessThanOrEqual(number: bigint): this { - return this.addConstraint(bigintLessThanOrEqual(number) as IConstraint); + public lessThanOrEqual(number: bigint, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(bigintLessThanOrEqual(number, options) as IConstraint); } - public greaterThan(number: bigint): this { - return this.addConstraint(bigintGreaterThan(number) as IConstraint); + public greaterThan(number: bigint, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(bigintGreaterThan(number, options) as IConstraint); } - public greaterThanOrEqual(number: bigint): this { - return this.addConstraint(bigintGreaterThanOrEqual(number) as IConstraint); + public greaterThanOrEqual(number: bigint, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(bigintGreaterThanOrEqual(number, options) as IConstraint); } - public equal(number: N): BigIntValidator { - return this.addConstraint(bigintEqual(number) as IConstraint) as unknown as BigIntValidator; + public equal(number: N, options: ValidatorOptions = this.validatorOptions): BigIntValidator { + return this.addConstraint(bigintEqual(number, options) as IConstraint) as unknown as BigIntValidator; } - public notEqual(number: bigint): this { - return this.addConstraint(bigintNotEqual(number) as IConstraint); + public notEqual(number: bigint, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(bigintNotEqual(number, options) as IConstraint); } - public get positive(): this { - return this.greaterThanOrEqual(0n); + public positive(options: ValidatorOptions = this.validatorOptions): this { + return this.greaterThanOrEqual(0n, options); } - public get negative(): this { - return this.lessThan(0n); + public negative(options: ValidatorOptions = this.validatorOptions): this { + return this.lessThan(0n, options); } - public divisibleBy(number: bigint): this { - return this.addConstraint(bigintDivisibleBy(number) as IConstraint); + public divisibleBy(number: bigint, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(bigintDivisibleBy(number, options) as IConstraint); } - public get abs(): this { - return this.transform((value) => (value < 0 ? -value : value) as T); + public abs(options: ValidatorOptions = this.validatorOptions): this { + return this.transform((value) => (value < 0 ? -value : value) as T, options); } - public intN(bits: number): this { - return this.transform((value) => BigInt.asIntN(bits, value) as T); + public intN(bits: number, options: ValidatorOptions = this.validatorOptions): this { + return this.transform((value) => BigInt.asIntN(bits, value) as T, options); } - public uintN(bits: number): this { - return this.transform((value) => BigInt.asUintN(bits, value) as T); + public uintN(bits: number, options: ValidatorOptions = this.validatorOptions): this { + return this.transform((value) => BigInt.asUintN(bits, value) as T, options); } protected handle(value: unknown): Result { return typeof value === 'bigint' // ? Result.ok(value as T) - : Result.err(new ValidationError('s.bigint', 'Expected a bigint primitive', value)); + : Result.err(new ValidationError('s.bigint()', this.validatorOptions.message ?? 'Expected a bigint primitive', value)); } } diff --git a/src/validators/BooleanValidator.ts b/src/validators/BooleanValidator.ts index af3b01ef..4e3c5581 100644 --- a/src/validators/BooleanValidator.ts +++ b/src/validators/BooleanValidator.ts @@ -2,28 +2,29 @@ import type { IConstraint } from '../constraints/base/IConstraint'; import { booleanFalse, booleanTrue } from '../constraints/BooleanConstraints'; import { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import { BaseValidator } from './imports'; export class BooleanValidator extends BaseValidator { - public get true(): BooleanValidator { - return this.addConstraint(booleanTrue as IConstraint) as BooleanValidator; + public true(options: ValidatorOptions = this.validatorOptions): BooleanValidator { + return this.addConstraint(booleanTrue(options) as IConstraint) as BooleanValidator; } - public get false(): BooleanValidator { - return this.addConstraint(booleanFalse as IConstraint) as BooleanValidator; + public false(options: ValidatorOptions = this.validatorOptions): BooleanValidator { + return this.addConstraint(booleanFalse(options) as IConstraint) as BooleanValidator; } - public equal(value: R): BooleanValidator { - return (value ? this.true : this.false) as BooleanValidator; + public equal(value: R, options: ValidatorOptions = this.validatorOptions): BooleanValidator { + return (value ? this.true(options) : this.false(options)) as BooleanValidator; } - public notEqual(value: R): BooleanValidator { - return (value ? this.false : this.true) as BooleanValidator; + public notEqual(value: R, options: ValidatorOptions = this.validatorOptions): BooleanValidator { + return (value ? this.false(options) : this.true(options)) as BooleanValidator; } protected handle(value: unknown): Result { return typeof value === 'boolean' // ? Result.ok(value as T) - : Result.err(new ValidationError('s.boolean', 'Expected a boolean primitive', value)); + : Result.err(new ValidationError('s.boolean()', this.validatorOptions.message ?? 'Expected a boolean primitive', value)); } } diff --git a/src/validators/DateValidator.ts b/src/validators/DateValidator.ts index 93c333ca..0d222255 100644 --- a/src/validators/DateValidator.ts +++ b/src/validators/DateValidator.ts @@ -10,50 +10,51 @@ import { } from '../constraints/DateConstraints'; import { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import { BaseValidator } from './imports'; export class DateValidator extends BaseValidator { - public lessThan(date: Date | number | string): this { - return this.addConstraint(dateLessThan(new Date(date))); + public lessThan(date: Date | number | string, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(dateLessThan(new Date(date), options)); } - public lessThanOrEqual(date: Date | number | string): this { - return this.addConstraint(dateLessThanOrEqual(new Date(date))); + public lessThanOrEqual(date: Date | number | string, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(dateLessThanOrEqual(new Date(date), options)); } - public greaterThan(date: Date | number | string): this { - return this.addConstraint(dateGreaterThan(new Date(date))); + public greaterThan(date: Date | number | string, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(dateGreaterThan(new Date(date), options)); } - public greaterThanOrEqual(date: Date | number | string): this { - return this.addConstraint(dateGreaterThanOrEqual(new Date(date))); + public greaterThanOrEqual(date: Date | number | string, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(dateGreaterThanOrEqual(new Date(date), options)); } - public equal(date: Date | number | string): this { + public equal(date: Date | number | string, options: ValidatorOptions = this.validatorOptions): this { const resolved = new Date(date); return Number.isNaN(resolved.getTime()) // - ? this.invalid - : this.addConstraint(dateEqual(resolved)); + ? this.invalid(options) + : this.addConstraint(dateEqual(resolved, options)); } - public notEqual(date: Date | number | string): this { + public notEqual(date: Date | number | string, options: ValidatorOptions = this.validatorOptions): this { const resolved = new Date(date); return Number.isNaN(resolved.getTime()) // - ? this.valid - : this.addConstraint(dateNotEqual(resolved)); + ? this.valid(options) + : this.addConstraint(dateNotEqual(resolved, options)); } - public get valid(): this { - return this.addConstraint(dateValid); + public valid(options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(dateValid(options)); } - public get invalid(): this { - return this.addConstraint(dateInvalid); + public invalid(options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(dateInvalid(options)); } protected handle(value: unknown): Result { return value instanceof Date // ? Result.ok(value) - : Result.err(new ValidationError('s.date', 'Expected a Date', value)); + : Result.err(new ValidationError('s.date()', this.validatorOptions.message ?? 'Expected a Date', value)); } } diff --git a/src/validators/DefaultValidator.ts b/src/validators/DefaultValidator.ts index a39e0f26..098b42cf 100644 --- a/src/validators/DefaultValidator.ts +++ b/src/validators/DefaultValidator.ts @@ -1,5 +1,6 @@ import type { IConstraint } from '../constraints/base/IConstraint'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import type { ValidatorError } from './BaseValidator'; import { BaseValidator } from './imports'; import { getValue } from './util/getValue'; @@ -8,14 +9,23 @@ export class DefaultValidator extends BaseValidator { private readonly validator: BaseValidator; private defaultValue: T | (() => T); - public constructor(validator: BaseValidator, value: T | (() => T), constraints: readonly IConstraint[] = []) { - super(constraints); + public constructor( + validator: BaseValidator, + value: T | (() => T), + validatorOptions: ValidatorOptions = {}, + constraints: readonly IConstraint[] = [] + ) { + super(validatorOptions, constraints); this.validator = validator; this.defaultValue = value; } - public override default(value: Exclude | (() => Exclude)): DefaultValidator> { + public override default( + value: Exclude | (() => Exclude), + options = this.validatorOptions + ): DefaultValidator> { const clone = this.clone() as unknown as DefaultValidator>; + clone.validatorOptions = options; clone.defaultValue = value; return clone; } @@ -27,6 +37,6 @@ export class DefaultValidator extends BaseValidator { } protected override clone(): this { - return Reflect.construct(this.constructor, [this.validator, this.defaultValue, this.constraints]); + return Reflect.construct(this.constructor, [this.validator, this.defaultValue, this.validatorOptions, this.constraints]); } } diff --git a/src/validators/InstanceValidator.ts b/src/validators/InstanceValidator.ts index 63cc32fd..910321e0 100644 --- a/src/validators/InstanceValidator.ts +++ b/src/validators/InstanceValidator.ts @@ -1,24 +1,24 @@ import type { IConstraint } from '../constraints/base/IConstraint'; import { ExpectedValidationError } from '../lib/errors/ExpectedValidationError'; import { Result } from '../lib/Result'; -import type { Constructor } from '../lib/util-types'; +import type { Constructor, ValidatorOptions } from '../lib/util-types'; import { BaseValidator } from './imports'; export class InstanceValidator extends BaseValidator { public readonly expected: Constructor; - public constructor(expected: Constructor, constraints: readonly IConstraint[] = []) { - super(constraints); + public constructor(expected: Constructor, validatorOptions: ValidatorOptions = {}, constraints: readonly IConstraint[] = []) { + super(validatorOptions, constraints); this.expected = expected; } protected handle(value: unknown): Result>> { return value instanceof this.expected // ? Result.ok(value) - : Result.err(new ExpectedValidationError('s.instance(V)', 'Expected', value, this.expected)); + : Result.err(new ExpectedValidationError('s.instance(V)', this.validatorOptions.message ?? 'Expected', value, this.expected)); } protected override clone(): this { - return Reflect.construct(this.constructor, [this.expected, this.constraints]); + return Reflect.construct(this.constructor, [this.expected, this.validatorOptions, this.constraints]); } } diff --git a/src/validators/LazyValidator.ts b/src/validators/LazyValidator.ts index 5c5a12f5..9abb42dc 100644 --- a/src/validators/LazyValidator.ts +++ b/src/validators/LazyValidator.ts @@ -1,17 +1,18 @@ +import type { IConstraint } from '../constraints/base/IConstraint'; import type { Result } from '../lib/Result'; -import type { IConstraint, Unwrap } from '../type-exports'; +import type { Unwrap, ValidatorOptions } from '../lib/util-types'; import { BaseValidator, type ValidatorError } from './imports'; export class LazyValidator, R = Unwrap> extends BaseValidator { private readonly validator: (value: unknown) => T; - public constructor(validator: (value: unknown) => T, constraints: readonly IConstraint[] = []) { - super(constraints); + public constructor(validator: (value: unknown) => T, validatorOptions: ValidatorOptions = {}, constraints: readonly IConstraint[] = []) { + super(validatorOptions, constraints); this.validator = validator; } protected override clone(): this { - return Reflect.construct(this.constructor, [this.validator, this.constraints]); + return Reflect.construct(this.constructor, [this.validator, this.validatorOptions, this.constraints]); } protected handle(values: unknown): Result { diff --git a/src/validators/LiteralValidator.ts b/src/validators/LiteralValidator.ts index e5a6bbcc..000060fe 100644 --- a/src/validators/LiteralValidator.ts +++ b/src/validators/LiteralValidator.ts @@ -1,23 +1,26 @@ import type { IConstraint } from '../constraints/base/IConstraint'; import { ExpectedValidationError } from '../lib/errors/ExpectedValidationError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import { BaseValidator } from './imports'; export class LiteralValidator extends BaseValidator { public readonly expected: T; - public constructor(literal: T, constraints: readonly IConstraint[] = []) { - super(constraints); + public constructor(literal: T, validatorOptions: ValidatorOptions = {}, constraints: readonly IConstraint[] = []) { + super(validatorOptions, constraints); this.expected = literal; } protected handle(value: unknown): Result> { return Object.is(value, this.expected) // ? Result.ok(value as T) - : Result.err(new ExpectedValidationError('s.literal(V)', 'Expected values to be equals', value, this.expected)); + : Result.err( + new ExpectedValidationError('s.literal(V)', this.validatorOptions.message ?? 'Expected values to be equals', value, this.expected) + ); } protected override clone(): this { - return Reflect.construct(this.constructor, [this.expected, this.constraints]); + return Reflect.construct(this.constructor, [this.expected, this.validatorOptions, this.constraints]); } } diff --git a/src/validators/MapValidator.ts b/src/validators/MapValidator.ts index f926ebfe..4264794e 100644 --- a/src/validators/MapValidator.ts +++ b/src/validators/MapValidator.ts @@ -3,25 +3,31 @@ import type { BaseError } from '../lib/errors/BaseError'; import { CombinedPropertyError } from '../lib/errors/CombinedPropertyError'; import { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import { BaseValidator } from './imports'; export class MapValidator extends BaseValidator> { private readonly keyValidator: BaseValidator; private readonly valueValidator: BaseValidator; - public constructor(keyValidator: BaseValidator, valueValidator: BaseValidator, constraints: readonly IConstraint>[] = []) { - super(constraints); + public constructor( + keyValidator: BaseValidator, + valueValidator: BaseValidator, + validatorOptions: ValidatorOptions = {}, + constraints: readonly IConstraint>[] = [] + ) { + super(validatorOptions, constraints); this.keyValidator = keyValidator; this.valueValidator = valueValidator; } protected override clone(): this { - return Reflect.construct(this.constructor, [this.keyValidator, this.valueValidator, this.constraints]); + return Reflect.construct(this.constructor, [this.keyValidator, this.valueValidator, this.validatorOptions, this.constraints]); } protected handle(value: unknown): Result, ValidationError | CombinedPropertyError> { if (!(value instanceof Map)) { - return Result.err(new ValidationError('s.map(K, V)', 'Expected a map', value)); + return Result.err(new ValidationError('s.map(K, V)', this.validatorOptions.message ?? 'Expected a map', value)); } if (!this.shouldRunConstraints) { @@ -42,6 +48,6 @@ export class MapValidator extends BaseValidator> { return errors.length === 0 // ? Result.ok(transformed) - : Result.err(new CombinedPropertyError(errors)); + : Result.err(new CombinedPropertyError(errors, this.validatorOptions)); } } diff --git a/src/validators/NativeEnumValidator.ts b/src/validators/NativeEnumValidator.ts index bfd6860b..f26acf98 100644 --- a/src/validators/NativeEnumValidator.ts +++ b/src/validators/NativeEnumValidator.ts @@ -1,6 +1,7 @@ import { UnknownEnumValueError } from '../lib/errors/UnknownEnumValueError'; import { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import { BaseValidator } from './imports'; export class NativeEnumValidator extends BaseValidator { @@ -9,8 +10,8 @@ export class NativeEnumValidator extends BaseValidator private readonly enumKeys: string[]; private readonly enumMapping = new Map(); - public constructor(enumShape: T) { - super(); + public constructor(enumShape: T, validatorOptions: ValidatorOptions = {}) { + super(validatorOptions); this.enumShape = enumShape; this.enumKeys = Object.keys(enumShape).filter((key) => { @@ -35,11 +36,15 @@ export class NativeEnumValidator extends BaseValidator if (typeOfValue === 'number') { if (!this.hasNumericElements) { - return Result.err(new ValidationError('s.nativeEnum(T)', 'Expected the value to be a string', value)); + return Result.err( + new ValidationError('s.nativeEnum(T)', this.validatorOptions.message ?? 'Expected the value to be a string', value) + ); } } else if (typeOfValue !== 'string') { // typeOfValue !== 'number' is implied here - return Result.err(new ValidationError('s.nativeEnum(T)', 'Expected the value to be a string or number', value)); + return Result.err( + new ValidationError('s.nativeEnum(T)', this.validatorOptions.message ?? 'Expected the value to be a string or number', value) + ); } const casted = value as string | number; @@ -47,12 +52,12 @@ export class NativeEnumValidator extends BaseValidator const possibleEnumValue = this.enumMapping.get(casted); return typeof possibleEnumValue === 'undefined' - ? Result.err(new UnknownEnumValueError(casted, this.enumKeys, this.enumMapping)) + ? Result.err(new UnknownEnumValueError(casted, this.enumKeys, this.enumMapping, this.validatorOptions)) : Result.ok(possibleEnumValue); } protected override clone(): this { - return Reflect.construct(this.constructor, [this.enumShape]); + return Reflect.construct(this.constructor, [this.enumShape, this.validatorOptions]); } } diff --git a/src/validators/NeverValidator.ts b/src/validators/NeverValidator.ts index 00c8a11a..7f69d7b6 100644 --- a/src/validators/NeverValidator.ts +++ b/src/validators/NeverValidator.ts @@ -4,6 +4,6 @@ import { BaseValidator } from './imports'; export class NeverValidator extends BaseValidator { protected handle(value: unknown): Result { - return Result.err(new ValidationError('s.never', 'Expected a value to not be passed', value)); + return Result.err(new ValidationError('s.never()', this.validatorOptions.message ?? 'Expected a value to not be passed', value)); } } diff --git a/src/validators/NullishValidator.ts b/src/validators/NullishValidator.ts index d46e7d85..d90de046 100644 --- a/src/validators/NullishValidator.ts +++ b/src/validators/NullishValidator.ts @@ -6,6 +6,6 @@ export class NullishValidator extends BaseValidator { protected handle(value: unknown): Result { return value === undefined || value === null // ? Result.ok(value) - : Result.err(new ValidationError('s.nullish', 'Expected undefined or null', value)); + : Result.err(new ValidationError('s.nullish()', this.validatorOptions.message ?? 'Expected undefined or null', value)); } } diff --git a/src/validators/NumberValidator.ts b/src/validators/NumberValidator.ts index 9d25d8ff..0f9b2c14 100644 --- a/src/validators/NumberValidator.ts +++ b/src/validators/NumberValidator.ts @@ -15,92 +15,93 @@ import { } from '../constraints/NumberConstraints'; import { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import { BaseValidator } from './imports'; export class NumberValidator extends BaseValidator { - public lessThan(number: number): this { - return this.addConstraint(numberLessThan(number) as IConstraint); + public lessThan(number: number, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(numberLessThan(number, options) as IConstraint); } - public lessThanOrEqual(number: number): this { - return this.addConstraint(numberLessThanOrEqual(number) as IConstraint); + public lessThanOrEqual(number: number, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(numberLessThanOrEqual(number, options) as IConstraint); } - public greaterThan(number: number): this { - return this.addConstraint(numberGreaterThan(number) as IConstraint); + public greaterThan(number: number, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(numberGreaterThan(number, options) as IConstraint); } - public greaterThanOrEqual(number: number): this { - return this.addConstraint(numberGreaterThanOrEqual(number) as IConstraint); + public greaterThanOrEqual(number: number, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(numberGreaterThanOrEqual(number, options) as IConstraint); } - public equal(number: N): NumberValidator { + public equal(number: N, options: ValidatorOptions = this.validatorOptions): NumberValidator { return Number.isNaN(number) // - ? (this.addConstraint(numberNaN as IConstraint) as unknown as NumberValidator) - : (this.addConstraint(numberEqual(number) as IConstraint) as unknown as NumberValidator); + ? (this.addConstraint(numberNaN(options) as IConstraint) as unknown as NumberValidator) + : (this.addConstraint(numberEqual(number, options) as IConstraint) as unknown as NumberValidator); } - public notEqual(number: number): this { + public notEqual(number: number, options: ValidatorOptions = this.validatorOptions): this { return Number.isNaN(number) // - ? this.addConstraint(numberNotNaN as IConstraint) - : this.addConstraint(numberNotEqual(number) as IConstraint); + ? this.addConstraint(numberNotNaN(options) as IConstraint) + : this.addConstraint(numberNotEqual(number, options) as IConstraint); } - public get int(): this { - return this.addConstraint(numberInt as IConstraint); + public int(options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(numberInt(options) as IConstraint); } - public get safeInt(): this { - return this.addConstraint(numberSafeInt as IConstraint); + public safeInt(options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(numberSafeInt(options) as IConstraint); } - public get finite(): this { - return this.addConstraint(numberFinite as IConstraint); + public finite(options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(numberFinite(options) as IConstraint); } - public get positive(): this { - return this.greaterThanOrEqual(0); + public positive(options: ValidatorOptions = this.validatorOptions): this { + return this.greaterThanOrEqual(0, options); } - public get negative(): this { - return this.lessThan(0); + public negative(options: ValidatorOptions = this.validatorOptions): this { + return this.lessThan(0, options); } - public divisibleBy(divider: number): this { - return this.addConstraint(numberDivisibleBy(divider) as IConstraint); + public divisibleBy(divider: number, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(numberDivisibleBy(divider, options) as IConstraint); } - public get abs(): this { - return this.transform(Math.abs as (value: number) => T); + public abs(options: ValidatorOptions = this.validatorOptions): this { + return this.transform(Math.abs as (value: number) => T, options); } - public get sign(): this { - return this.transform(Math.sign as (value: number) => T); + public sign(options: ValidatorOptions = this.validatorOptions): this { + return this.transform(Math.sign as (value: number) => T, options); } - public get trunc(): this { - return this.transform(Math.trunc as (value: number) => T); + public trunc(options: ValidatorOptions = this.validatorOptions): this { + return this.transform(Math.trunc as (value: number) => T, options); } - public get floor(): this { - return this.transform(Math.floor as (value: number) => T); + public floor(options: ValidatorOptions = this.validatorOptions): this { + return this.transform(Math.floor as (value: number) => T, options); } - public get fround(): this { - return this.transform(Math.fround as (value: number) => T); + public fround(options: ValidatorOptions = this.validatorOptions): this { + return this.transform(Math.fround as (value: number) => T, options); } - public get round(): this { - return this.transform(Math.round as (value: number) => T); + public round(options: ValidatorOptions = this.validatorOptions): this { + return this.transform(Math.round as (value: number) => T, options); } - public get ceil(): this { - return this.transform(Math.ceil as (value: number) => T); + public ceil(options: ValidatorOptions = this.validatorOptions): this { + return this.transform(Math.ceil as (value: number) => T, options); } protected handle(value: unknown): Result { return typeof value === 'number' // ? Result.ok(value as T) - : Result.err(new ValidationError('s.number', 'Expected a number primitive', value)); + : Result.err(new ValidationError('s.number()', this.validatorOptions.message ?? 'Expected a number primitive', value)); } } diff --git a/src/validators/ObjectValidator.ts b/src/validators/ObjectValidator.ts index 21d07b4d..12750305 100644 --- a/src/validators/ObjectValidator.ts +++ b/src/validators/ObjectValidator.ts @@ -5,7 +5,7 @@ import { MissingPropertyError } from '../lib/errors/MissingPropertyError'; import { UnknownPropertyError } from '../lib/errors/UnknownPropertyError'; import { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; -import type { MappedObjectValidator, UndefinedToOptional } from '../lib/util-types'; +import type { MappedObjectValidator, UndefinedToOptional, ValidatorOptions } from '../lib/util-types'; import { BaseValidator } from './BaseValidator'; import { DefaultValidator } from './DefaultValidator'; import { LiteralValidator } from './LiteralValidator'; @@ -25,9 +25,10 @@ export class ObjectValidator> exten public constructor( shape: MappedObjectValidator, strategy: ObjectValidatorStrategy = ObjectValidatorStrategy.Ignore, + validatorOptions: ValidatorOptions = {}, constraints: readonly IConstraint[] = [] ) { - super(constraints); + super(validatorOptions, constraints); this.shape = shape; this.strategy = strategy; @@ -81,65 +82,80 @@ export class ObjectValidator> exten } } - public get strict(): this { - return Reflect.construct(this.constructor, [this.shape, ObjectValidatorStrategy.Strict, this.constraints]); + public strict(options: ValidatorOptions = this.validatorOptions): this { + return Reflect.construct(this.constructor, [this.shape, ObjectValidatorStrategy.Strict, options, this.constraints]); } - public get ignore(): this { - return Reflect.construct(this.constructor, [this.shape, ObjectValidatorStrategy.Ignore, this.constraints]); + public ignore(options: ValidatorOptions = this.validatorOptions): this { + return Reflect.construct(this.constructor, [this.shape, ObjectValidatorStrategy.Ignore, options, this.constraints]); } - public get passthrough(): this { - return Reflect.construct(this.constructor, [this.shape, ObjectValidatorStrategy.Passthrough, this.constraints]); + public passthrough(options: ValidatorOptions = this.validatorOptions): this { + return Reflect.construct(this.constructor, [this.shape, ObjectValidatorStrategy.Passthrough, options, this.constraints]); } - public get partial(): ObjectValidator<{ [Key in keyof I]?: I[Key] }> { - const shape = Object.fromEntries(this.keys.map((key) => [key, this.shape[key as unknown as keyof typeof this.shape].optional])); - return Reflect.construct(this.constructor, [shape, this.strategy, this.constraints]); + public partial(options: ValidatorOptions = this.validatorOptions): ObjectValidator<{ [Key in keyof I]?: I[Key] }> { + const shape = Object.fromEntries(this.keys.map((key) => [key, this.shape[key as unknown as keyof typeof this.shape].optional(options)])); + return Reflect.construct(this.constructor, [shape, this.strategy, options, this.constraints]); } - public get required(): ObjectValidator<{ [Key in keyof I]-?: I[Key] }> { + public required(options: ValidatorOptions = this.validatorOptions): ObjectValidator<{ [Key in keyof I]-?: I[Key] }> { const shape = Object.fromEntries( this.keys.map((key) => { let validator = this.shape[key as unknown as keyof typeof this.shape]; - if (validator instanceof UnionValidator) validator = validator.required; + if (validator instanceof UnionValidator) validator = validator.required(options); return [key, validator]; }) ); - return Reflect.construct(this.constructor, [shape, this.strategy, this.constraints]); + return Reflect.construct(this.constructor, [shape, this.strategy, options, this.constraints]); } - public extend(schema: ObjectValidator | MappedObjectValidator): ObjectValidator { + public extend( + schema: ObjectValidator | MappedObjectValidator, + options: ValidatorOptions = this.validatorOptions + ): ObjectValidator { const shape = { ...this.shape, ...(schema instanceof ObjectValidator ? schema.shape : schema) }; - return Reflect.construct(this.constructor, [shape, this.strategy, this.constraints]); + return Reflect.construct(this.constructor, [shape, this.strategy, options, this.constraints]); } - public pick(keys: readonly K[]): ObjectValidator<{ [Key in keyof Pick]: I[Key] }> { + public pick( + keys: readonly K[], + options: ValidatorOptions = this.validatorOptions + ): ObjectValidator<{ [Key in keyof Pick]: I[Key] }> { const shape = Object.fromEntries( keys.filter((key) => this.keys.includes(key)).map((key) => [key, this.shape[key as unknown as keyof typeof this.shape]]) ); - return Reflect.construct(this.constructor, [shape, this.strategy, this.constraints]); + return Reflect.construct(this.constructor, [shape, this.strategy, options, this.constraints]); } - public omit(keys: readonly K[]): ObjectValidator<{ [Key in keyof Omit]: I[Key] }> { + public omit( + keys: readonly K[], + options: ValidatorOptions = this.validatorOptions + ): ObjectValidator<{ [Key in keyof Omit]: I[Key] }> { const shape = Object.fromEntries( this.keys.filter((key) => !keys.includes(key as any)).map((key) => [key, this.shape[key as unknown as keyof typeof this.shape]]) ); - return Reflect.construct(this.constructor, [shape, this.strategy, this.constraints]); + return Reflect.construct(this.constructor, [shape, this.strategy, options, this.constraints]); } protected override handle(value: unknown): Result { const typeOfValue = typeof value; if (typeOfValue !== 'object') { - return Result.err(new ValidationError('s.object(T)', `Expected the value to be an object, but received ${typeOfValue} instead`, value)); + return Result.err( + new ValidationError( + 's.object(T)', + this.validatorOptions.message ?? `Expected the value to be an object, but received ${typeOfValue} instead`, + value + ) + ); } if (value === null) { - return Result.err(new ValidationError('s.object(T)', 'Expected the value to not be null', value)); + return Result.err(new ValidationError('s.object(T)', this.validatorOptions.message ?? 'Expected the value to not be null', value)); } if (Array.isArray(value)) { - return Result.err(new ValidationError('s.object(T)', 'Expected the value to not be an array', value)); + return Result.err(new ValidationError('s.object(T)', this.validatorOptions.message ?? 'Expected the value to not be an array', value)); } if (!this.shouldRunConstraints) { @@ -154,7 +170,7 @@ export class ObjectValidator> exten } protected override clone(): this { - return Reflect.construct(this.constructor, [this.shape, this.strategy, this.constraints]); + return Reflect.construct(this.constructor, [this.shape, this.strategy, this.validatorOptions, this.constraints]); } private handleIgnoreStrategy(value: object): Result { @@ -177,7 +193,7 @@ export class ObjectValidator> exten if (inputEntries.delete(key)) { runPredicate(key, predicate); } else { - errors.push([key, new MissingPropertyError(key)]); + errors.push([key, new MissingPropertyError(key, this.validatorOptions)]); } } @@ -191,7 +207,7 @@ export class ObjectValidator> exten if (inputEntries.size === 0) { return errors.length === 0 // ? Result.ok(finalObject) - : Result.err(new CombinedPropertyError(errors)); + : Result.err(new CombinedPropertyError(errors, this.validatorOptions)); } // In the event the remaining keys to check are less than the number of possible undefined keys, we check those @@ -216,7 +232,7 @@ export class ObjectValidator> exten return errors.length === 0 // ? Result.ok(finalObject) - : Result.err(new CombinedPropertyError(errors)); + : Result.err(new CombinedPropertyError(errors, this.validatorOptions)); } private handleStrictStrategy(value: object): Result { @@ -239,7 +255,7 @@ export class ObjectValidator> exten if (inputEntries.delete(key)) { runPredicate(key, predicate); } else { - errors.push([key, new MissingPropertyError(key)]); + errors.push([key, new MissingPropertyError(key, this.validatorOptions)]); } } @@ -263,13 +279,13 @@ export class ObjectValidator> exten if (inputEntries.size !== 0) { for (const [key, value] of inputEntries.entries()) { - errors.push([key, new UnknownPropertyError(key, value)]); + errors.push([key, new UnknownPropertyError(key, value, this.validatorOptions)]); } } return errors.length === 0 // ? Result.ok(finalResult) - : Result.err(new CombinedPropertyError(errors)); + : Result.err(new CombinedPropertyError(errors, this.validatorOptions)); } private handlePassthroughStrategy(value: object): Result { diff --git a/src/validators/RecordValidator.ts b/src/validators/RecordValidator.ts index 6b16cb41..69a59c9e 100644 --- a/src/validators/RecordValidator.ts +++ b/src/validators/RecordValidator.ts @@ -3,31 +3,36 @@ import type { BaseError } from '../lib/errors/BaseError'; import { CombinedPropertyError } from '../lib/errors/CombinedPropertyError'; import { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import { BaseValidator } from './imports'; export class RecordValidator extends BaseValidator> { private readonly validator: BaseValidator; - public constructor(validator: BaseValidator, constraints: readonly IConstraint>[] = []) { - super(constraints); + public constructor( + validator: BaseValidator, + validatorOptions: ValidatorOptions = {}, + constraints: readonly IConstraint>[] = [] + ) { + super(validatorOptions, constraints); this.validator = validator; } protected override clone(): this { - return Reflect.construct(this.constructor, [this.validator, this.constraints]); + return Reflect.construct(this.constructor, [this.validator, this.validatorOptions, this.constraints]); } protected handle(value: unknown): Result, ValidationError | CombinedPropertyError> { if (typeof value !== 'object') { - return Result.err(new ValidationError('s.record(T)', 'Expected an object', value)); + return Result.err(new ValidationError('s.record(T)', this.validatorOptions.message ?? 'Expected an object', value)); } if (value === null) { - return Result.err(new ValidationError('s.record(T)', 'Expected the value to not be null', value)); + return Result.err(new ValidationError('s.record(T)', this.validatorOptions.message ?? 'Expected the value to not be null', value)); } if (Array.isArray(value)) { - return Result.err(new ValidationError('s.record(T)', 'Expected the value to not be an array', value)); + return Result.err(new ValidationError('s.record(T)', this.validatorOptions.message ?? 'Expected the value to not be an array', value)); } if (!this.shouldRunConstraints) { @@ -45,6 +50,6 @@ export class RecordValidator extends BaseValidator> { return errors.length === 0 // ? Result.ok(transformed) - : Result.err(new CombinedPropertyError(errors)); + : Result.err(new CombinedPropertyError(errors, this.validatorOptions)); } } diff --git a/src/validators/SetValidator.ts b/src/validators/SetValidator.ts index 53157bd1..af15908f 100644 --- a/src/validators/SetValidator.ts +++ b/src/validators/SetValidator.ts @@ -3,23 +3,24 @@ import type { BaseError } from '../lib/errors/BaseError'; import { CombinedError } from '../lib/errors/CombinedError'; import { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import { BaseValidator } from './imports'; export class SetValidator extends BaseValidator> { private readonly validator: BaseValidator; - public constructor(validator: BaseValidator, constraints: readonly IConstraint>[] = []) { - super(constraints); + public constructor(validator: BaseValidator, validatorOptions?: ValidatorOptions, constraints: readonly IConstraint>[] = []) { + super(validatorOptions, constraints); this.validator = validator; } protected override clone(): this { - return Reflect.construct(this.constructor, [this.validator, this.constraints]); + return Reflect.construct(this.constructor, [this.validator, this.validatorOptions, this.constraints]); } protected handle(values: unknown): Result, ValidationError | CombinedError> { if (!(values instanceof Set)) { - return Result.err(new ValidationError('s.set(T)', 'Expected a set', values)); + return Result.err(new ValidationError('s.set(T)', this.validatorOptions.message ?? 'Expected a set', values)); } if (!this.shouldRunConstraints) { @@ -37,6 +38,6 @@ export class SetValidator extends BaseValidator> { return errors.length === 0 // ? Result.ok(transformed) - : Result.err(new CombinedError(errors)); + : Result.err(new CombinedError(errors, this.validatorOptions)); } } diff --git a/src/validators/StringValidator.ts b/src/validators/StringValidator.ts index 5a5edf78..117c8085 100644 --- a/src/validators/StringValidator.ts +++ b/src/validators/StringValidator.ts @@ -18,72 +18,97 @@ import { } from '../constraints/StringConstraints'; import { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import { BaseValidator } from './imports'; export class StringValidator extends BaseValidator { - public lengthLessThan(length: number): this { - return this.addConstraint(stringLengthLessThan(length) as IConstraint); + public lengthLessThan(length: number, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(stringLengthLessThan(length, options) as IConstraint); } - public lengthLessThanOrEqual(length: number): this { - return this.addConstraint(stringLengthLessThanOrEqual(length) as IConstraint); + public lengthLessThanOrEqual(length: number, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(stringLengthLessThanOrEqual(length, options) as IConstraint); } - public lengthGreaterThan(length: number): this { - return this.addConstraint(stringLengthGreaterThan(length) as IConstraint); + public lengthGreaterThan(length: number, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(stringLengthGreaterThan(length, options) as IConstraint); } - public lengthGreaterThanOrEqual(length: number): this { - return this.addConstraint(stringLengthGreaterThanOrEqual(length) as IConstraint); + public lengthGreaterThanOrEqual(length: number, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(stringLengthGreaterThanOrEqual(length, options) as IConstraint); } - public lengthEqual(length: number): this { - return this.addConstraint(stringLengthEqual(length) as IConstraint); + public lengthEqual(length: number, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(stringLengthEqual(length, options) as IConstraint); } - public lengthNotEqual(length: number): this { - return this.addConstraint(stringLengthNotEqual(length) as IConstraint); + public lengthNotEqual(length: number, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(stringLengthNotEqual(length, options) as IConstraint); } - public get email(): this { - return this.addConstraint(stringEmail() as IConstraint); + public email(options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(stringEmail(options) as IConstraint); } - public url(options?: UrlOptions): this { - return this.addConstraint(stringUrl(options) as IConstraint); + public url(validatorOptions?: ValidatorOptions): this; + public url(options?: UrlOptions, validatorOptions?: ValidatorOptions): this; + public url(options?: UrlOptions | ValidatorOptions, validatorOptions: ValidatorOptions = this.validatorOptions): this { + const urlOptions = this.isUrlOptions(options); + + if (urlOptions) { + return this.addConstraint(stringUrl(options, validatorOptions) as IConstraint); + } + + return this.addConstraint(stringUrl(undefined, validatorOptions) as IConstraint); } - public uuid(options?: StringUuidOptions): this { - return this.addConstraint(stringUuid(options) as IConstraint); + public uuid(validatorOptions?: ValidatorOptions): this; + public uuid(options?: StringUuidOptions, validatorOptions?: ValidatorOptions): this; + public uuid(options?: StringUuidOptions | ValidatorOptions, validatorOptions: ValidatorOptions = this.validatorOptions): this { + const stringUuidOptions = this.isStringUuidOptions(options); + + if (stringUuidOptions) { + return this.addConstraint(stringUuid(options, validatorOptions) as IConstraint); + } + + return this.addConstraint(stringUuid(undefined, validatorOptions) as IConstraint); } - public regex(regex: RegExp): this { - return this.addConstraint(stringRegex(regex) as IConstraint); + public regex(regex: RegExp, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(stringRegex(regex, options) as IConstraint); } - public get date() { - return this.addConstraint(stringDate() as IConstraint); + public date(options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(stringDate(options) as IConstraint); } - public get ipv4(): this { - return this.ip(4); + public ipv4(options: ValidatorOptions = this.validatorOptions): this { + return this.ip(4, options); } - public get ipv6(): this { - return this.ip(6); + public ipv6(options: ValidatorOptions = this.validatorOptions): this { + return this.ip(6, options); } - public ip(version?: 4 | 6): this { - return this.addConstraint(stringIp(version) as IConstraint); + public ip(version?: 4 | 6, options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(stringIp(version, options) as IConstraint); } - public phone(): this { - return this.addConstraint(stringPhone() as IConstraint); + public phone(options: ValidatorOptions = this.validatorOptions): this { + return this.addConstraint(stringPhone(options) as IConstraint); } protected handle(value: unknown): Result { return typeof value === 'string' // ? Result.ok(value as T) - : Result.err(new ValidationError('s.string', 'Expected a string primitive', value)); + : Result.err(new ValidationError('s.string()', this.validatorOptions.message ?? 'Expected a string primitive', value)); + } + + private isUrlOptions(options?: UrlOptions | ValidatorOptions): options is UrlOptions { + return (options as ValidatorOptions)?.message === undefined; + } + + private isStringUuidOptions(options?: StringUuidOptions | ValidatorOptions): options is StringUuidOptions { + return (options as ValidatorOptions)?.message === undefined; } } diff --git a/src/validators/TupleValidator.ts b/src/validators/TupleValidator.ts index 63aa25f6..e588a331 100644 --- a/src/validators/TupleValidator.ts +++ b/src/validators/TupleValidator.ts @@ -3,27 +3,34 @@ import type { BaseError } from '../lib/errors/BaseError'; import { CombinedPropertyError } from '../lib/errors/CombinedPropertyError'; import { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import { BaseValidator } from './imports'; export class TupleValidator extends BaseValidator<[...T]> { private readonly validators: BaseValidator<[...T]>[] = []; - public constructor(validators: BaseValidator<[...T]>[], constraints: readonly IConstraint<[...T]>[] = []) { - super(constraints); + public constructor( + validators: BaseValidator<[...T]>[], + validatorOptions: ValidatorOptions = {}, + constraints: readonly IConstraint<[...T]>[] = [] + ) { + super(validatorOptions, constraints); this.validators = validators; } protected override clone(): this { - return Reflect.construct(this.constructor, [this.validators, this.constraints]); + return Reflect.construct(this.constructor, [this.validators, this.validatorOptions, this.constraints]); } protected handle(values: unknown): Result<[...T], ValidationError | CombinedPropertyError> { if (!Array.isArray(values)) { - return Result.err(new ValidationError('s.tuple(T)', 'Expected an array', values)); + return Result.err(new ValidationError('s.tuple(T)', this.validatorOptions.message ?? 'Expected an array', values)); } if (values.length !== this.validators.length) { - return Result.err(new ValidationError('s.tuple(T)', `Expected an array of length ${this.validators.length}`, values)); + return Result.err( + new ValidationError('s.tuple(T)', this.validatorOptions.message ?? `Expected an array of length ${this.validators.length}`, values) + ); } if (!this.shouldRunConstraints) { @@ -41,6 +48,6 @@ export class TupleValidator extends BaseValidator<[...T]> { return errors.length === 0 // ? Result.ok(transformed) - : Result.err(new CombinedPropertyError(errors)); + : Result.err(new CombinedPropertyError(errors, this.validatorOptions)); } } diff --git a/src/validators/TypedArrayValidator.ts b/src/validators/TypedArrayValidator.ts index a06bdafa..8144687e 100644 --- a/src/validators/TypedArrayValidator.ts +++ b/src/validators/TypedArrayValidator.ts @@ -23,95 +23,96 @@ import { aOrAn } from '../constraints/util/common/vowels'; import { TypedArrays, type TypedArray, type TypedArrayName } from '../constraints/util/typedArray'; import { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import { BaseValidator } from './imports'; export class TypedArrayValidator extends BaseValidator { private readonly type: TypedArrayName; - public constructor(type: TypedArrayName, constraints: readonly IConstraint[] = []) { - super(constraints); + public constructor(type: TypedArrayName, validatorOptions: ValidatorOptions = {}, constraints: readonly IConstraint[] = []) { + super(validatorOptions, constraints); this.type = type; } - public byteLengthLessThan(length: number) { - return this.addConstraint(typedArrayByteLengthLessThan(length)); + public byteLengthLessThan(length: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayByteLengthLessThan(length, options)); } - public byteLengthLessThanOrEqual(length: number) { - return this.addConstraint(typedArrayByteLengthLessThanOrEqual(length)); + public byteLengthLessThanOrEqual(length: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayByteLengthLessThanOrEqual(length, options)); } - public byteLengthGreaterThan(length: number) { - return this.addConstraint(typedArrayByteLengthGreaterThan(length)); + public byteLengthGreaterThan(length: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayByteLengthGreaterThan(length, options)); } - public byteLengthGreaterThanOrEqual(length: number) { - return this.addConstraint(typedArrayByteLengthGreaterThanOrEqual(length)); + public byteLengthGreaterThanOrEqual(length: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayByteLengthGreaterThanOrEqual(length, options)); } - public byteLengthEqual(length: number) { - return this.addConstraint(typedArrayByteLengthEqual(length)); + public byteLengthEqual(length: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayByteLengthEqual(length, options)); } - public byteLengthNotEqual(length: number) { - return this.addConstraint(typedArrayByteLengthNotEqual(length)); + public byteLengthNotEqual(length: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayByteLengthNotEqual(length, options)); } - public byteLengthRange(start: number, endBefore: number) { - return this.addConstraint(typedArrayByteLengthRange(start, endBefore)); + public byteLengthRange(start: number, endBefore: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayByteLengthRange(start, endBefore, options)); } - public byteLengthRangeInclusive(startAt: number, endAt: number) { - return this.addConstraint(typedArrayByteLengthRangeInclusive(startAt, endAt) as IConstraint); + public byteLengthRangeInclusive(startAt: number, endAt: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayByteLengthRangeInclusive(startAt, endAt, options) as IConstraint); } - public byteLengthRangeExclusive(startAfter: number, endBefore: number) { - return this.addConstraint(typedArrayByteLengthRangeExclusive(startAfter, endBefore)); + public byteLengthRangeExclusive(startAfter: number, endBefore: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayByteLengthRangeExclusive(startAfter, endBefore, options)); } - public lengthLessThan(length: number) { - return this.addConstraint(typedArrayLengthLessThan(length)); + public lengthLessThan(length: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayLengthLessThan(length, options)); } - public lengthLessThanOrEqual(length: number) { - return this.addConstraint(typedArrayLengthLessThanOrEqual(length)); + public lengthLessThanOrEqual(length: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayLengthLessThanOrEqual(length, options)); } - public lengthGreaterThan(length: number) { - return this.addConstraint(typedArrayLengthGreaterThan(length)); + public lengthGreaterThan(length: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayLengthGreaterThan(length, options)); } - public lengthGreaterThanOrEqual(length: number) { - return this.addConstraint(typedArrayLengthGreaterThanOrEqual(length)); + public lengthGreaterThanOrEqual(length: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayLengthGreaterThanOrEqual(length, options)); } - public lengthEqual(length: number) { - return this.addConstraint(typedArrayLengthEqual(length)); + public lengthEqual(length: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayLengthEqual(length, options)); } - public lengthNotEqual(length: number) { - return this.addConstraint(typedArrayLengthNotEqual(length)); + public lengthNotEqual(length: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayLengthNotEqual(length, options)); } - public lengthRange(start: number, endBefore: number) { - return this.addConstraint(typedArrayLengthRange(start, endBefore)); + public lengthRange(start: number, endBefore: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayLengthRange(start, endBefore, options)); } - public lengthRangeInclusive(startAt: number, endAt: number) { - return this.addConstraint(typedArrayLengthRangeInclusive(startAt, endAt)); + public lengthRangeInclusive(startAt: number, endAt: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayLengthRangeInclusive(startAt, endAt, options)); } - public lengthRangeExclusive(startAfter: number, endBefore: number) { - return this.addConstraint(typedArrayLengthRangeExclusive(startAfter, endBefore)); + public lengthRangeExclusive(startAfter: number, endBefore: number, options: ValidatorOptions = this.validatorOptions) { + return this.addConstraint(typedArrayLengthRangeExclusive(startAfter, endBefore, options)); } protected override clone(): this { - return Reflect.construct(this.constructor, [this.type, this.constraints]); + return Reflect.construct(this.constructor, [this.type, this.validatorOptions, this.constraints]); } protected handle(value: unknown): Result { return TypedArrays[this.type](value) ? Result.ok(value as T) - : Result.err(new ValidationError('s.typedArray', `Expected ${aOrAn(this.type)}`, value)); + : Result.err(new ValidationError('s.typedArray()', this.validatorOptions.message ?? `Expected ${aOrAn(this.type)}`, value)); } } diff --git a/src/validators/UnionValidator.ts b/src/validators/UnionValidator.ts index a938dc69..cdef0996 100644 --- a/src/validators/UnionValidator.ts +++ b/src/validators/UnionValidator.ts @@ -3,18 +3,20 @@ import type { BaseError } from '../lib/errors/BaseError'; import { CombinedError } from '../lib/errors/CombinedError'; import type { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; +import type { ValidatorOptions } from '../lib/util-types'; import { BaseValidator, LiteralValidator, NullishValidator } from './imports'; export class UnionValidator extends BaseValidator { private validators: readonly BaseValidator[]; - public constructor(validators: readonly BaseValidator[], constraints: readonly IConstraint[] = []) { - super(constraints); + public constructor(validators: readonly BaseValidator[], validatorOptions?: ValidatorOptions, constraints: readonly IConstraint[] = []) { + super(validatorOptions, constraints); this.validators = validators; } - public override get optional(): UnionValidator { - if (this.validators.length === 0) return new UnionValidator([new LiteralValidator(undefined)], this.constraints); + public override optional(options: ValidatorOptions = this.validatorOptions): UnionValidator { + if (this.validators.length === 0) + return new UnionValidator([new LiteralValidator(undefined, options)], this.validatorOptions, this.constraints); const [validator] = this.validators; if (validator instanceof LiteralValidator) { @@ -24,7 +26,8 @@ export class UnionValidator extends BaseValidator { // If it's nullable, convert the nullable validator into a nullish validator to optimize `null | undefined`: if (validator.expected === null) { return new UnionValidator( - [new NullishValidator(), ...this.validators.slice(1)], + [new NullishValidator(options), ...this.validators.slice(1)], + this.validatorOptions, this.constraints ) as UnionValidator; } @@ -33,26 +36,34 @@ export class UnionValidator extends BaseValidator { return this.clone(); } - return new UnionValidator([new LiteralValidator(undefined), ...this.validators]); + return new UnionValidator([new LiteralValidator(undefined, options), ...this.validators], this.validatorOptions); } - public get required(): UnionValidator> { + public required(options: ValidatorOptions = this.validatorOptions): UnionValidator> { type RequiredValidator = UnionValidator>; if (this.validators.length === 0) return this.clone() as unknown as RequiredValidator; const [validator] = this.validators; if (validator instanceof LiteralValidator) { - if (validator.expected === undefined) return new UnionValidator(this.validators.slice(1), this.constraints) as RequiredValidator; + if (validator.expected === undefined) { + return new UnionValidator(this.validators.slice(1), this.validatorOptions, this.constraints) as RequiredValidator; + } } else if (validator instanceof NullishValidator) { - return new UnionValidator([new LiteralValidator(null), ...this.validators.slice(1)], this.constraints) as RequiredValidator; + return new UnionValidator( + [new LiteralValidator(null, options), ...this.validators.slice(1)], + this.validatorOptions, + this.constraints + ) as RequiredValidator; } return this.clone() as unknown as RequiredValidator; } - public override get nullable(): UnionValidator { - if (this.validators.length === 0) return new UnionValidator([new LiteralValidator(null)], this.constraints); + public override nullable(options: ValidatorOptions = this.validatorOptions): UnionValidator { + if (this.validators.length === 0) { + return new UnionValidator([new LiteralValidator(null, options)], this.validatorOptions, this.constraints); + } const [validator] = this.validators; if (validator instanceof LiteralValidator) { @@ -62,7 +73,8 @@ export class UnionValidator extends BaseValidator { // If it's optional, convert the optional validator into a nullish validator to optimize `null | undefined`: if (validator.expected === undefined) { return new UnionValidator( - [new NullishValidator(), ...this.validators.slice(1)], + [new NullishValidator(options), ...this.validators.slice(1)], + this.validatorOptions, this.constraints ) as UnionValidator; } @@ -71,32 +83,38 @@ export class UnionValidator extends BaseValidator { return this.clone(); } - return new UnionValidator([new LiteralValidator(null), ...this.validators]); + return new UnionValidator([new LiteralValidator(null, options), ...this.validators], this.validatorOptions); } - public override get nullish(): UnionValidator { - if (this.validators.length === 0) return new UnionValidator([new NullishValidator()], this.constraints); + public override nullish(options: ValidatorOptions = this.validatorOptions): UnionValidator { + if (this.validators.length === 0) { + return new UnionValidator([new NullishValidator(options)], options, this.constraints); + } const [validator] = this.validators; if (validator instanceof LiteralValidator) { // If already nullable or optional, promote the union to nullish: if (validator.expected === null || validator.expected === undefined) { - return new UnionValidator([new NullishValidator(), ...this.validators.slice(1)], this.constraints); + return new UnionValidator( + [new NullishValidator(options), ...this.validators.slice(1)], + options, + this.constraints + ); } } else if (validator instanceof NullishValidator) { // If it's already nullish, return a clone: return this.clone(); } - return new UnionValidator([new NullishValidator(), ...this.validators]); + return new UnionValidator([new NullishValidator(options), ...this.validators], options); } public override or(...predicates: readonly BaseValidator[]): UnionValidator { - return new UnionValidator([...this.validators, ...predicates]); + return new UnionValidator([...this.validators, ...predicates], this.validatorOptions); } protected override clone(): this { - return Reflect.construct(this.constructor, [this.validators, this.constraints]); + return Reflect.construct(this.constructor, [this.validators, this.validatorOptions, this.constraints]); } protected handle(value: unknown): Result { @@ -108,6 +126,6 @@ export class UnionValidator extends BaseValidator { errors.push(result.error!); } - return Result.err(new CombinedError(errors)); + return Result.err(new CombinedError(errors, this.validatorOptions)); } } diff --git a/tests/browser/browser.test.ts b/tests/browser/browser.test.ts index e4ec5666..29508352 100644 --- a/tests/browser/browser.test.ts +++ b/tests/browser/browser.test.ts @@ -31,7 +31,7 @@ describe('browser-bundle-test', () => { }); test('GIVEN an unique array THEN return the given value', () => { - expect(window.SapphireShapeshift.s.string.parse('Hello')).toBe('Hello'); + expect(window.SapphireShapeshift.s.string().parse('Hello')).toBe('Hello'); }); }); diff --git a/tests/lib/configs.test.ts b/tests/lib/configs.test.ts index 68ff447a..272a5b3d 100644 --- a/tests/lib/configs.test.ts +++ b/tests/lib/configs.test.ts @@ -1,15 +1,15 @@ import { s, setGlobalValidationEnabled, type BaseValidator } from '../../src'; describe('Validation enabled and disabled configurations', () => { - const stringPredicate = s.string.lengthGreaterThan(5); - const arrayPredicate = s.array(s.string).lengthGreaterThan(2); - const mapPredicate = s.map(s.string, s.number); + const stringPredicate = s.string().lengthGreaterThan(5); + const arrayPredicate = s.array(s.string()).lengthGreaterThan(2); + const mapPredicate = s.map(s.string(), s.number()); const objectPredicate = s.object({ - owo: s.boolean + owo: s.boolean() }); - const recordPredicate = s.record(s.number); - const setPredicate = s.set(s.number); - const tuplePredicate = s.tuple([s.string, s.number]); + const recordPredicate = s.record(s.number()); + const setPredicate = s.set(s.number()); + const tuplePredicate = s.tuple([s.string(), s.number()]); const predicateAndValues: [string, BaseValidator, unknown][] = [ // @@ -50,7 +50,7 @@ describe('Validation enabled and disabled configurations', () => { }); test("GIVEN disabled predicate THEN checking if it's disabled should return true", () => { - const predicate = s.string.setValidationEnabled(false); + const predicate = s.string().setValidationEnabled(false); expect(predicate.getValidationEnabled()).toBe(false); }); diff --git a/tests/lib/errors/BaseConstraintError.test.ts b/tests/lib/errors/BaseConstraintError.test.ts new file mode 100644 index 00000000..9f3f04c5 --- /dev/null +++ b/tests/lib/errors/BaseConstraintError.test.ts @@ -0,0 +1,54 @@ +import { BaseConstraintError, type ConstraintErrorNames } from '../../../src/lib/errors/BaseConstraintError'; + +describe('BaseConstraintError', () => { + test('GIVEN constraint, message and value THEN converts to JSON', () => { + const constraint: ConstraintErrorNames = 's.string().url()'; + const message = 'Test message'; + const given = ['test']; + + // @ts-expect-error abstract class + const error = new BaseConstraintError(constraint, message, given); + const json = error.toJSON(); + + expect(json).toEqual({ + name: error.name, + constraint: error.constraint, + given: error.given, + message: error.message + }); + }); + + test('GIVEN object formatted constraint, message, and value THEN converts to JSON', () => { + const constraint: ConstraintErrorNames = 's.array(T).lengthEqual()'; + const message = 'Different test message'; + const given = { test: 'value' }; + + // @ts-expect-error abstract class + const error = new BaseConstraintError(constraint, message, given); + const json = error.toJSON(); + + expect(json).toEqual({ + name: error.name, + constraint: error.constraint, + given: error.given, + message: error.message + }); + }); + + test('GIVEN empty message and value THEN converts to JSON', () => { + const constraint: ConstraintErrorNames = 's.boolean().false()'; + const message = ''; + const given = null; + + // @ts-expect-error abstract class + const error = new BaseConstraintError(constraint, message, given); + const json = error.toJSON(); + + expect(json).toEqual({ + name: error.name, + constraint: error.constraint, + given: error.given, + message: error.message + }); + }); +}); diff --git a/tests/lib/errors/BaseError.test.ts b/tests/lib/errors/BaseError.test.ts new file mode 100644 index 00000000..5a9ed5e2 --- /dev/null +++ b/tests/lib/errors/BaseError.test.ts @@ -0,0 +1,25 @@ +import { BaseError, customInspectSymbolStackLess } from '../../../src/lib/errors/BaseError'; +import type { InspectOptionsStylized } from 'node:util'; + +describe('BaseError', () => { + test('GIVEN method call of toJson THEN converts to JSON correctly', () => { + // @ts-expect-error abstract class + const error = new BaseError(); + const json = error.toJSON(); + + expect(json).toEqual({ + name: error.name, + message: error.message + }); + }); + + test('GIVEN thrown error when customInspectSymbolStackLess is called THEN rethrows error', () => { + // @ts-expect-error abstract class + const error = new BaseError(); + const depth = 0; + // @ts-expect-error dummy object + const options: InspectOptionsStylized = {}; + + expect(() => error[customInspectSymbolStackLess](depth, options)).toThrow(); + }); +}); diff --git a/tests/lib/errors/ExpectedConstraintError.test.ts b/tests/lib/errors/ExpectedConstraintError.test.ts index da589211..3cb7e8e3 100644 --- a/tests/lib/errors/ExpectedConstraintError.test.ts +++ b/tests/lib/errors/ExpectedConstraintError.test.ts @@ -2,11 +2,11 @@ import { inspect } from 'node:util'; import { ExpectedConstraintError } from '../../../src/lib/errors/ExpectedConstraintError'; describe('ExpectedConstraintError', () => { - const error = new ExpectedConstraintError('s.number.int', 'Given value is not an integer', 42.1, 'Number.isInteger(expected) to be true'); + const error = new ExpectedConstraintError('s.number().int()', 'Given value is not an integer', 42.1, 'Number.isInteger(expected) to be true'); test('GIVEN an instance THEN assigns fields correctly', () => { expect(error.message).toBe('Given value is not an integer'); - expect(error.constraint).toBe('s.number.int'); + expect(error.constraint).toBe('s.number().int()'); expect(error.given).toBe(42.1); expect(error.expected).toBe('Number.isInteger(expected) to be true'); }); @@ -15,7 +15,7 @@ describe('ExpectedConstraintError', () => { test('GIVEN an inspected instance THEN formats data correctly', () => { const content = inspect(error, { colors: false }); const expected = [ - 'ExpectedConstraintError > s.number.int', // + 'ExpectedConstraintError > s.number().int()', // ' Given value is not an integer', '', ' Expected: Number.isInteger(expected) to be true', @@ -31,7 +31,7 @@ describe('ExpectedConstraintError', () => { test('GIVEN an inspected instance with negative depth THEN formats name only', () => { const content = inspect(error, { colors: false, depth: -1 }); const expected = [ - '[ExpectedConstraintError: s.number.int]' // + '[ExpectedConstraintError: s.number().int()]' // ]; expect(content.startsWith(expected.join('\n'))).toBe(true); @@ -39,10 +39,11 @@ describe('ExpectedConstraintError', () => { }); describe('toJSON', () => { - test('toJSON should return an object with name, constraint, given and expected', () => { + test('toJSON should return an object with name, message, constraint, given and expected', () => { expect(error.toJSON()).toStrictEqual({ name: 'Error', - constraint: 's.number.int', + message: 'Given value is not an integer', + constraint: 's.number().int()', given: 42.1, expected: 'Number.isInteger(expected) to be true' }); diff --git a/tests/lib/errors/ExpectedValidationError.test.ts b/tests/lib/errors/ExpectedValidationError.test.ts index d8bbfc5f..ac3cde1d 100644 --- a/tests/lib/errors/ExpectedValidationError.test.ts +++ b/tests/lib/errors/ExpectedValidationError.test.ts @@ -40,9 +40,10 @@ describe('ExpectedValidationError', () => { }); describe('toJSON', () => { - test('toJSON should return an object with name, validator, given and expected', () => { + test('toJSON should return an object with name, message, validator, given and expected', () => { expect(error.toJSON()).toStrictEqual({ name: 'Error', + message: 'Expected values to be equals', validator: 'LiteralValidator', given: 'world', expected: 'hello' diff --git a/tests/lib/errors/MissingPropertyError.test.ts b/tests/lib/errors/MissingPropertyError.test.ts index 341b7bf8..5b7ddc6e 100644 --- a/tests/lib/errors/MissingPropertyError.test.ts +++ b/tests/lib/errors/MissingPropertyError.test.ts @@ -32,9 +32,10 @@ describe('MissingPropertyError', () => { }); describe('toJSON', () => { - test('toJSON should return an object with name and property', () => { + test('toJSON should return an object with name, message, and property', () => { expect(error.toJSON()).toEqual({ name: 'Error', + message: 'A required property is missing', property: 'foo' }); }); diff --git a/tests/lib/errors/MultiplePossibilitiesConstraintError.test.ts b/tests/lib/errors/MultiplePossibilitiesConstraintError.test.ts index ae7a103e..d2ebdd8e 100644 --- a/tests/lib/errors/MultiplePossibilitiesConstraintError.test.ts +++ b/tests/lib/errors/MultiplePossibilitiesConstraintError.test.ts @@ -2,14 +2,14 @@ import { inspect } from 'node:util'; import { MultiplePossibilitiesConstraintError } from '../../../src'; describe('MultiplePossibilitiesConstraintError', () => { - const error = new MultiplePossibilitiesConstraintError('s.string.url', 'Invalid URL domain', 'https://example.org', [ + const error = new MultiplePossibilitiesConstraintError('s.string().url()', 'Invalid URL domain', 'https://example.org', [ 'discord.js.org', 'sapphirejs.dev' ]); test('GIVEN an instance THEN assigns fields correctly', () => { expect(error.message).toBe('Invalid URL domain'); - expect(error.constraint).toBe('s.string.url'); + expect(error.constraint).toBe('s.string().url()'); expect(error.given).toBe('https://example.org'); expect(error.expected).toStrictEqual(['discord.js.org', 'sapphirejs.dev']); }); @@ -18,7 +18,7 @@ describe('MultiplePossibilitiesConstraintError', () => { test('GIVEN an inspected instance THEN formats data correctly', () => { const content = inspect(error, { colors: false }); const expected = [ - 'MultiplePossibilitiesConstraintError > s.string.url', + 'MultiplePossibilitiesConstraintError > s.string().url()', ' Invalid URL domain', '', ' Expected any of the following:', @@ -36,7 +36,7 @@ describe('MultiplePossibilitiesConstraintError', () => { test('GIVEN an inspected instance with negative depth THEN formats name only', () => { const content = inspect(error, { colors: false, depth: -1 }); const expected = [ - '[MultiplePossibilitiesConstraintError: s.string.url]' // + '[MultiplePossibilitiesConstraintError: s.string().url()]' // ]; expect(content.startsWith(expected.join('\n'))).toBe(true); @@ -44,10 +44,11 @@ describe('MultiplePossibilitiesConstraintError', () => { }); describe('toJSON', () => { - test('toJSON should return an object with name, constraint, given and expected', () => { + test('toJSON should return an object with name, message, constraint, given and expected', () => { expect(error.toJSON()).toStrictEqual({ name: 'Error', - constraint: 's.string.url', + message: 'Invalid URL domain', + constraint: 's.string().url()', given: 'https://example.org', expected: ['discord.js.org', 'sapphirejs.dev'] }); diff --git a/tests/lib/errors/UnknownEnumValueError.test.ts b/tests/lib/errors/UnknownEnumValueError.test.ts index a3e397cc..7fb0c9ca 100644 --- a/tests/lib/errors/UnknownEnumValueError.test.ts +++ b/tests/lib/errors/UnknownEnumValueError.test.ts @@ -48,9 +48,10 @@ describe('UnknownEnumValueError', () => { }); describe('toJSON', () => { - test('toJSON should return an object with name and property', () => { + test('toJSON should return an object with name, message, value, enumKeys, and enumMappings', () => { expect(error.toJSON()).toEqual({ name: 'Error', + message: 'Expected the value to be one of the following enum values:', value: 'foo', enumKeys: ['bar', 'baz'], enumMappings: [ diff --git a/tests/lib/errors/UnknownPropertyError.test.ts b/tests/lib/errors/UnknownPropertyError.test.ts index 4ae245a5..e75bb71e 100644 --- a/tests/lib/errors/UnknownPropertyError.test.ts +++ b/tests/lib/errors/UnknownPropertyError.test.ts @@ -36,9 +36,10 @@ describe('UnknownPropertyError', () => { }); describe('toJSON', () => { - test('toJSON should return an object with name, property and value', () => { + test('toJSON should return an object with name, message, property and value', () => { expect(error.toJSON()).toStrictEqual({ name: 'Error', + message: 'Received unexpected property', property: 'foo', value: 42 }); diff --git a/tests/lib/errors/ValidationError.test.ts b/tests/lib/errors/ValidationError.test.ts index 0d56b9f8..01d0abb6 100644 --- a/tests/lib/errors/ValidationError.test.ts +++ b/tests/lib/errors/ValidationError.test.ts @@ -36,9 +36,10 @@ describe('ValidationError', () => { }); describe('toJSON', () => { - test('toJSON should return an object with name, validator and given', () => { + test('toJSON should return an object with name, message, validator and given', () => { expect(error.toJSON()).toStrictEqual({ name: 'Error', + message: 'Unknown validation error occurred.', validator: 'StringValidator', given: 42 }); diff --git a/tests/validators/array.test.ts b/tests/validators/array.test.ts index 62b63250..4aeb6b11 100644 --- a/tests/validators/array.test.ts +++ b/tests/validators/array.test.ts @@ -1,151 +1,161 @@ import { CombinedPropertyError, ExpectedConstraintError, ValidationError, s, type ArrayValidator } from '../../src'; import { expectClonedValidator, expectError } from '../common/macros/comparators'; -describe('ArrayValidator', () => { - const predicate = s.string.array; +describe.each(['custom message', undefined])('ArrayValidator (%s)', (message) => { + const predicate = s.string({ message }).array({ message }); test('GIVEN an array THEN returns an array', () => { expect(predicate.parse(['Hello', 'there'])).toEqual(['Hello', 'there']); }); test('GIVEN a non-array THEN throws ValidationError', () => { - expectError(() => predicate.parse('Hello there'), new ValidationError('s.array(T)', 'Expected an array', 'Hello there')); + const errorMessage = message ?? 'Expected an array'; + expectError(() => predicate.parse('Hello there'), new ValidationError('s.array(T)', errorMessage, 'Hello there')); }); test.each([123, true, {}, [], null])('GIVEN an array with value %j other than string THEN throws CombinedPropertyError', (input) => { + const errorMessage = message ?? 'Expected a string primitive'; expectError( () => predicate.parse([input]), - new CombinedPropertyError([ - // - [0, new ValidationError('s.string', 'Expected a string primitive', input)] - ]) + new CombinedPropertyError( + [ + // + [0, new ValidationError('s.string()', errorMessage, input)] + ], + { + message: message ?? 'Received one or more errors' + } + ) ); }); describe('Comparators', () => { describe('lengthLessThan', () => { - const lengthLessThanPredicate = s.string.array.lengthLessThan(2); + const lengthLessThanPredicate = s.string().array({ message }).lengthLessThan(2); test.each([[['Hello']]])('GIVEN %j THEN returns given value', (value) => { expect<[string] | []>(lengthLessThanPredicate.parse(value)).toEqual(value); }); test.each([[['Hello', 'there']], [['foo', 'bar', 'baaz']]])('GIVEN %j THEN throws ConstraintError', (value) => { + const errorMessage = message ?? 'Invalid Array length'; expectError( () => lengthLessThanPredicate.parse(value), - new ExpectedConstraintError('s.array(T).lengthLessThan', 'Invalid Array length', value, 'expected.length < 2') + new ExpectedConstraintError('s.array(T).lengthLessThan()', errorMessage, value, 'expected.length < 2') ); }); }); describe('lengthLessThanOrEqual', () => { - const lengthLePredicate = s.string.array.lengthLessThanOrEqual(2); + const lengthLePredicate = s.string().array({ message }).lengthLessThanOrEqual(2); test.each([[['Hello']], [['Hello', 'there']]])('GIVEN %j THEN returns given value', (value) => { expect<[string, string] | [string] | []>(lengthLePredicate.parse(value)).toEqual(value); }); test.each([[['foo', 'bar', 'baaz']]])('GIVEN %j THEN throws ConstraintError', (value) => { + const errorMessage = message ?? 'Invalid Array length'; expectError( () => lengthLePredicate.parse(value), - new ExpectedConstraintError('s.array(T).lengthLessThanOrEqual', 'Invalid Array length', value, 'expected.length <= 2') + new ExpectedConstraintError('s.array(T).lengthLessThanOrEqual()', errorMessage, value, 'expected.length <= 2') ); }); }); describe('lengthGreaterThan', () => { - const lengthGtPredicate = s.string.array.lengthGreaterThan(2); + const lengthGtPredicate = s.string().array({ message }).lengthGreaterThan(2); test.each([[['foo', 'bar', 'baaz']]])('GIVEN %j THEN returns given value', (value) => { expect<[string, string, string, ...string[]]>(lengthGtPredicate.parse(value)).toEqual(value); }); test.each([[['Hello']], [[]]])('GIVEN %j THEN throws ConstraintError', (value) => { + const errorMessage = message ?? 'Invalid Array length'; expectError( () => lengthGtPredicate.parse(value), - new ExpectedConstraintError('s.array(T).lengthGreaterThan', 'Invalid Array length', value, 'expected.length > 2') + new ExpectedConstraintError('s.array(T).lengthGreaterThan()', errorMessage, value, 'expected.length > 2') ); }); }); describe('lengthGreaterThanOrEqual', () => { - const lengthGePredicate = s.string.array.lengthGreaterThanOrEqual(2); + const lengthGePredicate = s.string().array({ message }).lengthGreaterThanOrEqual(2); test.each([[['Hello', 'there']], [['foo', 'bar', 'baaz']]])('GIVEN %j THEN returns given value', (value) => { expect<[string, string, ...string[]]>(lengthGePredicate.parse(value)).toEqual(value); }); test.each([[[]], [['foo']]])('GIVEN %j THEN throws ConstraintError', (value) => { + const errorMessage = message ?? 'Invalid Array length'; expectError( () => lengthGePredicate.parse(value), - new ExpectedConstraintError('s.array(T).lengthGreaterThanOrEqual', 'Invalid Array length', value, 'expected.length >= 2') + new ExpectedConstraintError('s.array(T).lengthGreaterThanOrEqual()', errorMessage, value, 'expected.length >= 2') ); }); }); describe('lengthEqual', () => { - const lengthPredicate = s.string.array.lengthEqual(2); + const lengthPredicate = s.string().array({ message }).lengthEqual(2); test.each([[['Hello', 'there']]])('GIVEN %j THEN returns given value', (value) => { expect<[string, string]>(lengthPredicate.parse(value)).toEqual(value); }); test.each([[[]], [['Hello']]])('GIVEN %j THEN throws ConstraintError', (value) => { + const errorMessage = message ?? 'Invalid Array length'; expectError( () => lengthPredicate.parse(value), - new ExpectedConstraintError('s.array(T).lengthEqual', 'Invalid Array length', value, 'expected.length === 2') + new ExpectedConstraintError('s.array(T).lengthEqual()', errorMessage, value, 'expected.length === 2') ); }); }); describe('lengthNotEqual', () => { - const lengthNotEqPredicate = s.string.array.lengthNotEqual(2); + const lengthNotEqPredicate = s.string().array({ message }).lengthNotEqual(2); test.each([[['foo', 'bar', 'baaz']], [['foo']]])('GIVEN %j THEN returns given value', (value) => { expect(lengthNotEqPredicate.parse(value)).toEqual(value); }); test.each([[['Hello', 'there']], [['foo', 'bar']]])('GIVEN %j THEN throws ConstraintError', (value) => { + const errorMessage = message ?? 'Invalid Array length'; expectError( () => lengthNotEqPredicate.parse(value), - new ExpectedConstraintError('s.array(T).lengthNotEqual', 'Invalid Array length', value, 'expected.length !== 2') + new ExpectedConstraintError('s.array(T).lengthNotEqual()', errorMessage, value, 'expected.length !== 2') ); }); }); describe('lengthRange', () => { - const lengthRangePredicate = s.string.array.lengthRange(0, 2); + const lengthRangePredicate = s.string().array({ message }).lengthRange(0, 2); test.each([[[] as string[]], [['foo']]])('GIVEN %j THEN returns given value', (value) => { expect<[] | [string]>(lengthRangePredicate.parse(value)).toEqual(value); }); test.each([[['hewwo', 'there']]])('GIVEN %j THEN throws ConstraintError', (value) => { + const errorMessage = message ?? 'Invalid Array length'; expectError( () => lengthRangePredicate.parse(value), - new ExpectedConstraintError( - 's.array(T).lengthRange', - 'Invalid Array length', - value, - 'expected.length >= 0 && expected.length < 2' - ) + new ExpectedConstraintError('s.array(T).lengthRange()', errorMessage, value, 'expected.length >= 0 && expected.length < 2') ); }); }); describe('lengthRangeInclusive', () => { - const lengthRangeInclusivePredicate = s.string.array.lengthRangeInclusive(0, 2); + const lengthRangeInclusivePredicate = s.string().array({ message }).lengthRangeInclusive(0, 2); test.each([[[] as string[]], [['foo']], [['hewwo', 'there']]])('GIVEN %j THEN returns given value', (value) => { expect<[] | [string] | [string, string]>(lengthRangeInclusivePredicate.parse(value)).toEqual(value); }); test.each([[['hewwo', 'there', 'buddy']]])('GIVEN %j THEN throws ConstraintError', (value) => { + const errorMessage = message ?? 'Invalid Array length'; expectError( () => lengthRangeInclusivePredicate.parse(value), new ExpectedConstraintError( - 's.array(T).lengthRangeInclusive', - 'Invalid Array length', + 's.array(T).lengthRangeInclusive()', + errorMessage, value, 'expected.length >= 0 && expected.length <= 2' ) @@ -153,7 +163,7 @@ describe('ArrayValidator', () => { }); describe('lengthRangeExclusive', () => { - const lengthRangeExclusivePredicate = s.string.array.lengthRangeExclusive(0, 2); + const lengthRangeExclusivePredicate = s.string().array({ message }).lengthRangeExclusive(0, 2); test.each([[['foo']]])('GIVEN %j THEN returns given value', (value) => { expect<[string]>(lengthRangeExclusivePredicate.parse(value)).toEqual(value); @@ -162,11 +172,12 @@ describe('ArrayValidator', () => { test.each([[[] as string[]], [['hewwo', 'there']], [['hewwo', 'there', 'buddy']]])( 'GIVEN %j THEN throws ConstraintError', (value) => { + const errorMessage = message ?? 'Invalid Array length'; expectError( () => lengthRangeExclusivePredicate.parse(value), new ExpectedConstraintError( - 's.array(T).lengthRangeExclusive', - 'Invalid Array length', + 's.array(T).lengthRangeExclusive()', + errorMessage, value, 'expected.length > 0 && expected.length < 2' ) @@ -177,16 +188,17 @@ describe('ArrayValidator', () => { }); describe('chainedLengthCheck', () => { - const lengthRangePredicate = s.string.array.lengthGreaterThan(0).lengthLessThan(2); + const lengthRangePredicate = s.string().array({ message }).lengthGreaterThan(0).lengthLessThan(2); test.each([[['foo']]])('GIVEN %j THEN returns given value', (value) => { expect(lengthRangePredicate.parse(value)).toEqual(value); }); test.each([[['hewwo', 'there']]])('GIVEN %j THEN throws ConstraintError', (value) => { + const errorMessage = message ?? 'Invalid Array length'; expectError( () => lengthRangePredicate.parse(value), - new ExpectedConstraintError('s.array(T).lengthLessThan', 'Invalid Array length', value, 'expected.length < 2') + new ExpectedConstraintError('s.array(T).lengthLessThan()', errorMessage, value, 'expected.length < 2') ); }); }); @@ -194,19 +206,19 @@ describe('ArrayValidator', () => { describe('Unique', () => { const validInputPredicate: [unknown, ArrayValidator][] = [ - [['Hello'], s.string.array.unique], - [['Hello', 'There'], s.string.array.unique], - [[{ name: 'Hello' }, { name: 'Hi' }], s.object({ name: s.string }).array.unique], - [[['Hello'], ['Hi']], s.string.array.array.unique], - [[[{ name: 'Hello' }], [{ name: 'Hi' }]], s.object({ name: s.string }).array.array.unique] + [['Hello'], s.string().array({ message }).unique()], + [['Hello', 'There'], s.string().array({ message }).unique()], + [[{ name: 'Hello' }, { name: 'Hi' }], s.object({ name: s.string() }).array({ message }).unique()], + [[['Hello'], ['Hi']], s.string().array({ message }).array({ message }).unique()], + [[[{ name: 'Hello' }], [{ name: 'Hi' }]], s.object({ name: s.string() }).array({ message }).array({ message }).unique()] ]; const invalidInputPredicate: [unknown, ArrayValidator][] = [ - [['Hello', 'Hello'], s.string.array.unique], - [[1, 2, 4, 2, 1], s.number.array.unique], - [[{ name: 'Hello' }, { name: 'Hello' }], s.object({ name: s.string }).array.unique], - [[['Hello'], ['Hello']], s.string.array.array.unique], - [[[{ name: 'Hello' }], [{ name: 'Hello' }]], s.object({ name: s.string }).array.array.unique] + [['Hello', 'Hello'], s.string().array({ message }).unique()], + [[1, 2, 4, 2, 1], s.number().array({ message }).unique()], + [[{ name: 'Hello' }, { name: 'Hello' }], s.object({ name: s.string() }).array({ message }).unique()], + [[['Hello'], ['Hello']], s.string().array({ message }).array({ message }).unique()], + [[[{ name: 'Hello' }], [{ name: 'Hello' }]], s.object({ name: s.string() }).array({ message }).array({ message }).unique()] ]; test.each(validInputPredicate)('GIVEN %j THEN return the given value', (value, p) => { @@ -214,15 +226,16 @@ describe('ArrayValidator', () => { }); test.each(invalidInputPredicate)('GIVEN %j THEN throws ExpectedConstraintError', (value, p) => { + const errorMessage = message ?? 'Array values are not unique'; expectError( () => p.parse(value), - new ExpectedConstraintError('s.array(T).unique', 'Array values are not unique', value, 'Expected all values to be unique') + new ExpectedConstraintError('s.array(T).unique()', errorMessage, value, 'Expected all values to be unique') ); }); }); test('GIVEN clone THEN returns similar instance', () => { - const arrayPredicate = s.string.array; + const arrayPredicate = s.string().array({ message }); expectClonedValidator(arrayPredicate, arrayPredicate['clone']()); }); diff --git a/tests/validators/base.test.ts b/tests/validators/base.test.ts index ca8a962a..aa4449df 100644 --- a/tests/validators/base.test.ts +++ b/tests/validators/base.test.ts @@ -1,10 +1,10 @@ import { CombinedError, ExpectedValidationError, Result, s, ValidationError } from '../../src'; import { expectClonedValidator, expectError, expectModifiedClonedValidator } from '../common/macros/comparators'; -describe('BaseValidator', () => { +describe.each(['custom message', undefined])('BaseValidator (%s)', (message) => { describe('description', () => { test('GIVEN a validator THEN returns the description', () => { - const validator = s.string.describe('The name of the user'); + const validator = s.string({ message }).describe('The name of the user'); expect(validator.description).toBe('The name of the user'); }); @@ -12,7 +12,7 @@ describe('BaseValidator', () => { describe('is', () => { test("GIVEN any value THEN it's narrowed to schema type", () => { - const predicate = s.string; + const predicate = s.string({ message }); const value = 'Hello there' as string | null; const is = predicate.is(value); @@ -27,7 +27,7 @@ describe('BaseValidator', () => { }); describe('optional', () => { - const optionalPredicate = s.string.optional; + const optionalPredicate = s.string({ message }).optional({ message }); test.each([undefined, 'hello'])('GIVEN %j THEN returns given value', (input) => { expect(optionalPredicate.parse(input)).toEqual(input); @@ -36,16 +36,21 @@ describe('BaseValidator', () => { test.each([null, 0, false, true])('GIVEN %o THEN throws CombinedError', (input) => { expectError( () => optionalPredicate.parse(input), - new CombinedError([ - new ExpectedValidationError('s.literal(V)', 'Expected values to be equals', input, undefined), - new ValidationError('s.string', 'Expected a string primitive', input) - ]) + new CombinedError( + [ + new ExpectedValidationError('s.literal(V)', message ?? 'Expected values to be equals', input, undefined), + new ValidationError('s.string()', message ?? 'Expected a string primitive', input) + ], + { + message: message ?? 'Received one or more errors' + } + ) ); }); }); describe('nullable', () => { - const nullablePredicate = s.string.nullable; + const nullablePredicate = s.string({ message }).nullable({ message }); test.each([null, 'Hello There'])('GIVEN %j THEN returns given value', (input) => { expect(nullablePredicate.parse(input)).toBe(input); @@ -54,16 +59,21 @@ describe('BaseValidator', () => { test.each([0, false, true])('GIVEN %j THEN throws CombinedError', (input) => { expectError( () => nullablePredicate.parse(input), - new CombinedError([ - new ExpectedValidationError('s.literal(V)', 'Expected values to be equals', input, null), - new ValidationError('s.string', 'Expected a string primitive', input) - ]) + new CombinedError( + [ + new ExpectedValidationError('s.literal(V)', message ?? 'Expected values to be equals', input, null), + new ValidationError('s.string()', message ?? 'Expected a string primitive', input) + ], + { + message: message ?? 'Received one or more errors' + } + ) ); }); }); describe('nullish', () => { - const nullishPredicate = s.string.nullish; + const nullishPredicate = s.string({ message }).nullish({ message }); test.each(['Hello There', undefined, null])('GIVEN %j THEN returns the given value', (input) => { expect(nullishPredicate.parse(input)).toBe(input); @@ -72,24 +82,29 @@ describe('BaseValidator', () => { test.each([0, false, true])('GIVEN %j THEN throws CombinedError', (input) => { expectError( () => nullishPredicate.parse(input), - new CombinedError([ - new ValidationError('s.nullish', 'Expected undefined or null', input), - new ValidationError('s.string', 'Expected a string primitive', input) - ]) + new CombinedError( + [ + new ValidationError('s.nullish()', message ?? 'Expected undefined or null', input), + new ValidationError('s.string()', message ?? 'Expected a string primitive', input) + ], + { + message: message ?? 'Received one or more errors' + } + ) ); }); }); describe('array', () => { - const numberArrayPredicate = s.number.array; + const numberArrayPredicate = s.number({ message }).array({ message }); const input = [1, 2, 3]; test('GIVEN an array of number THEN returns the given value', () => { expect(numberArrayPredicate.parse(input)).toStrictEqual(input); }); - test('GIVEN s.number.array THEN returns s.array(s.number)', () => { - const arrayNumberPredicate = s.array(s.number); + test('GIVEN s.number().array() THEN returns s.array(s.number())', () => { + const arrayNumberPredicate = s.array(s.number({ message }), { message }); expect(numberArrayPredicate.parse([1])).toStrictEqual([1]); expect(arrayNumberPredicate.parse([1, 2, 3])).toStrictEqual([1, 2, 3]); @@ -98,22 +113,22 @@ describe('BaseValidator', () => { }); describe('set', () => { - const numberSetPredicate = s.number.set; + const numberSetPredicate = s.number({ message }).set({ message }); const input = new Set([1, 2, 3]); test('GIVEN a set of numbers THEN returns the given value', () => { expect>(numberSetPredicate.parse(input)).toStrictEqual(input); }); - test('GIVEN s.number.set THEN returns s.set(s.number)', () => { - const setNumberPredicate = s.set(s.number); + test('GIVEN s.number().set() THEN returns s.set(s.number())', () => { + const setNumberPredicate = s.set(s.number({ message }), { message }); expectClonedValidator(numberSetPredicate, setNumberPredicate); }); }); describe('or', () => { - const stringOrPredicate = s.string.or(s.number); + const stringOrPredicate = s.string({ message }).or(s.number({ message })); test.each(['Hello There', 6])('GIVEN a string or number (%j) THEN returns a string or number', (input) => { expect(stringOrPredicate.parse(input)).toBe(input); @@ -122,22 +137,27 @@ describe('BaseValidator', () => { test.each([false, true, null])('GIVEN %j THEN throws CombinedError', (input) => { expectError( () => stringOrPredicate.parse(input), - new CombinedError([ - new ValidationError('s.string', 'Expected a string primitive', input), - new ValidationError('s.number', 'Expected a number primitive', input) - ]) + new CombinedError( + [ + new ValidationError('s.string()', message ?? 'Expected a string primitive', input), + new ValidationError('s.number()', message ?? 'Expected a number primitive', input) + ], + { + message: message ?? 'Received one or more errors' + } + ) ); }); - test('GIVEN s.string.or(s.number) THEN returns s.union(s.number)', () => { - const unionPredicate = s.union(s.string, s.number); + test('GIVEN s.string().or(s.number()) THEN returns s.union(s.number())', () => { + const unionPredicate = s.union([s.string({ message }), s.number({ message })], { message }); expectClonedValidator(stringOrPredicate, unionPredicate); }); }); describe('transform', () => { - const transformPredicate = s.string.transform((value) => value.toUpperCase()); + const transformPredicate = s.string({ message }).transform((value) => value.toUpperCase()); test('GIVEN a string THEN returns uppercase string', () => { expect(transformPredicate.parse('Hello There')).toStrictEqual('HELLO THERE'); @@ -145,7 +165,10 @@ describe('BaseValidator', () => { }); describe('transform with union', () => { - const unionTransformPredicate = s.string.transform((value) => value.toUpperCase()).or(s.number); + const unionTransformPredicate = s + .string({ message }) + .transform((value) => value.toUpperCase()) + .or(s.number({ message })); test('GIVEN string THEN returns uppercase string', () => { expect(unionTransformPredicate.parse('Hello There')).toStrictEqual('HELLO THERE'); @@ -157,18 +180,26 @@ describe('BaseValidator', () => { test.each([false, true, null, undefined])('GIVEN %j THEN throws CombinedError', (input) => { expectError( () => unionTransformPredicate.parse(input), - new CombinedError([ - new ValidationError('s.string', 'Expected a string primitive', input), - new ValidationError('s.number', 'Expected a number primitive', input) - ]) + new CombinedError( + [ + new ValidationError('s.string()', message ?? 'Expected a string primitive', input), + new ValidationError('s.number()', message ?? 'Expected a number primitive', input) + ], + { + message: message ?? 'Received one or more errors' + } + ) ); }); }); describe('Reshape', () => { - const predicate = s.string.reshape((value) => { - return value.length > 5 ? Result.ok(value.length) : Result.err(new Error('Too short')); - }); + const predicate = s.string({ message }).reshape( + (value) => { + return value.length > 5 ? Result.ok(value.length) : Result.err(new Error('Too short')); + }, + { message } + ); test('GIVEN a string with length > 5 THEN returns the given value', () => { expect(predicate.parse('Sapphire')).toBe(8); @@ -181,7 +212,7 @@ describe('BaseValidator', () => { describe('default', () => { describe('required', () => { - const defaultPredicate = s.string.default('foo'); + const defaultPredicate = s.string({ message }).default('foo', { message }); test('GIVEN a string THEN returns the given string', () => { expect(defaultPredicate.parse('bar')).toBe('bar'); @@ -193,7 +224,7 @@ describe('BaseValidator', () => { }); describe('optional', () => { - const defaultPredicate = s.string.optional.default('foo'); + const defaultPredicate = s.string({ message }).optional({ message }).default('foo', { message }); test('GIVEN a string THEN returns the given string', () => { expect(defaultPredicate.parse('bar')).toBe('bar'); @@ -205,23 +236,23 @@ describe('BaseValidator', () => { }); describe('default', () => { - const defaultPredicate = s.string.default('foo').default('bar'); + const defaultPredicate = s.string({ message }).default('foo', { message }).default('bar', { message }); test('GIVEN a double default THEN returns a clone with updated default', () => { - expectClonedValidator(s.string.default('bar'), defaultPredicate); + expectClonedValidator(s.string({ message }).default('bar', { message }), defaultPredicate); }); }); }); describe('clone', () => { test('GIVEN clone THEN returns similar instance', () => { - const predicate = s.string; + const predicate = s.string({ message }); expectClonedValidator(predicate, predicate['clone']()); }); test('GIVEN clone of default THEN returns similar instance', () => { - const predicate = s.number.default(5); + const predicate = s.number({ message }).default(5); expectClonedValidator(predicate, predicate['clone']()); }); @@ -229,7 +260,7 @@ describe('BaseValidator', () => { describe('Methods and Getters returns a clone', () => { test('GIVEN string.length constraint THEN returns modified clone of the validator', () => { - const stringPredicate = s.string; + const stringPredicate = s.string({ message }); expectModifiedClonedValidator(stringPredicate, stringPredicate.lengthEqual(1)); expectModifiedClonedValidator(stringPredicate, stringPredicate.lengthGreaterThanOrEqual(1)); @@ -240,7 +271,7 @@ describe('BaseValidator', () => { }); test('GIVEN number.comparator constraint THEN returns modified clone of the validator', () => { - const numberPredicate = s.number; + const numberPredicate = s.number({ message }); expectModifiedClonedValidator(numberPredicate, numberPredicate.equal(1)); expectModifiedClonedValidator(numberPredicate, numberPredicate.greaterThanOrEqual(1)); @@ -251,7 +282,7 @@ describe('BaseValidator', () => { }); test('GIVEN bigint.comparator constraint THEN returns modified clone of the validator', () => { - const bigintPredicate = s.bigint; + const bigintPredicate = s.bigint({ message }); expectModifiedClonedValidator(bigintPredicate, bigintPredicate.equal(1n)); expectModifiedClonedValidator(bigintPredicate, bigintPredicate.greaterThanOrEqual(1n)); diff --git a/tests/validators/bigint.test.ts b/tests/validators/bigint.test.ts index 300660c5..c035831e 100644 --- a/tests/validators/bigint.test.ts +++ b/tests/validators/bigint.test.ts @@ -4,104 +4,111 @@ import { expectError } from '../common/macros/comparators'; const smallInteger = 42n; const largeInteger = 242043489611808769n; -describe('BigIntValidator', () => { - const predicate = s.bigint; +describe.each(['custom message', undefined])('BigIntValidator (%s)', (message) => { + const predicate = s.bigint({ message }); test('GIVEN a bigint THEN returns a bigint', () => { expect(predicate.parse(42n)).toBe(42n); }); test('GIVEN a non-bigint THEN throws ValidationError', () => { - expectError(() => predicate.parse('Hello there'), new ValidationError('s.bigint', 'Expected a bigint primitive', 'Hello there')); + const errorMessage = message ?? 'Expected a bigint primitive'; + expectError(() => predicate.parse('Hello there'), new ValidationError('s.bigint()', errorMessage, 'Hello there')); }); describe('Comparators', () => { describe('lessThan', () => { - const ltPredicate = s.bigint.lessThan(42n); + const ltPredicate = s.bigint().lessThan(42n, { message }); test.each([10n])('GIVEN %d THEN returns given value', (value) => { expect(ltPredicate.parse(value)).toBe(value); }); test.each([42n, 100n])('GIVEN %d THEN throws ConstraintError', (value) => { + const errorMessage = message ?? 'Invalid bigint value'; expectError( () => ltPredicate.parse(value), - new ExpectedConstraintError('s.bigint.lessThan', 'Invalid bigint value', value, 'expected < 42n') + new ExpectedConstraintError('s.bigint().lessThan()', errorMessage, value, 'expected < 42n') ); }); }); describe('lessThanOrEqual', () => { - const lePredicate = s.bigint.lessThanOrEqual(42n); + const lePredicate = s.bigint().lessThanOrEqual(42n, { message }); test.each([10n, 42n])('GIVEN %d THEN returns given value', (input) => { expect(lePredicate.parse(input)).toBe(input); }); test.each([100n])('GIVEN %d THEN throws ConstraintError', (input) => { + const errorMessage = message ?? 'Invalid bigint value'; expectError( () => lePredicate.parse(input), - new ExpectedConstraintError('s.bigint.lessThanOrEqual', 'Invalid bigint value', input, 'expected <= 42n') + new ExpectedConstraintError('s.bigint().lessThanOrEqual()', errorMessage, input, 'expected <= 42n') ); }); }); describe('greaterThan', () => { - const gtPredicate = s.bigint.greaterThan(42n); + const gtPredicate = s.bigint().greaterThan(42n, { message }); test.each([100n])('GIVEN %d THEN returns given value', (value) => { expect(gtPredicate.parse(value)).toBe(value); }); test.each([10n, 42n])('GIVEN %d THEN throws ConstraintError', (value) => { + const errorMessage = message ?? 'Invalid bigint value'; expectError( () => gtPredicate.parse(value), - new ExpectedConstraintError('s.bigint.greaterThan', 'Invalid bigint value', value, 'expected > 42n') + new ExpectedConstraintError('s.bigint().greaterThan()', errorMessage, value, 'expected > 42n') ); }); }); describe('greaterThanOrEqual', () => { - const gePredicate = s.bigint.greaterThanOrEqual(42n); + const gePredicate = s.bigint().greaterThanOrEqual(42n, { message }); test.each([42n, 100n])('GIVEN %d THEN returns given value', (value) => { expect(gePredicate.parse(value)).toBe(value); }); test.each([10n])('GIVEN %d THEN throws ConstraintError', (value) => { + const errorMessage = message ?? 'Invalid bigint value'; expectError( () => gePredicate.parse(value), - new ExpectedConstraintError('s.bigint.greaterThanOrEqual', 'Invalid bigint value', value, 'expected >= 42n') + new ExpectedConstraintError('s.bigint().greaterThanOrEqual()', errorMessage, value, 'expected >= 42n') ); }); }); describe('equal', () => { - const eqPredicate = s.bigint.equal(42n); + const eqPredicate = s.bigint().equal(42n, { message }); test.each([42n])('GIVEN %d THEN returns given value', (value) => { expect(eqPredicate.parse(value)).toBe(value); }); test.each([10n, 100n])('GIVEN %d THEN throws ConstraintError', (value) => { + const errorMessage = message ?? 'Invalid bigint value'; expectError( () => eqPredicate.parse(value), - new ExpectedConstraintError('s.bigint.equal', 'Invalid bigint value', value, 'expected === 42n') + new ExpectedConstraintError('s.bigint().equal()', errorMessage, value, 'expected === 42n') ); }); }); describe('notEqual', () => { - const nePredicate = s.bigint.notEqual(42n); + const nePredicate = s.bigint().notEqual(42n, { message }); test.each([10n, 100n])('GIVEN %d THEN returns given value', (value) => { expect(nePredicate.parse(value)).toBe(value); }); test.each([42n])('GIVEN %d THEN throws ConstraintError', (value) => { + const errorMessage = message ?? 'Invalid bigint value'; expectError( () => nePredicate.parse(value), - new ExpectedConstraintError('s.bigint.notEqual', 'Invalid bigint value', value, 'expected !== 42n') + new ExpectedConstraintError('s.bigint().notEqual()', errorMessage, value, 'expected !== 42n') ); }); }); @@ -109,46 +116,49 @@ describe('BigIntValidator', () => { describe('Constraints', () => { describe('Positive', () => { - const positivePredicate = s.bigint.positive; + const positivePredicate = s.bigint().positive({ message }); test.each([smallInteger, largeInteger])('GIVEN %d THEN returns given value', (input) => { expect(positivePredicate.parse(input)).toBe(input); }); test.each([-smallInteger, -largeInteger])('GIVEN %d THEN throws a ConstraintError', (input) => { + const errorMessage = message ?? 'Invalid bigint value'; expectError( () => positivePredicate.parse(input), - new ExpectedConstraintError('s.bigint.greaterThanOrEqual', 'Invalid bigint value', input, 'expected >= 0n') + new ExpectedConstraintError('s.bigint().greaterThanOrEqual()', errorMessage, input, 'expected >= 0n') ); }); }); describe('Negative', () => { - const positivePredicate = s.bigint.negative; + const positivePredicate = s.bigint().negative({ message }); test.each([-smallInteger, -largeInteger])('GIVEN %d THEN returns given value', (input) => { expect(positivePredicate.parse(input)).toBe(input); }); test.each([smallInteger, largeInteger])('GIVEN %d THEN throws a ConstraintError', (input) => { + const errorMessage = message ?? 'Invalid bigint value'; expectError( () => positivePredicate.parse(input), - new ExpectedConstraintError('s.bigint.lessThan', 'Invalid bigint value', input, 'expected < 0n') + new ExpectedConstraintError('s.bigint().lessThan()', errorMessage, input, 'expected < 0n') ); }); }); describe('DivisibleBy', () => { - const divisibleByPredicate = s.bigint.divisibleBy(5n); + const divisibleByPredicate = s.bigint().divisibleBy(5n, { message }); test.each([5n, 10n, 20n, 500n])('GIVEN %d THEN returns given value', (input) => { expect(divisibleByPredicate.parse(input)).toBe(input); }); test.each([smallInteger, largeInteger, 6n])('GIVEN %d THEN throws a ConstraintError', (input) => { + const errorMessage = message ?? 'BigInt is not divisible'; expectError( () => divisibleByPredicate.parse(input), - new ExpectedConstraintError('s.bigint.divisibleBy', 'BigInt is not divisible', input, 'expected % 5n === 0n') + new ExpectedConstraintError('s.bigint().divisibleBy()', errorMessage, input, 'expected % 5n === 0n') ); }); }); @@ -156,7 +166,7 @@ describe('BigIntValidator', () => { describe('Transformers', () => { describe('abs', () => { - const absPredicate = s.bigint.abs; + const absPredicate = s.bigint().abs({ message }); test.each([smallInteger, largeInteger, -smallInteger, -largeInteger])('GIVEN %d THEN returns transformed the result', (input) => { expect(absPredicate.parse(input)).toBe(input < 0 ? -input : input); @@ -164,7 +174,7 @@ describe('BigIntValidator', () => { }); describe('intN', () => { - const intNPredicate = s.bigint.intN(5); + const intNPredicate = s.bigint().intN(5, { message }); test.each([smallInteger, largeInteger])('GIVEN %d THEN returns transformed the result from BigInt.asIntN', (input) => { expect(intNPredicate.parse(input)).toBe(BigInt.asIntN(5, input)); @@ -172,7 +182,7 @@ describe('BigIntValidator', () => { }); describe('uintN', () => { - const uintNPredicate = s.bigint.uintN(5); + const uintNPredicate = s.bigint().uintN(5, { message }); test.each([smallInteger, largeInteger])('GIVEN %d THEN returns transformed the result from BigInt.asUintN', (input) => { expect(uintNPredicate.parse(input)).toBe(BigInt.asUintN(5, input)); @@ -180,8 +190,8 @@ describe('BigIntValidator', () => { }); describe('default', () => { - const defaultPredicate = s.bigint.default(5n); - const defaultFunctionPredicate = s.bigint.default(() => 5n); + const defaultPredicate = s.bigint({ message }).default(5n); + const defaultFunctionPredicate = s.bigint({ message }).default(() => 5n); test.each([smallInteger, largeInteger])('GIVEN %d THEN returns the input', (input) => { expect(defaultPredicate.parse(input)).toBe(input); diff --git a/tests/validators/boolean.test.ts b/tests/validators/boolean.test.ts index ac61d8e9..4ea14f59 100644 --- a/tests/validators/boolean.test.ts +++ b/tests/validators/boolean.test.ts @@ -1,66 +1,81 @@ import { ExpectedConstraintError, s, ValidationError } from '../../src'; import { expectError } from '../common/macros/comparators'; -describe('BooleanValidator', () => { - const predicate = s.boolean; +describe.each(['custom message', undefined])('BooleanValidator (%s)', (message) => { + const predicate = s.boolean({ message }); + + const invalidBooleanErrorMessage = message ?? 'Invalid boolean value'; test('GIVEN a boolean THEN returns the given value', () => { expect(predicate.parse(true)).toBe(true); }); test('GIVEN a non-boolean THEN throws ValidationError', () => { - expectError(() => predicate.parse('Hello there'), new ValidationError('s.boolean', 'Expected a boolean primitive', 'Hello there')); + const errorMessage = message ?? 'Expected a boolean primitive'; + expectError(() => predicate.parse('Hello there'), new ValidationError('s.boolean()', errorMessage, 'Hello there')); }); describe('Comparators', () => { // equal, notEqual describe('equal', () => { - const eqPredicate = s.boolean.equal(true); + const eqPredicate = s.boolean().equal(true, { message }); test('GIVEN true THEN returns given value', () => { expect(eqPredicate.parse(true)).toBe(true); }); test('GIVEN false THEN throws ConstraintError', () => { - expectError(() => eqPredicate.parse(false), new ExpectedConstraintError('s.boolean.true', 'Invalid boolean value', false, 'true')); + expectError( + () => eqPredicate.parse(false), + new ExpectedConstraintError('s.boolean().true()', invalidBooleanErrorMessage, false, 'true') + ); }); }); describe('notEqual', () => { - const nePredicate = s.boolean.notEqual(true); + const nePredicate = s.boolean().notEqual(true, { message }); test('GIVEN false THEN returns given value', () => { expect(nePredicate.parse(false)).toBe(false); }); test('GIVEN true THEN throws ConstraintError', () => { - expectError(() => nePredicate.parse(true), new ExpectedConstraintError('s.boolean.false', 'Invalid boolean value', true, 'false')); + expectError( + () => nePredicate.parse(true), + new ExpectedConstraintError('s.boolean().false()', invalidBooleanErrorMessage, true, 'false') + ); }); }); }); describe('Constraints', () => { describe('true', () => { - const truePredicate = s.boolean.true; + const truePredicate = s.boolean().true({ message }); test('GIVEN true THEN returns given value', () => { expect(truePredicate.parse(true)).toBe(true); }); test('GIVEN false THEN throws ConstraintError', () => { - expectError(() => truePredicate.parse(false), new ExpectedConstraintError('s.boolean.true', 'Invalid boolean value', false, 'true')); + expectError( + () => truePredicate.parse(false), + new ExpectedConstraintError('s.boolean().true()', invalidBooleanErrorMessage, false, 'true') + ); }); }); describe('false', () => { - const falsePredicate = s.boolean.false; + const falsePredicate = s.boolean().false({ message }); test('GIVEN false THEN returns given value', () => { expect(falsePredicate.parse(false)).toBe(false); }); test('GIVEN true THEN throws ConstraintError', () => { - expectError(() => falsePredicate.parse(true), new ExpectedConstraintError('s.boolean.false', 'Invalid boolean value', true, 'false')); + expectError( + () => falsePredicate.parse(true), + new ExpectedConstraintError('s.boolean().false()', invalidBooleanErrorMessage, true, 'false') + ); }); }); }); diff --git a/tests/validators/date.test.ts b/tests/validators/date.test.ts index 10ddf20e..6dd49adb 100644 --- a/tests/validators/date.test.ts +++ b/tests/validators/date.test.ts @@ -1,8 +1,10 @@ import { ExpectedConstraintError, s, ValidationError } from '../../src'; import { expectClonedValidator, expectError } from '../common/macros/comparators'; -describe('DateValidator', () => { - const predicate = s.date; +describe.each(['custom message', undefined])('DateValidator (%s)', (message) => { + const predicate = s.date({ message }); + + const invalidDateErrorMessage = message ?? 'Invalid Date value'; test('GIVEN a date THEN returns the given value', () => { const date = new Date(); @@ -10,7 +12,8 @@ describe('DateValidator', () => { }); test.each(['abc', '', null, undefined])('GIVEN a non-date (%j) THEN throws ValidationError', (input) => { - expectError(() => predicate.parse(input), new ValidationError('s.date', 'Expected a Date', input)); + const errorMessage = message ?? 'Expected a Date'; + expectError(() => predicate.parse(input), new ValidationError('s.date()', errorMessage, input)); }); describe('Comparator', () => { @@ -19,7 +22,7 @@ describe('DateValidator', () => { const datesInPast = [new Date('2022-01-01'), new Date('2020-01-01')]; describe('lessThan', () => { - const ltPredicate = s.date.lessThan(date); + const ltPredicate = s.date().lessThan(date, { message }); test.each(datesInPast)('GIVEN %j THEN returns given value', (value) => { expect(ltPredicate.parse(value)).toBe(value); @@ -28,13 +31,13 @@ describe('DateValidator', () => { test.each(datesInFuture)('GIVEN %j THEN throws ConstraintError', (value) => { expectError( () => ltPredicate.parse(value), - new ExpectedConstraintError('s.date.lessThan', 'Invalid Date value', value, 'expected < 2022-02-01T00:00:00.000Z') + new ExpectedConstraintError('s.date().lessThan()', invalidDateErrorMessage, value, 'expected < 2022-02-01T00:00:00.000Z') ); }); }); describe('lessThanOrEqual', () => { - const lePredicate = s.date.lessThanOrEqual(date); + const lePredicate = s.date().lessThanOrEqual(date, { message }); test.each([...datesInPast, date])('GIVEN %j THEN returns given value', (value) => { expect(lePredicate.parse(value)).toBe(value); @@ -43,13 +46,13 @@ describe('DateValidator', () => { test.each(datesInFuture)('GIVEN %j THEN throws ConstraintError', (value) => { expectError( () => lePredicate.parse(value), - new ExpectedConstraintError('s.date.lessThanOrEqual', 'Invalid Date value', value, 'expected <= 2022-02-01T00:00:00.000Z') + new ExpectedConstraintError('s.date().lessThanOrEqual()', invalidDateErrorMessage, value, 'expected <= 2022-02-01T00:00:00.000Z') ); }); }); describe('greaterThan', () => { - const gtPredicate = s.date.greaterThan(date); + const gtPredicate = s.date().greaterThan(date, { message }); test.each(datesInFuture)('GIVEN %j THEN returns given value', (value) => { expect(gtPredicate.parse(value)).toBe(value); @@ -58,13 +61,13 @@ describe('DateValidator', () => { test.each(datesInPast)('GIVEN %j THEN throws ConstraintError', (value) => { expectError( () => gtPredicate.parse(value), - new ExpectedConstraintError('s.date.greaterThan', 'Invalid Date value', value, 'expected > 2022-02-01T00:00:00.000Z') + new ExpectedConstraintError('s.date().greaterThan()', invalidDateErrorMessage, value, 'expected > 2022-02-01T00:00:00.000Z') ); }); }); describe('greaterThanOrEqual', () => { - const gePredicate = s.date.greaterThanOrEqual(date); + const gePredicate = s.date().greaterThanOrEqual(date, { message }); test.each([date, ...datesInFuture])('GIVEN %j THEN returns given value', (value) => { expect(gePredicate.parse(value)).toBe(value); @@ -73,13 +76,18 @@ describe('DateValidator', () => { test.each(datesInPast)('GIVEN %j THEN throws ConstraintError', (value) => { expectError( () => gePredicate.parse(value), - new ExpectedConstraintError('s.date.greaterThanOrEqual', 'Invalid Date value', value, 'expected >= 2022-02-01T00:00:00.000Z') + new ExpectedConstraintError( + 's.date().greaterThanOrEqual()', + invalidDateErrorMessage, + value, + 'expected >= 2022-02-01T00:00:00.000Z' + ) ); }); }); describe('equal', () => { - const eqPredicate = s.date.equal(date); + const eqPredicate = s.date().equal(date, { message }); test('GIVEN date THEN returns given value', () => { expect(eqPredicate.parse(date)).toBe(date); @@ -88,19 +96,19 @@ describe('DateValidator', () => { test.each([...datesInPast, ...datesInFuture])('GIVEN %j THEN throws ConstraintError', (value) => { expectError( () => eqPredicate.parse(value), - new ExpectedConstraintError('s.date.equal', 'Invalid Date value', value, 'expected === 2022-02-01T00:00:00.000Z') + new ExpectedConstraintError('s.date().equal()', invalidDateErrorMessage, value, 'expected === 2022-02-01T00:00:00.000Z') ); }); describe('equal > NaN', () => { - test.each(['not-a-date', NaN])('GIVEN %j THEN returns s.date.invalid', (value) => { - expectClonedValidator(s.date.equal(value), s.date.invalid); + test.each(['not-a-date', NaN])('GIVEN %j THEN returns s.date().invalid', (value) => { + expectClonedValidator(s.date().equal(value), s.date().invalid()); }); }); }); describe('notEqual', () => { - const nePredicate = s.date.notEqual(date); + const nePredicate = s.date().notEqual(date, { message }); test.each([...datesInPast, ...datesInFuture])('GIVEN %j THEN returns given value', (value) => { expect(nePredicate.parse(value)).toBe(value); @@ -109,20 +117,20 @@ describe('DateValidator', () => { test('GIVEN date THEN throws ConstraintError', () => { expectError( () => nePredicate.parse(date), - new ExpectedConstraintError('s.date.notEqual', 'Invalid Date value', date, 'expected !== 2022-02-01T00:00:00.000Z') + new ExpectedConstraintError('s.date().notEqual()', invalidDateErrorMessage, date, 'expected !== 2022-02-01T00:00:00.000Z') ); }); describe('notEqual > NaN', () => { - test.each(['not-a-date', NaN])('GIVEN %j THEN returns s.date.invalid', (value) => { - expectClonedValidator(s.date.notEqual(value), s.date.invalid); + test.each(['not-a-date', NaN])('GIVEN %j THEN returns s.date().invalid', (value) => { + expectClonedValidator(s.date().notEqual(value), s.date().invalid()); }); }); }); }); describe('valid', () => { - const validPredicate = s.date.valid; + const validPredicate = s.date().valid({ message }); test.each(['2022-03-13T11:19:13.698Z', 1647170353698])('GIVEN a valid date (%j) THEN returns the given value', (value) => { const date = new Date(value); @@ -131,15 +139,16 @@ describe('DateValidator', () => { test.each([NaN, Infinity, -Infinity])('GIVEN an invalid date (%j) THEN throws ValidationError', (value) => { const date = new Date(value); + expectError( () => validPredicate.parse(date), - new ExpectedConstraintError('s.date.valid', 'Invalid Date value', date, 'expected !== NaN') + new ExpectedConstraintError('s.date().valid()', invalidDateErrorMessage, date, 'expected !== NaN') ); }); }); describe('invalid', () => { - const invalidPredicate = s.date.invalid; + const invalidPredicate = s.date().invalid({ message }); test.each([NaN, Infinity, -Infinity])('GIVEN an invalid date (%j) THEN returns the given value', (value) => { const date = new Date(value); @@ -150,7 +159,7 @@ describe('DateValidator', () => { const date = new Date(value); expectError( () => invalidPredicate.parse(date), - new ExpectedConstraintError('s.date.invalid', 'Invalid Date value', date, 'expected === NaN') + new ExpectedConstraintError('s.date().invalid()', invalidDateErrorMessage, date, 'expected === NaN') ); }); }); diff --git a/tests/validators/enum.test.ts b/tests/validators/enum.test.ts index 1f39bf7b..b74c1675 100644 --- a/tests/validators/enum.test.ts +++ b/tests/validators/enum.test.ts @@ -1,21 +1,27 @@ import { CombinedError, ExpectedValidationError, s } from '../../src'; import { expectError } from '../common/macros/comparators'; -describe('EnumValidator', () => { - const predicate = s.enum('a', 'b', 'c'); +describe.each(['custom message', undefined])('EnumValidator', (message) => { + const predicate = s.enum(['a', 'b', 'c'], { message }); test.each(['a', 'b', 'c'])('GIVEN a string (%j) THEN returns a string', (input) => { expect(predicate.parse(input)).toBe(input); }); test.each(['d', 'e', 'f', 1, null, true])('GIVEN a invalid value (%j) THEN throws CombinedError', (input) => { + const errorMessage = message ?? 'Expected values to be equals'; expectError( () => predicate.parse(input), - new CombinedError([ - new ExpectedValidationError('s.literal(V)', 'Expected values to be equals', input, 'a'), - new ExpectedValidationError('s.literal(V)', 'Expected values to be equals', input, 'b'), - new ExpectedValidationError('s.literal(V)', 'Expected values to be equals', input, 'c') - ]) + new CombinedError( + [ + new ExpectedValidationError('s.literal(V)', errorMessage, input, 'a'), + new ExpectedValidationError('s.literal(V)', errorMessage, input, 'b'), + new ExpectedValidationError('s.literal(V)', errorMessage, input, 'c') + ], + { + message: message ?? 'Received one or more errors' + } + ) ); }); }); diff --git a/tests/validators/instance.test.ts b/tests/validators/instance.test.ts index 8af5abdb..e89d778d 100644 --- a/tests/validators/instance.test.ts +++ b/tests/validators/instance.test.ts @@ -1,18 +1,18 @@ import { ExpectedValidationError, s } from '../../src'; import { expectClonedValidator, expectError } from '../common/macros/comparators'; -describe('InstanceValidator', () => { +describe.each(['custom message', undefined])('InstanceValidator (%s)', (message) => { class User { public constructor(public name: string) {} } - const predicate = s.instance(User); + const predicate = s.instance(User, { message }); test('GIVEN an instance of User THEN returns the given value', () => { expect(predicate.parse(new User('Sapphire'))).toStrictEqual(new User('Sapphire')); }); test('GIVEN anything which is not and instance of User THEN throws ValidationError', () => { - expectError(() => predicate.parse(123), new ExpectedValidationError('s.instance(V)', 'Expected', 123, User)); + expectError(() => predicate.parse(123), new ExpectedValidationError('s.instance(V)', message ?? 'Expected', 123, User)); }); test('GIVEN clone THEN returns similar instance', () => { diff --git a/tests/validators/lazy.test.ts b/tests/validators/lazy.test.ts index 074f4fc1..455bfaff 100644 --- a/tests/validators/lazy.test.ts +++ b/tests/validators/lazy.test.ts @@ -1,10 +1,10 @@ import { CombinedPropertyError, ExpectedConstraintError, MissingPropertyError, ValidationError, s, type SchemaOf } from '../../src'; import { expectError } from '../common/macros/comparators'; -describe('LazyValidator', () => { +describe.each(['custom message', undefined])('LazyValidator (%s)', (message) => { const predicate = s.lazy((value) => { - if (typeof value === 'boolean') return s.boolean.true; - return s.string; + if (typeof value === 'boolean') return s.boolean().true(); + return s.string({ message }); }); test.each([true, 'hello'])('GIVEN %j THEN returns the given value', (input) => { @@ -12,16 +12,17 @@ describe('LazyValidator', () => { }); test('GIVEN an invalid value THEN throw ValidationError', () => { - expectError(() => predicate.parse(123), new ValidationError('s.string', 'Expected a string primitive', 123)); + const errorMessage = message ?? 'Expected a string primitive'; + expectError(() => predicate.parse(123), new ValidationError('s.string()', errorMessage, 123)); }); }); -describe('NestedLazyValidator', () => { +describe.each(['custom message', undefined])('NestedLazyValidator (%s)', (message) => { const predicate = s.lazy((value) => { - if (typeof value === 'boolean') return s.boolean.true; + if (typeof value === 'boolean') return s.boolean().true(); return s.lazy((value) => { - if (typeof value === 'string') return s.string.lengthEqual(5); - return s.number; + if (typeof value === 'string') return s.string().lengthEqual(5, { message }); + return s.number({ message }); }); }); @@ -30,21 +31,22 @@ describe('NestedLazyValidator', () => { }); test('GIVEN an invalid value THEN throw ValidationError', () => { + const errorMessage = message ?? 'Invalid string length'; expectError( () => predicate.parse('Sapphire'), - new ExpectedConstraintError('s.string.lengthEqual', 'Invalid string length', 'Sapphire', 'expected.length === 5') + new ExpectedConstraintError('s.string().lengthEqual()', errorMessage, 'Sapphire', 'expected.length === 5') ); }); }); -describe('CircularLazyValidator', () => { +describe.each(['custom message', undefined])('CircularLazyValidator (%s)', (message) => { interface PredicateSchema { id: string; items: PredicateSchema; } const predicate: SchemaOf = s.object({ - id: s.string, + id: s.string({ message }), items: s.lazy>(() => predicate) }); @@ -58,15 +60,15 @@ describe('CircularLazyValidator', () => { }); }); -describe('PassingCircularLazyValidator', () => { +describe.each(['custom message', undefined])('PassingCircularLazyValidator (%s)', (message) => { interface PredicateSchema { id: string; items?: PredicateSchema; } const predicate: SchemaOf = s.object({ - id: s.string, - items: s.lazy>(() => predicate).optional + id: s.string({ message }), + items: s.lazy>(() => predicate).optional({ message }) }); test('GIVEN circular schema THEN return given value', () => { diff --git a/tests/validators/literal.test.ts b/tests/validators/literal.test.ts index 047feaa4..7a64ffd8 100644 --- a/tests/validators/literal.test.ts +++ b/tests/validators/literal.test.ts @@ -1,20 +1,28 @@ import { ExpectedValidationError, s } from '../../src'; import { expectClonedValidator, expectError } from '../common/macros/comparators'; -describe('LiteralValidator', () => { - const predicate = s.literal('sapphire'); +describe.each(['custom message', undefined])('LiteralValidator (%s)', (message) => { + const predicate = s.literal('sapphire', { + dateOptions: { + message + }, + equalsOptions: { + message + } + }); test('GIVEN a literal THEN returns the given value', () => { expect(predicate.parse('sapphire')).toBe('sapphire'); }); test('GIVEN anything which is not the literal THEN throws ExpectedValidationError', () => { - expectError(() => predicate.parse('hello'), new ExpectedValidationError('s.literal(V)', 'Expected values to be equals', 'hello', 'sapphire')); + const errorMessage = message ?? 'Expected values to be equals'; + expectError(() => predicate.parse('hello'), new ExpectedValidationError('s.literal(V)', errorMessage, 'hello', 'sapphire')); }); - test('GIVEN date literal THEN returns s.date.equal(V)', () => { + test('GIVEN date literal THEN returns s.date().equal(V)', () => { const date = new Date('2022-01-01'); - expectClonedValidator(s.literal(date), s.date.equal(date)); + expectClonedValidator(s.literal(date), s.date().equal(date)); }); test('GIVEN clone THEN returns similar instance', () => { diff --git a/tests/validators/map.test.ts b/tests/validators/map.test.ts index be2b33fa..0505a1fc 100644 --- a/tests/validators/map.test.ts +++ b/tests/validators/map.test.ts @@ -1,15 +1,16 @@ import { CombinedPropertyError, s, ValidationError } from '../../src'; import { expectClonedValidator, expectError } from '../common/macros/comparators'; -describe('MapValidator', () => { +describe.each(['custom message', undefined])('MapValidator (%s)', (message) => { const value = new Map([ ['a', 1], ['b', 2] ]); - const predicate = s.map(s.string, s.number); + const predicate = s.map(s.string({ message }), s.number({ message }), { message }); test('GIVEN a non-map THEN throws ValidationError', () => { - expectError(() => predicate.parse(false), new ValidationError('s.map(K, V)', 'Expected a map', false)); + const errorMessage = message ?? 'Expected a map'; + expectError(() => predicate.parse(false), new ValidationError('s.map(K, V)', errorMessage, false)); }); test('GIVEN a matching map THEN returns a map', () => { @@ -26,12 +27,17 @@ describe('MapValidator', () => { expectError( () => predicate.parse(map), - new CombinedPropertyError([ - [2, new ValidationError('s.string', 'Expected a string primitive', 2)], - ['foo', new ValidationError('s.number', 'Expected a number primitive', 'bar')], - [4, new ValidationError('s.string', 'Expected a string primitive', 4)], - [4, new ValidationError('s.number', 'Expected a number primitive', 'buzz')] - ]) + new CombinedPropertyError( + [ + [2, new ValidationError('s.string()', message ?? 'Expected a string primitive', 2)], + ['foo', new ValidationError('s.number()', message ?? 'Expected a number primitive', 'bar')], + [4, new ValidationError('s.string()', message ?? 'Expected a string primitive', 4)], + [4, new ValidationError('s.number()', message ?? 'Expected a number primitive', 'buzz')] + ], + { + message: message ?? 'Received one or more errors' + } + ) ); }); diff --git a/tests/validators/nativeEnum.test.ts b/tests/validators/nativeEnum.test.ts index 015c4c31..9c6eb0fa 100644 --- a/tests/validators/nativeEnum.test.ts +++ b/tests/validators/nativeEnum.test.ts @@ -1,12 +1,13 @@ import { s, UnknownEnumValueError, ValidationError } from '../../src'; import { expectClonedValidator, expectError } from '../common/macros/comparators'; -describe('NativeEnumValidator', () => { +describe.each(['custom message', undefined])('NativeEnumValidator (%s)', (message) => { describe('invalid inputs', () => { - const predicate = s.nativeEnum({ hello: 'world' }); + const predicate = s.nativeEnum({ hello: 'world' }, { message }); test.each([true, null, undefined, {}])('GIVEN %j THEN throws ValidationError', (value) => { - expectError(() => predicate.parse(value), new ValidationError('s.nativeEnum(T)', 'Expected the value to be a string or number', value)); + const errorMessage = message ?? 'Expected the value to be a string or number'; + expectError(() => predicate.parse(value), new ValidationError('s.nativeEnum(T)', errorMessage, value)); }); }); @@ -15,7 +16,7 @@ describe('NativeEnumValidator', () => { Hi = 'hi' } - const stringPredicate = s.nativeEnum(StringEnum); + const stringPredicate = s.nativeEnum(StringEnum, { message }); test.each([ ['Hi', StringEnum.Hi], @@ -25,7 +26,8 @@ describe('NativeEnumValidator', () => { }); it('GIVEN a number input for a string enum THEN throws ValidationError', () => { - expectError(() => stringPredicate.parse(1), new ValidationError('s.nativeEnum(T)', 'Expected the value to be a string', 1)); + const errorMessage = message ?? 'Expected the value to be a string'; + expectError(() => stringPredicate.parse(1), new ValidationError('s.nativeEnum(T)', errorMessage, 1)); }); }); @@ -35,7 +37,7 @@ describe('NativeEnumValidator', () => { Kyra, Favna } - const numberPredicate = s.nativeEnum(NumberEnum); + const numberPredicate = s.nativeEnum(NumberEnum, { message }); test.each([ ['Vladdy', NumberEnum.Vladdy], @@ -51,7 +53,7 @@ describe('NativeEnumValidator', () => { Vladdy = 420 } - const mixedPredicate = s.nativeEnum(MixedEnum); + const mixedPredicate = s.nativeEnum(MixedEnum, { message }); test.each([ ['Sapphire', MixedEnum.Sapphire], @@ -64,10 +66,11 @@ describe('NativeEnumValidator', () => { }); describe('valid input but invalid enum value', () => { - const predicate = s.nativeEnum({ owo: 42 }); + const predicate = s.nativeEnum({ owo: 42 }, { message }); test.each(['uwu', 69])('GIVEN valid type for input but not part of enum (%j) THEN throws ValidationError', (value) => { - expectError(() => predicate.parse(value), new UnknownEnumValueError(value, ['owo'], new Map([['owo', 42]]))); + const errorMessage = message ?? 'Expected the value to be one of the following enum values:'; + expectError(() => predicate.parse(value), new UnknownEnumValueError(value, ['owo'], new Map([['owo', 42]]), { message: errorMessage })); }); }); diff --git a/tests/validators/never.test.ts b/tests/validators/never.test.ts index c4924e77..d2323364 100644 --- a/tests/validators/never.test.ts +++ b/tests/validators/never.test.ts @@ -1,10 +1,10 @@ import { s, ValidationError } from '../../src'; import { expectError } from '../common/macros/comparators'; -describe('NeverValidator', () => { - const predicate = s.never; +describe.each(['custom message', undefined])('NeverValidator (%s)', (message) => { + const predicate = s.never({ message }); test.each([123, 'hello'])('GIVEN %j THEN throws ConstraintError', (input) => { - expectError(() => predicate.parse(input), new ValidationError('s.never', 'Expected a value to not be passed', input)); + expectError(() => predicate.parse(input), new ValidationError('s.never()', message ?? 'Expected a value to not be passed', input)); }); }); diff --git a/tests/validators/null.test.ts b/tests/validators/null.test.ts index 81f72eff..e56e6a4e 100644 --- a/tests/validators/null.test.ts +++ b/tests/validators/null.test.ts @@ -1,14 +1,17 @@ import { ExpectedValidationError, s } from '../../src'; import { expectError } from '../common/macros/comparators'; -describe('NullValidator', () => { - const predicate = s.null; +describe.each(['custom message', undefined])('NullValidator (%s)', (message) => { + const predicate = s.null({ message }); test('GIVEN null THEN returns null', () => { expect(predicate.parse(null)).toBe(null); }); test.each([undefined, 123, 'Hello', {}])('GIVEN %j THEN throws ExpectedValidationError', (input) => { - expectError(() => predicate.parse(input), new ExpectedValidationError('s.literal(V)', 'Expected values to be equals', input, null)); + expectError( + () => predicate.parse(input), + new ExpectedValidationError('s.literal(V)', message ?? 'Expected values to be equals', input, null) + ); }); }); diff --git a/tests/validators/nullish.test.ts b/tests/validators/nullish.test.ts index bc51fe26..7b346f81 100644 --- a/tests/validators/nullish.test.ts +++ b/tests/validators/nullish.test.ts @@ -1,14 +1,14 @@ import { s, ValidationError } from '../../src'; import { expectError } from '../common/macros/comparators'; -describe('NullishValidator', () => { - const predicate = s.nullish; +describe.each(['custom message', undefined])('NullishValidator (%s)', (message) => { + const predicate = s.nullish({ message }); test.each([null, undefined])('GIVEN %j THEN returns the given value', (input) => { expect(predicate.parse(input)).toBe(input); }); test.each([123, 'hello'])('GIVEN %j THEN throws ValidationError', (input) => { - expectError(() => predicate.parse(input), new ValidationError('s.nullish', 'Expected undefined or null', input)); + expectError(() => predicate.parse(input), new ValidationError('s.nullish()', message ?? 'Expected undefined or null', input)); }); }); diff --git a/tests/validators/number.test.ts b/tests/validators/number.test.ts index 9c04c7e8..15fa01ef 100644 --- a/tests/validators/number.test.ts +++ b/tests/validators/number.test.ts @@ -2,23 +2,25 @@ import { ExpectedConstraintError, s, ValidationError } from '../../src'; import { expectError } from '../common/macros/comparators'; const safeInteger = 42; -// eslint-disable-next-line @typescript-eslint/no-loss-of-precision -const unsafeInteger = 242043489611808769; +const unsafeInteger = Number.MAX_SAFE_INTEGER + 1; -describe('NumberValidator', () => { - const predicate = s.number; +describe.each(['custom message', undefined])('NumberValidator (%s)', (message) => { + const predicate = s.number({ message }); + + const invalidNumberValueErrorMessage = message ?? 'Invalid number value'; test('GIVEN a number THEN returns a number', () => { expect(predicate.parse(42)).toBe(42); }); test('GIVEN a non-number THEN throws ValidationError', () => { - expectError(() => predicate.parse('Hello there'), new ValidationError('s.number', 'Expected a number primitive', 'Hello there')); + const errorMessage = message ?? 'Expected a number primitive'; + expectError(() => predicate.parse('Hello there'), new ValidationError('s.number()', errorMessage, 'Hello there')); }); describe('Comparators', () => { describe('lessThan', () => { - const ltPredicate = s.number.lessThan(42); + const ltPredicate = s.number().lessThan(42, { message }); test.each([10])('GIVEN %d THEN returns given value', (value) => { expect(ltPredicate.parse(value)).toBe(value); @@ -27,13 +29,13 @@ describe('NumberValidator', () => { test.each([42, 100])('GIVEN %d THEN throws ConstraintError', (value) => { expectError( () => ltPredicate.parse(value), - new ExpectedConstraintError('s.number.lessThan', 'Invalid number value', value, 'expected < 42') + new ExpectedConstraintError('s.number().lessThan()', invalidNumberValueErrorMessage, value, 'expected < 42') ); }); }); describe('lessThanOrEqual', () => { - const lePredicate = s.number.lessThanOrEqual(42); + const lePredicate = s.number().lessThanOrEqual(42, { message }); test.each([10, 42])('GIVEN %d THEN returns given value', (input) => { expect(lePredicate.parse(input)).toBe(input); @@ -42,13 +44,13 @@ describe('NumberValidator', () => { test.each([100])('GIVEN %d THEN throws ConstraintError', (input) => { expectError( () => lePredicate.parse(input), - new ExpectedConstraintError('s.number.lessThanOrEqual', 'Invalid number value', input, 'expected <= 42') + new ExpectedConstraintError('s.number().lessThanOrEqual()', invalidNumberValueErrorMessage, input, 'expected <= 42') ); }); }); describe('greaterThan', () => { - const gtPredicate = s.number.greaterThan(42); + const gtPredicate = s.number().greaterThan(42, { message }); test.each([100])('GIVEN %d THEN returns given value', (value) => { expect(gtPredicate.parse(value)).toBe(value); @@ -57,13 +59,13 @@ describe('NumberValidator', () => { test.each([10, 42])('GIVEN %d THEN throws ConstraintError', (value) => { expectError( () => gtPredicate.parse(value), - new ExpectedConstraintError('s.number.greaterThan', 'Invalid number value', value, 'expected > 42') + new ExpectedConstraintError('s.number().greaterThan()', invalidNumberValueErrorMessage, value, 'expected > 42') ); }); }); describe('greaterThanOrEqual', () => { - const gePredicate = s.number.greaterThanOrEqual(42); + const gePredicate = s.number().greaterThanOrEqual(42, { message }); test.each([42, 100])('GIVEN %d THEN returns given value', (value) => { expect(gePredicate.parse(value)).toBe(value); @@ -72,13 +74,13 @@ describe('NumberValidator', () => { test.each([10])('GIVEN %d THEN throws ConstraintError', (value) => { expectError( () => gePredicate.parse(value), - new ExpectedConstraintError('s.number.greaterThanOrEqual', 'Invalid number value', value, 'expected >= 42') + new ExpectedConstraintError('s.number().greaterThanOrEqual()', invalidNumberValueErrorMessage, value, 'expected >= 42') ); }); }); describe('equal', () => { - const eqPredicate = s.number.equal(42); + const eqPredicate = s.number().equal(42, { message }); test.each([42])('GIVEN %d THEN returns given value', (value) => { expect(eqPredicate.parse(value)).toBe(value); @@ -87,13 +89,13 @@ describe('NumberValidator', () => { test.each([10, 100])('GIVEN %d THEN throws ConstraintError', (value) => { expectError( () => eqPredicate.parse(value), - new ExpectedConstraintError('s.number.equal', 'Invalid number value', value, 'expected === 42') + new ExpectedConstraintError('s.number().equal()', invalidNumberValueErrorMessage, value, 'expected === 42') ); }); }); describe('equal(NaN)', () => { - const eqNanPredicate = s.number.equal(NaN); + const eqNanPredicate = s.number().equal(NaN, { message }); test.each([NaN])('GIVEN %d THEN returns given value', (input) => { expect(eqNanPredicate.parse(input)).toBe(input); @@ -102,13 +104,13 @@ describe('NumberValidator', () => { test.each([safeInteger, unsafeInteger, 42.1, Infinity, -Infinity])('GIVEN %d THEN throws a ConstraintError', (input) => { expectError( () => eqNanPredicate.parse(input), - new ExpectedConstraintError('s.number.equal(NaN)', 'Invalid number value', input, 'expected === NaN') + new ExpectedConstraintError('s.number().equal(NaN)', invalidNumberValueErrorMessage, input, 'expected === NaN') ); }); }); describe('notEqual', () => { - const nePredicate = s.number.notEqual(42); + const nePredicate = s.number().notEqual(42, { message }); test.each([10, 100])('GIVEN %d THEN returns given value', (value) => { expect(nePredicate.parse(value)).toBe(value); @@ -117,13 +119,13 @@ describe('NumberValidator', () => { test.each([42])('GIVEN %d THEN throws ConstraintError', (value) => { expectError( () => nePredicate.parse(value), - new ExpectedConstraintError('s.number.notEqual', 'Invalid number value', value, 'expected !== 42') + new ExpectedConstraintError('s.number().notEqual()', invalidNumberValueErrorMessage, value, 'expected !== 42') ); }); }); describe('notEqual(NaN)', () => { - const neNanPredicate = s.number.notEqual(NaN); + const neNanPredicate = s.number().notEqual(NaN, { message }); test.each([safeInteger, unsafeInteger, 42.1, Infinity, -Infinity])('GIVEN %d THEN returns given value', (input) => { expect(neNanPredicate.parse(input)).toBe(input); @@ -132,7 +134,7 @@ describe('NumberValidator', () => { test.each([NaN])('GIVEN %d THEN throws a ConstraintError', (input) => { expectError( () => neNanPredicate.parse(input), - new ExpectedConstraintError('s.number.notEqual(NaN)', 'Invalid number value', input, 'expected !== NaN') + new ExpectedConstraintError('s.number().notEqual(NaN)', invalidNumberValueErrorMessage, input, 'expected !== NaN') ); }); }); @@ -140,22 +142,23 @@ describe('NumberValidator', () => { describe('Constraints', () => { describe('Integer', () => { - const intPredicate = s.number.int; + const intPredicate = s.number().int({ message }); test.each([safeInteger, unsafeInteger])('GIVEN %d THEN returns given value', (input) => { expect(intPredicate.parse(input)).toBe(input); }); test.each([42.1, Infinity, NaN])('GIVEN %d THEN throws a ConstraintError', (input) => { + const errorMessage = message ?? 'Given value is not an integer'; expectError( () => intPredicate.parse(input), - new ExpectedConstraintError('s.number.int', 'Given value is not an integer', input, 'Number.isInteger(expected) to be true') + new ExpectedConstraintError('s.number().int()', errorMessage, input, 'Number.isInteger(expected) to be true') ); }); }); describe('SafeInteger', () => { - const safeIntPredicate = s.number.safeInt; + const safeIntPredicate = s.number().safeInt({ message }); test.each([safeInteger])('GIVEN %d THEN returns given value', (input) => { expect(safeIntPredicate.parse(input)).toBe(input); @@ -165,8 +168,8 @@ describe('NumberValidator', () => { expectError( () => safeIntPredicate.parse(input), new ExpectedConstraintError( - 's.number.safeInt', - 'Given value is not a safe integer', + 's.number().safeInt()', + message ?? 'Given value is not a safe integer', input, 'Number.isSafeInteger(expected) to be true' ) @@ -175,7 +178,7 @@ describe('NumberValidator', () => { }); describe('Positive', () => { - const positivePredicate = s.number.positive; + const positivePredicate = s.number().positive({ message }); test.each([safeInteger, unsafeInteger, 42.1, Infinity])('GIVEN %d THEN returns given value', (input) => { expect(positivePredicate.parse(input)).toBe(input); @@ -184,13 +187,13 @@ describe('NumberValidator', () => { test.each([-safeInteger, -unsafeInteger, -42.1, -Infinity])('GIVEN %d THEN throws a ConstraintError', (input) => { expectError( () => positivePredicate.parse(input), - new ExpectedConstraintError('s.number.greaterThanOrEqual', 'Invalid number value', input, 'expected >= 0') + new ExpectedConstraintError('s.number().greaterThanOrEqual()', invalidNumberValueErrorMessage, input, 'expected >= 0') ); }); }); describe('Negative', () => { - const positivePredicate = s.number.negative; + const positivePredicate = s.number().negative({ message }); test.each([-safeInteger, -unsafeInteger, -42.1, -Infinity])('GIVEN %d THEN returns given value', (input) => { expect(positivePredicate.parse(input)).toBe(input); @@ -199,13 +202,13 @@ describe('NumberValidator', () => { test.each([safeInteger, unsafeInteger, 42.1, Infinity])('GIVEN %d THEN throws a ConstraintError', (input) => { expectError( () => positivePredicate.parse(input), - new ExpectedConstraintError('s.number.lessThan', 'Invalid number value', input, 'expected < 0') + new ExpectedConstraintError('s.number().lessThan()', invalidNumberValueErrorMessage, input, 'expected < 0') ); }); }); describe('Finite', () => { - const finitePredicate = s.number.finite; + const finitePredicate = s.number().finite({ message }); test.each([safeInteger, unsafeInteger, 42.1])('GIVEN %d THEN returns given value', (input) => { expect(finitePredicate.parse(input)).toBe(input); @@ -214,13 +217,18 @@ describe('NumberValidator', () => { test.each([Infinity, -Infinity, NaN])('GIVEN %d THEN throws a ConstraintError', (input) => { expectError( () => finitePredicate.parse(input), - new ExpectedConstraintError('s.number.finite', 'Given value is not finite', input, 'Number.isFinite(expected) to be true') + new ExpectedConstraintError( + 's.number().finite()', + message ?? 'Given value is not finite', + input, + 'Number.isFinite(expected) to be true' + ) ); }); }); describe('DivisibleBy', () => { - const divisibleByPredicate = s.number.divisibleBy(5); + const divisibleByPredicate = s.number().divisibleBy(5, { message }); test.each([5, 10, 20, 500])('GIVEN %d THEN returns given value', (input) => { expect(divisibleByPredicate.parse(input)).toBe(input); @@ -229,7 +237,7 @@ describe('NumberValidator', () => { test.each([safeInteger, unsafeInteger, 6, 42.1, Infinity, -Infinity, NaN])('GIVEN %d THEN throws a ConstraintError', (input) => { expectError( () => divisibleByPredicate.parse(input), - new ExpectedConstraintError('s.number.divisibleBy', 'Number is not divisible', input, 'expected % 5 === 0') + new ExpectedConstraintError('s.number().divisibleBy()', message ?? 'Number is not divisible', input, 'expected % 5 === 0') ); }); }); @@ -237,7 +245,7 @@ describe('NumberValidator', () => { describe('Transformers', () => { describe('abs', () => { - const absPredicate = s.number.abs; + const absPredicate = s.number().abs({ message }); test.each([safeInteger, unsafeInteger, 42.1, Infinity])('GIVEN %d THEN returns transformed the result from Math.abs', (input) => { expect(absPredicate.parse(input)).toBe(Math.abs(input)); @@ -245,7 +253,7 @@ describe('NumberValidator', () => { }); describe('sign', () => { - const signPredicate = s.number.sign; + const signPredicate = s.number().sign({ message }); test.each([safeInteger, unsafeInteger, 42.1, Infinity])('GIVEN %d THEN returns transformed the result from Math.sign', (input) => { expect(signPredicate.parse(input)).toBe(Math.sign(input)); @@ -253,7 +261,7 @@ describe('NumberValidator', () => { }); describe('trunc', () => { - const truncPredicate = s.number.trunc; + const truncPredicate = s.number().trunc({ message }); test.each([safeInteger, unsafeInteger, 42.1, Infinity])('GIVEN %d THEN returns transformed the result from Math.trunc', (input) => { expect(truncPredicate.parse(input)).toBe(Math.trunc(input)); @@ -261,7 +269,7 @@ describe('NumberValidator', () => { }); describe('floor', () => { - const floorPredicate = s.number.floor; + const floorPredicate = s.number().floor({ message }); test.each([safeInteger, unsafeInteger, 42.1, Infinity])('GIVEN %d THEN returns transformed the result from Math.floor', (input) => { expect(floorPredicate.parse(input)).toBe(Math.floor(input)); @@ -269,7 +277,7 @@ describe('NumberValidator', () => { }); describe('fround', () => { - const froundPredicate = s.number.fround; + const froundPredicate = s.number().fround({ message }); test.each([safeInteger, unsafeInteger, 42.1, Infinity])('GIVEN %d THEN returns transformed the result from Math.fround', (input) => { expect(froundPredicate.parse(input)).toBe(Math.fround(input)); @@ -277,7 +285,7 @@ describe('NumberValidator', () => { }); describe('round', () => { - const roundPredicate = s.number.round; + const roundPredicate = s.number().round({ message }); test.each([safeInteger, unsafeInteger, 42.1, Infinity])('GIVEN %d THEN returns transformed the result from Math.round', (input) => { expect(roundPredicate.parse(input)).toBe(Math.round(input)); @@ -285,7 +293,7 @@ describe('NumberValidator', () => { }); describe('ceil', () => { - const ceilPredicate = s.number.ceil; + const ceilPredicate = s.number().ceil({ message }); test.each([safeInteger, unsafeInteger, 42.1, Infinity])('GIVEN %d THEN returns transformed the result from Math.ceil', (input) => { expect(ceilPredicate.parse(input)).toBe(Math.ceil(input)); @@ -293,8 +301,8 @@ describe('NumberValidator', () => { }); describe('default', () => { - const defaultPredicate = s.number.default(5); - const defaultFunctionPredicate = s.number.default(() => 5); + const defaultPredicate = s.number({ message }).default(5); + const defaultFunctionPredicate = s.number({ message }).default(() => 5); test.each([safeInteger, unsafeInteger, 42.1, Infinity])('GIVEN %d THEN returns the input', (input) => { expect(defaultPredicate.parse(input)).toBe(input); diff --git a/tests/validators/object.test.ts b/tests/validators/object.test.ts index 46470d9c..4ba0bc1c 100644 --- a/tests/validators/object.test.ts +++ b/tests/validators/object.test.ts @@ -10,25 +10,33 @@ import { } from '../../src'; import { expectError } from '../common/macros/comparators'; -describe('ObjectValidator', () => { - const predicate = s.object({ - username: s.string, - password: s.string - }); +describe.each(['custom message', undefined])('ObjectValidator (%s)', (message) => { + const unexpectedPropertyErrorMessage = message ?? 'Received unexpected property'; + const oneOrMoreErrorsErrorMessage = message ?? 'Received one or more errors'; + const requiredPropertyMissingErrorMessage = message ?? 'A required property is missing'; + const expectedStringPrimitedErrorMessage = message ?? 'Expected a string primitive'; + + const predicate = s.object( + { + username: s.string({ message }), + password: s.string({ message }) + }, + { message } + ); test('GIVEN a non-object value THEN throws ValidationError', () => { expectError( () => predicate.parse('hello'), - new ValidationError('s.object(T)', 'Expected the value to be an object, but received string instead', 'hello') + new ValidationError('s.object(T)', message ?? 'Expected the value to be an object, but received string instead', 'hello') ); }); test('GIVEN a null object value THEN throws ValidationError', () => { - expectError(() => predicate.parse(null), new ValidationError('s.object(T)', 'Expected the value to not be null', null)); + expectError(() => predicate.parse(null), new ValidationError('s.object(T)', message ?? 'Expected the value to not be null', null)); }); test('GIVEN an array value THEN throws ValidationError', () => { - expectError(() => predicate.parse([]), new ValidationError('s.object(T)', 'Expected the value to not be an array', [])); + expectError(() => predicate.parse([]), new ValidationError('s.object(T)', message ?? 'Expected the value to not be an array', [])); }); test('GIVEN a valid object THEN returns processed object', () => { @@ -38,59 +46,84 @@ describe('ObjectValidator', () => { test('GIVEN mismatching in one property THEN throws CombinedError with one error', () => { expectError( () => predicate.parse({ username: 42, password: 'helloworld' }), - new CombinedPropertyError([ - // - ['username', new ValidationError('s.string', 'Expected a string primitive', 42)] - ]) + new CombinedPropertyError( + [ + // + ['username', new ValidationError('s.string()', message ?? 'Expected a string primitive', 42)] + ], + { + message: message ?? 'Received one or more errors' + } + ) ); }); test('GIVEN mismatching in two properties THEN throws CombinedError with two errors', () => { expectError( () => predicate.parse({ username: 42, password: true }), - new CombinedPropertyError([ - ['username', new ValidationError('s.string', 'Expected a string primitive', 42)], - ['password', new ValidationError('s.string', 'Expected a string primitive', true)] - ]) + new CombinedPropertyError( + [ + ['username', new ValidationError('s.string()', message ?? 'Expected a string primitive', 42)], + ['password', new ValidationError('s.string()', message ?? 'Expected a string primitive', true)] + ], + { + message: message ?? 'Received one or more errors' + } + ) ); }); test('GIVEN LiteralValidator with undefined THEN it should be counted as a possibly undefined key', () => { - const predicate = s.object({ - owo: s.undefined - }); + const predicate = s.object( + { + owo: s.undefined({ message }) + }, + { message } + ); expect(predicate['possiblyUndefinedKeys'].size).toEqual(1); }); test('GIVEN LiteralValidator with null THEN it should be counted as a required key', () => { - const predicate = s.object({ - owo: s.null - }); + const predicate = s.object( + { + owo: s.null({ message }) + }, + { message } + ); expect(predicate['requiredKeys'].size).toEqual(1); }); test('GIVEN NullishValidator then it should count as a possibly undefined key', () => { - const predicate = s.object({ - owo: s.nullish - }); + const predicate = s.object( + { + owo: s.nullish({ message }) + }, + { message } + ); expect(predicate['possiblyUndefinedKeys'].size).toEqual(1); }); test('GIVEN UnionValidator with NullishValidator inside THEN it should be counted as a possibly undefined key', () => { - const predicate = s.object({ - owo: s.string.nullish - }); + const predicate = s.object( + { + owo: s.string({ message }).nullish({ message }) + }, + { message } + ); expect(predicate['possiblyUndefinedKeys'].size).toEqual(1); }); test('GIVEN a validator with a default value THEN it should be counted as a possibly undefined key with defaults', () => { - const predicate = s.object({ - owo: s.string.default('hello') - }); + const predicate = s.object( + { + owo: s.string({ message }).default('hello') + }, + { message } + ); expect(predicate['possiblyUndefinedKeysWithDefaults'].size).toEqual(1); @@ -98,16 +131,30 @@ describe('ObjectValidator', () => { }); test("GIVEN UnionValidator with LiteralValidator with 'owo' THEN it should be counted as a required key", () => { - const predicate = s.object({ - owo: s.union(s.literal('owo'), s.number) - }); + const predicate = s.object( + { + owo: s.union( + [ + s.literal('owo', { + dateOptions: { + message + }, + equalsOptions: { message } + }), + s.number({ message }) + ], + { message } + ) + }, + { message } + ); expect(predicate['requiredKeys'].size).toEqual(1); }); test('GIVEN UnionValidator with LiteralValidator with null THEN it should be counted as a required key', () => { const predicate = s.object({ - owo: s.union(s.string, s.literal(null)) + owo: s.union([s.string({ message }), s.literal(null, { dateOptions: { message }, equalsOptions: { message } })], { message }) }); expect(predicate['requiredKeys'].size).toEqual(1); @@ -115,20 +162,23 @@ describe('ObjectValidator', () => { // Unit test for lines 167-190 of ObjectValidator.ts test('GIVEN a big schema THEN it should validate using the shortest possible solution', () => { - const predicate = s.object({ - a: s.string, - b: s.string, - c: s.string.optional, - d: s.string.optional, - e: s.string.optional - }); + const predicate = s.object( + { + a: s.string({ message }), + b: s.string({ message }), + c: s.string({ message }).optional({ message }), + d: s.string({ message }).optional({ message }), + e: s.string({ message }).optional({ message }) + }, + { message } + ); expect(predicate.parse({ a: 'a', b: 'b', c: 'c', d: 'd' })).toStrictEqual({ a: 'a', b: 'b', c: 'c', d: 'd' }); expect(predicate.parse({ a: 'a', b: 'b', c: 'c', d: 'd', e: 'e', f: 'f', g: 'g' })).toStrictEqual({ a: 'a', b: 'b', c: 'c', d: 'd', e: 'e' }); }); describe('Strict', () => { - const strictPredicate = predicate.strict; + const strictPredicate = predicate.strict({ message }); test('GIVEN matching keys and values THEN returns no errors', () => { expect(strictPredicate.parse({ username: 'Sapphire', password: 'helloworld' })).toStrictEqual({ @@ -140,37 +190,55 @@ describe('ObjectValidator', () => { test('GIVEN mismatching in one property THEN throws CombinedError with one error', () => { expectError( () => strictPredicate.parse({ username: 42, password: 'helloworld' }), - new CombinedPropertyError([ - // - ['username', new ValidationError('s.string', 'Expected a string primitive', 42)] - ]) + new CombinedPropertyError( + [ + // + ['username', new ValidationError('s.string()', expectedStringPrimitedErrorMessage, 42)] + ], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); test('GIVEN mismatching in one property and one unknown key THEN throws CombinedError with two errors', () => { expectError( () => strictPredicate.parse({ username: 42, password: 'helloworld', foo: 'bar' }), - new CombinedPropertyError([ - ['username', new ValidationError('s.string', 'Expected a string primitive', 42)], - ['foo', new UnknownPropertyError('foo', 'bar')] - ]) + new CombinedPropertyError( + [ + ['username', new ValidationError('s.string()', expectedStringPrimitedErrorMessage, 42)], + ['foo', new UnknownPropertyError('foo', 'bar', { message: unexpectedPropertyErrorMessage })] + ], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); test('GIVEN mismatching in one property and one missing key THEN throws CombinedError with two errors', () => { expectError( () => strictPredicate.parse({ username: 42, foo: 'owo' }), - new CombinedPropertyError([ - ['username', new ValidationError('s.string', 'Expected a string primitive', 42)], - ['password', new MissingPropertyError('password')], - ['foo', new UnknownPropertyError('foo', 'owo')] - ]) + new CombinedPropertyError( + [ + ['username', new ValidationError('s.string()', expectedStringPrimitedErrorMessage, 42)], + ['password', new MissingPropertyError('password', { message: requiredPropertyMissingErrorMessage })], + ['foo', new UnknownPropertyError('foo', 'owo', { message: unexpectedPropertyErrorMessage })] + ], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); - const optionalStrict = strictPredicate.extend({ - optionalKey: s.string.optional - }); + const optionalStrict = strictPredicate.extend( + { + optionalKey: s.string({ message }).optional({ message }) + }, + { message } + ); test('GIVEN matching keys and values without optional keys THEN returns no errors', () => { expect(optionalStrict.parse({ username: 'Sapphire', password: 'helloworld' })).toStrictEqual({ @@ -187,7 +255,7 @@ describe('ObjectValidator', () => { }); describe('Ignore', () => { - const ignorePredicate = predicate.ignore; + const ignorePredicate = predicate.ignore({ message }); test('GIVEN matching keys and values THEN returns no errors', () => { expect(ignorePredicate.parse({ username: 'Sapphire', password: 'helloworld', email: 'foo@bar.com' })).toStrictEqual({ @@ -199,32 +267,42 @@ describe('ObjectValidator', () => { test('GIVEN missing keys THEN throws CombinedPropertyError with MissingPropertyError', () => { expectError( () => ignorePredicate.parse({ username: 'Sapphire' }), - new CombinedPropertyError([['password', new MissingPropertyError('password')]]) + new CombinedPropertyError([['password', new MissingPropertyError('password', { message: requiredPropertyMissingErrorMessage })]], { + message: oneOrMoreErrorsErrorMessage + }) ); }); test('GIVEN matching keys with an optional key that fails validation, THEN throws CombinedPropertyError with ValidationError', () => { - const predicate = ignorePredicate.extend({ - owo: s.boolean.optional - }); + const predicate = ignorePredicate.extend( + { + owo: s.boolean().optional() + }, + { message } + ); expectError( () => predicate.parse({ username: 'Sapphire', password: 'helloworld', owo: 'owo' }), - new CombinedPropertyError([ + new CombinedPropertyError( [ - 'owo', - new CombinedError([ - new ExpectedValidationError('s.literal(V)', 'Expected values to be equals', 'owo', undefined), - new ValidationError('s.boolean', 'Expected a boolean primitive', 'owo') - ]) - ] - ]) + [ + 'owo', + new CombinedError([ + new ExpectedValidationError('s.literal(V)', 'Expected values to be equals', 'owo', undefined), + new ValidationError('s.boolean()', 'Expected a boolean primitive', 'owo') + ]) + ] + ], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); }); describe('Passthrough', () => { - const passthroughPredicate = predicate.passthrough; + const passthroughPredicate = predicate.passthrough({ message }); test('GIVEN matching keys and values THEN returns no errors', () => { expect(passthroughPredicate.parse({ username: 'Sapphire', password: 'helloworld', email: 'foo@bar.com' })).toStrictEqual({ @@ -237,13 +315,15 @@ describe('ObjectValidator', () => { test('GIVEN missing keys THEN throws CombinedPropertyError with MissingPropertyError', () => { expectError( () => passthroughPredicate.parse({ username: 'Sapphire' }), - new CombinedPropertyError([['password', new MissingPropertyError('password')]]) + new CombinedPropertyError([['password', new MissingPropertyError('password', { message: requiredPropertyMissingErrorMessage })]], { + message: oneOrMoreErrorsErrorMessage + }) ); }); }); describe('Partial', () => { - const partialPredicate = predicate.partial; + const partialPredicate = predicate.partial({ message }); test('GIVEN empty object THEN returns an empty object', () => { expect(partialPredicate.parse({})).toStrictEqual({}); @@ -251,7 +331,7 @@ describe('ObjectValidator', () => { }); describe('Required', () => { - const partialPredicate = predicate.partial.required; + const partialPredicate = predicate.partial({ message }).required({ message }); test('GIVEN empty object THEN returns an empty object', () => { expect( @@ -268,17 +348,22 @@ describe('ObjectValidator', () => { test('GIVEN empty object THEN returns an empty object', () => { expectError( () => partialPredicate.parse({}), - new CombinedPropertyError([ - ['username', new MissingPropertyError('username')], - ['password', new MissingPropertyError('password')] - ]) + new CombinedPropertyError( + [ + ['username', new MissingPropertyError('username', { message: requiredPropertyMissingErrorMessage })], + ['password', new MissingPropertyError('password', { message: requiredPropertyMissingErrorMessage })] + ], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); }); describe('Extend', () => { test('GIVEN a plain object THEN returns a predicate validator with merged shapes', () => { - const extendPredicate = predicate.extend({ foo: s.number }); + const extendPredicate = predicate.extend({ foo: s.number() }, { message }); expect(Object.keys(extendPredicate.shape)).toStrictEqual(['username', 'password', 'foo']); expect(extendPredicate.parse({ username: 'Sapphire', password: 'helloworld', foo: 42 })).toStrictEqual({ @@ -289,7 +374,7 @@ describe('ObjectValidator', () => { }); test('GIVEN an object predicate THEN returns a predicate validator with merged shapes', () => { - const extendPredicate = predicate.extend(s.object({ foo: s.number })); + const extendPredicate = predicate.extend(s.object({ foo: s.number() }), { message }); expect(Object.keys(extendPredicate.shape)).toStrictEqual(['username', 'password', 'foo']); expect(extendPredicate.parse({ username: 'Sapphire', password: 'helloworld', foo: 42 })).toStrictEqual({ @@ -302,21 +387,21 @@ describe('ObjectValidator', () => { describe('Pick', () => { test('GIVEN no keys THEN returns an empty predicate validator', () => { - const pickPredicate = predicate.pick([]); + const pickPredicate = predicate.pick([], { message }); expect(Object.keys(pickPredicate.shape)).toStrictEqual([]); expect(pickPredicate.parse({ username: 'Sapphire', password: 'helloworld' })).toStrictEqual({}); }); test('GIVEN one key THEN returns a subset of the object predicate', () => { - const pickPredicate = predicate.pick(['password']); + const pickPredicate = predicate.pick(['password'], { message }); expect(Object.keys(pickPredicate.shape)).toStrictEqual(['password']); expect(pickPredicate.parse({ username: 'Sapphire', password: 'helloworld' })).toStrictEqual({ password: 'helloworld' }); }); test('GIVEN an unknown key THEN is ignored from the new object predicate', () => { - const pickPredicate = predicate.pick(['password', 'foo' as any]); + const pickPredicate = predicate.pick(['password', 'foo' as any], { message }); expect(Object.keys(pickPredicate.shape)).toStrictEqual(['password']); expect(pickPredicate.parse({ username: 'Sapphire', password: 'helloworld' })).toStrictEqual({ password: 'helloworld' }); @@ -325,7 +410,7 @@ describe('ObjectValidator', () => { describe('Omit', () => { test('GIVEN no keys THEN returns a clone of the predicate validator', () => { - const pickPredicate = predicate.omit([]); + const pickPredicate = predicate.omit([], { message }); expect(Object.keys(pickPredicate.shape)).toStrictEqual(['username', 'password']); expect(pickPredicate.parse({ username: 'Sapphire', password: 'helloworld' })).toStrictEqual({ @@ -335,14 +420,14 @@ describe('ObjectValidator', () => { }); test('GIVEN one key THEN returns a subset of the object predicate', () => { - const pickPredicate = predicate.omit(['password']); + const pickPredicate = predicate.omit(['password'], { message }); expect(Object.keys(pickPredicate.shape)).toStrictEqual(['username']); expect(pickPredicate.parse({ username: 'Sapphire', password: 'helloworld' })).toStrictEqual({ username: 'Sapphire' }); }); test('GIVEN an unknown key THEN is ignored from the new object predicate', () => { - const pickPredicate = predicate.omit(['password', 'foo' as any]); + const pickPredicate = predicate.omit(['password', 'foo' as any], { message }); expect(Object.keys(pickPredicate.shape)).toStrictEqual(['username']); expect(pickPredicate.parse({ username: 'Sapphire', password: 'helloworld' })).toStrictEqual({ username: 'Sapphire' }); @@ -351,97 +436,131 @@ describe('ObjectValidator', () => { describe('When', () => { test('Given a key WITH is function THEN return value based on the value at key position', () => { - const whenPredicate = s.object({ - booleanLike: s.boolean, - numberLike: s.number.when('booleanLike', { - is: (value) => value === true, - then: (schema) => schema.greaterThanOrEqual(5), - otherwise: (schema) => schema.lessThanOrEqual(5) - }) - }); + const whenPredicate = s.object( + { + booleanLike: s.boolean(), + numberLike: s.number().when('booleanLike', { + is: (value) => value === true, + then: (schema) => schema.greaterThanOrEqual(5), + otherwise: (schema) => schema.lessThanOrEqual(5) + }) + }, + { + message + } + ); expect(whenPredicate.parse({ booleanLike: true, numberLike: 6 })).toStrictEqual({ booleanLike: true, numberLike: 6 }); expectError( () => whenPredicate.parse({ booleanLike: true, numberLike: 4 }), - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.greaterThanOrEqual', 'Invalid number value', 4, 'expected >= 5')] - ]) + new CombinedPropertyError( + [['numberLike', new ExpectedConstraintError('s.number().greaterThanOrEqual()', 'Invalid number value', 4, 'expected >= 5')]], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); expect(whenPredicate.parse({ booleanLike: false, numberLike: 4 })).toStrictEqual({ booleanLike: false, numberLike: 4 }); expectError( () => whenPredicate.parse({ booleanLike: false, numberLike: 6 }), - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.lessThanOrEqual', 'Invalid number value', 6, 'expected <= 5')] - ]) + new CombinedPropertyError( + [['numberLike', new ExpectedConstraintError('s.number().lessThanOrEqual()', 'Invalid number value', 6, 'expected <= 5')]], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); test('Given a key WITH is primitive literal THEN return value based on the value strictly equal to the primitive literal', () => { - const whenPredicate = s.object({ - booleanLike: s.boolean, - numberLike: s.number.when('booleanLike', { - is: true, - then: (schema) => schema.greaterThanOrEqual(5), - otherwise: (schema) => schema.lessThanOrEqual(5) - }) - }); + const whenPredicate = s.object( + { + booleanLike: s.boolean(), + numberLike: s.number().when('booleanLike', { + is: true, + then: (schema) => schema.greaterThanOrEqual(5), + otherwise: (schema) => schema.lessThanOrEqual(5) + }) + }, + { + message + } + ); expect(whenPredicate.parse({ booleanLike: true, numberLike: 6 })).toStrictEqual({ booleanLike: true, numberLike: 6 }); expectError( () => whenPredicate.parse({ booleanLike: true, numberLike: 4 }), - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.greaterThanOrEqual', 'Invalid number value', 4, 'expected >= 5')] - ]) + new CombinedPropertyError( + [['numberLike', new ExpectedConstraintError('s.number().greaterThanOrEqual()', 'Invalid number value', 4, 'expected >= 5')]], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); expect(whenPredicate.parse({ booleanLike: false, numberLike: 4 })).toStrictEqual({ booleanLike: false, numberLike: 4 }); expectError( () => whenPredicate.parse({ booleanLike: false, numberLike: 6 }), - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.lessThanOrEqual', 'Invalid number value', 6, 'expected <= 5')] - ]) + new CombinedPropertyError( + [['numberLike', new ExpectedConstraintError('s.number().lessThanOrEqual()', 'Invalid number value', 6, 'expected <= 5')]], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); test('Given a key WITHOUT is THEN return value based on the value at key position', () => { - const whenPredicate = s.object({ - booleanLike: s.boolean, - numberLike: s.number.when('booleanLike', { - then: (schema) => schema.greaterThanOrEqual(5), - otherwise: (schema) => schema.lessThanOrEqual(5) - }) - }); + const whenPredicate = s.object( + { + booleanLike: s.boolean(), + numberLike: s.number().when('booleanLike', { + then: (schema) => schema.greaterThanOrEqual(5), + otherwise: (schema) => schema.lessThanOrEqual(5) + }) + }, + { + message + } + ); expect(whenPredicate.parse({ booleanLike: true, numberLike: 6 })).toStrictEqual({ booleanLike: true, numberLike: 6 }); expectError( () => whenPredicate.parse({ booleanLike: true, numberLike: 4 }), - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.greaterThanOrEqual', 'Invalid number value', 4, 'expected >= 5')] - ]) + new CombinedPropertyError( + [['numberLike', new ExpectedConstraintError('s.number().greaterThanOrEqual()', 'Invalid number value', 4, 'expected >= 5')]], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); expect(whenPredicate.parse({ booleanLike: false, numberLike: 4 })).toStrictEqual({ booleanLike: false, numberLike: 4 }); expectError( () => whenPredicate.parse({ booleanLike: false, numberLike: 6 }), - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.lessThanOrEqual', 'Invalid number value', 6, 'expected <= 5')] - ]) + new CombinedPropertyError( + [['numberLike', new ExpectedConstraintError('s.number().lessThanOrEqual()', 'Invalid number value', 6, 'expected <= 5')]], + { message: oneOrMoreErrorsErrorMessage } + ) ); }); test('Given an array of keys WITHOUT is THEN check truly of each values', () => { - const whenPredicate = s.object({ - booleanLike: s.boolean, - stringLike: s.string, - numberLike: s.number.when(['booleanLike', 'stringLike'], { - then: (schema) => schema.greaterThanOrEqual(5), - otherwise: (schema) => schema.lessThanOrEqual(5) - }) - }); + const whenPredicate = s.object( + { + booleanLike: s.boolean(), + stringLike: s.string(), + numberLike: s.number().when(['booleanLike', 'stringLike'], { + then: (schema) => schema.greaterThanOrEqual(5), + otherwise: (schema) => schema.lessThanOrEqual(5) + }) + }, + { message } + ); expect(whenPredicate.parse({ booleanLike: true, stringLike: 'foo', numberLike: 6 })).toStrictEqual({ booleanLike: true, @@ -450,9 +569,12 @@ describe('ObjectValidator', () => { }); expectError( () => whenPredicate.parse({ booleanLike: true, stringLike: 'foo', numberLike: 4 }), - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.greaterThanOrEqual', 'Invalid number value', 4, 'expected >= 5')] - ]) + new CombinedPropertyError( + [['numberLike', new ExpectedConstraintError('s.number().greaterThanOrEqual()', 'Invalid number value', 4, 'expected >= 5')]], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); expect(whenPredicate.parse({ booleanLike: false, stringLike: 'foo', numberLike: 4 })).toStrictEqual({ @@ -469,57 +591,79 @@ describe('ObjectValidator', () => { expectError( () => whenPredicate.parse({ booleanLike: false, stringLike: 'foo', numberLike: 6 }), - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.lessThanOrEqual', 'Invalid number value', 6, 'expected <= 5')] - ]) + new CombinedPropertyError( + [['numberLike', new ExpectedConstraintError('s.number().lessThanOrEqual()', 'Invalid number value', 6, 'expected <= 5')]], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); expectError( () => whenPredicate.parse({ booleanLike: true, stringLike: '', numberLike: 6 }), - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.lessThanOrEqual', 'Invalid number value', 6, 'expected <= 5')] - ]) + new CombinedPropertyError( + [['numberLike', new ExpectedConstraintError('s.number().lessThanOrEqual()', 'Invalid number value', 6, 'expected <= 5')]], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); test('Given a number key THEN return return value based on the key', () => { - const whenPredicate = s.object({ - 1: s.boolean, - numberLike: s.number.when(1, { - is: (value) => value === true, - then: (schema) => schema.greaterThanOrEqual(5), - otherwise: (schema) => schema.lessThanOrEqual(5) - }) - }); + const whenPredicate = s.object( + { + 1: s.boolean(), + numberLike: s.number().when(1, { + is: (value) => value === true, + then: (schema) => schema.greaterThanOrEqual(5), + otherwise: (schema) => schema.lessThanOrEqual(5) + }) + }, + { + message + } + ); expect(whenPredicate.parse({ 1: true, numberLike: 6 })).toStrictEqual({ 1: true, numberLike: 6 }); expectError( () => whenPredicate.parse({ 1: true, numberLike: 4 }), - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.greaterThanOrEqual', 'Invalid number value', 4, 'expected >= 5')] - ]) + new CombinedPropertyError( + [['numberLike', new ExpectedConstraintError('s.number().greaterThanOrEqual()', 'Invalid number value', 4, 'expected >= 5')]], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); expect(whenPredicate.parse({ 1: false, numberLike: 4 })).toStrictEqual({ 1: false, numberLike: 4 }); expectError( () => whenPredicate.parse({ 1: false, numberLike: 6 }), - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.lessThanOrEqual', 'Invalid number value', 6, 'expected <= 5')] - ]) + new CombinedPropertyError( + [['numberLike', new ExpectedConstraintError('s.number().lessThanOrEqual()', 'Invalid number value', 6, 'expected <= 5')]], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); test('Given multiple keys THEN return return value based on the keys', () => { - const whenPredicate = s.object({ - booleanLike: s.boolean, - stringLike: s.string, - numberLike: s.number.when(['booleanLike', 'stringLike'], { - is: ([booleanLikeValue, stringLikeValue]) => booleanLikeValue === true && stringLikeValue === 'foo', - then: (schema) => schema.greaterThanOrEqual(5), - otherwise: (schema) => schema.lessThanOrEqual(5) - }) - }); + const whenPredicate = s.object( + { + booleanLike: s.boolean(), + stringLike: s.string(), + numberLike: s.number().when(['booleanLike', 'stringLike'], { + is: ([booleanLikeValue, stringLikeValue]) => booleanLikeValue === true && stringLikeValue === 'foo', + then: (schema) => schema.greaterThanOrEqual(5), + otherwise: (schema) => schema.lessThanOrEqual(5) + }) + }, + { + message + } + ); expect(whenPredicate.parse({ booleanLike: true, stringLike: 'foo', numberLike: 6 })).toStrictEqual({ booleanLike: true, @@ -539,33 +683,47 @@ describe('ObjectValidator', () => { expectError( () => whenPredicate.parse({ booleanLike: false, stringLike: 'foo', numberLike: 6 }), - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.lessThanOrEqual', 'Invalid number value', 6, 'expected <= 5')] - ]) + new CombinedPropertyError( + [['numberLike', new ExpectedConstraintError('s.number().lessThanOrEqual()', 'Invalid number value', 6, 'expected <= 5')]], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); expectError( () => expect(whenPredicate.parse({ booleanLike: true, stringLike: 'bar', numberLike: 6 })), - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.lessThanOrEqual', 'Invalid number value', 6, 'expected <= 5')] - ]) + new CombinedPropertyError( + [['numberLike', new ExpectedConstraintError('s.number().lessThanOrEqual()', 'Invalid number value', 6, 'expected <= 5')]], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); test('Given a key without `otherwise` THEN return the same value for false condition', () => { - const whenPredicate = s.object({ - booleanLike: s.boolean, - numberLike: s.number.when('booleanLike', { - is: (value) => value === true, - then: (schema) => schema.greaterThanOrEqual(5) - }) - }); + const whenPredicate = s.object( + { + booleanLike: s.boolean(), + numberLike: s.number().when('booleanLike', { + is: (value) => value === true, + then: (schema) => schema.greaterThanOrEqual(5) + }) + }, + { + message + } + ); expect(whenPredicate.parse({ booleanLike: true, numberLike: 6 })).toStrictEqual({ booleanLike: true, numberLike: 6 }); expectError( () => whenPredicate.parse({ booleanLike: true, numberLike: 4 }), - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.greaterThanOrEqual', 'Invalid number value', 4, 'expected >= 5')] - ]) + new CombinedPropertyError( + [['numberLike', new ExpectedConstraintError('s.number().greaterThanOrEqual()', 'Invalid number value', 4, 'expected >= 5')]], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); expect(whenPredicate.parse({ booleanLike: false, numberLike: 4 })).toStrictEqual({ booleanLike: false, numberLike: 4 }); @@ -573,43 +731,62 @@ describe('ObjectValidator', () => { }); test('Given a predicate with no parent THEN throw ExpectedConstraintError', () => { - const whenPredicate = s.number.when('booleanLike', { - is: (value) => value === true, - then: (schema) => schema.greaterThanOrEqual(5), - otherwise: (schema) => schema.lessThanOrEqual(5) - }); + const whenPredicate = s.number().when( + 'booleanLike', + { + is: (value) => value === true, + then: (schema) => schema.greaterThanOrEqual(5), + otherwise: (schema) => schema.lessThanOrEqual(5) + }, + { + message + } + ); expectError( () => whenPredicate.parse(5), - new ExpectedConstraintError('s.object(T.when)', 'Validator has no parent', undefined, 'Validator to have a parent') + new ExpectedConstraintError('s.object(T.when)', message ?? 'Validator has no parent', undefined, 'Validator to have a parent') ); }); test('Given a nested object and a key with dot THEN return return value based on the key', () => { - const whenPredicate = s.object({ - objectLike: s.object({ - booleanLike: s.boolean, - numberLike: s.number.when('objectLike.booleanLike', { - is: (value) => value === true, - then: (schema) => schema.greaterThanOrEqual(5), - otherwise: (schema) => schema.lessThanOrEqual(5) + const whenPredicate = s.object( + { + objectLike: s.object({ + booleanLike: s.boolean(), + numberLike: s.number().when('objectLike.booleanLike', { + is: (value) => value === true, + then: (schema) => schema.greaterThanOrEqual(5), + otherwise: (schema) => schema.lessThanOrEqual(5) + }) }) - }) - }); + }, + { + message + } + ); expect(whenPredicate.parse({ objectLike: { booleanLike: true, numberLike: 6 } })).toStrictEqual({ objectLike: { booleanLike: true, numberLike: 6 } }); expectError( () => whenPredicate.parse({ objectLike: { booleanLike: true, numberLike: 4 } }), - new CombinedPropertyError([ + new CombinedPropertyError( [ - 'objectLike', - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.greaterThanOrEqual', 'Invalid number value', 4, 'expected >= 5')] - ]) - ] - ]) + [ + 'objectLike', + new CombinedPropertyError([ + [ + 'numberLike', + new ExpectedConstraintError('s.number().greaterThanOrEqual()', 'Invalid number value', 4, 'expected >= 5') + ] + ]) + ] + ], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); expect(whenPredicate.parse({ objectLike: { booleanLike: false, numberLike: 4 } })).toStrictEqual({ @@ -618,14 +795,22 @@ describe('ObjectValidator', () => { expectError( () => whenPredicate.parse({ objectLike: { booleanLike: false, numberLike: 6 } }), - new CombinedPropertyError([ + new CombinedPropertyError( [ - 'objectLike', - new CombinedPropertyError([ - ['numberLike', new ExpectedConstraintError('s.number.lessThanOrEqual', 'Invalid number value', 6, 'expected <= 5')] - ]) - ] - ]) + [ + 'objectLike', + new CombinedPropertyError([ + [ + 'numberLike', + new ExpectedConstraintError('s.number().lessThanOrEqual()', 'Invalid number value', 6, 'expected <= 5') + ] + ]) + ] + ], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); }); diff --git a/tests/validators/passthrough.test.ts b/tests/validators/passthrough.test.ts index 3bf8bfcb..00c4ba40 100644 --- a/tests/validators/passthrough.test.ts +++ b/tests/validators/passthrough.test.ts @@ -1,7 +1,7 @@ import { s } from '../../src'; -describe('AnyValidator', () => { - const predicate = s.any; +describe.each(['custom message', undefined])('AnyValidator (%s)', (message) => { + const predicate = s.any({ message }); test.each([1, 'hello', null])('GIVEN anything (%j) THEN returns the given value', (input) => { expect(predicate.parse(input)).toBe(input); @@ -9,7 +9,7 @@ describe('AnyValidator', () => { }); describe('UnknownValidator', () => { - const predicate = s.unknown; + const predicate = s.unknown(); test.each([1, 'hello', null])('GIVEN anything (%j) THEN returns the given value', (input) => { expect(predicate.parse(input)).toBe(input); diff --git a/tests/validators/record.test.ts b/tests/validators/record.test.ts index 991404ff..f53857e1 100644 --- a/tests/validators/record.test.ts +++ b/tests/validators/record.test.ts @@ -1,16 +1,16 @@ import { CombinedPropertyError, s, ValidationError } from '../../src'; import { expectClonedValidator, expectError } from '../common/macros/comparators'; -describe('RecordValidator', () => { +describe.each(['custom message', undefined])('RecordValidator (%s)', (message) => { const value = { foo: 'bar', fizz: 'buzz' }; - const predicate = s.record(s.string); + const predicate = s.record(s.string({ message }), { message }); test('GIVEN a non-record THEN throws ValidationError', () => { - expectError(() => predicate.parse(false), new ValidationError('s.record(T)', 'Expected an object', false)); + expectError(() => predicate.parse(false), new ValidationError('s.record(T)', message ?? 'Expected an object', false)); }); test('GIVEN null THEN throws ValidationError', () => { - expectError(() => predicate.parse(null), new ValidationError('s.record(T)', 'Expected the value to not be null', null)); + expectError(() => predicate.parse(null), new ValidationError('s.record(T)', message ?? 'Expected the value to not be null', null)); }); test('GIVEN a matching record THEN returns a record', () => { @@ -20,14 +20,26 @@ describe('RecordValidator', () => { test('GIVEN a non-matching record THEN throws CombinedError', () => { expectError( () => predicate.parse({ foo: 1, fizz: true }), - new CombinedPropertyError([ - ['foo', new ValidationError('s.string', 'Expected a string primitive', 1)], - ['fizz', new ValidationError('s.string', 'Expected a string primitive', true)] - ]) + new CombinedPropertyError( + [ + ['foo', new ValidationError('s.string()', message ?? 'Expected a string primitive', 1)], + ['fizz', new ValidationError('s.string()', message ?? 'Expected a string primitive', true)] + ], + { + message: message ?? 'Received one or more errors' + } + ) ); }); test('GIVEN clone THEN returns similar instance', () => { expectClonedValidator(predicate, predicate['clone']()); }); + + test('GIVEN an array THEN throws ValidationError', () => { + expectError( + () => predicate.parse([1, 2, 3]), + new ValidationError('s.record(T)', message ?? 'Expected the value to not be an array', [1, 2, 3]) + ); + }); }); diff --git a/tests/validators/set.test.ts b/tests/validators/set.test.ts index a57c59a9..3d22da7f 100644 --- a/tests/validators/set.test.ts +++ b/tests/validators/set.test.ts @@ -1,11 +1,11 @@ import { CombinedError, s, ValidationError } from '../../src'; import { expectClonedValidator, expectError } from '../common/macros/comparators'; -describe('SetValidator', () => { - const predicate = s.set(s.string); +describe.each(['custom message', undefined])('SetValidator (%s)', (message) => { + const predicate = s.set(s.string({ message }), { message }); test.each([123, 'foo', [], {}, new Map()])("GIVEN a value which isn't a set (%j) THEN throws ValidationError", (input) => { - expectError(() => predicate.parse(input), new ValidationError('s.set(T)', 'Expected a set', input)); + expectError(() => predicate.parse(input), new ValidationError('s.set(T)', message ?? 'Expected a set', input)); }); test.each(['1', 'a', 'foo'])('GIVEN a set with string value (%j) THEN returns the given set', (input) => { @@ -17,7 +17,12 @@ describe('SetValidator', () => { test.each([123, [], {}])('GIVEN a set with non-string value (%j) THEN throw CombinedError', (input) => { const set = new Set([input]); - expectError(() => predicate.parse(set), new CombinedError([new ValidationError('s.string', 'Expected a string primitive', input)])); + expectError( + () => predicate.parse(set), + new CombinedError([new ValidationError('s.string()', message ?? 'Expected a string primitive', input)], { + message: message ?? 'Received one or more errors' + }) + ); }); test('GIVEN clone THEN returns similar instance', () => { diff --git a/tests/validators/string.test.ts b/tests/validators/string.test.ts index 01d046d3..a59de5f3 100644 --- a/tests/validators/string.test.ts +++ b/tests/validators/string.test.ts @@ -1,20 +1,23 @@ import { ExpectedConstraintError, MultiplePossibilitiesConstraintError, s, ValidationError } from '../../src'; import { expectError } from '../common/macros/comparators'; -describe('StringValidator', () => { - const predicate = s.string; +describe.each(['custom message', undefined])('StringValidator (%s)', (message) => { + const predicate = s.string({ message }); test('GIVEN a string THEN returns a string', () => { expect(predicate.parse('Hello There')).toBe('Hello There'); }); test.each([undefined, null, 42])('GIVEN %j THEN throws a ValidationError', (input) => { - expectError(() => predicate.parse(input), new ValidationError('s.string', 'Expected a string primitive', input)); + const errorMessage = message ?? 'Expected a string primitive'; + expectError(() => predicate.parse(input), new ValidationError('s.string()', errorMessage, input)); }); describe('Comparators', () => { + const lengthErrorMessage = message ?? 'Invalid string length'; + describe('lengthLessThan', () => { - const lengthLessThanPredicate = s.string.lengthLessThan(5); + const lengthLessThanPredicate = s.string({ message }).lengthLessThan(5, { message }); test.each(['Hi'])('GIVEN %j THEN returns given value', (input) => { expect(lengthLessThanPredicate.parse(input)).toBe(input); @@ -23,13 +26,13 @@ describe('StringValidator', () => { test.each(['Hello', 'Foo Bar'])('GIVEN %j THEN throws a ConstraintError', (input) => { expectError( () => lengthLessThanPredicate.parse(input), - new ExpectedConstraintError('s.string.lengthLessThan', 'Invalid string length', input, 'expected.length < 5') + new ExpectedConstraintError('s.string().lengthLessThan()', lengthErrorMessage, input, 'expected.length < 5') ); }); }); describe('lengthLessThanOrEqual', () => { - const lengthLePredicate = s.string.lengthLessThanOrEqual(5); + const lengthLePredicate = s.string({ message }).lengthLessThanOrEqual(5, { message }); test.each(['Hi', 'Hello'])('GIVEN %j THEN returns given value', (input) => { expect(lengthLePredicate.parse(input)).toBe(input); @@ -38,13 +41,13 @@ describe('StringValidator', () => { test.each(['Foo Bar'])('GIVEN %j THEN throws a ConstraintError', (input) => { expectError( () => lengthLePredicate.parse(input), - new ExpectedConstraintError('s.string.lengthLessThanOrEqual', 'Invalid string length', input, 'expected.length <= 5') + new ExpectedConstraintError('s.string().lengthLessThanOrEqual()', lengthErrorMessage, input, 'expected.length <= 5') ); }); }); describe('lengthGreaterThan', () => { - const lengthGtPredicate = s.string.lengthGreaterThan(5); + const lengthGtPredicate = s.string({ message }).lengthGreaterThan(5, { message }); test.each(['Foo Bar'])('GIVEN %j THEN returns given value', (input) => { expect(lengthGtPredicate.parse(input)).toBe(input); @@ -53,13 +56,13 @@ describe('StringValidator', () => { test.each(['Hi', 'Hello'])('GIVEN %j THEN throws a ConstraintError', (input) => { expectError( () => lengthGtPredicate.parse(input), - new ExpectedConstraintError('s.string.lengthGreaterThan', 'Invalid string length', input, 'expected.length > 5') + new ExpectedConstraintError('s.string().lengthGreaterThan()', lengthErrorMessage, input, 'expected.length > 5') ); }); }); describe('lengthGreaterThanOrEqual', () => { - const lengthGePredicate = s.string.lengthGreaterThanOrEqual(5); + const lengthGePredicate = s.string({ message }).lengthGreaterThanOrEqual(5, { message }); test.each(['Hello', 'Foo Bar'])('GIVEN %j THEN returns given value', (input) => { expect(lengthGePredicate.parse(input)).toBe(input); @@ -68,13 +71,13 @@ describe('StringValidator', () => { test.each(['Hi'])('GIVEN %j THEN throws a ConstraintError', (input) => { expectError( () => lengthGePredicate.parse(input), - new ExpectedConstraintError('s.string.lengthGreaterThanOrEqual', 'Invalid string length', input, 'expected.length >= 5') + new ExpectedConstraintError('s.string().lengthGreaterThanOrEqual()', lengthErrorMessage, input, 'expected.length >= 5') ); }); }); describe('lengthEqual', () => { - const lengthEqPredicate = s.string.lengthEqual(5); + const lengthEqPredicate = s.string({ message }).lengthEqual(5, { message }); test.each(['Hello'])('GIVEN %j THEN returns given value', (input) => { expect(lengthEqPredicate.parse(input)).toBe(input); @@ -83,13 +86,13 @@ describe('StringValidator', () => { test.each(['Hi', 'Foo Bar'])('GIVEN %j THEN throws a ConstraintError', (input) => { expectError( () => lengthEqPredicate.parse(input), - new ExpectedConstraintError('s.string.lengthEqual', 'Invalid string length', input, 'expected.length === 5') + new ExpectedConstraintError('s.string().lengthEqual()', lengthErrorMessage, input, 'expected.length === 5') ); }); }); describe('lengthNotEqual', () => { - const lengthNePredicate = s.string.lengthNotEqual(5); + const lengthNePredicate = s.string({ message }).lengthNotEqual(5, { message }); test.each(['Hi', 'Foo Bar'])('GIVEN %j THEN returns given value', (input) => { expect(lengthNePredicate.parse(input)).toBe(input); @@ -98,7 +101,7 @@ describe('StringValidator', () => { test.each(['Hello'])('GIVEN %j THEN throws a ConstraintError', (input) => { expectError( () => lengthNePredicate.parse(input), - new ExpectedConstraintError('s.string.lengthNotEqual', 'Invalid string length', input, 'expected.length !== 5') + new ExpectedConstraintError('s.string().lengthNotEqual()', lengthErrorMessage, input, 'expected.length !== 5') ); }); }); @@ -106,7 +109,7 @@ describe('StringValidator', () => { describe('Formats', () => { describe('email', () => { - const emailPredicate = s.string.email; + const emailPredicate = s.string({ message }).email({ message }); test.each(['hi@hello.com', 'foo@bar.net', 'hello+world@example.com'])('GIVEN %j THEN returns given value', (input) => { expect(emailPredicate.parse(input)).toBe(input); @@ -126,86 +129,96 @@ describe('StringValidator', () => { 'short-account-name@domain-name-that-has-more-than-sixty-three-characters-and-is-then.followed-by-another-segment-that-got-split-by-a-full-stop-symbol.com', `foo@bar.${'a'.repeat(64)}` ])('GIVEN %j THEN throws a ConstraintError', (input) => { + const errorMessage = message ?? 'Invalid email address'; expectError( () => emailPredicate.parse(input), - new ExpectedConstraintError('s.string.email', 'Invalid email address', input, 'expected to be an email address') + new ExpectedConstraintError('s.string().email()', errorMessage, input, 'expected to be an email address') ); }); }); describe('url', () => { describe('Without any options', () => { - const urlPredicate = s.string.url(); + const urlPredicate = s.string({ message }).url({ message }); test.each(['https://google.com', 'http://foo.bar'])('GIVEN %j THEN returns given value', (input) => { expect(urlPredicate.parse(input)).toBe(input); }); test.each(['google.com', 'foo.bar'])('GIVEN %j THEN throws a ConstraintError', (input) => { + const errorMessage = message ?? 'Invalid URL'; expectError( () => urlPredicate.parse(input), - new ExpectedConstraintError('s.string.url', 'Invalid URL', input, 'expected to match a URL') + new ExpectedConstraintError('s.string().url()', errorMessage, input, 'expected to match a URL') ); }); }); describe('With protocol', () => { - const urlPredicateWithProtocol = s.string.url({ allowedProtocols: ['git:'] }); + const urlPredicateWithProtocol = s.string({ message }).url({ allowedProtocols: ['git:'] }, { message }); test.each(['git://foo.bar'])('GIVEN %j THEN returns given value', (input) => { expect(urlPredicateWithProtocol.parse(input)).toBe(input); }); test.each(['https://google.com', 'http://foo.bar'])('GIVEN %j THEN throws a ConstraintError', (input) => { + const errorMessage = message ?? 'Invalid URL protocol'; expectError( () => urlPredicateWithProtocol.parse(input), - new MultiplePossibilitiesConstraintError('s.string.url', 'Invalid URL protocol', input, ['git:']) + new MultiplePossibilitiesConstraintError('s.string().url()', errorMessage, input, ['git:']) ); }); }); describe('With domain', () => { - const urlPredicateWithDomain = s.string.url({ allowedDomains: ['google.com'] }); + const urlPredicateWithDomain = s.string({ message }).url({ allowedDomains: ['google.com'] }, { message }); test.each(['https://google.com', 'http://google.com'])('GIVEN %j THEN returns given value', (input) => { expect(urlPredicateWithDomain.parse(input)).toBe(input); }); test.each(['https://foo.bar', 'http://foo.bar'])('GIVEN %j THEN throws a ConstraintError', (input) => { + const errorMessage = message ?? 'Invalid URL domain'; expectError( () => urlPredicateWithDomain.parse(input), - new MultiplePossibilitiesConstraintError('s.string.url', 'Invalid URL domain', input, ['google.com']) + new MultiplePossibilitiesConstraintError('s.string().url()', errorMessage, input, ['google.com']) ); }); }); describe('With domain and protocol', () => { - const urlPredicateWithDomainAndProtocol = s.string.url({ - allowedProtocols: ['https:'], - allowedDomains: ['google.com', 'example.org'] - }); + const urlPredicateWithDomainAndProtocol = s.string({ message }).url( + { + allowedProtocols: ['https:'], + allowedDomains: ['google.com', 'example.org'] + }, + { message } + ); test.each(['https://google.com', 'https://example.org'])('GIVEN %j THEN returns given value', (input) => { expect(urlPredicateWithDomainAndProtocol.parse(input)).toBe(input); }); test.each(['http://example.org', 'git://example.org'])('GIVEN %j THEN throws a ConstraintError', (input) => { + const errorMessage = message ?? 'Invalid URL protocol'; expectError( () => urlPredicateWithDomainAndProtocol.parse(input), - new MultiplePossibilitiesConstraintError('s.string.url', 'Invalid URL protocol', input, ['https:']) + new MultiplePossibilitiesConstraintError('s.string().url()', errorMessage, input, ['https:']) ); }); test.each(['https://discord.js.org', 'https://google.es'])('GIVEN %j THEN throws a ConstraintError', (input) => { + const errorMessage = message ?? 'Invalid URL domain'; expectError( () => urlPredicateWithDomainAndProtocol.parse(input), - new MultiplePossibilitiesConstraintError('s.string.url', 'Invalid URL domain', input, ['google.com', 'example.org']) + new MultiplePossibilitiesConstraintError('s.string().url()', errorMessage, input, ['google.com', 'example.org']) ); }); }); }); describe('uuid', () => { + const errorMessage = message ?? 'Invalid string format'; const uuid5 = '2a7ff881-2944-55ae-94b0-b2ed34432297'; const uuid4 = '450d6a23-9e6f-45d9-9d5a-fd4f6e014f16'; const uuid3 = '2962d7f2-92f2-3105-8606-2234808bdfc8'; @@ -214,7 +227,7 @@ describe('StringValidator', () => { const invalidUuids = ['6e8bc430-9a1b-4f7f-b7a5', '6e8bc430-9a1b-4f7f-b7a5']; describe('uuid5', () => { - const uuid5Predicate = s.string.uuid({ version: 5 }); + const uuid5Predicate = s.string({ message }).uuid({ version: 5 }, { message }); test.each([uuid5])('GIVEN %j THEN returns given value', (input) => { expect(uuid5Predicate.parse(input)).toBe(input); @@ -223,13 +236,13 @@ describe('StringValidator', () => { test.each([uuid1, uuid3, uuid4])('GIVEN %j THEN throws a ConstraintError', (input) => { expectError( () => uuid5Predicate.parse(input), - new ExpectedConstraintError('s.string.uuid', 'Invalid string format', input, 'expected to match UUIDv5') + new ExpectedConstraintError('s.string().uuid()', errorMessage, input, 'expected to match UUIDv5') ); }); }); describe('uuid4', () => { - const uuid4Predicate = s.string.uuid({ version: 4 }); + const uuid4Predicate = s.string({ message }).uuid({ version: 4 }, { message }); test.each([uuid4])('GIVEN %j THEN returns given value', (input) => { expect(uuid4Predicate.parse(input)).toBe(input); @@ -238,12 +251,12 @@ describe('StringValidator', () => { test.each([...invalidUuids, uuid5])('GIVEN %j THEN throws a ConstraintError', (input) => { expectError( () => uuid4Predicate.parse(input), - new ExpectedConstraintError('s.string.uuid', 'Invalid string format', input, 'expected to match UUIDv4') + new ExpectedConstraintError('s.string().uuid()', errorMessage, input, 'expected to match UUIDv4') ); }); describe('Default behavior', () => { - const defaultPredicate = s.string.uuid(); + const defaultPredicate = s.string({ message }).uuid({ message }); test('GIVEN v4 UUID THEN return the input', () => { expect(uuid4Predicate.parse(uuid4)).toStrictEqual(defaultPredicate.parse(uuid4)); }); @@ -251,14 +264,14 @@ describe('StringValidator', () => { test.each([uuid1, ...invalidUuids])('GIVEN UUID other than v4 THEN throws a ConstraintError', (input) => { expectError( () => defaultPredicate.parse(input), - new ExpectedConstraintError('s.string.uuid', 'Invalid string format', input, 'expected to match UUIDv4') + new ExpectedConstraintError('s.string().uuid()', errorMessage, input, 'expected to match UUIDv4') ); }); }); }); describe('uuid3', () => { - const uuid3Predicate = s.string.uuid({ version: 3 }); + const uuid3Predicate = s.string({ message }).uuid({ version: 3 }, { message }); test.each([uuid3])('GIVEN %j THEN returns given value', (input) => { expect(uuid3Predicate.parse(input)).toBe(input); @@ -267,13 +280,13 @@ describe('StringValidator', () => { test.each([...invalidUuids, uuid4, uuid5])('GIVEN %j THEN throws a ConstraintError', (input) => { expectError( () => uuid3Predicate.parse(input), - new ExpectedConstraintError('s.string.uuid', 'Invalid string format', input, 'expected to match UUIDv3') + new ExpectedConstraintError('s.string().uuid()', errorMessage, input, 'expected to match UUIDv3') ); }); }); describe('with version range', () => { - const uuidRangePredicate = s.string.uuid({ version: '1-4' }); + const uuidRangePredicate = s.string({ message }).uuid({ version: '1-4' }, { message }); test.each([uuid1, uuid3, uuid3])('GIVEN %j THEN returns given value', (input) => { expect(uuidRangePredicate.parse(input)).toBe(input); @@ -282,14 +295,14 @@ describe('StringValidator', () => { test.each([uuid5, nullUuid])('GIVEN %j THEN throws a ConstraintError', (input) => { expectError( () => uuidRangePredicate.parse(input), - new ExpectedConstraintError('s.string.uuid', 'Invalid string format', input, `expected to match UUID in range of 1-4`) + new ExpectedConstraintError('s.string().uuid()', errorMessage, input, `expected to match UUID in range of 1-4`) ); }); }); describe('Nullable', () => { test('GIVEN null UUID THEN returns null UUID', () => { - const uuidPredicate = s.string.uuid({ version: '1-5', nullable: true }); + const uuidPredicate = s.string({ message }).uuid({ version: '1-5', nullable: true }, { message }); expect(uuidPredicate.parse(nullUuid)).toBe(nullUuid); }); }); @@ -297,16 +310,17 @@ describe('StringValidator', () => { describe('regex', () => { const regex = /^[a-z]+$/; - const regexPredicate = s.string.regex(regex); + const regexPredicate = s.string({ message }).regex(regex, { message }); test.each(['abc', 'xyz'])('GIVEN %j THEN returns given value', (input) => { expect(regexPredicate.parse(input)).toBe(input); }); test.each(['ABC', '123A'])('GIVEN %j THEN throws a ConstraintError', (input) => { + const errorMessage = message ?? 'Invalid string format'; expectError( () => regexPredicate.parse(input), - new ExpectedConstraintError('s.string.regex', 'Invalid string format', input, `expected ${regex}.test(expected) to be true`) + new ExpectedConstraintError('s.string().regex()', errorMessage, input, `expected ${regex}.test(expected) to be true`) ); }); }); @@ -322,52 +336,55 @@ describe('StringValidator', () => { '2001:0db8:85a3:0000:0000:8a2e:0370:7334/24/24' ]; describe('default', () => { - const ipPredicate = s.string.ip(); + const ipPredicate = s.string({ message }).ip(undefined, { message }); test.each([...v4Ips, ...v6Ips])('GIVEN %j THEN returns given value', (input) => { expect(ipPredicate.parse(input)).toBe(input); }); test.each(invalidIps)('GIVEN %j THEN throws a ConstraintError', (input) => { + const errorMessage = message ?? 'Invalid IP address'; expectError( () => ipPredicate.parse(input), - new ExpectedConstraintError('s.string.ip', 'Invalid IP address', input, 'expected to be an IP address') + new ExpectedConstraintError('s.string().ip()', errorMessage, input, 'expected to be an IP address') ); }); }); describe('v4', () => { - const ipv4Predicate = s.string.ipv4; + const ipv4Predicate = s.string({ message }).ipv4({ message }); test.each(v4Ips)('GIVEN %j THEN returns given value', (input) => { expect(ipv4Predicate.parse(input)).toBe(input); }); test.each([...v6Ips, ...invalidIps])('GIVEN %j THEN throws a ConstraintError', (input) => { + const errorMessage = message ?? 'Invalid IPv4 address'; expectError( () => ipv4Predicate.parse(input), - new ExpectedConstraintError('s.string.ipv4', 'Invalid IPv4 address', input, 'expected to be an IPv4 address') + new ExpectedConstraintError('s.string().ipv4()', errorMessage, input, 'expected to be an IPv4 address') ); }); }); describe('v6', () => { - const ipv6Predicate = s.string.ipv6; + const ipv6Predicate = s.string({ message }).ipv6({ message }); test.each(v6Ips)('GIVEN %j THEN returns given value', (input) => { expect(ipv6Predicate.parse(input)).toBe(input); }); test.each([...v4Ips, ...invalidIps])('GIVEN %j THEN throws a ConstraintError', (input) => { + const errorMessage = message ?? 'Invalid IPv6 address'; expectError( () => ipv6Predicate.parse(input), - new ExpectedConstraintError('s.string.ipv6', 'Invalid IPv6 address', input, 'expected to be an IPv6 address') + new ExpectedConstraintError('s.string().ipv6()', errorMessage, input, 'expected to be an IPv6 address') ); }); }); }); describe('date', () => { - const stringDatePredicate = s.string.date; + const stringDatePredicate = s.string({ message }).date({ message }); test.each([ '6969-01-01T02:20:00.000Z', @@ -379,11 +396,12 @@ describe('StringValidator', () => { }); test('GIVEN invalid date string THEN throws a ConstraintError', () => { + const errorMessage = message ?? 'Invalid date string'; expectError( () => stringDatePredicate.parse('owo'), new ExpectedConstraintError( - 's.string.date', - 'Invalid date string', + 's.string().date()', + errorMessage, 'owo', 'expected to be a valid date string (in the ISO 8601 or ECMA-262 format)' ) @@ -392,7 +410,7 @@ describe('StringValidator', () => { }); describe('phone', () => { - const phonePredicate = s.string.phone(); + const phonePredicate = s.string({ message }).phone({ message }); test.each([ '+79919542975', @@ -411,9 +429,10 @@ describe('StringValidator', () => { test.each(['+1 555-555-5555 ext', '987-123-4567 x12345', '(123) 456-7890 ext12345', '123456', '+1 (555) (555) (555)'])( 'GIVEN %j THEN throws a ConstraintError', (input) => { + const errorMessage = message ?? 'Invalid phone number'; expectError( () => phonePredicate.parse(input), - new ExpectedConstraintError('s.string.phone', 'Invalid phone number', input, 'expected to be a phone number') + new ExpectedConstraintError('s.string().phone()', errorMessage, input, 'expected to be a phone number') ); } ); diff --git a/tests/validators/tuple.test.ts b/tests/validators/tuple.test.ts index 2f47d04d..e625d1fc 100644 --- a/tests/validators/tuple.test.ts +++ b/tests/validators/tuple.test.ts @@ -1,15 +1,15 @@ import { CombinedPropertyError, s, ValidationError } from '../../src'; import { expectClonedValidator, expectError } from '../common/macros/comparators'; -describe('TupleValidator', () => { - const predicate = s.tuple([s.string, s.number]); +describe.each(['custom message', undefined])('TupleValidator', (message) => { + const predicate = s.tuple([s.string({ message }), s.number({ message })], { message }); test('GIVEN a matching tuple THEN returns a tuple', () => { expect<[string, number]>(predicate.parse(['foo', 1])).toStrictEqual(['foo', 1]); }); test.each([false, 1, 'Hello', null, undefined])('GIVEN %j THEN throws ValidationError', (input) => { - expectError(() => predicate.parse(input), new ValidationError('s.tuple(T)', 'Expected an array', input)); + expectError(() => predicate.parse(input), new ValidationError('s.tuple(T)', message ?? 'Expected an array', input)); }); test.each([ @@ -20,19 +20,25 @@ describe('TupleValidator', () => { ])('GIVEN [%j, %j] tuple THEN throws CombinedError', (a, b) => { expectError( () => predicate.parse([a, b]), - new CombinedPropertyError([ - [0, new ValidationError('s.string', 'Expected a string primitive', a)], - [1, new ValidationError('s.number', 'Expected a number primitive', b)] - ]) + new CombinedPropertyError( + [ + [0, new ValidationError('s.string()', message ?? 'Expected a string primitive', a)], + [1, new ValidationError('s.number()', message ?? 'Expected a number primitive', b)] + ], + { message } + ) ); }); test('GIVEN a tuple with too few elements THEN throws ValidationError', () => { - expectError(() => predicate.parse(['foo']), new ValidationError('s.tuple(T)', 'Expected an array of length 2', ['foo'])); + expectError(() => predicate.parse(['foo']), new ValidationError('s.tuple(T)', message ?? 'Expected an array of length 2', ['foo'])); }); test('GIVEN a tuple with too many elements THEN throws ValidationError', () => { - expectError(() => predicate.parse(['foo', 1, 'bar']), new ValidationError('s.tuple(T)', 'Expected an array of length 2', ['foo', 1, 'bar'])); + expectError( + () => predicate.parse(['foo', 1, 'bar']), + new ValidationError('s.tuple(T)', message ?? 'Expected an array of length 2', ['foo', 1, 'bar']) + ); }); test('GIVEN clone THEN returns similar instance', () => { diff --git a/tests/validators/typedArray.test.ts b/tests/validators/typedArray.test.ts index 7ed7c105..0427ba53 100644 --- a/tests/validators/typedArray.test.ts +++ b/tests/validators/typedArray.test.ts @@ -1,9 +1,9 @@ import { ExpectedConstraintError, s, ValidationError } from '../../src'; import { expectClonedValidator } from '../common/macros/comparators'; -describe('TypedArray', () => { +describe.each(['custom message', undefined])('TypedArray (%s)', (message) => { describe('Any type of typed array', () => { - const predicate = s.typedArray(); + const predicate = s.typedArray('TypedArray', { message }); test.each([ new Int8Array(), @@ -22,12 +22,12 @@ describe('TypedArray', () => { }); test.each([1, true, 'sapphire'])('GIVEN %j THEN throw', (input) => { - expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray', `Expected a TypedArray`, input)); + expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray()', message ?? `Expected a TypedArray`, input)); }); }); describe('Int8Array', () => { - const predicate = s.int8Array; + const predicate = s.int8Array({ message }); test('GIVEN typed array THEN return the input', () => { const typedArray = new Int8Array(); @@ -35,12 +35,12 @@ describe('TypedArray', () => { }); test.each([1, true, 'sapphire', new Int16Array()])('GIVEN %j THEN throw', (input) => { - expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray', `Expected an Int8Array`, input)); + expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray()', message ?? `Expected an Int8Array`, input)); }); }); describe('Uint8Array', () => { - const predicate = s.uint8Array; + const predicate = s.uint8Array({ message }); test('GIVEN typed array THEN return the input', () => { const typedArray = new Uint8Array(); @@ -48,12 +48,12 @@ describe('TypedArray', () => { }); test.each([1, true, 'sapphire', new Uint16Array()])('GIVEN %j THEN throw', (input) => { - expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray', `Expected an Uint8Array`, input)); + expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray()', message ?? `Expected an Uint8Array`, input)); }); }); describe('Uint8ClampedArray', () => { - const predicate = s.uint8ClampedArray; + const predicate = s.uint8ClampedArray({ message }); test('GIVEN typed array THEN return the input', () => { const typedArray = new Uint8ClampedArray(); @@ -61,12 +61,12 @@ describe('TypedArray', () => { }); test.each([1, true, 'sapphire', new Uint16Array()])('GIVEN %j THEN throw', (input) => { - expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray', `Expected an Uint8ClampedArray`, input)); + expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray()', message ?? `Expected an Uint8ClampedArray`, input)); }); }); describe('Int16Array', () => { - const predicate = s.int16Array; + const predicate = s.int16Array({ message }); test('GIVEN typed array THEN return the input', () => { const typedArray = new Int16Array(); @@ -74,12 +74,12 @@ describe('TypedArray', () => { }); test.each([1, true, 'sapphire', new Int32Array()])('GIVEN %j THEN throw', (input) => { - expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray', `Expected an Int16Array`, input)); + expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray()', message ?? `Expected an Int16Array`, input)); }); }); describe('Uint16Array', () => { - const predicate = s.uint16Array; + const predicate = s.uint16Array({ message }); test('GIVEN typed array THEN return the input', () => { const typedArray = new Uint16Array(); @@ -87,12 +87,12 @@ describe('TypedArray', () => { }); test.each([1, true, 'sapphire', new Uint32Array()])('GIVEN %j THEN throw', (input) => { - expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray', `Expected an Uint16Array`, input)); + expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray()', message ?? `Expected an Uint16Array`, input)); }); }); describe('Int32Array', () => { - const predicate = s.int32Array; + const predicate = s.int32Array({ message }); test('GIVEN typed array THEN return the input', () => { const typedArray = new Int32Array(); @@ -100,12 +100,12 @@ describe('TypedArray', () => { }); test.each([1, true, 'sapphire', new Int16Array()])('GIVEN %j THEN throw', (input) => { - expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray', `Expected an Int32Array`, input)); + expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray()', message ?? `Expected an Int32Array`, input)); }); }); describe('Uint32Array', () => { - const predicate = s.uint32Array; + const predicate = s.uint32Array({ message }); test('GIVEN typed array THEN return the input', () => { const typedArray = new Uint32Array(); @@ -113,12 +113,12 @@ describe('TypedArray', () => { }); test.each([1, true, 'sapphire', new Uint16Array()])('GIVEN %j THEN throw', (input) => { - expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray', `Expected an Uint32Array`, input)); + expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray()', message ?? `Expected an Uint32Array`, input)); }); }); describe('Float32Array', () => { - const predicate = s.float32Array; + const predicate = s.float32Array({ message }); test('GIVEN typed array THEN return the input', () => { const typedArray = new Float32Array(); @@ -126,12 +126,12 @@ describe('TypedArray', () => { }); test.each([1, true, 'sapphire', new Float64Array()])('GIVEN %j THEN throw', (input) => { - expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray', `Expected a Float32Array`, input)); + expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray()', message ?? `Expected a Float32Array`, input)); }); }); describe('Float64Array', () => { - const predicate = s.float64Array; + const predicate = s.float64Array({ message }); test('GIVEN typed array THEN return the input', () => { const typedArray = new Float64Array(); @@ -139,12 +139,12 @@ describe('TypedArray', () => { }); test.each([1, true, 'sapphire'])('GIVEN %j THEN throw', (input) => { - expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray', `Expected a Float64Array`, input)); + expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray()', message ?? `Expected a Float64Array`, input)); }); }); describe('BigInt64Array', () => { - const predicate = s.bigInt64Array; + const predicate = s.bigInt64Array({ message }); test('GIVEN typed array THEN return the input', () => { const typedArray = new BigInt64Array(); @@ -152,12 +152,12 @@ describe('TypedArray', () => { }); test.each([1, true, 'sapphire', new BigUint64Array()])('GIVEN %j THEN throw', (input) => { - expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray', `Expected a BigInt64Array`, input)); + expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray()', message ?? `Expected a BigInt64Array`, input)); }); }); describe('BigUint64Array', () => { - const predicate = s.bigUint64Array; + const predicate = s.bigUint64Array({ message }); test('GIVEN typed array THEN return the input', () => { const typedArray = new BigUint64Array(); @@ -165,12 +165,12 @@ describe('TypedArray', () => { }); test.each([1, true, 'sapphire'])('GIVEN %j THEN throw', (input) => { - expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray', `Expected a BigUint64Array`, input)); + expect(() => predicate.parse(input)).toThrow(new ValidationError('s.typedArray()', message ?? `Expected a BigUint64Array`, input)); }); }); describe('lengthEqual', () => { - const bytePredicate = s.typedArray().byteLengthEqual(10); + const bytePredicate = s.typedArray().byteLengthEqual(10, { message }); test('GIVEN typed array with byte length 10 THEN return the input', () => { const typedArray = new Uint8Array(10); @@ -179,11 +179,16 @@ describe('TypedArray', () => { test.each([new Uint8Array(5)])('GIVEN %j THEN throw', (input) => { expect(() => bytePredicate.parse(input)).toThrow( - new ExpectedConstraintError('s.typedArray(T).byteLengthEqual', 'Invalid Typed Array byte length', input, 'expected.byteLength === 10') + new ExpectedConstraintError( + 's.typedArray(T).byteLengthEqual()', + message ?? 'Invalid Typed Array byte length', + input, + 'expected.byteLength === 10' + ) ); }); - const lengthPredicate = s.typedArray().lengthEqual(10); + const lengthPredicate = s.typedArray().lengthEqual(10, { message }); test('GIVEN typed array with length 10 THEN return the input', () => { const typedArray = new Uint8Array(10); @@ -192,13 +197,13 @@ describe('TypedArray', () => { test.each([new Uint8Array(5)])('GIVEN %j THEN throw', (input) => { expect(() => lengthPredicate.parse(input)).toThrow( - new ExpectedConstraintError('s.typedArray(T).lengthEqual', 'Invalid Typed Array length', input, 'expected.length === 10') + new ExpectedConstraintError('s.typedArray(T).lengthEqual()', message ?? 'Invalid Typed Array length', input, 'expected.length === 10') ); }); }); describe('lengthNotEqual', () => { - const bytePredicate = s.typedArray().byteLengthNotEqual(10); + const bytePredicate = s.typedArray().byteLengthNotEqual(10, { message }); test.each([new Uint8Array(5), new Uint8Array(15)])('GIVEN typed array with byte length 5 THEN return the input', (input) => { expect(bytePredicate.parse(input)).toBe(input); @@ -207,15 +212,15 @@ describe('TypedArray', () => { test('GIVEN a typed array of byte length 10 THEN throw', () => { expect(() => bytePredicate.parse(new Uint8Array(10))).toThrow( new ExpectedConstraintError( - 's.typedArray(T).byteLengthNotEqual', - 'Invalid Typed Array byte length', + 's.typedArray(T).byteLengthNotEqual()', + message ?? 'Invalid Typed Array byte length', new Uint8Array(10), 'expected.byteLength !== 10' ) ); }); - const lengthPredicate = s.typedArray().lengthNotEqual(10); + const lengthPredicate = s.typedArray().lengthNotEqual(10, { message }); test.each([new Uint8Array(5), new Uint8Array(15)])('GIVEN typed array with length 5 THEN return the input', (input) => { expect(lengthPredicate.parse(input)).toBe(input); }); @@ -223,8 +228,8 @@ describe('TypedArray', () => { test('GIVEN a typed array of length 10 THEN throw', () => { expect(() => lengthPredicate.parse(new Uint8Array(10))).toThrow( new ExpectedConstraintError( - 's.typedArray(T).lengthNotEqual', - 'Invalid Typed Array length', + 's.typedArray(T).lengthNotEqual()', + message ?? 'Invalid Typed Array length', new Uint8Array(10), 'expected.length !== 10' ) @@ -233,7 +238,7 @@ describe('TypedArray', () => { }); describe('lengthLessThan', () => { - const bytePredicate = s.typedArray().byteLengthLessThan(10); + const bytePredicate = s.typedArray().byteLengthLessThan(10, { message }); test('GIVEN typed array with byte length < 10 THEN return the input', () => { const typedArray = new Uint8Array(5); @@ -243,15 +248,15 @@ describe('TypedArray', () => { test.each([new Uint8Array(10)])('GIVEN %j THEN throw', (input) => { expect(() => bytePredicate.parse(input)).toThrow( new ExpectedConstraintError( - 's.typedArray(T).byteLengthLessThan', - 'Invalid Typed Array byte length', + 's.typedArray(T).byteLengthLessThan()', + message ?? 'Invalid Typed Array byte length', input, 'expected.byteLength < 10' ) ); }); - const lengthPredicate = s.typedArray().lengthLessThan(10); + const lengthPredicate = s.typedArray().lengthLessThan(10, { message }); test('GIVEN typed array with length < 10 THEN return the input', () => { const typedArray = new Uint8Array(5); @@ -260,13 +265,18 @@ describe('TypedArray', () => { test.each([new Uint8Array(10)])('GIVEN %j THEN throw', (input) => { expect(() => lengthPredicate.parse(input)).toThrow( - new ExpectedConstraintError('s.typedArray(T).lengthLessThan', 'Invalid Typed Array length', input, 'expected.length < 10') + new ExpectedConstraintError( + 's.typedArray(T).lengthLessThan()', + message ?? 'Invalid Typed Array length', + input, + 'expected.length < 10' + ) ); }); }); describe('lengthLessThanOrEqual', () => { - const bytePredicate = s.typedArray().byteLengthLessThanOrEqual(10); + const bytePredicate = s.typedArray().byteLengthLessThanOrEqual(10, { message }); test('GIVEN typed array with byte length <= 10 THEN return the input', () => { const typedArray = new Uint8Array(5); @@ -276,15 +286,15 @@ describe('TypedArray', () => { test.each([new Uint8Array(11)])('GIVEN %j THEN throw', (input) => { expect(() => bytePredicate.parse(input)).toThrow( new ExpectedConstraintError( - 's.typedArray(T).byteLengthLessThanOrEqual', - 'Invalid Typed Array byte length', + 's.typedArray(T).byteLengthLessThanOrEqual()', + message ?? 'Invalid Typed Array byte length', input, 'expected.byteLength <= 10' ) ); }); - const lengthPredicate = s.typedArray().lengthLessThanOrEqual(10); + const lengthPredicate = s.typedArray().lengthLessThanOrEqual(10, { message }); test('GIVEN typed array with length <= 10 THEN return the input', () => { const typedArray = new Uint8Array(5); @@ -293,13 +303,18 @@ describe('TypedArray', () => { test.each([new Uint8Array(11)])('GIVEN %j THEN throw', (input) => { expect(() => lengthPredicate.parse(input)).toThrow( - new ExpectedConstraintError('s.typedArray(T).lengthLessThanOrEqual', 'Invalid Typed Array length', input, 'expected.length <= 10') + new ExpectedConstraintError( + 's.typedArray(T).lengthLessThanOrEqual()', + message ?? 'Invalid Typed Array length', + input, + 'expected.length <= 10' + ) ); }); }); describe('lengthGreaterThan', () => { - const bytePredicate = s.typedArray().byteLengthGreaterThan(10); + const bytePredicate = s.typedArray().byteLengthGreaterThan(10, { message }); test('GIVEN typed array with byte length > 10 THEN return the input', () => { const typedArray = new Uint8Array(15); @@ -309,15 +324,15 @@ describe('TypedArray', () => { test.each([new Uint8Array(5)])('GIVEN %j THEN throw', (input) => { expect(() => bytePredicate.parse(input)).toThrow( new ExpectedConstraintError( - 's.typedArray(T).byteLengthGreaterThan', - 'Invalid Typed Array byte length', + 's.typedArray(T).byteLengthGreaterThan()', + message ?? 'Invalid Typed Array byte length', input, 'expected.byteLength > 10' ) ); }); - const lengthPredicate = s.typedArray().lengthGreaterThan(10); + const lengthPredicate = s.typedArray().lengthGreaterThan(10, { message }); test('GIVEN typed array with length > 10 THEN return the input', () => { const typedArray = new Uint8Array(15); @@ -326,13 +341,18 @@ describe('TypedArray', () => { test.each([new Uint8Array(5)])('GIVEN %j THEN throw', (input) => { expect(() => lengthPredicate.parse(input)).toThrow( - new ExpectedConstraintError('s.typedArray(T).lengthGreaterThan', 'Invalid Typed Array length', input, 'expected.length > 10') + new ExpectedConstraintError( + 's.typedArray(T).lengthGreaterThan()', + message ?? 'Invalid Typed Array length', + input, + 'expected.length > 10' + ) ); }); }); describe('lengthGreaterThanOrEqual', () => { - const bytePredicate = s.typedArray().byteLengthGreaterThanOrEqual(10); + const bytePredicate = s.typedArray().byteLengthGreaterThanOrEqual(10, { message }); test('GIVEN typed array with byte length >= 10 THEN return the input', () => { const typedArray = new Uint8Array(15); @@ -342,15 +362,15 @@ describe('TypedArray', () => { test.each([new Uint8Array(5)])('GIVEN %j THEN throw', (input) => { expect(() => bytePredicate.parse(input)).toThrow( new ExpectedConstraintError( - 's.typedArray(T).byteLengthGreaterThanOrEqual', - 'Invalid Typed Array byte length', + 's.typedArray(T).byteLengthGreaterThanOrEqual()', + message ?? 'Invalid Typed Array byte length', input, 'expected.byteLength >= 10' ) ); }); - const lengthPredicate = s.typedArray().lengthGreaterThanOrEqual(10); + const lengthPredicate = s.typedArray().lengthGreaterThanOrEqual(10, { message }); test('GIVEN typed array with length >= 10 THEN return the input', () => { const typedArray = new Uint8Array(15); @@ -359,13 +379,18 @@ describe('TypedArray', () => { test.each([new Uint8Array(5)])('GIVEN %j THEN throw', (input) => { expect(() => lengthPredicate.parse(input)).toThrow( - new ExpectedConstraintError('s.typedArray(T).lengthGreaterThanOrEqual', 'Invalid Typed Array length', input, 'expected.length >= 10') + new ExpectedConstraintError( + 's.typedArray(T).lengthGreaterThanOrEqual()', + message ?? 'Invalid Typed Array length', + input, + 'expected.length >= 10' + ) ); }); }); describe('lengthRange', () => { - const bytePredicate = s.typedArray().byteLengthRange(10, 20); + const bytePredicate = s.typedArray().byteLengthRange(10, 20, { message }); test('GIVEN typed array with byte length >= 10 AND <= 20 THEN return the input', () => { const typedArray = new Uint8Array(15); @@ -375,15 +400,15 @@ describe('TypedArray', () => { test.each([new Uint8Array(5), new Uint8Array(25)])('GIVEN %j THEN throw', (input) => { expect(() => bytePredicate.parse(input)).toThrow( new ExpectedConstraintError( - 's.typedArray(T).byteLengthRange', - 'Invalid Typed Array byte length', + 's.typedArray(T).byteLengthRange()', + message ?? 'Invalid Typed Array byte length', input, 'expected.byteLength >= 10 AND <= 20' ) ); }); - const lengthPredicate = s.typedArray().lengthRange(10, 20); + const lengthPredicate = s.typedArray().lengthRange(10, 20, { message }); test('GIVEN typed array with length >= 10 AND <= 20 THEN return the input', () => { const typedArray = new Uint8Array(15); @@ -392,13 +417,18 @@ describe('TypedArray', () => { test.each([new Uint8Array(5), new Uint8Array(25)])('GIVEN %j THEN throw', (input) => { expect(() => lengthPredicate.parse(input)).toThrow( - new ExpectedConstraintError('s.typedArray(T).lengthRange', 'Invalid Typed Array length', input, 'expected.length >= 10 AND <= 20') + new ExpectedConstraintError( + 's.typedArray(T).lengthRange()', + message ?? 'Invalid Typed Array length', + input, + 'expected.length >= 10 AND <= 20' + ) ); }); }); describe('LengthRangeInclusive', () => { - const bytePredicate = s.typedArray().byteLengthRangeInclusive(10, 20); + const bytePredicate = s.typedArray().byteLengthRangeInclusive(10, 20, { message }); test('GIVEN typed array with byte length >= 10 AND <= 20 THEN return the input', () => { const typedArray = new Uint8Array(15); @@ -408,15 +438,15 @@ describe('TypedArray', () => { test.each([new Uint8Array(5), new Uint8Array(25)])('GIVEN %j THEN throw', (input) => { expect(() => bytePredicate.parse(input)).toThrow( new ExpectedConstraintError( - 's.typedArray(T).byteLengthRangeInclusive', - 'Invalid Typed Array byte length', + 's.typedArray(T).byteLengthRangeInclusive()', + message ?? 'Invalid Typed Array byte length', input, 'expected.byteLength >= 10 AND <= 20' ) ); }); - const lengthPredicate = s.typedArray().lengthRangeInclusive(10, 20); + const lengthPredicate = s.typedArray().lengthRangeInclusive(10, 20, { message }); test('GIVEN typed array with length >= 10 AND <= 20 THEN return the input', () => { const typedArray = new Uint8Array(15); @@ -426,8 +456,8 @@ describe('TypedArray', () => { test.each([new Uint8Array(5), new Uint8Array(25)])('GIVEN %j THEN throw', (input) => { expect(() => lengthPredicate.parse(input)).toThrow( new ExpectedConstraintError( - 's.typedArray(T).lengthRangeInclusive', - 'Invalid Typed Array length', + 's.typedArray(T).lengthRangeInclusive()', + message ?? 'Invalid Typed Array length', input, 'expected.length >= 10 AND <= 20' ) @@ -436,7 +466,7 @@ describe('TypedArray', () => { }); describe('LengthRangeExclusive', () => { - const bytePredicate = s.typedArray().byteLengthRangeExclusive(10, 20); + const bytePredicate = s.typedArray().byteLengthRangeExclusive(10, 20, { message }); test('GIVEN typed array with byte length > 10 AND < 20 THEN return the input', () => { const typedArray = new Uint8Array(15); @@ -446,15 +476,15 @@ describe('TypedArray', () => { test.each([new Uint8Array(5), new Uint8Array(25)])('GIVEN %j THEN throw', (input) => { expect(() => bytePredicate.parse(input)).toThrow( new ExpectedConstraintError( - 's.typedArray(T).byteLengthRangeExclusive', - 'Invalid Typed Array byte length', + 's.typedArray(T).byteLengthRangeExclusive()', + message ?? 'Invalid Typed Array byte length', input, 'expected.byteLength > 10 AND < 20' ) ); }); - const lengthPredicate = s.typedArray().lengthRangeExclusive(10, 20); + const lengthPredicate = s.typedArray().lengthRangeExclusive(10, 20, { message }); test('GIVEN typed array with length > 10 AND < 20 THEN return the input', () => { const typedArray = new Uint8Array(15); @@ -464,8 +494,8 @@ describe('TypedArray', () => { test.each([new Uint8Array(5), new Uint8Array(25)])('GIVEN %j THEN throw', (input) => { expect(() => lengthPredicate.parse(input)).toThrow( new ExpectedConstraintError( - 's.typedArray(T).lengthRangeExclusive', - 'Invalid Typed Array length', + 's.typedArray(T).lengthRangeExclusive()', + message ?? 'Invalid Typed Array length', input, 'expected.length > 10 AND < 20' ) diff --git a/tests/validators/undefined.test.ts b/tests/validators/undefined.test.ts index 04a39a58..2fc9bb21 100644 --- a/tests/validators/undefined.test.ts +++ b/tests/validators/undefined.test.ts @@ -1,14 +1,17 @@ import { ExpectedValidationError, s } from '../../src'; import { expectError } from '../common/macros/comparators'; -describe('UndefinedValidator', () => { - const predicate = s.undefined; +describe.each(['custom message', undefined])('UndefinedValidator (%s)', (message) => { + const predicate = s.undefined({ message }); test('GIVEN undefined THEN returns undefined', () => { expect(predicate.parse(undefined)).toBe(undefined); }); test.each([null, 123, 'Hello'])('GIVEN %j THEN throws ExpectedValidationError', (input) => { - expectError(() => predicate.parse(input), new ExpectedValidationError('s.literal(V)', 'Expected values to be equals', input, undefined)); + expectError( + () => predicate.parse(input), + new ExpectedValidationError('s.literal(V)', message ?? 'Expected values to be equals', input, undefined) + ); }); }); diff --git a/tests/validators/union.test.ts b/tests/validators/union.test.ts index c824697b..575df0b0 100644 --- a/tests/validators/union.test.ts +++ b/tests/validators/union.test.ts @@ -1,11 +1,13 @@ import { CombinedError, ExpectedValidationError, s, ValidationError } from '../../src'; import { expectClonedValidator, expectError } from '../common/macros/comparators'; -describe('UnionValidator', () => { - const stringPredicate = s.string; - const numberPredicate = s.number; +describe.each(['custom message', undefined])('UnionValidator (%s)', (message) => { + const stringPredicate = s.string({ message }); + const numberPredicate = s.number({ message }); - const predicate = s.union(stringPredicate, numberPredicate); + const predicate = s.union([stringPredicate, numberPredicate], { message }); + + const oneOrMoreErrorsErrorMessage = message ?? 'Received one or more errors'; test('GIVEN a string THEN returns string', () => { expect(predicate.parse('hello')).toBe('hello'); @@ -18,15 +20,20 @@ describe('UnionValidator', () => { test('GIVEN a boolean THEN throw a ConstraintError', () => { expectError( () => predicate.parse(true), - new CombinedError([ - new ValidationError('s.string', 'Expected a string primitive', true), - new ValidationError('s.number', 'Expected a number primitive', true) - ]) + new CombinedError( + [ + new ValidationError('s.string()', message ?? 'Expected a string primitive', true), + new ValidationError('s.number()', message ?? 'Expected a number primitive', true) + ], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); describe('or', () => { - const orPredicate = predicate.or(s.string.array); + const orPredicate = predicate.or(s.string({ message }).array({ message })); test.each([5, 'foo', ['bar']])('GIVEN %j THEN returns the input', (value) => { expect(orPredicate.parse(value)).toStrictEqual(value); @@ -35,21 +42,31 @@ describe('UnionValidator', () => { test.each([null, undefined, true])('GIVEN %j THEN throws CombinedError', (value) => { expectError( () => orPredicate.parse(value), - new CombinedError([ - new ValidationError('s.string', 'Expected a string primitive', value), - new ValidationError('s.number', 'Expected a number primitive', value), - new ValidationError('s.array(T)', 'Expected an array', value) - ]) + new CombinedError( + [ + new ValidationError('s.string()', message ?? 'Expected a string primitive', value), + new ValidationError('s.number()', message ?? 'Expected a number primitive', value), + new ValidationError('s.array(T)', message ?? 'Expected an array', value) + ], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); - test('GIVEN s.union(s.string, s.number).or(s.string.array) THEN returns s.union(s.string, s.number, s.string.array)', () => { - expectClonedValidator(orPredicate, s.union(s.string, s.number, s.string.array)); + test('GIVEN s.union(s.string(), s.number()).or(s.string().array()) THEN returns s.union(s.string(), s.number(), s.string().array())', () => { + expectClonedValidator(orPredicate, s.union([s.string(), s.number(), s.string().array()])); }); }); describe('optional', () => { - const optionalPredicate = predicate.optional; + const optionalPredicate = predicate.optional(); + + test('GIVEN no validators inside the union THEN returns a LiteralValidator(undefined)', () => { + const emptyPredicate = s.union([], { message }).optional(); + expect(emptyPredicate.parse(undefined)).toBe(undefined); + }); test.each([undefined, 'hello', 5])('GIVEN %j THEN returns %j', (value) => { expect(optionalPredicate.parse(value)).toBe(value); @@ -58,45 +75,60 @@ describe('UnionValidator', () => { test.each([null, true, {}])('GIVEN %j THEN throws CombinedError', (value) => { expectError( () => optionalPredicate.parse(value), - new CombinedError([ - new ExpectedValidationError('s.literal(V)', 'Expected values to be equals', value, undefined), - new ValidationError('s.string', 'Expected a string primitive', value), - new ValidationError('s.number', 'Expected a number primitive', value) - ]) + new CombinedError( + [ + new ExpectedValidationError('s.literal(V)', message ?? 'Expected values to be equals', value, undefined), + new ValidationError('s.string()', message ?? 'Expected a string primitive', value), + new ValidationError('s.number()', message ?? 'Expected a number primitive', value) + ], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); - test('GIVEN s.union(s.string, s.number).optional THEN returns s.union(s.undefined, s.string, s.number)', () => { - expectClonedValidator(optionalPredicate, s.union(s.undefined, s.string, s.number)); + test('GIVEN s.union(s.string(), s.number()).optional() THEN returns s.union(s.undefined(), s.string(), s.number())', () => { + expectClonedValidator(optionalPredicate, s.union([s.undefined(), s.string(), s.number()])); }); describe('optional', () => { - const doubleOptionalPredicate = optionalPredicate.optional; + const doubleOptionalPredicate = optionalPredicate.optional({ message }); - test('GIVEN s.union(s.string, s.number).optional.optional THEN returns s.union(s.undefined, s.string, s.number)', () => { - expectClonedValidator(s.union(s.undefined, s.string, s.number), doubleOptionalPredicate); + test('GIVEN s.union(s.string(), s.number()).optional().optional() THEN returns s.union(s.undefined(), s.string(), s.number())', () => { + expectClonedValidator(s.union([s.undefined(), s.string(), s.number()]), doubleOptionalPredicate); }); }); describe('nullable', () => { - const nullableOptionalPredicate = optionalPredicate.nullable; + const nullableOptionalPredicate = optionalPredicate.nullable({ message }); - test('GIVEN s.union(s.string, s.number).optional.nullable THEN returns s.union(s.nullish, s.string, s.number)', () => { - expectClonedValidator(nullableOptionalPredicate, s.union(s.nullish, s.string, s.number)); + test('GIVEN s.union(s.string(), s.number()).optional().nullable() THEN returns s.union(s.nullish(), s.string(), s.number())', () => { + expectClonedValidator(nullableOptionalPredicate, s.union([s.nullish(), s.string(), s.number()])); }); }); describe('nullish', () => { - const nullishOptionalPredicate = optionalPredicate.nullish; + const nullishOptionalPredicate = optionalPredicate.nullish({ message }); - test('GIVEN s.union(s.string, s.number).nullable.nullish THEN returns s.union(s.nullish, s.string, s.number)', () => { - expectClonedValidator(nullishOptionalPredicate, s.union(s.nullish, s.string, s.number)); + test('GIVEN s.union(s.string(), s.number()).nullable().nullish() THEN returns s.union(s.nullish(), s.string(), s.number())', () => { + expectClonedValidator(nullishOptionalPredicate, s.union([s.nullish(), s.string(), s.number()])); }); }); }); describe('required', () => { - const requiredPredicate = predicate.optional.required; + const requiredPredicate = predicate.required({ message }); + + test('GIVEN no validators inside the union THEN clones the validator as a RequiredValidator', () => { + const emptyPredicate = s.union([], { message }).required({ message }); + expectError( + () => emptyPredicate.parse(undefined), + new CombinedError([], { + message: oneOrMoreErrorsErrorMessage + }) + ); + }); test.each(['hello', 5])('GIVEN %j THEN returns %j', (value) => { expect(requiredPredicate.parse(value)).toBe(value); @@ -105,24 +137,37 @@ describe('UnionValidator', () => { test.each([null, true, {}, undefined])('GIVEN %j THEN throws CombinedError', (value) => { expectError( () => requiredPredicate.parse(value), - new CombinedError([ - new ValidationError('s.string', 'Expected a string primitive', value), - new ValidationError('s.number', 'Expected a number primitive', value) - ]) + new CombinedError( + [ + new ValidationError('s.string()', message ?? 'Expected a string primitive', value), + new ValidationError('s.number()', message ?? 'Expected a number primitive', value) + ], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); describe('nullish', () => { - const nullishRequiredPredicate = requiredPredicate.nullish.required; + const nullishRequiredPredicate = requiredPredicate.nullish({ message }).required({ message }); - test('GIVEN s.union(s.string, s.number).nullish.required THEN returns s.union(s.literal(null), s.string, s.number)', () => { - expectClonedValidator(nullishRequiredPredicate, s.union(s.literal(null), s.string, s.number)); + test('GIVEN s.union(s.string(), s.number()).nullish().required() THEN returns s.union(s.literal(null), s.string(), s.number())', () => { + expectClonedValidator( + nullishRequiredPredicate, + s.union([s.literal(null), s.string({ message }), s.number({ message })], { message }) + ); }); }); }); describe('nullable', () => { - const nullablePredicate = predicate.nullable; + const nullablePredicate = predicate.nullable({ message }); + + test('GIVEN no validators inside the union THEN returns a LiteralValidator(null)', () => { + const emptyPredicate = s.union([], { message }).nullable(); + expect(emptyPredicate.parse(null)).toBe(null); + }); test.each([null, 'hello', 5])('GIVEN %j THEN returns %j', (value) => { expect(nullablePredicate.parse(value)).toBe(value); @@ -131,45 +176,64 @@ describe('UnionValidator', () => { test.each([undefined, true, {}])('GIVEN %j THEN throws CombinedError', (value) => { expectError( () => nullablePredicate.parse(value), - new CombinedError([ - new ExpectedValidationError('s.literal(V)', 'Expected values to be equals', value, null), - new ValidationError('s.string', 'Expected a string primitive', value), - new ValidationError('s.number', 'Expected a number primitive', value) - ]) + new CombinedError( + [ + new ExpectedValidationError('s.literal(V)', message ?? 'Expected values to be equals', value, null), + new ValidationError('s.string()', message ?? 'Expected a string primitive', value), + new ValidationError('s.number()', message ?? 'Expected a number primitive', value) + ], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); - test('GIVEN s.union(s.string, s.number).nullable THEN returns s.union(s.null, s.string, s.number)', () => { - expectClonedValidator(nullablePredicate, s.union(s.null, s.string, s.number)); + test('GIVEN s.union(s.string(), s.number()).nullable THEN returns s.union(s.null(), s.string(), s.number())', () => { + expectClonedValidator(nullablePredicate, s.union([s.null({ message }), s.string({ message }), s.number({ message })], { message })); }); describe('optional', () => { - const optionalNullablePredicate = nullablePredicate.optional; + const optionalNullablePredicate = nullablePredicate.optional({ message }); - test('GIVEN s.union(s.string, s.number).nullable.optional THEN returns s.union(s.nullish, s.string, s.number)', () => { - expectClonedValidator(optionalNullablePredicate, s.union(s.nullish, s.string, s.number)); + test('GIVEN s.union(s.string(), s.number()).nullable.optional THEN returns s.union(s.nullish(), s.string(), s.number())', () => { + expectClonedValidator( + optionalNullablePredicate, + s.union([s.nullish({ message }), s.string({ message }), s.number({ message })], { message }) + ); }); }); describe('nullable', () => { - const doubleNullablePredicate = nullablePredicate.nullable; + const doubleNullablePredicate = nullablePredicate.nullable({ message }); - test('GIVEN s.union(s.string, s.number).nullable.nullable THEN returns s.union(s.null, s.string, s.number)', () => { - expectClonedValidator(doubleNullablePredicate, s.union(s.null, s.string, s.number)); + test('GIVEN s.union(s.string(), s.number()).nullable.nullable THEN returns s.union(s.null(), s.string(), s.number())', () => { + expectClonedValidator( + doubleNullablePredicate, + s.union([s.null({ message }), s.string({ message }), s.number({ message })], { message }) + ); }); }); describe('nullish', () => { - const nullishNullablePredicate = nullablePredicate.nullish; + const nullishNullablePredicate = nullablePredicate.nullish({ message }); - test('GIVEN s.union(s.string, s.number).nullable.nullish THEN returns s.union(s.nullish, s.string, s.number)', () => { - expectClonedValidator(nullishNullablePredicate, s.union(s.nullish, s.string, s.number)); + test('GIVEN s.union(s.string(), s.number()).nullable().nullish() THEN returns s.union(s.nullish(), s.string(), s.number())', () => { + expectClonedValidator( + nullishNullablePredicate, + s.union([s.nullish({ message }), s.string({ message }), s.number({ message })], { message }) + ); }); }); }); describe('nullish', () => { - const nullishPredicate = predicate.nullish; + const nullishPredicate = predicate.nullish({ message }); + + test('GIVEN no validators inside the union THEN returns a NullishValidator()', () => { + const emptyPredicate = s.union([], { message }).nullish(); + expect(emptyPredicate.parse(undefined)).toBe(undefined); + }); test.each([null, undefined, 'hello', 5])('GIVEN %j THEN returns %j', (value) => { expect(nullishPredicate.parse(value)).toBe(value); @@ -178,39 +242,53 @@ describe('UnionValidator', () => { test.each([true, {}])('GIVEN %j THEN throws CombinedError', (value) => { expectError( () => nullishPredicate.parse(value), - new CombinedError([ - new ValidationError('s.nullish', 'Expected undefined or null', value), - new ValidationError('s.string', 'Expected a string primitive', value), - new ValidationError('s.number', 'Expected a number primitive', value) - ]) + new CombinedError( + [ + new ValidationError('s.nullish()', message ?? 'Expected undefined or null', value), + new ValidationError('s.string()', message ?? 'Expected a string primitive', value), + new ValidationError('s.number()', message ?? 'Expected a number primitive', value) + ], + { + message: oneOrMoreErrorsErrorMessage + } + ) ); }); - test('GIVEN s.union(s.string, s.number).nullable THEN returns s.union(s.null, s.string, s.number)', () => { - expectClonedValidator(nullishPredicate, s.union(s.null, s.string, s.number)); + test('GIVEN s.union(s.string(), s.number()).nullable THEN returns s.union(s.null(), s.string(), s.number())', () => { + expectClonedValidator(nullishPredicate, s.union([s.null({ message }), s.string({ message }), s.number({ message })], { message })); }); describe('optional', () => { - const optionalNullishPredicate = nullishPredicate.optional; + const optionalNullishPredicate = nullishPredicate.optional({ message }); - test('GIVEN s.union(s.string, s.number).nullish.optional THEN returns s.union(s.nullish, s.string, s.number)', () => { - expectClonedValidator(optionalNullishPredicate, s.union(s.nullish, s.string, s.number)); + test('GIVEN s.union(s.string(), s.number()).nullish().optional() THEN returns s.union(s.nullish(), s.string(), s.number())', () => { + expectClonedValidator( + optionalNullishPredicate, + s.union([s.nullish({ message }), s.string({ message }), s.number({ message })], { message }) + ); }); }); describe('nullable', () => { - const nullableNullishPredicate = nullishPredicate.nullable; + const nullableNullishPredicate = nullishPredicate.nullable({ message }); - test('GIVEN s.union(s.string, s.number).nullish.nullable THEN returns s.union(s.nullish, s.string, s.number)', () => { - expectClonedValidator(nullableNullishPredicate, s.union(s.nullish, s.string, s.number)); + test('GIVEN s.union(s.string(), s.number()).nullish.nullable THEN returns s.union(s.nullish(), s.string(), s.number())', () => { + expectClonedValidator( + nullableNullishPredicate, + s.union([s.nullish({ message }), s.string({ message }), s.number({ message })], { message }) + ); }); }); describe('nullish', () => { - const doubleNullishPredicate = nullishPredicate.nullish; + const doubleNullishPredicate = nullishPredicate.nullish({ message }); - test('GIVEN s.union(s.string, s.number).nullish.nullish THEN returns s.union(s.nullish, s.string, s.number)', () => { - expectClonedValidator(doubleNullishPredicate, s.union(s.nullish, s.string, s.number)); + test('GIVEN s.union(s.string(), s.number()).nullish.nullish THEN returns s.union(s.nullish(), s.string(), s.number())', () => { + expectClonedValidator( + doubleNullishPredicate, + s.union([s.nullish({ message }), s.string({ message }), s.number({ message })], { message }) + ); }); }); }); diff --git a/vitest.config.ts b/vitest.config.ts index f566416e..b2d7c0e7 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,8 +4,11 @@ export default defineConfig({ test: { globals: true, coverage: { + provider: 'v8', enabled: true, - reporter: ['text', 'lcov', 'cobertura'] + reporter: ['text', 'lcov', 'cobertura'], + include: ['src/**/*.ts'], + exclude: ['src/constraints/base/IConstraint.ts', 'src/constraints/type-exports.ts'] } }, esbuild: { diff --git a/yarn.lock b/yarn.lock index 2f974546..e987583d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,75 +5,69 @@ __metadata: version: 8 cacheKey: 10 -"@aashutoshrathi/word-wrap@npm:^1.2.3": - version: 1.2.6 - resolution: "@aashutoshrathi/word-wrap@npm:1.2.6" - checksum: 10/6eebd12a5cd03cee38fcb915ef9f4ea557df6a06f642dfc7fe8eb4839eb5c9ca55a382f3604d52c14200b0c214c12af5e1f23d2a6d8e23ef2d016b105a9d6c0a - languageName: node - linkType: hard - "@ampproject/remapping@npm:^2.2.1": - version: 2.2.1 - resolution: "@ampproject/remapping@npm:2.2.1" + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.0" - "@jridgewell/trace-mapping": "npm:^0.3.9" - checksum: 10/e15fecbf3b54c988c8b4fdea8ef514ab482537e8a080b2978cc4b47ccca7140577ca7b65ad3322dcce65bc73ee6e5b90cbfe0bbd8c766dad04d5c62ec9634c42 + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10/f3451525379c68a73eb0a1e65247fbf28c0cccd126d93af21c75fceff77773d43c0d4a2d51978fb131aff25b5f2cb41a9fe48cc296e61ae65e679c4f6918b0ab languageName: node linkType: hard "@babel/code-frame@npm:^7.0.0": - version: 7.23.5 - resolution: "@babel/code-frame@npm:7.23.5" + version: 7.24.2 + resolution: "@babel/code-frame@npm:7.24.2" dependencies: - "@babel/highlight": "npm:^7.23.4" - chalk: "npm:^2.4.2" - checksum: 10/44e58529c9d93083288dc9e649c553c5ba997475a7b0758cc3ddc4d77b8a7d985dbe78cc39c9bbc61f26d50af6da1ddf0a3427eae8cc222a9370619b671ed8f5 + "@babel/highlight": "npm:^7.24.2" + picocolors: "npm:^1.0.0" + checksum: 10/7db8f5b36ffa3f47a37f58f61e3d130b9ecad21961f3eede7e2a4ac2c7e4a5efb6e9d03a810c669bc986096831b6c0dfc2c3082673d93351b82359c1b03e0590 languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.23.4": - version: 7.23.4 - resolution: "@babel/helper-string-parser@npm:7.23.4" - checksum: 10/c352082474a2ee1d2b812bd116a56b2e8b38065df9678a32a535f151ec6f58e54633cc778778374f10544b930703cca6ddf998803888a636afa27e2658068a9c +"@babel/helper-string-parser@npm:^7.24.1": + version: 7.24.1 + resolution: "@babel/helper-string-parser@npm:7.24.1" + checksum: 10/04c0ede77b908b43e6124753b48bc485528112a9335f0a21a226bff1ace75bb6e64fab24c85cb4b1610ef3494dacd1cb807caeb6b79a7b36c43d48c289b35949 languageName: node linkType: hard -"@babel/helper-validator-identifier@npm:^7.22.20": - version: 7.22.20 - resolution: "@babel/helper-validator-identifier@npm:7.22.20" - checksum: 10/df882d2675101df2d507b95b195ca2f86a3ef28cb711c84f37e79ca23178e13b9f0d8b522774211f51e40168bf5142be4c1c9776a150cddb61a0d5bf3e95750b +"@babel/helper-validator-identifier@npm:^7.24.5": + version: 7.24.5 + resolution: "@babel/helper-validator-identifier@npm:7.24.5" + checksum: 10/38aaf6a64a0ea2e84766165b461deda3c24fd2173dff18419a2cc9e1ea1d3e709039aee94db29433a07011492717c80900a5eb564cdca7d137757c3c69e26898 languageName: node linkType: hard -"@babel/highlight@npm:^7.23.4": - version: 7.23.4 - resolution: "@babel/highlight@npm:7.23.4" +"@babel/highlight@npm:^7.24.2": + version: 7.24.5 + resolution: "@babel/highlight@npm:7.24.5" dependencies: - "@babel/helper-validator-identifier": "npm:^7.22.20" + "@babel/helper-validator-identifier": "npm:^7.24.5" chalk: "npm:^2.4.2" js-tokens: "npm:^4.0.0" - checksum: 10/62fef9b5bcea7131df4626d009029b1ae85332042f4648a4ce6e740c3fd23112603c740c45575caec62f260c96b11054d3be5987f4981a5479793579c3aac71f + picocolors: "npm:^1.0.0" + checksum: 10/afde0403154ad69ecd58a98903058e776760444bf4d0363fb740a8596bc6278b72c5226637c4f6b3674d70acb1665207fe2fcecfe93a74f2f4ab033e89fd7e8c languageName: node linkType: hard -"@babel/parser@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/parser@npm:7.23.6" +"@babel/parser@npm:^7.24.4": + version: 7.24.5 + resolution: "@babel/parser@npm:7.24.5" bin: parser: ./bin/babel-parser.js - checksum: 10/6be3a63d3c9d07b035b5a79c022327cb7e16cbd530140ecb731f19a650c794c315a72c699a22413ebeafaff14aa8f53435111898d59e01a393d741b85629fa7d + checksum: 10/f5ed1c5fd4b0045a364fb906f54fd30e2fff93a45069068b6d80d3ab2b64f5569c90fb41d39aff80fb7e925ca4d44917965a76776a3ca11924ec1fae3be5d1ea languageName: node linkType: hard -"@babel/types@npm:^7.23.6, @babel/types@npm:^7.8.3": - version: 7.23.6 - resolution: "@babel/types@npm:7.23.6" +"@babel/types@npm:^7.24.0, @babel/types@npm:^7.8.3": + version: 7.24.5 + resolution: "@babel/types@npm:7.24.5" dependencies: - "@babel/helper-string-parser": "npm:^7.23.4" - "@babel/helper-validator-identifier": "npm:^7.22.20" + "@babel/helper-string-parser": "npm:^7.24.1" + "@babel/helper-validator-identifier": "npm:^7.24.5" to-fast-properties: "npm:^2.0.0" - checksum: 10/07e70bb94d30b0231396b5e9a7726e6d9227a0a62e0a6830c0bd3232f33b024092e3d5a7d1b096a65bbf2bb43a9ab4c721bf618e115bfbb87b454fa060f88cbf + checksum: 10/259e7512476ae64830e73f2addf143159232bcbf0eba6a6a27cab25a960cd353a11c826eb54185fdf7d8d9865922cbcd6522149e9ec55b967131193f9c9111a1 languageName: node linkType: hard @@ -276,163 +270,324 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/aix-ppc64@npm:0.19.11" +"@esbuild/aix-ppc64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/aix-ppc64@npm:0.19.12" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/aix-ppc64@npm:0.20.2" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/android-arm64@npm:0.19.11" +"@esbuild/android-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/android-arm64@npm:0.19.12" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/android-arm64@npm:0.20.2" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/android-arm@npm:0.19.11" +"@esbuild/android-arm@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/android-arm@npm:0.19.12" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/android-arm@npm:0.20.2" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/android-x64@npm:0.19.11" +"@esbuild/android-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/android-x64@npm:0.19.12" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/android-x64@npm:0.20.2" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/darwin-arm64@npm:0.19.11" +"@esbuild/darwin-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/darwin-arm64@npm:0.19.12" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/darwin-arm64@npm:0.20.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/darwin-x64@npm:0.19.11" +"@esbuild/darwin-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/darwin-x64@npm:0.19.12" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/darwin-x64@npm:0.20.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/freebsd-arm64@npm:0.19.11" +"@esbuild/freebsd-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/freebsd-arm64@npm:0.19.12" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/freebsd-x64@npm:0.19.11" +"@esbuild/freebsd-arm64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/freebsd-arm64@npm:0.20.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/freebsd-x64@npm:0.19.12" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/freebsd-x64@npm:0.20.2" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-arm64@npm:0.19.11" +"@esbuild/linux-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-arm64@npm:0.19.12" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-arm64@npm:0.20.2" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-arm@npm:0.19.11" +"@esbuild/linux-arm@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-arm@npm:0.19.12" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-arm@npm:0.20.2" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-ia32@npm:0.19.11" +"@esbuild/linux-ia32@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-ia32@npm:0.19.12" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-loong64@npm:0.19.11" +"@esbuild/linux-ia32@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-ia32@npm:0.20.2" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-loong64@npm:0.19.12" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-loong64@npm:0.20.2" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-mips64el@npm:0.19.11" +"@esbuild/linux-mips64el@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-mips64el@npm:0.19.12" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-ppc64@npm:0.19.11" +"@esbuild/linux-mips64el@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-mips64el@npm:0.20.2" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-ppc64@npm:0.19.12" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-riscv64@npm:0.19.11" +"@esbuild/linux-ppc64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-ppc64@npm:0.20.2" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-riscv64@npm:0.19.12" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-riscv64@npm:0.20.2" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-s390x@npm:0.19.11" +"@esbuild/linux-s390x@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-s390x@npm:0.19.12" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-s390x@npm:0.20.2" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/linux-x64@npm:0.19.11" +"@esbuild/linux-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-x64@npm:0.19.12" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/linux-x64@npm:0.20.2" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/netbsd-x64@npm:0.19.11" +"@esbuild/netbsd-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/netbsd-x64@npm:0.19.12" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/netbsd-x64@npm:0.20.2" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/openbsd-x64@npm:0.19.11" +"@esbuild/openbsd-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/openbsd-x64@npm:0.19.12" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/openbsd-x64@npm:0.20.2" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/sunos-x64@npm:0.19.11" +"@esbuild/sunos-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/sunos-x64@npm:0.19.12" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/sunos-x64@npm:0.20.2" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/win32-arm64@npm:0.19.11" +"@esbuild/win32-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/win32-arm64@npm:0.19.12" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/win32-arm64@npm:0.20.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/win32-ia32@npm:0.19.11" +"@esbuild/win32-ia32@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/win32-ia32@npm:0.19.12" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/win32-ia32@npm:0.20.2" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.19.11": - version: 0.19.11 - resolution: "@esbuild/win32-x64@npm:0.19.11" +"@esbuild/win32-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/win32-x64@npm:0.19.12" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.20.2": + version: 0.20.2 + resolution: "@esbuild/win32-x64@npm:0.20.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -548,9 +703,9 @@ __metadata: linkType: hard "@humanwhocodes/object-schema@npm:^2.0.2": - version: 2.0.2 - resolution: "@humanwhocodes/object-schema@npm:2.0.2" - checksum: 10/ef915e3e2f34652f3d383b28a9a99cfea476fa991482370889ab14aac8ecd2b38d47cc21932526c6d949da0daf4a4a6bf629d30f41b0caca25e146819cbfa70e + version: 2.0.3 + resolution: "@humanwhocodes/object-schema@npm:2.0.3" + checksum: 10/05bb99ed06c16408a45a833f03a732f59bf6184795d4efadd33238ff8699190a8c871ad1121241bb6501589a9598dc83bf25b99dcbcf41e155cdf36e35e937a3 languageName: node linkType: hard @@ -584,28 +739,28 @@ __metadata: languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2": - version: 0.3.3 - resolution: "@jridgewell/gen-mapping@npm:0.3.3" +"@jridgewell/gen-mapping@npm:^0.3.2, @jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.5 + resolution: "@jridgewell/gen-mapping@npm:0.3.5" dependencies: - "@jridgewell/set-array": "npm:^1.0.1" + "@jridgewell/set-array": "npm:^1.2.1" "@jridgewell/sourcemap-codec": "npm:^1.4.10" - "@jridgewell/trace-mapping": "npm:^0.3.9" - checksum: 10/072ace159c39ab85944bdabe017c3de15c5e046a4a4a772045b00ff05e2ebdcfa3840b88ae27e897d473eb4d4845b37be3c78e28910c779f5aeeeae2fb7f0cc2 + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10/81587b3c4dd8e6c60252122937cea0c637486311f4ed208b52b62aae2e7a87598f63ec330e6cd0984af494bfb16d3f0d60d3b21d7e5b4aedd2602ff3fe9d32e2 languageName: node linkType: hard "@jridgewell/resolve-uri@npm:^3.1.0": - version: 3.1.1 - resolution: "@jridgewell/resolve-uri@npm:3.1.1" - checksum: 10/64d59df8ae1a4e74315eb1b61e012f1c7bc8aac47a3a1e683f6fe7008eab07bc512a742b7aa7c0405685d1421206de58c9c2e6adbfe23832f8bd69408ffc183e + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10/97106439d750a409c22c8bff822d648f6a71f3aa9bc8e5129efdc36343cd3096ddc4eeb1c62d2fe48e9bdd4db37b05d4646a17114ecebd3bbcacfa2de51c3c1d languageName: node linkType: hard -"@jridgewell/set-array@npm:^1.0.1": - version: 1.1.2 - resolution: "@jridgewell/set-array@npm:1.1.2" - checksum: 10/69a84d5980385f396ff60a175f7177af0b8da4ddb81824cb7016a9ef914eee9806c72b6b65942003c63f7983d4f39a5c6c27185bbca88eb4690b62075602e28e +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 10/832e513a85a588f8ed4f27d1279420d8547743cc37fcad5a5a76fc74bb895b013dfe614d0eed9cb860048e6546b798f8f2652020b4b2ba0561b05caa8c654b10 languageName: node linkType: hard @@ -616,7 +771,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.9": +"@jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" dependencies: @@ -661,24 +816,24 @@ __metadata: linkType: hard "@npmcli/agent@npm:^2.0.0": - version: 2.2.0 - resolution: "@npmcli/agent@npm:2.2.0" + version: 2.2.2 + resolution: "@npmcli/agent@npm:2.2.2" dependencies: agent-base: "npm:^7.1.0" http-proxy-agent: "npm:^7.0.0" https-proxy-agent: "npm:^7.0.1" lru-cache: "npm:^10.0.1" - socks-proxy-agent: "npm:^8.0.1" - checksum: 10/822ea077553cd9cfc5cbd6d92380b0950fcb054a7027cd1b63a33bd0cbb16b0c6626ea75d95ec0e804643c8904472d3361d2da8c2444b1fb02a9b525d9c07c41 + socks-proxy-agent: "npm:^8.0.3" + checksum: 10/96fc0036b101bae5032dc2a4cd832efb815ce9b33f9ee2f29909ee49d96a0026b3565f73c507a69eb8603f5cb32e0ae45a70cab1e2655990a4e06ae99f7f572a languageName: node linkType: hard "@npmcli/fs@npm:^3.1.0": - version: 3.1.0 - resolution: "@npmcli/fs@npm:3.1.0" + version: 3.1.1 + resolution: "@npmcli/fs@npm:3.1.1" dependencies: semver: "npm:^7.3.5" - checksum: 10/f3a7ab3a31de65e42aeb6ed03ed035ef123d2de7af4deb9d4a003d27acc8618b57d9fb9d259fe6c28ca538032a028f37337264388ba27d26d37fff7dde22476e + checksum: 10/1e0e04087049b24b38bc0b30d87a9388ee3ca1d3fdfc347c2f77d84fcfe6a51f250bc57ba2c1f614d7e4285c6c62bf8c769bc19aa0949ea39e5b043ee023b0bd languageName: node linkType: hard @@ -696,93 +851,114 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.9.5": - version: 4.9.5 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.9.5" +"@rollup/rollup-android-arm-eabi@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.17.2" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.9.5": - version: 4.9.5 - resolution: "@rollup/rollup-android-arm64@npm:4.9.5" +"@rollup/rollup-android-arm64@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-android-arm64@npm:4.17.2" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.9.5": - version: 4.9.5 - resolution: "@rollup/rollup-darwin-arm64@npm:4.9.5" +"@rollup/rollup-darwin-arm64@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-darwin-arm64@npm:4.17.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.9.5": - version: 4.9.5 - resolution: "@rollup/rollup-darwin-x64@npm:4.9.5" +"@rollup/rollup-darwin-x64@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-darwin-x64@npm:4.17.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.9.5": - version: 4.9.5 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.9.5" - conditions: os=linux & cpu=arm +"@rollup/rollup-linux-arm-gnueabihf@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.17.2" + conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.9.5": - version: 4.9.5 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.9.5" +"@rollup/rollup-linux-arm-musleabihf@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.17.2" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.17.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.9.5": - version: 4.9.5 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.9.5" +"@rollup/rollup-linux-arm64-musl@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.17.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.9.5": - version: 4.9.5 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.9.5" +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.17.2" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.17.2" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.9.5": - version: 4.9.5 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.9.5" +"@rollup/rollup-linux-s390x-gnu@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.17.2" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.17.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.9.5": - version: 4.9.5 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.9.5" +"@rollup/rollup-linux-x64-musl@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.17.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.9.5": - version: 4.9.5 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.9.5" +"@rollup/rollup-win32-arm64-msvc@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.17.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.9.5": - version: 4.9.5 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.9.5" +"@rollup/rollup-win32-ia32-msvc@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.17.2" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.9.5": - version: 4.9.5 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.9.5" +"@rollup/rollup-win32-x64-msvc@npm:4.17.2": + version: 4.17.2 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.17.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -847,7 +1023,7 @@ __metadata: "@sapphire/ts-config": "npm:^5.0.1" "@types/jsdom": "npm:^21.1.6" "@types/lodash": "npm:^4.17.4" - "@types/node": "npm:^20.11.5" + "@types/node": "npm:^20.12.12" "@typescript-eslint/eslint-plugin": "npm:^7.9.0" "@typescript-eslint/parser": "npm:^7.9.0" "@vitest/coverage-v8": "npm:^1.6.0" @@ -880,13 +1056,20 @@ __metadata: languageName: node linkType: hard -"@sapphire/utilities@npm:3.15.3, @sapphire/utilities@npm:^3.11.0": +"@sapphire/utilities@npm:3.15.3": version: 3.15.3 resolution: "@sapphire/utilities@npm:3.15.3" checksum: 10/966db788486c01719d3d54cf00eccbc378fcd79f143c994480644f3af4dd22daa65f5e9ea7da6af0973d9730e5d10b762eaf391770d746278023fd012438eb13 languageName: node linkType: hard +"@sapphire/utilities@npm:^3.11.0": + version: 3.16.0 + resolution: "@sapphire/utilities@npm:3.16.0" + checksum: 10/f87b4cd785f5f3a6efb0316bdd43aa5fc26d9693d391bb4f5b4adc92012c5020349c5dd79386a07abc30916258f3bd04a5694f97c64dc213c8fe780e15cd6a73 + languageName: node + linkType: hard + "@sinclair/typebox@npm:^0.27.8": version: 0.27.8 resolution: "@sinclair/typebox@npm:0.27.8" @@ -935,12 +1118,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:^20.11.5": - version: 20.11.5 - resolution: "@types/node@npm:20.11.5" +"@types/node@npm:*, @types/node@npm:^20.12.12": + version: 20.12.12 + resolution: "@types/node@npm:20.12.12" dependencies: undici-types: "npm:~5.26.4" - checksum: 10/9f31c471047d7b3e240ce7b77ff29b0d15e83be7e3feafb3d0b0d0931122b438b1eefa302a5a2e1e9849914ff3fd76aafbd8ccb372efb1331ba048da63bce6f8 + checksum: 10/e3945da0a3017bdc1f88f15bdfb823f526b2a717bd58d4640082d6eb0bd2794b5c99bfb914b9e9324ec116dce36066990353ed1c777e8a7b0641f772575793c4 languageName: node linkType: hard @@ -1206,12 +1389,12 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0": - version: 7.1.0 - resolution: "agent-base@npm:7.1.0" +"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": + version: 7.1.1 + resolution: "agent-base@npm:7.1.1" dependencies: debug: "npm:^4.3.4" - checksum: 10/f7828f991470a0cc22cb579c86a18cbae83d8a3cbed39992ab34fc7217c4d126017f1c74d0ab66be87f71455318a8ea3e757d6a37881b8d0f2a2c6aa55e5418f + checksum: 10/c478fec8f79953f118704d007a38f2a185458853f5c45579b9669372bd0e12602e88dc2ad0233077831504f7cd6fcc8251c383375bba5eaaf563b102938bda26 languageName: node linkType: hard @@ -1247,14 +1430,14 @@ __metadata: linkType: hard "ajv@npm:^8.11.0": - version: 8.12.0 - resolution: "ajv@npm:8.12.0" + version: 8.13.0 + resolution: "ajv@npm:8.13.0" dependencies: - fast-deep-equal: "npm:^3.1.1" + fast-deep-equal: "npm:^3.1.3" json-schema-traverse: "npm:^1.0.0" require-from-string: "npm:^2.0.2" - uri-js: "npm:^4.2.2" - checksum: 10/b406f3b79b5756ac53bfe2c20852471b08e122bc1ee4cde08ae4d6a800574d9cd78d60c81c69c63ff81e4da7cd0b638fafbb2303ae580d49cf1600b9059efb85 + uri-js: "npm:^4.4.1" + checksum: 10/4ada268c9a6e44be87fd295df0f0a91267a7bae8dbc8a67a2d5799c3cb459232839c99d18b035597bb6e3ffe88af6979f7daece854f590a81ebbbc2dfa80002c languageName: node linkType: hard @@ -1268,11 +1451,9 @@ __metadata: linkType: hard "ansi-escapes@npm:^6.2.0": - version: 6.2.0 - resolution: "ansi-escapes@npm:6.2.0" - dependencies: - type-fest: "npm:^3.0.0" - checksum: 10/442f91b04650b35bc4815f47c20412d69ddbba5d4bf22f72ec03be352fca2de6819c7e3f4dfd17816ee4e0c6c965fe85e6f1b3f09683996a8d12fd366afd924e + version: 6.2.1 + resolution: "ansi-escapes@npm:6.2.1" + checksum: 10/3b064937dc8a0645ed8094bc8b09483ee718f3aa3139746280e6c2ea80e28c0a3ce66973d0f33e88e60021abbf67e5f877deabfc810e75edf8a19dfa128850be languageName: node linkType: hard @@ -1396,9 +1577,9 @@ __metadata: linkType: hard "binary-extensions@npm:^2.0.0": - version: 2.2.0 - resolution: "binary-extensions@npm:2.2.0" - checksum: 10/ccd267956c58d2315f5d3ea6757cf09863c5fc703e50fbeb13a7dc849b812ef76e3cf9ca8f35a0c48498776a7478d7b4a0418e1e2b8cb9cb9731f2922aaad7f8 + version: 2.3.0 + resolution: "binary-extensions@npm:2.3.0" + checksum: 10/bcad01494e8a9283abf18c1b967af65ee79b0c6a9e6fcfafebfe91dbe6e0fc7272bafb73389e198b310516ae04f7ad17d79aacf6cb4c0d5d5202a7e2e52c7d98 languageName: node linkType: hard @@ -1451,23 +1632,14 @@ __metadata: languageName: node linkType: hard -"builtins@npm:^5.0.0": - version: 5.0.1 - resolution: "builtins@npm:5.0.1" - dependencies: - semver: "npm:^7.0.0" - checksum: 10/90136fa0ba98b7a3aea33190b1262a5297164731efb6a323b0231acf60cc2ea0b2b1075dbf107038266b8b77d6045fa9631d1c3f90efc1c594ba61218fbfbb4c - languageName: node - linkType: hard - "bundle-require@npm:^4.0.0": - version: 4.0.2 - resolution: "bundle-require@npm:4.0.2" + version: 4.1.0 + resolution: "bundle-require@npm:4.1.0" dependencies: load-tsconfig: "npm:^0.2.3" peerDependencies: esbuild: ">=0.17" - checksum: 10/22178607249adb52cc76e409add67930b81cdc6507ed8cbd7b162dc2824ce53c51b669d01bf073e9b7cd9d98f10f3dbf9a3285345813085b856d437cdc97e162 + checksum: 10/9d01d30cf7097239d6da5ec33ce00a91313a21b88f8e96e0e3c5b339a50b561ad70641a055227da9295c43962ac025078aee2f26b696c3c7f0703caa11263b46 languageName: node linkType: hard @@ -1499,8 +1671,8 @@ __metadata: linkType: hard "cacache@npm:^18.0.0": - version: 18.0.2 - resolution: "cacache@npm:18.0.2" + version: 18.0.3 + resolution: "cacache@npm:18.0.3" dependencies: "@npmcli/fs": "npm:^3.1.0" fs-minipass: "npm:^3.0.0" @@ -1514,7 +1686,7 @@ __metadata: ssri: "npm:^10.0.0" tar: "npm:^6.1.11" unique-filename: "npm:^3.0.0" - checksum: 10/5ca58464f785d4d64ac2019fcad95451c8c89bea25949f63acd8987fcc3493eaef1beccc0fa39e673506d879d3fc1ab420760f8a14f8ddf46ea2d121805a5e96 + checksum: 10/d4c161f071524bb636334b8cf94780c014e29c180a886b8184da8f2f44d2aca88d5664797c661e9f74bdbd34697c2f231ed7c24c256cecbb0a0563ad1ada2219 languageName: node linkType: hard @@ -1592,8 +1764,8 @@ __metadata: linkType: hard "chokidar@npm:^3.5.1": - version: 3.5.3 - resolution: "chokidar@npm:3.5.3" + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" dependencies: anymatch: "npm:~3.1.2" braces: "npm:~3.0.2" @@ -1606,7 +1778,7 @@ __metadata: dependenciesMeta: fsevents: optional: true - checksum: 10/863e3ff78ee7a4a24513d2a416856e84c8e4f5e60efbe03e8ab791af1a183f569b62fc6f6b8044e2804966cb81277ddbbc1dc374fba3265bd609ea8efd62f5b3 + checksum: 10/c327fb07704443f8d15f7b4a7ce93b2f0bc0e6cea07ec28a7570aa22cd51fcf0379df589403976ea956c369f25aa82d84561947e227cd925902e1751371658df languageName: node linkType: hard @@ -1740,9 +1912,9 @@ __metadata: linkType: hard "commander@npm:^12.0.0": - version: 12.0.0 - resolution: "commander@npm:12.0.0" - checksum: 10/62062e2ffe6abd5aa42a551e62fd5eb9b2620f6ac4299382b2aa9fb02f95cda0242d7e84acb890479bd6491edb805f7f91aecb5b4f5c70dc57df49ed7f02ef14 + version: 12.1.0 + resolution: "commander@npm:12.1.0" + checksum: 10/cdaeb672d979816853a4eed7f1310a9319e8b976172485c2a6b437ed0db0a389a44cfb222bfbde772781efa9f215bdd1b936f80d6b249485b465c6cb906e1f93 languageName: node linkType: hard @@ -1803,6 +1975,13 @@ __metadata: languageName: node linkType: hard +"confbox@npm:^0.1.7": + version: 0.1.7 + resolution: "confbox@npm:0.1.7" + checksum: 10/3086687b9a2a70d44d4b40a2d376536fe7e1baec4a2a34261b21b8a836026b419cbf89ded6054216631823e7d63c415dad4b4d53591d6edbb202bb9820dfa6fa + languageName: node + linkType: hard + "conventional-changelog-angular@npm:^7.0.0": version: 7.0.0 resolution: "conventional-changelog-angular@npm:7.0.0" @@ -2152,33 +2331,33 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.19.2, esbuild@npm:^0.19.3": - version: 0.19.11 - resolution: "esbuild@npm:0.19.11" - dependencies: - "@esbuild/aix-ppc64": "npm:0.19.11" - "@esbuild/android-arm": "npm:0.19.11" - "@esbuild/android-arm64": "npm:0.19.11" - "@esbuild/android-x64": "npm:0.19.11" - "@esbuild/darwin-arm64": "npm:0.19.11" - "@esbuild/darwin-x64": "npm:0.19.11" - "@esbuild/freebsd-arm64": "npm:0.19.11" - "@esbuild/freebsd-x64": "npm:0.19.11" - "@esbuild/linux-arm": "npm:0.19.11" - "@esbuild/linux-arm64": "npm:0.19.11" - "@esbuild/linux-ia32": "npm:0.19.11" - "@esbuild/linux-loong64": "npm:0.19.11" - "@esbuild/linux-mips64el": "npm:0.19.11" - "@esbuild/linux-ppc64": "npm:0.19.11" - "@esbuild/linux-riscv64": "npm:0.19.11" - "@esbuild/linux-s390x": "npm:0.19.11" - "@esbuild/linux-x64": "npm:0.19.11" - "@esbuild/netbsd-x64": "npm:0.19.11" - "@esbuild/openbsd-x64": "npm:0.19.11" - "@esbuild/sunos-x64": "npm:0.19.11" - "@esbuild/win32-arm64": "npm:0.19.11" - "@esbuild/win32-ia32": "npm:0.19.11" - "@esbuild/win32-x64": "npm:0.19.11" +"esbuild@npm:^0.19.2": + version: 0.19.12 + resolution: "esbuild@npm:0.19.12" + dependencies: + "@esbuild/aix-ppc64": "npm:0.19.12" + "@esbuild/android-arm": "npm:0.19.12" + "@esbuild/android-arm64": "npm:0.19.12" + "@esbuild/android-x64": "npm:0.19.12" + "@esbuild/darwin-arm64": "npm:0.19.12" + "@esbuild/darwin-x64": "npm:0.19.12" + "@esbuild/freebsd-arm64": "npm:0.19.12" + "@esbuild/freebsd-x64": "npm:0.19.12" + "@esbuild/linux-arm": "npm:0.19.12" + "@esbuild/linux-arm64": "npm:0.19.12" + "@esbuild/linux-ia32": "npm:0.19.12" + "@esbuild/linux-loong64": "npm:0.19.12" + "@esbuild/linux-mips64el": "npm:0.19.12" + "@esbuild/linux-ppc64": "npm:0.19.12" + "@esbuild/linux-riscv64": "npm:0.19.12" + "@esbuild/linux-s390x": "npm:0.19.12" + "@esbuild/linux-x64": "npm:0.19.12" + "@esbuild/netbsd-x64": "npm:0.19.12" + "@esbuild/openbsd-x64": "npm:0.19.12" + "@esbuild/sunos-x64": "npm:0.19.12" + "@esbuild/win32-arm64": "npm:0.19.12" + "@esbuild/win32-ia32": "npm:0.19.12" + "@esbuild/win32-x64": "npm:0.19.12" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -2228,14 +2407,94 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10/a40b3858c29618c8c893389372f469245a6b2d1319782af75d33d8ba5dcadfe181fcc935f8e1a907be667946384950a4cf482ebe1e79c99c932d2b8eb35a09d0 + checksum: 10/861fa8eb2428e8d6521a4b7c7930139e3f45e8d51a86985cc29408172a41f6b18df7b3401e7e5e2d528cdf83742da601ddfdc77043ddc4f1c715a8ddb2d8a255 + languageName: node + linkType: hard + +"esbuild@npm:^0.20.1": + version: 0.20.2 + resolution: "esbuild@npm:0.20.2" + dependencies: + "@esbuild/aix-ppc64": "npm:0.20.2" + "@esbuild/android-arm": "npm:0.20.2" + "@esbuild/android-arm64": "npm:0.20.2" + "@esbuild/android-x64": "npm:0.20.2" + "@esbuild/darwin-arm64": "npm:0.20.2" + "@esbuild/darwin-x64": "npm:0.20.2" + "@esbuild/freebsd-arm64": "npm:0.20.2" + "@esbuild/freebsd-x64": "npm:0.20.2" + "@esbuild/linux-arm": "npm:0.20.2" + "@esbuild/linux-arm64": "npm:0.20.2" + "@esbuild/linux-ia32": "npm:0.20.2" + "@esbuild/linux-loong64": "npm:0.20.2" + "@esbuild/linux-mips64el": "npm:0.20.2" + "@esbuild/linux-ppc64": "npm:0.20.2" + "@esbuild/linux-riscv64": "npm:0.20.2" + "@esbuild/linux-s390x": "npm:0.20.2" + "@esbuild/linux-x64": "npm:0.20.2" + "@esbuild/netbsd-x64": "npm:0.20.2" + "@esbuild/openbsd-x64": "npm:0.20.2" + "@esbuild/sunos-x64": "npm:0.20.2" + "@esbuild/win32-arm64": "npm:0.20.2" + "@esbuild/win32-ia32": "npm:0.20.2" + "@esbuild/win32-x64": "npm:0.20.2" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10/663215ab7e599651e00d61b528a63136e1f1d397db8b9c3712540af928c9476d61da95aefa81b7a8dfc7a9fdd7616fcf08395c27be68be8c99953fb461863ce4 languageName: node linkType: hard "escalade@npm:^3.1.1": - version: 3.1.1 - resolution: "escalade@npm:3.1.1" - checksum: 10/afa618e73362576b63f6ca83c975456621095a1ed42ff068174e3f5cea48afc422814dda548c96e6ebb5333e7265140c7292abcc81bbd6ccb1757d50d3a4e182 + version: 3.1.2 + resolution: "escalade@npm:3.1.2" + checksum: 10/a1e07fea2f15663c30e40b9193d658397846ffe28ce0a3e4da0d8e485fedfeca228ab846aee101a05015829adf39f9934ff45b2a3fca47bed37a29646bd05cd3 languageName: node linkType: hard @@ -2511,11 +2770,11 @@ __metadata: linkType: hard "fastq@npm:^1.6.0": - version: 1.16.0 - resolution: "fastq@npm:1.16.0" + version: 1.17.1 + resolution: "fastq@npm:1.17.1" dependencies: reusify: "npm:^1.0.4" - checksum: 10/de151543aab9d91900ed5da88860c46987ece925c628df586fac664235f25e020ec20729e1c032edb5fd2520fd4aa5b537d69e39b689e65e82112cfbecb4479e + checksum: 10/a443180068b527dd7b3a63dc7f2a47ceca2f3e97b9c00a1efe5538757e6cc4056a3526df94308075d7727561baf09ebaa5b67da8dcbddb913a021c5ae69d1f69 languageName: node linkType: hard @@ -2608,9 +2867,9 @@ __metadata: linkType: hard "flatted@npm:^3.2.9": - version: 3.2.9 - resolution: "flatted@npm:3.2.9" - checksum: 10/dc2b89e46a2ebde487199de5a4fcb79e8c46f984043fea5c41dbf4661eb881fefac1c939b5bdcd8a09d7f960ec364f516970c7ec44e58ff451239c07fd3d419b + version: 3.3.1 + resolution: "flatted@npm:3.3.1" + checksum: 10/7b8376061d5be6e0d3658bbab8bde587647f68797cf6bfeae9dea0e5137d9f27547ab92aaff3512dd9d1299086a6d61be98e9d48a56d17531b634f77faadbc49 languageName: node linkType: hard @@ -2726,59 +2985,59 @@ __metadata: languageName: node linkType: hard -"git-cliff-darwin-arm64@npm:2.2.1": - version: 2.2.1 - resolution: "git-cliff-darwin-arm64@npm:2.2.1" +"git-cliff-darwin-arm64@npm:2.2.2": + version: 2.2.2 + resolution: "git-cliff-darwin-arm64@npm:2.2.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"git-cliff-darwin-x64@npm:2.2.1": - version: 2.2.1 - resolution: "git-cliff-darwin-x64@npm:2.2.1" +"git-cliff-darwin-x64@npm:2.2.2": + version: 2.2.2 + resolution: "git-cliff-darwin-x64@npm:2.2.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"git-cliff-linux-arm64@npm:2.2.1": - version: 2.2.1 - resolution: "git-cliff-linux-arm64@npm:2.2.1" +"git-cliff-linux-arm64@npm:2.2.2": + version: 2.2.2 + resolution: "git-cliff-linux-arm64@npm:2.2.2" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"git-cliff-linux-x64@npm:2.2.1": - version: 2.2.1 - resolution: "git-cliff-linux-x64@npm:2.2.1" +"git-cliff-linux-x64@npm:2.2.2": + version: 2.2.2 + resolution: "git-cliff-linux-x64@npm:2.2.2" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"git-cliff-windows-arm64@npm:2.2.1": - version: 2.2.1 - resolution: "git-cliff-windows-arm64@npm:2.2.1" +"git-cliff-windows-arm64@npm:2.2.2": + version: 2.2.2 + resolution: "git-cliff-windows-arm64@npm:2.2.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"git-cliff-windows-x64@npm:2.2.1": - version: 2.2.1 - resolution: "git-cliff-windows-x64@npm:2.2.1" +"git-cliff-windows-x64@npm:2.2.2": + version: 2.2.2 + resolution: "git-cliff-windows-x64@npm:2.2.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "git-cliff@npm:^2.2.1": - version: 2.2.1 - resolution: "git-cliff@npm:2.2.1" + version: 2.2.2 + resolution: "git-cliff@npm:2.2.2" dependencies: execa: "npm:^8.0.1" - git-cliff-darwin-arm64: "npm:2.2.1" - git-cliff-darwin-x64: "npm:2.2.1" - git-cliff-linux-arm64: "npm:2.2.1" - git-cliff-linux-x64: "npm:2.2.1" - git-cliff-windows-arm64: "npm:2.2.1" - git-cliff-windows-x64: "npm:2.2.1" + git-cliff-darwin-arm64: "npm:2.2.2" + git-cliff-darwin-x64: "npm:2.2.2" + git-cliff-linux-arm64: "npm:2.2.2" + git-cliff-linux-x64: "npm:2.2.2" + git-cliff-windows-arm64: "npm:2.2.2" + git-cliff-windows-x64: "npm:2.2.2" dependenciesMeta: git-cliff-darwin-arm64: optional: true @@ -2794,7 +3053,7 @@ __metadata: optional: true bin: git-cliff: lib/cli/cli.js - checksum: 10/c69a790137a058b46c7f09da736150d74ee52e6c72050fa4226bf80d9f6154d2dbb8169175610271ec9da26e2ef4b5090928bcab2e0150f58ac4446abb6ff331 + checksum: 10/9d3ba4d9f85ba41c7429fdf558fb0c4c616c4fc34369d89687753e16d4d4b2aa003533ec058dc5b9d12fe2a27ecad1e0f05c6a12c1f803bcef20407ad33d9eea languageName: node linkType: hard @@ -2856,17 +3115,17 @@ __metadata: linkType: hard "glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7": - version: 10.3.10 - resolution: "glob@npm:10.3.10" + version: 10.3.15 + resolution: "glob@npm:10.3.15" dependencies: foreground-child: "npm:^3.1.0" - jackspeak: "npm:^2.3.5" + jackspeak: "npm:^2.3.6" minimatch: "npm:^9.0.1" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry: "npm:^1.10.1" + minipass: "npm:^7.0.4" + path-scurry: "npm:^1.11.0" bin: glob: dist/esm/bin.mjs - checksum: 10/38bdb2c9ce75eb5ed168f309d4ed05b0798f640b637034800a6bf306f39d35409bf278b0eaaffaec07591085d3acb7184a201eae791468f0f617771c2486a6a8 + checksum: 10/b2b1c74309979b34fd6010afb50418a12525def32f1d3758d5827fc75d6143fc3ee5d1f3180a43111f6386c9e297c314f208d9d09955a6c6b69f22e92ee97635 languageName: node linkType: hard @@ -3007,12 +3266,12 @@ __metadata: linkType: hard "http-proxy-agent@npm:^7.0.0": - version: 7.0.0 - resolution: "http-proxy-agent@npm:7.0.0" + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" dependencies: agent-base: "npm:^7.1.0" debug: "npm:^4.3.4" - checksum: 10/dbaaf3d9f3fc4df4a5d7ec45d456ec50f575240b557160fa63427b447d1f812dd7fe4a4f17d2e1ba003d231f07edf5a856ea6d91cb32d533062ff20a7803ccac + checksum: 10/d062acfa0cb82beeb558f1043c6ba770ea892b5fb7b28654dbc70ea2aeea55226dd34c02a294f6c1ca179a5aa483c4ea641846821b182edbd9cc5d89b54c6848 languageName: node linkType: hard @@ -3027,12 +3286,12 @@ __metadata: linkType: hard "https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.2": - version: 7.0.2 - resolution: "https-proxy-agent@npm:7.0.2" + version: 7.0.4 + resolution: "https-proxy-agent@npm:7.0.4" dependencies: agent-base: "npm:^7.0.2" debug: "npm:4" - checksum: 10/9ec844f78fd643608239c9c3f6819918631df5cd3e17d104cc507226a39b5d4adda9d790fc9fd63ac0d2bb8a761b2f9f60faa80584a9bf9d7f2e8c5ed0acd330 + checksum: 10/405fe582bba461bfe5c7e2f8d752b384036854488b828ae6df6a587c654299cbb2c50df38c4b6ab303502c3c5e029a793fbaac965d1e86ee0be03faceb554d63 languageName: node linkType: hard @@ -3102,9 +3361,9 @@ __metadata: linkType: hard "import-meta-resolve@npm:^4.0.0": - version: 4.0.0 - resolution: "import-meta-resolve@npm:4.0.0" - checksum: 10/73f0f1d68f7280cb4415e3a212a6e5d57fbfe61ab6f467df3dad5361529fbd89ac7d8ea2b694412b74985a4226d218ad3fb22fd8f06f5429beda521dc9f0229c + version: 4.1.0 + resolution: "import-meta-resolve@npm:4.1.0" + checksum: 10/40162f67eb406c8d5d49266206ef12ff07b54f5fad8cfd806db9efe3a055958e9969be51d6efaf82e34b8bea6758113dcc17bb79ff148292a4badcabc3472f22 languageName: node linkType: hard @@ -3176,10 +3435,13 @@ __metadata: languageName: node linkType: hard -"ip@npm:^2.0.0": - version: 2.0.0 - resolution: "ip@npm:2.0.0" - checksum: 10/1270b11e534a466fb4cf4426cbcc3a907c429389f7f4e4e3b288b42823562e88d6a509ceda8141a507de147ca506141f745005c0aa144569d94cf24a54eb52bc +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: "npm:1.1.0" + sprintf-js: "npm:^1.1.3" + checksum: 10/1ed81e06721af012306329b31f532b5e24e00cb537be18ddc905a84f19fe8f83a09a1699862bf3a1ec4b9dea93c55a3fa5faf8b5ea380431469df540f38b092c languageName: node linkType: hard @@ -3368,16 +3630,16 @@ __metadata: linkType: hard "istanbul-reports@npm:^3.1.6": - version: 3.1.6 - resolution: "istanbul-reports@npm:3.1.6" + version: 3.1.7 + resolution: "istanbul-reports@npm:3.1.7" dependencies: html-escaper: "npm:^2.0.0" istanbul-lib-report: "npm:^3.0.0" - checksum: 10/135c178e509b21af5c446a6951fc01c331331bb0fdb1ed1dd7f68a8c875603c2e2ee5c82801db5feb868e5cc35e9babe2d972d322afc50f6de6cce6431b9b2ff + checksum: 10/f1faaa4684efaf57d64087776018d7426312a59aa6eeb4e0e3a777347d23cd286ad18f427e98f0e3dee666103d7404c9d7abc5f240406a912fa16bd6695437fa languageName: node linkType: hard -"jackspeak@npm:^2.3.5": +"jackspeak@npm:^2.3.6": version: 2.3.6 resolution: "jackspeak@npm:2.3.6" dependencies: @@ -3413,10 +3675,10 @@ __metadata: languageName: node linkType: hard -"js-tokens@npm:^8.0.2": - version: 8.0.3 - resolution: "js-tokens@npm:8.0.3" - checksum: 10/af5ed8ddbc446a868c026599214f4a482ab52461edb82e547949255f98910a14bd81ddab88a8d570d74bd7dc96c6d4df7f963794ec5aaf13c53918cc46b9caa6 +"js-tokens@npm:^9.0.0": + version: 9.0.0 + resolution: "js-tokens@npm:9.0.0" + checksum: 10/65e7a55a1a18d61f1cf94bfd7704da870b74337fa08d4c58118e69a8b10225b5ad887ff3ae595d720301b0924811a9b0594c679621a85ecbac6e3aac8533c53b languageName: node linkType: hard @@ -3431,6 +3693,13 @@ __metadata: languageName: node linkType: hard +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 10/bebe7ae829bbd586ce8cbe83501dd8cb8c282c8902a8aeeed0a073a89dc37e8103b1244f3c6acd60278bcbfe12d93a3f83c9ac396868a3b3bbc3c5e5e3b648ef + languageName: node + linkType: hard + "jsdom@npm:^24.0.0": version: 24.0.0 resolution: "jsdom@npm:24.0.0" @@ -3501,9 +3770,9 @@ __metadata: linkType: hard "jsonc-parser@npm:^3.2.0": - version: 3.2.0 - resolution: "jsonc-parser@npm:3.2.0" - checksum: 10/bd68b902e5f9394f01da97921f49c5084b2dc03a0c5b4fdb2a429f8d6f292686c1bf87badaeb0a8148d024192a88f5ad2e57b2918ba43fe25cf15f3371db64d4 + version: 3.2.1 + resolution: "jsonc-parser@npm:3.2.1" + checksum: 10/fe2df6f39e21653781d52cae20c5b9e0ab62461918d97f9430b216cea9b6500efc1d8b42c6584cc0a7548b4c996055e9cdc39f09b9782fa6957af2f45306c530 languageName: node linkType: hard @@ -3546,13 +3815,20 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:3.0.0, lilconfig@npm:^3.0.0": +"lilconfig@npm:3.0.0": version: 3.0.0 resolution: "lilconfig@npm:3.0.0" checksum: 10/55f60f4f9f7b41358cc33875e3696919412683a35aec30c6c60c4f6ecb16fb6d11f7ac856b8458b9b82b21d5f4629649fbfca1de034e8d5b0cc7a70836266db6 languageName: node linkType: hard +"lilconfig@npm:^3.0.0": + version: 3.1.1 + resolution: "lilconfig@npm:3.1.1" + checksum: 10/c80fbf98ae7d1daf435e16a83fe3c63743b9d92804cac6dc53ee081c7c265663645c3162d8a0d04ff1874f9c07df145519743317dee67843234c6ed279300f83 + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -3752,19 +4028,10 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^10.0.1, lru-cache@npm:^9.1.1 || ^10.0.0": - version: 10.1.0 - resolution: "lru-cache@npm:10.1.0" - checksum: 10/207278d6fa711fb1f94a0835d4d4737441d2475302482a14785b10515e4c906a57ebf9f35bf060740c9560e91c7c1ad5a04fd7ed030972a9ba18bce2a228e95b - languageName: node - linkType: hard - -"lru-cache@npm:^6.0.0": - version: 6.0.0 - resolution: "lru-cache@npm:6.0.0" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10/fc1fe2ee205f7c8855fa0f34c1ab0bcf14b6229e35579ec1fd1079f31d6fc8ef8eb6fd17f2f4d99788d7e339f50e047555551ebd5e434dda503696e7c6591825 +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": + version: 10.2.2 + resolution: "lru-cache@npm:10.2.2" + checksum: 10/ff1a496d30b5eaec2c9079080965bb0cede203cf878371f7033a007f1e54cd4aa13cc8abf7ccec4c994a83a22ed5476e83a55bb57cc07e6c1547a42937e42c37 languageName: node linkType: hard @@ -3783,22 +4050,22 @@ __metadata: linkType: hard "magic-string@npm:^0.30.5": - version: 0.30.5 - resolution: "magic-string@npm:0.30.5" + version: 0.30.10 + resolution: "magic-string@npm:0.30.10" dependencies: "@jridgewell/sourcemap-codec": "npm:^1.4.15" - checksum: 10/c8a6b25f813215ca9db526f3a407d6dc0bf35429c2b8111d6f1c2cf6cf6afd5e2d9f9cd189416a0e3959e20ecd635f73639f9825c73de1074b29331fe36ace59 + checksum: 10/9f8bf6363a14c98a9d9f32ef833b194702a5c98fb931b05ac511b76f0b06fd30ed92beda6ca3261d2d52d21e39e891ef1136fbd032023f6cbb02d0b7d5767201 languageName: node linkType: hard "magicast@npm:^0.3.3": - version: 0.3.3 - resolution: "magicast@npm:0.3.3" + version: 0.3.4 + resolution: "magicast@npm:0.3.4" dependencies: - "@babel/parser": "npm:^7.23.6" - "@babel/types": "npm:^7.23.6" - source-map-js: "npm:^1.0.2" - checksum: 10/04af6f60d80a3a51344a864c6479af428e672c249b3f9211d78eee6ac8649257daad610ba4a68d4151e38d393b48b0f4a8f3db178422674fb5d418bc8c939055 + "@babel/parser": "npm:^7.24.4" + "@babel/types": "npm:^7.24.0" + source-map-js: "npm:^1.2.0" + checksum: 10/704f86639b01c8e063155408cb181d89d4444db3a4a473fb501107f30f19d9c39a159dd315ef9e54a22291c090170044efd9b49a9b3ab8d6deb948a9c99d90b3 languageName: node linkType: hard @@ -3835,8 +4102,8 @@ __metadata: linkType: hard "make-fetch-happen@npm:^13.0.0": - version: 13.0.0 - resolution: "make-fetch-happen@npm:13.0.0" + version: 13.0.1 + resolution: "make-fetch-happen@npm:13.0.1" dependencies: "@npmcli/agent": "npm:^2.0.0" cacache: "npm:^18.0.0" @@ -3847,9 +4114,10 @@ __metadata: minipass-flush: "npm:^1.0.5" minipass-pipeline: "npm:^1.2.4" negotiator: "npm:^0.6.3" + proc-log: "npm:^4.2.0" promise-retry: "npm:^2.0.1" ssri: "npm:^10.0.0" - checksum: 10/ded5a91a02b76381b06a4ec4d5c1d23ebbde15d402b3c3e4533b371dac7e2f7ca071ae71ae6dae72aa261182557b7b1b3fd3a705b39252dc17f74fa509d3e76f + checksum: 10/11bae5ad6ac59b654dbd854f30782f9de052186c429dfce308eda42374528185a100ee40ac9ffdc36a2b6c821ecaba43913e4730a12f06f15e895ea9cb23fa59 languageName: node linkType: hard @@ -3974,8 +4242,8 @@ __metadata: linkType: hard "minipass-fetch@npm:^3.0.0": - version: 3.0.4 - resolution: "minipass-fetch@npm:3.0.4" + version: 3.0.5 + resolution: "minipass-fetch@npm:3.0.5" dependencies: encoding: "npm:^0.1.13" minipass: "npm:^7.0.3" @@ -3984,7 +4252,7 @@ __metadata: dependenciesMeta: encoding: optional: true - checksum: 10/3edf72b900e30598567eafe96c30374432a8709e61bb06b87198fa3192d466777e2ec21c52985a0999044fa6567bd6f04651585983a1cbb27e2c1770a07ed2a2 + checksum: 10/c669948bec1373313aaa8f104b962a3ced9f45c49b26366a4b0ae27ccdfa9c5740d72c8a84d3f8623d7a61c5fc7afdfda44789008c078f61a62441142efc4a97 languageName: node linkType: hard @@ -4041,10 +4309,10 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3": - version: 7.0.4 - resolution: "minipass@npm:7.0.4" - checksum: 10/e864bd02ceb5e0707696d58f7ce3a0b89233f0d686ef0d447a66db705c0846a8dc6f34865cd85256c1472ff623665f616b90b8ff58058b2ad996c5de747d2d18 +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4": + version: 7.1.1 + resolution: "minipass@npm:7.1.1" + checksum: 10/6f4f920f1b5ea585d08fa3739b9bd81726cd85a0c972fb371c0fa6c1544d468813fb1694c7bc64ad81f138fd8abf665e2af0f406de9ba5741d8e4a377ed346b1 languageName: node linkType: hard @@ -4067,15 +4335,15 @@ __metadata: languageName: node linkType: hard -"mlly@npm:^1.2.0, mlly@npm:^1.4.2": - version: 1.5.0 - resolution: "mlly@npm:1.5.0" +"mlly@npm:^1.4.2, mlly@npm:^1.7.0": + version: 1.7.0 + resolution: "mlly@npm:1.7.0" dependencies: acorn: "npm:^8.11.3" pathe: "npm:^1.1.2" - pkg-types: "npm:^1.0.3" - ufo: "npm:^1.3.2" - checksum: 10/c030ecb7f17a9080f04746cc9bf1a73f55a86dcad55c1597d20349737e07ec66a09ea1bcac0d36984cb1d532b79200c235086ab2291d678224f9082946cf530e + pkg-types: "npm:^1.1.0" + ufo: "npm:^1.5.3" + checksum: 10/a52f17767f1aa8133ad4354065e579c3d1cc72e866102bde7e466123772f5e571327b95ce777d1d655724f0c479a82acaafc6e81e25781851779d865682c8823 languageName: node linkType: hard @@ -4149,8 +4417,8 @@ __metadata: linkType: hard "node-gyp@npm:latest": - version: 10.0.1 - resolution: "node-gyp@npm:10.0.1" + version: 10.1.0 + resolution: "node-gyp@npm:10.1.0" dependencies: env-paths: "npm:^2.2.0" exponential-backoff: "npm:^3.1.1" @@ -4164,18 +4432,18 @@ __metadata: which: "npm:^4.0.0" bin: node-gyp: bin/node-gyp.js - checksum: 10/578cf0c821f258ce4b6ebce4461eca4c991a4df2dee163c0624f2fe09c7d6d37240be4942285a0048d307230248ee0b18382d6623b9a0136ce9533486deddfa8 + checksum: 10/89e105e495e66cd4568af3cf79cdeb67d670eb069e33163c7781d3366470a30367c9bd8dea59e46db16370020139e5bf78b1fbc03284cb571754dfaa59744db5 languageName: node linkType: hard "nopt@npm:^7.0.0": - version: 7.2.0 - resolution: "nopt@npm:7.2.0" + version: 7.2.1 + resolution: "nopt@npm:7.2.1" dependencies: abbrev: "npm:^2.0.0" bin: nopt: bin/nopt.js - checksum: 10/1e7489f17cbda452c8acaf596a8defb4ae477d2a9953b76eb96f4ec3f62c6b421cd5174eaa742f88279871fde9586d8a1d38fb3f53fa0c405585453be31dff4c + checksum: 10/95a1f6dec8a81cd18cdc2fed93e6f0b4e02cf6bdb4501c848752c6e34f9883d9942f036a5e3b21a699047d8a448562d891e67492df68ec9c373e6198133337ae languageName: node linkType: hard @@ -4223,18 +4491,18 @@ __metadata: linkType: hard "npm-run-path@npm:^5.1.0": - version: 5.2.0 - resolution: "npm-run-path@npm:5.2.0" + version: 5.3.0 + resolution: "npm-run-path@npm:5.3.0" dependencies: path-key: "npm:^4.0.0" - checksum: 10/c5325e016014e715689c4014f7e0be16cc4cbf529f32a1723e511bc4689b5f823b704d2bca61ac152ce2bda65e0205dc8b3ba0ec0f5e4c3e162d302f6f5b9efb + checksum: 10/ae8e7a89da9594fb9c308f6555c73f618152340dcaae423e5fb3620026fefbec463618a8b761920382d666fa7a2d8d240b6fe320e8a6cdd54dc3687e2b659d25 languageName: node linkType: hard "nwsapi@npm:^2.2.7": - version: 2.2.7 - resolution: "nwsapi@npm:2.2.7" - checksum: 10/22c002080f0297121ad138aba5a6509e724774d6701fe2c4777627bd939064ecd9e1b6dc1c2c716bb7ca0b9f16247892ff2f664285202ac7eff6ec9543725320 + version: 2.2.10 + resolution: "nwsapi@npm:2.2.10" + checksum: 10/b310e9dd0886da338cbbb1be9fec473a50269e2935d537f95a03d0038f7ea831ce12b4816d97f42e458e5273158aea2a6c86bc4bb60f79911226154aa66740f7 languageName: node linkType: hard @@ -4273,16 +4541,16 @@ __metadata: linkType: hard "optionator@npm:^0.9.3": - version: 0.9.3 - resolution: "optionator@npm:0.9.3" + version: 0.9.4 + resolution: "optionator@npm:0.9.4" dependencies: - "@aashutoshrathi/word-wrap": "npm:^1.2.3" deep-is: "npm:^0.1.3" fast-levenshtein: "npm:^2.0.6" levn: "npm:^0.4.1" prelude-ls: "npm:^1.2.1" type-check: "npm:^0.4.0" - checksum: 10/fa28d3016395974f7fc087d6bbf0ac7f58ac3489f4f202a377e9c194969f329a7b88c75f8152b33fb08794a30dcd5c079db6bb465c28151357f113d80bbf67da + word-wrap: "npm:^1.2.5" + checksum: 10/a8398559c60aef88d7f353a4f98dcdff6090a4e70f874c827302bf1213d9106a1c4d5fcb68dacb1feb3c30a04c4102f41047aa55d4c576b863d6fc876e001af6 languageName: node linkType: hard @@ -4436,13 +4704,13 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.10.1": - version: 1.10.1 - resolution: "path-scurry@npm:1.10.1" +"path-scurry@npm:^1.11.0": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" dependencies: - lru-cache: "npm:^9.1.1 || ^10.0.0" + lru-cache: "npm:^10.2.0" minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10/eebfb8304fef1d4f7e1486df987e4fd77413de4fce16508dea69fcf8eb318c09a6b15a7a2f4c22877cec1cb7ecbd3071d18ca9de79eeece0df874a00f1f0bdc8 + checksum: 10/5e8845c159261adda6f09814d7725683257fcc85a18f329880ab4d7cc1d12830967eae5d5894e453f341710d5484b8fdbbd4d75181b4d6e1eb2f4dc7aeadc434 languageName: node linkType: hard @@ -4453,7 +4721,7 @@ __metadata: languageName: node linkType: hard -"pathe@npm:^1.1.0, pathe@npm:^1.1.1, pathe@npm:^1.1.2": +"pathe@npm:^1.1.1, pathe@npm:^1.1.2": version: 1.1.2 resolution: "pathe@npm:1.1.2" checksum: 10/f201d796351bf7433d147b92c20eb154a4e0ea83512017bf4ec4e492a5d6e738fb45798be4259a61aa81270179fce11026f6ff0d3fa04173041de044defe9d80 @@ -4468,9 +4736,9 @@ __metadata: linkType: hard "picocolors@npm:^1.0.0": - version: 1.0.0 - resolution: "picocolors@npm:1.0.0" - checksum: 10/a2e8092dd86c8396bdba9f2b5481032848525b3dc295ce9b57896f931e63fc16f79805144321f72976383fc249584672a75cc18d6777c6b757603f372f745981 + version: 1.0.1 + resolution: "picocolors@npm:1.0.1" + checksum: 10/fa68166d1f56009fc02a34cdfd112b0dd3cf1ef57667ac57281f714065558c01828cdf4f18600ad6851cbe0093952ed0660b1e0156bddf2184b6aaf5817553a5 languageName: node linkType: hard @@ -4497,14 +4765,14 @@ __metadata: languageName: node linkType: hard -"pkg-types@npm:^1.0.3": - version: 1.0.3 - resolution: "pkg-types@npm:1.0.3" +"pkg-types@npm:^1.0.3, pkg-types@npm:^1.1.0": + version: 1.1.1 + resolution: "pkg-types@npm:1.1.1" dependencies: - jsonc-parser: "npm:^3.2.0" - mlly: "npm:^1.2.0" - pathe: "npm:^1.1.0" - checksum: 10/e17e1819ce579c9ea390e4c41a9ed9701d8cff14b463f9577cc4f94688da8917c66dabc40feacd47a21eb3de9b532756a78becd882b76add97053af307c1240a + confbox: "npm:^0.1.7" + mlly: "npm:^1.7.0" + pathe: "npm:^1.1.2" + checksum: 10/225eaf7c0339027e176dd0d34a6d9a1384c21e0aab295e57dfbef1f1b7fc132f008671da7e67553e352b80b17ba38c531c720c914061d277410eef1bdd9d9608 languageName: node linkType: hard @@ -4526,14 +4794,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.32": - version: 8.4.33 - resolution: "postcss@npm:8.4.33" +"postcss@npm:^8.4.38": + version: 8.4.38 + resolution: "postcss@npm:8.4.38" dependencies: nanoid: "npm:^3.3.7" picocolors: "npm:^1.0.0" - source-map-js: "npm:^1.0.2" - checksum: 10/e22a4594c255f26117f38419fb494d7ecab0f596cd409f7aadc8a6173abf180ed7ea970cd13fd366ab12b5840be901d2a09b25197700c2ebcb5a8077326bf519 + source-map-js: "npm:^1.2.0" + checksum: 10/6e44a7ed835ffa9a2b096e8d3e5dfc6bcf331a25c48aeb862dd54e3aaecadf814fa22be224fd308f87d08adf2299164f88c5fd5ab1c4ef6cbd693ceb295377f4 languageName: node linkType: hard @@ -4580,6 +4848,13 @@ __metadata: languageName: node linkType: hard +"proc-log@npm:^4.2.0": + version: 4.2.0 + resolution: "proc-log@npm:4.2.0" + checksum: 10/4e1394491b717f6c1ade15c570ecd4c2b681698474d3ae2d303c1e4b6ab9455bd5a81566211e82890d5a5ae9859718cc6954d5150bb18b09b72ecb297beae90a + languageName: node + linkType: hard + "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -4619,9 +4894,9 @@ __metadata: linkType: hard "react-is@npm:^18.0.0": - version: 18.2.0 - resolution: "react-is@npm:18.2.0" - checksum: 10/200cd65bf2e0be7ba6055f647091b725a45dd2a6abef03bf2380ce701fd5edccee40b49b9d15edab7ac08a762bf83cb4081e31ec2673a5bfb549a36ba21570df + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10/d5f60c87d285af24b1e1e7eaeb123ec256c3c8bdea7061ab3932e3e14685708221bf234ec50b21e10dd07f008f1b966a2730a0ce4ff67905b3872ff2042aec22 languageName: node linkType: hard @@ -4732,9 +5007,9 @@ __metadata: linkType: hard "rfdc@npm:^1.3.0": - version: 1.3.0 - resolution: "rfdc@npm:1.3.0" - checksum: 10/76dedd9700cdf132947fde7ce1a8838c9cbb7f3e8f9188af0aaf97194cce745f42094dd2cf547426934cc83252ee2c0e432b2e0222a4415ab0db32de82665c69 + version: 1.3.1 + resolution: "rfdc@npm:1.3.1" + checksum: 10/44cc6a82e2fe1db13b7d3c54e9ffd0b40ef070cbde69ffbfbb38dab8cee46bd68ba686784b96365ff08d04798bc121c3465663a0c91f2c421c90546c4366f4a6 languageName: node linkType: hard @@ -4760,23 +5035,26 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^4.0.2, rollup@npm:^4.2.0": - version: 4.9.5 - resolution: "rollup@npm:4.9.5" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.9.5" - "@rollup/rollup-android-arm64": "npm:4.9.5" - "@rollup/rollup-darwin-arm64": "npm:4.9.5" - "@rollup/rollup-darwin-x64": "npm:4.9.5" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.9.5" - "@rollup/rollup-linux-arm64-gnu": "npm:4.9.5" - "@rollup/rollup-linux-arm64-musl": "npm:4.9.5" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.9.5" - "@rollup/rollup-linux-x64-gnu": "npm:4.9.5" - "@rollup/rollup-linux-x64-musl": "npm:4.9.5" - "@rollup/rollup-win32-arm64-msvc": "npm:4.9.5" - "@rollup/rollup-win32-ia32-msvc": "npm:4.9.5" - "@rollup/rollup-win32-x64-msvc": "npm:4.9.5" +"rollup@npm:^4.0.2, rollup@npm:^4.13.0": + version: 4.17.2 + resolution: "rollup@npm:4.17.2" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.17.2" + "@rollup/rollup-android-arm64": "npm:4.17.2" + "@rollup/rollup-darwin-arm64": "npm:4.17.2" + "@rollup/rollup-darwin-x64": "npm:4.17.2" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.17.2" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.17.2" + "@rollup/rollup-linux-arm64-gnu": "npm:4.17.2" + "@rollup/rollup-linux-arm64-musl": "npm:4.17.2" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.17.2" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.17.2" + "@rollup/rollup-linux-s390x-gnu": "npm:4.17.2" + "@rollup/rollup-linux-x64-gnu": "npm:4.17.2" + "@rollup/rollup-linux-x64-musl": "npm:4.17.2" + "@rollup/rollup-win32-arm64-msvc": "npm:4.17.2" + "@rollup/rollup-win32-ia32-msvc": "npm:4.17.2" + "@rollup/rollup-win32-x64-msvc": "npm:4.17.2" "@types/estree": "npm:1.0.5" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -4790,12 +5068,18 @@ __metadata: optional: true "@rollup/rollup-linux-arm-gnueabihf": optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true "@rollup/rollup-linux-arm64-gnu": optional: true "@rollup/rollup-linux-arm64-musl": optional: true + "@rollup/rollup-linux-powerpc64le-gnu": + optional: true "@rollup/rollup-linux-riscv64-gnu": optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true "@rollup/rollup-linux-x64-gnu": optional: true "@rollup/rollup-linux-x64-musl": @@ -4810,7 +5094,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10/4debf528e63edea5c3f5d38e399c6dd7287e2977d90d2d3ce38d4b3412289e2081aff8f8488a11b1699c786f2e904e9e150f30d576fe9316b5b97df0e80b1bce + checksum: 10/a021d57f73d746340a1c2b3a03ef0b3bb7f3c837e6acd9aa78b1b1234011aa5b5271b0ef25abba2c1ed268b5e2c90c39a0f8194bcf825728be720f9f2496b248 languageName: node linkType: hard @@ -4869,14 +5153,12 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.0.0, semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.6.0": - version: 7.6.0 - resolution: "semver@npm:7.6.0" - dependencies: - lru-cache: "npm:^6.0.0" +"semver@npm:^7.3.5, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.6.0": + version: 7.6.2 + resolution: "semver@npm:7.6.2" bin: semver: bin/semver.js - checksum: 10/1b41018df2d8aca5a1db4729985e8e20428c650daea60fcd16e926e9383217d00f574fab92d79612771884a98d2ee2a1973f49d630829a8d54d6570defe62535 + checksum: 10/296b17d027f57a87ef645e9c725bff4865a38dfc9caf29b26aa084b85820972fbe7372caea1ba6857162fa990702c6d9c1d82297cecb72d56c78ab29070d2ca2 languageName: node linkType: hard @@ -4974,31 +5256,31 @@ __metadata: languageName: node linkType: hard -"socks-proxy-agent@npm:^8.0.1": - version: 8.0.2 - resolution: "socks-proxy-agent@npm:8.0.2" +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.3 + resolution: "socks-proxy-agent@npm:8.0.3" dependencies: - agent-base: "npm:^7.0.2" + agent-base: "npm:^7.1.1" debug: "npm:^4.3.4" socks: "npm:^2.7.1" - checksum: 10/ea727734bd5b2567597aa0eda14149b3b9674bb44df5937bbb9815280c1586994de734d965e61f1dd45661183d7b41f115fb9e432d631287c9063864cfcc2ecc + checksum: 10/c2112c66d6322e497d68e913c3780f3683237fd394bfd480b9283486a86e36095d0020db96145d88f8ccd9cc73261b98165b461f9c1bf5dc17abfe75c18029ce languageName: node linkType: hard "socks@npm:^2.6.2, socks@npm:^2.7.1": - version: 2.7.1 - resolution: "socks@npm:2.7.1" + version: 2.8.3 + resolution: "socks@npm:2.8.3" dependencies: - ip: "npm:^2.0.0" + ip-address: "npm:^9.0.5" smart-buffer: "npm:^4.2.0" - checksum: 10/5074f7d6a13b3155fa655191df1c7e7a48ce3234b8ccf99afa2ccb56591c195e75e8bb78486f8e9ea8168e95a29573cbaad55b2b5e195160ae4d2ea6811ba833 + checksum: 10/ffcb622c22481dfcd7589aae71fbfd71ca34334064d181df64bf8b7feaeee19706aba4cffd1de35cc7bbaeeaa0af96be2d7f40fcbc7bc0ab69533a7ae9ffc4fb languageName: node linkType: hard -"source-map-js@npm:^1.0.2": - version: 1.0.2 - resolution: "source-map-js@npm:1.0.2" - checksum: 10/38e2d2dd18d2e331522001fc51b54127ef4a5d473f53b1349c5cca2123562400e0986648b52e9407e348eaaed53bce49248b6e2641e6d793ca57cb2c360d6d51 +"source-map-js@npm:^1.2.0": + version: 1.2.0 + resolution: "source-map-js@npm:1.2.0" + checksum: 10/74f331cfd2d121c50790c8dd6d3c9de6be21926de80583b23b37029b0f37aefc3e019fa91f9a10a5e120c08135297e1ecf312d561459c45908cb1e0e365f49e5 languageName: node linkType: hard @@ -5018,12 +5300,19 @@ __metadata: languageName: node linkType: hard +"sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 10/e7587128c423f7e43cc625fe2f87e6affdf5ca51c1cc468e910d8aaca46bb44a7fbcfa552f787b1d3987f7043aeb4527d1b99559e6621e01b42b3f45e5a24cbb + languageName: node + linkType: hard + "ssri@npm:^10.0.0": - version: 10.0.5 - resolution: "ssri@npm:10.0.5" + version: 10.0.6 + resolution: "ssri@npm:10.0.6" dependencies: minipass: "npm:^7.0.3" - checksum: 10/453f9a1c241c13f5dfceca2ab7b4687bcff354c3ccbc932f35452687b9ef0ccf8983fd13b8a3baa5844c1a4882d6e3ddff48b0e7fd21d743809ef33b80616d79 + checksum: 10/f92c1b3cc9bfd0a925417412d07d999935917bc87049f43ebec41074661d64cf720315661844106a77da9f8204b6d55ae29f9514e673083cae39464343af2a8b languageName: node linkType: hard @@ -5071,13 +5360,13 @@ __metadata: linkType: hard "string-width@npm:^7.0.0": - version: 7.0.0 - resolution: "string-width@npm:7.0.0" + version: 7.1.0 + resolution: "string-width@npm:7.1.0" dependencies: emoji-regex: "npm:^10.3.0" get-east-asian-width: "npm:^1.0.0" strip-ansi: "npm:^7.1.0" - checksum: 10/bc0de5700a2690895169fce447ec4ed44bc62de80312c2093d5606bfd48319bb88e48a99e97f269dff2bc9577448b91c26b3804c16e7d9b389699795e4655c3b + checksum: 10/a183573fe7209e0d294f661846d33f8caf72aa86d983e5b48a0ed45ab15bcccb02c6f0344b58b571988871105457137b8207855ea536827dbc4a376a0f31bf8f languageName: node linkType: hard @@ -5137,11 +5426,11 @@ __metadata: linkType: hard "strip-literal@npm:^2.0.0": - version: 2.0.0 - resolution: "strip-literal@npm:2.0.0" + version: 2.1.0 + resolution: "strip-literal@npm:2.1.0" dependencies: - js-tokens: "npm:^8.0.2" - checksum: 10/efb3197175a7e403d0eaaaf5382b9574be77f8fa006b57b669856a38b58ca9caf76cbc75d9f69d56324dad0b8babe1d4ea7ad1eb12106228830bcdd5d4bf12b5 + js-tokens: "npm:^9.0.0" + checksum: 10/21c813aa1e669944e7e2318c8c927939fb90b0c52f53f57282bfc3dd6e19d53f70004f1f1693e33e5e790ad5ef102b0fce2b243808229d1ce07ae71f326c0e82 languageName: node linkType: hard @@ -5199,8 +5488,8 @@ __metadata: linkType: hard "tar@npm:^6.1.11, tar@npm:^6.1.2": - version: 6.2.0 - resolution: "tar@npm:6.2.0" + version: 6.2.1 + resolution: "tar@npm:6.2.1" dependencies: chownr: "npm:^2.0.0" fs-minipass: "npm:^2.0.0" @@ -5208,7 +5497,7 @@ __metadata: minizlib: "npm:^2.1.1" mkdirp: "npm:^1.0.3" yallist: "npm:^4.0.0" - checksum: 10/2042bbb14830b5cd0d584007db0eb0a7e933e66d1397e72a4293768d2332449bc3e312c266a0887ec20156dea388d8965e53b4fc5097f42d78593549016da089 + checksum: 10/bfbfbb2861888077fc1130b84029cdc2721efb93d1d1fb80f22a7ac3a98ec6f8972f29e564103bbebf5e97be67ebc356d37fa48dbc4960600a1eb7230fbd1ea0 languageName: node linkType: hard @@ -5263,23 +5552,23 @@ __metadata: linkType: hard "tinybench@npm:^2.5.1": - version: 2.6.0 - resolution: "tinybench@npm:2.6.0" - checksum: 10/6d35f0540bbf6208e8f47fa88cad733bc4b35b3bea75ec995004a9a44f70b8947eff3d271a3b4a4f7e787a82211df0dec9370fa566ccf50441067c559382b3ed + version: 2.8.0 + resolution: "tinybench@npm:2.8.0" + checksum: 10/9731d070bedee6d44f3bb565862c284776e6adfd70d81a051a5c79b77479408509b448ad8d467d538d18bc0ae857b3ead8168d7e98d7f1355f8a0b01aa2f163b languageName: node linkType: hard "tinypool@npm:^0.8.3": - version: 0.8.3 - resolution: "tinypool@npm:0.8.3" - checksum: 10/0372a8de2a0f792a36f23e199809a1b635ba1470df8be7a7183e382af55518a6f7c9d75e776952598955c1e8c54ea1157b0c680288edd0554aac4722f3900fa2 + version: 0.8.4 + resolution: "tinypool@npm:0.8.4" + checksum: 10/7365944c2532f240111443e7012be31a634faf1a02db08a91db3aa07361c26a374d0be00a0f2ea052c4bee39c107ba67f1f814c108d9d51dfc725c559c1a9c03 languageName: node linkType: hard "tinyspy@npm:^2.2.0": - version: 2.2.0 - resolution: "tinyspy@npm:2.2.0" - checksum: 10/bcc5a08c2dc7574d32e6dcc2e760ad95a3cf30249c22799815b6389179427c95573d27d2d965ebc5fca2b6d338c46678cd7337ea2a9cebacee3dc662176b07cb + version: 2.2.1 + resolution: "tinyspy@npm:2.2.1" + checksum: 10/170d6232e87f9044f537b50b406a38fbfd6f79a261cd12b92879947bd340939a833a678632ce4f5c4a6feab4477e9c21cd43faac3b90b68b77dd0536c4149736 languageName: node linkType: hard @@ -5309,14 +5598,14 @@ __metadata: linkType: hard "tough-cookie@npm:^4.1.3": - version: 4.1.3 - resolution: "tough-cookie@npm:4.1.3" + version: 4.1.4 + resolution: "tough-cookie@npm:4.1.4" dependencies: psl: "npm:^1.1.33" punycode: "npm:^2.1.1" universalify: "npm:^0.2.0" url-parse: "npm:^1.5.3" - checksum: 10/cf148c359b638a7069fc3ba9a5257bdc9616a6948a98736b92c3570b3f8401cf9237a42bf716878b656f372a1fb65b74dd13a46ccff8eceba14ffd053d33f72a + checksum: 10/75663f4e2cd085f16af0b217e4218772adf0617fb3227171102618a54ce0187a164e505d61f773ed7d65988f8ff8a8f935d381f87da981752c1171b076b4afac languageName: node linkType: hard @@ -5446,13 +5735,6 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^3.0.0": - version: 3.13.1 - resolution: "type-fest@npm:3.13.1" - checksum: 10/9a8a2359ada34c9b3affcaf3a8f73ee14c52779e89950db337ce66fb74c3399776c697c99f2532e9b16e10e61cfdba3b1c19daffb93b338b742f0acd0117ce12 - languageName: node - linkType: hard - "typedoc-json-parser@npm:^10.0.0": version: 10.0.0 resolution: "typedoc-json-parser@npm:10.0.0" @@ -5507,10 +5789,10 @@ __metadata: languageName: node linkType: hard -"ufo@npm:^1.3.2": - version: 1.3.2 - resolution: "ufo@npm:1.3.2" - checksum: 10/7133290d495e2b3f9416de69982019e81cff40d28cfd3a07accff1122ee52f23d9165e495a140a1b34b183244e88fc4001cb649591385ecbad1d3d0d2264fa6e +"ufo@npm:^1.5.3": + version: 1.5.3 + resolution: "ufo@npm:1.5.3" + checksum: 10/2b30dddd873c643efecdb58cfe457183cd4d95937ccdacca6942c697b87a2c578232c25a5149fda85436696bf0fdbc213bf2b220874712bc3e58c0fb00a2c950 languageName: node linkType: hard @@ -5560,7 +5842,7 @@ __metadata: languageName: node linkType: hard -"uri-js@npm:^4.2.2": +"uri-js@npm:^4.2.2, uri-js@npm:^4.4.1": version: 4.4.1 resolution: "uri-js@npm:4.4.1" dependencies: @@ -5587,11 +5869,9 @@ __metadata: linkType: hard "validate-npm-package-name@npm:^5.0.0": - version: 5.0.0 - resolution: "validate-npm-package-name@npm:5.0.0" - dependencies: - builtins: "npm:^5.0.0" - checksum: 10/5342a994986199b3c28e53a8452a14b2bb5085727691ea7aa0d284a6606b127c371e0925ae99b3f1ef7cc7d2c9de75f52eb61a3d1cc45e39bca1e3a9444cbb4e + version: 5.0.1 + resolution: "validate-npm-package-name@npm:5.0.1" + checksum: 10/0d583a1af23aeffea7748742cf22b6802458736fb8b60323ba5949763824d46f796474b0e1b9206beb716f9d75269e19dbd7795d6b038b29d561be95dd827381 languageName: node linkType: hard @@ -5611,13 +5891,13 @@ __metadata: linkType: hard "vite@npm:^5.0.0": - version: 5.0.11 - resolution: "vite@npm:5.0.11" + version: 5.2.11 + resolution: "vite@npm:5.2.11" dependencies: - esbuild: "npm:^0.19.3" + esbuild: "npm:^0.20.1" fsevents: "npm:~2.3.3" - postcss: "npm:^8.4.32" - rollup: "npm:^4.2.0" + postcss: "npm:^8.4.38" + rollup: "npm:^4.13.0" peerDependencies: "@types/node": ^18.0.0 || >=20.0.0 less: "*" @@ -5646,7 +5926,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10/f1a8fea35ed9f162d7a10fd13efb2c96637028b0a319d726aeec8b31e20e4d047272bda5df82167618e7774a520236c66f3093ed172802660aec5227814072f4 + checksum: 10/ee0ad038f0831c9514796522deb1e2dcb84bc311abbccb77e4b12216d37fc9559137f4f1b8e75187d51007b954e845c6518e36ee3acac2e2a2789c1181ebb16c languageName: node linkType: hard @@ -5845,7 +6125,7 @@ __metadata: languageName: node linkType: hard -"word-wrap@npm:^1.0.3": +"word-wrap@npm:^1.0.3, word-wrap@npm:^1.2.5": version: 1.2.5 resolution: "word-wrap@npm:1.2.5" checksum: 10/1ec6f6089f205f83037be10d0c4b34c9183b0b63fca0834a5b3cee55dd321429d73d40bb44c8fc8471b5203d6e8f8275717f49a8ff4b2b0ab41d7e1b563e0854 @@ -5893,8 +6173,8 @@ __metadata: linkType: hard "ws@npm:^8.16.0": - version: 8.16.0 - resolution: "ws@npm:8.16.0" + version: 8.17.0 + resolution: "ws@npm:8.17.0" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -5903,7 +6183,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10/7c511c59e979bd37b63c3aea4a8e4d4163204f00bd5633c053b05ed67835481995f61a523b0ad2b603566f9a89b34cb4965cb9fab9649fbfebd8f740cea57f17 + checksum: 10/5e1dcb0ae70c6e2f158f5b446e0a72a2cd335b07aba73ee1872e9bae1285382286a10e53ed479db21bdd690a5dfd05641a768611ebb236253c62fefa43ef58b4 languageName: node linkType: hard @@ -5935,13 +6215,22 @@ __metadata: languageName: node linkType: hard -"yaml@npm:2.3.4, yaml@npm:^2.3.4": +"yaml@npm:2.3.4": version: 2.3.4 resolution: "yaml@npm:2.3.4" checksum: 10/f8207ce43065a22268a2806ea6a0fa3974c6fde92b4b2fa0082357e487bc333e85dc518910007e7ac001b532c7c84bd3eccb6c7757e94182b564028b0008f44b languageName: node linkType: hard +"yaml@npm:^2.3.4": + version: 2.4.2 + resolution: "yaml@npm:2.4.2" + bin: + yaml: bin.mjs + checksum: 10/6eafbcd68dead734035f6f72af21bd820c29214caf7d8e40c595671a3c908535cef8092b9660a1c055c5833aa148aa640e0c5fa4adb5af2dacd6d28296ccd81c + languageName: node + linkType: hard + "yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1"