From 44a5cea211ebe409316d3d9e83afb01051a14498 Mon Sep 17 00:00:00 2001 From: Jeroen Claassens Date: Mon, 20 May 2024 16:51:32 +0200 Subject: [PATCH] feat(*)!: add custom message options to all shapes, validators and constraints (#231) BREAKING CHANGE: Most shapes and validators that were previously getters are now functions to allow for custom options. The following list should show all of the changes, but if we have forgot any and you get an error saying something should be a function where you have provided a constant it is safe to assume you simply need to add `()` to your code for it to work again. BREAKING CHANGE: `PickDefined` utility type has been removed. BREAKING CHANGE: `PickUndefinedMakeOptional` utility type has been removed. BREAKING CHANGE: `NonNullObject` utility type has been removed. BREAKING CHANGE: `s.any` is now `s.any()` to allow for custom options as argument. BREAKING CHANGE: `s.array(T).lengthEqual` is now `s.array(T).lengthEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.array(T).lengthGreaterThan` is now `s.array(T).lengthGreaterThan()` to allow for custom options as argument. BREAKING CHANGE: `s.array(T).lengthGreaterThanOrEqual` is now `s.array(T).lengthGreaterThanOrEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.array(T).lengthLessThan` is now `s.array(T).lengthLessThan()` to allow for custom options as argument. BREAKING CHANGE: `s.array(T).lengthLessThanOrEqual` is now `s.array(T).lengthLessThanOrEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.array(T).lengthNotEqual` is now `s.array(T).lengthNotEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.array(T).lengthRange` is now `s.array(T).lengthRange()` to allow for custom options as argument. BREAKING CHANGE: `s.array(T).lengthRangeExclusive` is now `s.array(T).lengthRangeExclusive()` to allow for custom options as argument. BREAKING CHANGE: `s.array(T).lengthRangeInclusive` is now `s.array(T).lengthRangeInclusive()` to allow for custom options as argument. BREAKING CHANGE: `s.array(T).unique` is now `s.array(T).unique()` to allow for custom options as argument. BREAKING CHANGE: `s.array` is now `s.array()` to allow for custom options as argument. BREAKING CHANGE: `s.bigint.divisibleBy` is now `s.bigint().divisibleBy()` to allow for custom options as argument. BREAKING CHANGE: `s.bigint.equal` is now `s.bigint().equal()` to allow for custom options as argument. BREAKING CHANGE: `s.bigint.greaterThan` is now `s.bigint().greaterThan()` to allow for custom options as argument. BREAKING CHANGE: `s.bigint.greaterThanOrEqual` is now `s.bigint().greaterThanOrEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.bigint.lessThan` is now `s.bigint().lessThan()` to allow for custom options as argument. BREAKING CHANGE: `s.bigint.lessThanOrEqual` is now `s.bigint().lessThanOrEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.bigint.notEqual` is now `s.bigint().notEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.bigint().abs` is now `s.bigint().abs()` to allow for custom options as second argument. BREAKING CHANGE: `s.bigint().negative` is now `s.bigint().negative()` to allow for custom options as second argument. BREAKING CHANGE: `s.bigint().positive` is now `s.bigint().positive()` to allow for custom options as second argument. BREAKING CHANGE: `s.bigint` is now `s.bigint()` to allow for custom options as argument. BREAKING CHANGE: `s.bigInt64Array` is now `s.bigInt64Array()` to allow for custom options as argument. BREAKING CHANGE: `s.bigUint64Array` is now `s.bigUint64Array()` to allow for custom options as argument. BREAKING CHANGE: `s.boolean.false` is now `s.boolean().false()` to allow for custom options as second argument. BREAKING CHANGE: `s.boolean.true` is now `s.boolean().true()` to allow for custom options as second argument. BREAKING CHANGE: `s.boolean` is now `s.boolean()` to allow for custom options as argument. BREAKING CHANGE: `s.default(...)` now gets a second parameter to allow for custom options as argument. BREAKING CHANGE: `s.default(...).default(...)` now gets a second parameter to allow for custom options as argument. BREAKING CHANGE: `s.date.equal` is now `s.date().equal()` to allow for custom options as argument. BREAKING CHANGE: `s.date.greaterThan` is now `s.date().greaterThan()` to allow for custom options as argument. BREAKING CHANGE: `s.date.greaterThanOrEqual` is now `s.date().greaterThanOrEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.date.invalid` is now `s.date().invalid()` to allow for custom options as argument. BREAKING CHANGE: `s.date.lessThan` is now `s.date().lessThan()` to allow for custom options as argument. BREAKING CHANGE: `s.date.lessThanOrEqual` is now `s.date().lessThanOrEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.date.notEqual` is now `s.date().notEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.date.valid` is now `s.date().valid()` to allow for custom options as argument. BREAKING CHANGE: `s.date` is now `s.date()` to allow for custom options as argument. BREAKING CHANGE: `s.enum(1, 2, 3)` is now `s.enum([1, 2, 3])` to allow for custom options as second argument. BREAKING CHANGE: `s.float32Array` is now `s.float32Array()` to allow for custom options as argument. BREAKING CHANGE: `s.float64Array` is now `s.float64Array()` to allow for custom options as argument. BREAKING CHANGE: `s.int16Array` is now `s.int16Array()` to allow for custom options as argument. BREAKING CHANGE: `s.int32Array` is now `s.int32Array()` to allow for custom options as argument. BREAKING CHANGE: `s.int8Array` is now `s.int8Array()` to allow for custom options as argument. BREAKING CHANGE: `s.never` is now `s.never()` to allow for custom options as argument. BREAKING CHANGE: `s.null` is now `s.null()` to allow for custom options as argument. BREAKING CHANGE: `s.nullable` is now `s.nullable()` to allow for custom options as argument. BREAKING CHANGE: `s.nullish` is now `s.nullish()` to allow for custom options as argument. BREAKING CHANGE: `s.nullish` is now `s.nullish()` to allow for custom options as argument. BREAKING CHANGE: `s.number.abs` is now `s.number().abs()` to allow for custom options as argument. BREAKING CHANGE: `s.number.ceil` is now `s.number().ceil()` to allow for custom options as argument. BREAKING CHANGE: `s.number.divisibleBy` is now `s.number().divisibleBy()` to allow for custom options as argument. BREAKING CHANGE: `s.number.equal` is now `s.number().equal()` to allow for custom options as argument. BREAKING CHANGE: `s.number.finite` is now `s.number().finite()` to allow for custom options as argument. BREAKING CHANGE: `s.number.floor` is now `s.number().floor()` to allow for custom options as argument. BREAKING CHANGE: `s.number.fround` is now `s.number().fround()` to allow for custom options as argument. BREAKING CHANGE: `s.number.greaterThan` is now `s.number().greaterThan()` to allow for custom options as argument. BREAKING CHANGE: `s.number.greaterThanOrEqual` is now `s.number().greaterThanOrEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.number.int` is now `s.number().int()` to allow for custom options as argument. BREAKING CHANGE: `s.number.lessThan` is now `s.number().lessThan()` to allow for custom options as argument. BREAKING CHANGE: `s.number.lessThanOrEqual` is now `s.number().lessThanOrEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.number.negative` is now `s.number().negative()` to allow for custom options as argument. BREAKING CHANGE: `s.number.notEqual` is now `s.number().notEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.number.positive` is now `s.number().positive()` to allow for custom options as argument. BREAKING CHANGE: `s.number.round` is now `s.number().round()` to allow for custom options as argument. BREAKING CHANGE: `s.number.safeInt` is now `s.number().safeInt()` to allow for custom options as argument. BREAKING CHANGE: `s.number.sign` is now `s.number().sign()` to allow for custom options as argument. BREAKING CHANGE: `s.number.trunc` is now `s.number().trunc()` to allow for custom options as argument. BREAKING CHANGE: `s.number` is now `s.number()` to allow for custom options as argument. BREAKING CHANGE: `s.object.ignore` is now `s.object().ignore()` to allow for custom options as argument. BREAKING CHANGE: `s.object.partial` is now `s.object().partial()` to allow for custom options as argument. BREAKING CHANGE: `s.object.passthrough` is now `s.object().passthrough()` to allow for custom options as argument. BREAKING CHANGE: `s.object.required` is now `s.object().required()` to allow for custom options as argument. BREAKING CHANGE: `s.object.strict` is now `s.object().strict()` to allow for custom options as argument. BREAKING CHANGE: `s.optional` is now `s.optional()` to allow for custom options as argument. BREAKING CHANGE: `s.required(...)` now gets a second parameter to allow for custom options as argument. BREAKING CHANGE: `s.set` is now `s.set()` to allow for custom options as argument. BREAKING CHANGE: `s.string.date` is now `s.string().date()` to allow for custom options as argument. BREAKING CHANGE: `s.string.email` is now `s.string().email()` to allow for custom options as argument. BREAKING CHANGE: `s.string.ipv4` is now `s.string().ipv4()` to allow for custom options as argument. BREAKING CHANGE: `s.string.ipv6` is now `s.string().ipv6()` to allow for custom options as argument. BREAKING CHANGE: `s.string().ip` is now `s.string().ip()` to allow for custom options as argument. BREAKING CHANGE: `s.string().lengthEqual` is now `s.string().lengthEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.string().lengthGreaterThan` is now `s.string().lengthGreaterThan()` to allow for custom options as argument. BREAKING CHANGE: `s.string().lengthGreaterThanOrEqual` is now `s.string().lengthGreaterThanOrEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.string().lengthLessThan` is now `s.string().lengthLessThan()` to allow for custom options as argument. BREAKING CHANGE: `s.string().lengthLessThanOrEqual` is now `s.string().lengthLessThanOrEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.string().lengthNotEqual` is now `s.string().lengthNotEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.string().phone` is now `s.string().phone()` to allow for custom options as argument. BREAKING CHANGE: `s.string().regex` is now `s.string().regex()` to allow for custom options as argument. BREAKING CHANGE: `s.string().url` is now `s.string().url()` to allow for custom options as argument. BREAKING CHANGE: `s.string` is now `s.string()` to allow for custom options as argument. BREAKING CHANGE: `s.tuple(1, 2, 3)` is now `s.tuple([1, 2, 3])` to allow for custom options as second argument. BREAKING CHANGE: `s.typedArray(T).byteLengthEqual` is now `s.typedArray(T).byteLengthEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).byteLengthGreaterThan` is now `s.typedArray(T).byteLengthGreaterThan()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).byteLengthGreaterThanOrEqual` is now `s.typedArray(T).byteLengthGreaterThanOrEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).byteLengthLessThan` is now `s.typedArray(T).byteLengthLessThan()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).byteLengthLessThanOrEqual` is now `s.typedArray(T).byteLengthLessThanOrEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).byteLengthNotEqual` is now `s.typedArray(T).byteLengthNotEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).byteLengthRange` is now `s.typedArray(T).byteLengthRange()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).byteLengthRangeExclusive` is now `s.typedArray(T).byteLengthRangeExclusive()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).byteLengthRangeInclusive` is now `s.typedArray(T).byteLengthRangeInclusive()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).lengthEqual` is now `s.typedArray(T).lengthEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).lengthGreaterThan` is now `s.typedArray(T).lengthGreaterThan()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).lengthGreaterThanOrEqual` is now `s.typedArray(T).lengthGreaterThanOrEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).lengthLessThan` is now `s.typedArray(T).lengthLessThan()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).lengthLessThanOrEqual` is now `s.typedArray(T).lengthLessThanOrEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).lengthNotEqual` is now `s.typedArray(T).lengthNotEqual()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).lengthRange` is now `s.typedArray(T).lengthRange()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).lengthRangeExclusive` is now `s.typedArray(T).lengthRangeExclusive()` to allow for custom options as argument. BREAKING CHANGE: `s.typedArray(T).lengthRangeInclusive` is now `s.typedArray(T).lengthRangeInclusive()` to allow for custom options as argument. BREAKING CHANGE: `s.uint16Array` is now `s.uint16Array()` to allow for custom options as argument. BREAKING CHANGE: `s.uint32Array` is now `s.uint32Array()` to allow for custom options as argument. BREAKING CHANGE: `s.uint8Array` is now `s.uint8Array()` to allow for custom options as argument. BREAKING CHANGE: `s.uint8ClampedArray` is now `s.uint8ClampedArray()` to allow for custom options as argument. BREAKING CHANGE: `s.undefined` is now `s.undefined()` to allow for custom options as argument. BREAKING CHANGE: `s.union(1, 2, 3).required` is now `s.union(1, 2, 3).required()` to allow for custom options as argument. BREAKING CHANGE: `s.union(1, 2, 3)` is now `s.union([1, 2, 3])` to allow for custom options as second argument. BREAKING CHANGE: `s.unknown` is now `s.unknown()` to allow for custom options as argument. BREAKING CHANGE: `uniqueArray` is now a function (instead of a constant) to allow for custom options as argument. BREAKING CHANGE: `dateInvalid` is now a function (instead of a constant) to allow for custom options as argument. BREAKING CHANGE: `dateValid` is now a function (instead of a constant) to allow for custom options as argument. BREAKING CHANGE: `numberFinite` is now a function (instead of a constant) to allow for custom options as argument. BREAKING CHANGE: `numberInt` is now a function (instead of a constant) to allow for custom options as argument. BREAKING CHANGE: `numberNaN` is now a function (instead of a constant) to allow for custom options as argument. BREAKING CHANGE: `numberNotNaN` is now a function (instead of a constant) to allow for custom options as argument. BREAKING CHANGE: `numberSafeInt` is now a function (instead of a constant) to allow for custom options as argument. --- .github/workflows/auto-deprecate.yml | 2 +- .github/workflows/continuous-integration.yml | 12 +- .github/workflows/deprecate-on-merge.yml | 2 +- .github/workflows/documentation.yml | 2 +- .prettierrc.mjs | 2 +- .vscode/settings.json | 2 +- README.md | 434 +++--- UPGRADING-v3-v4.md | 211 +++ package.json | 5 +- src/constraints/ArrayConstraints.ts | 76 +- src/constraints/BigIntConstraints.ts | 43 +- src/constraints/BooleanConstraints.ts | 35 +- src/constraints/DateConstraints.ts | 71 +- src/constraints/NumberConstraints.ts | 162 ++- src/constraints/ObjectConstrains.ts | 17 +- src/constraints/StringConstraints.ts | 95 +- .../TypedArrayLengthConstraints.ts | 131 +- src/constraints/util/urlValidators.ts | 15 +- src/lib/Shapes.ts | 155 +- src/lib/errors/BaseConstraintError.ts | 10 + src/lib/errors/BaseError.ts | 8 + src/lib/errors/CombinedError.ts | 5 +- src/lib/errors/CombinedPropertyError.ts | 5 +- src/lib/errors/ExpectedConstraintError.ts | 8 +- src/lib/errors/ExpectedValidationError.ts | 6 +- src/lib/errors/MissingPropertyError.ts | 9 +- .../MultiplePossibilitiesConstraintError.ts | 6 +- src/lib/errors/UnknownEnumValueError.ts | 14 +- src/lib/errors/UnknownPropertyError.ts | 9 +- src/lib/errors/ValidationError.ts | 4 +- src/lib/errors/error-types.ts | 43 + src/lib/util-types.ts | 23 +- src/type-exports.ts | 59 +- src/validators/ArrayValidator.ts | 67 +- src/validators/BaseValidator.ts | 90 +- src/validators/BigIntValidator.ts | 51 +- src/validators/BooleanValidator.ts | 19 +- src/validators/DateValidator.ts | 39 +- src/validators/DefaultValidator.ts | 18 +- src/validators/InstanceValidator.ts | 10 +- src/validators/LazyValidator.ts | 9 +- src/validators/LiteralValidator.ts | 11 +- src/validators/MapValidator.ts | 16 +- src/validators/NativeEnumValidator.ts | 17 +- src/validators/NeverValidator.ts | 2 +- src/validators/NullishValidator.ts | 2 +- src/validators/NumberValidator.ts | 83 +- src/validators/ObjectValidator.ts | 76 +- src/validators/RecordValidator.ts | 19 +- src/validators/SetValidator.ts | 11 +- src/validators/StringValidator.ts | 87 +- src/validators/TupleValidator.ts | 19 +- src/validators/TypedArrayValidator.ts | 81 +- src/validators/UnionValidator.ts | 58 +- tests/browser/browser.test.ts | 2 +- tests/lib/configs.test.ts | 16 +- tests/lib/errors/BaseConstraintError.test.ts | 54 + tests/lib/errors/BaseError.test.ts | 25 + .../errors/ExpectedConstraintError.test.ts | 13 +- .../errors/ExpectedValidationError.test.ts | 3 +- tests/lib/errors/MissingPropertyError.test.ts | 3 +- ...ltiplePossibilitiesConstraintError.test.ts | 13 +- .../lib/errors/UnknownEnumValueError.test.ts | 3 +- tests/lib/errors/UnknownPropertyError.test.ts | 3 +- tests/lib/errors/ValidationError.test.ts | 3 +- tests/validators/array.test.ts | 105 +- tests/validators/base.test.ts | 129 +- tests/validators/bigint.test.ts | 62 +- tests/validators/boolean.test.ts | 37 +- tests/validators/date.test.ts | 55 +- tests/validators/enum.test.ts | 20 +- tests/validators/instance.test.ts | 6 +- tests/validators/lazy.test.ts | 30 +- tests/validators/literal.test.ts | 18 +- tests/validators/map.test.ts | 24 +- tests/validators/nativeEnum.test.ts | 21 +- tests/validators/never.test.ts | 6 +- tests/validators/null.test.ts | 9 +- tests/validators/nullish.test.ts | 6 +- tests/validators/number.test.ts | 94 +- tests/validators/object.test.ts | 613 +++++--- tests/validators/passthrough.test.ts | 6 +- tests/validators/record.test.ts | 28 +- tests/validators/set.test.ts | 13 +- tests/validators/string.test.ts | 125 +- tests/validators/tuple.test.ts | 24 +- tests/validators/typedArray.test.ts | 174 ++- tests/validators/undefined.test.ts | 9 +- tests/validators/union.test.ts | 228 ++- vitest.config.ts | 5 +- yarn.lock | 1255 ++++++++++------- 91 files changed, 3569 insertions(+), 2137 deletions(-) create mode 100644 UPGRADING-v3-v4.md create mode 100644 src/lib/errors/error-types.ts create mode 100644 tests/lib/errors/BaseConstraintError.test.ts create mode 100644 tests/lib/errors/BaseError.test.ts 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"