@badrap/valita
Advanced tools
Comparing version
@@ -188,3 +188,2 @@ type PrettyIntersection<V> = Extract<{ | ||
constructor(head: Head, rest?: Rest); | ||
toTerminals(func: (t: TerminalType) => void): void; | ||
func(arr: unknown, mode: FuncMode): RawResult<ArrayOutput<Head, Rest>>; | ||
@@ -191,0 +190,0 @@ } |
@@ -11,12 +11,13 @@ "use strict"; | ||
function cloneIssueWithPath(tree, path) { | ||
switch (tree.code) { | ||
const code = tree.code; | ||
switch (code) { | ||
case "invalid_type": | ||
return { code: "invalid_type", path, expected: tree.expected }; | ||
return { code, path, expected: tree.expected }; | ||
case "invalid_literal": | ||
return { code: "invalid_literal", path, expected: tree.expected }; | ||
return { code, path, expected: tree.expected }; | ||
case "missing_value": | ||
return { code: "missing_value", path }; | ||
return { code, path }; | ||
case "invalid_length": | ||
return { | ||
code: "invalid_length", | ||
code, | ||
path, | ||
@@ -27,7 +28,7 @@ minLength: tree.minLength, | ||
case "unrecognized_keys": | ||
return { code: "unrecognized_keys", path, keys: tree.keys }; | ||
return { code, path, keys: tree.keys }; | ||
case "invalid_union": | ||
return { code: "invalid_union", path, tree: tree.tree }; | ||
return { code, path, tree: tree.tree }; | ||
default: | ||
return { code: "custom_error", path, error: tree.error }; | ||
return { code, path, error: tree.error }; | ||
} | ||
@@ -390,13 +391,2 @@ } | ||
} | ||
// When an object matcher needs to create a copied version of the input, | ||
// it initializes the new objects with Object.create(protoless). | ||
// | ||
// Using Object.create(protoless) instead of just {} makes setting | ||
// "__proto__" key safe. Previously we set object properties with a helper | ||
// function that special-cased "__proto__". Now we can just do | ||
// `output[key] = value` directly. | ||
// | ||
// Using Object.create(protoless) instead of Object.create(null) seems to | ||
// be faster on V8 at the time of writing this (2023-08-07). | ||
const protoless = Object.freeze(Object.create(null)); | ||
function createObjectMatcher(shape, rest, checks) { | ||
@@ -424,3 +414,3 @@ const requiredKeys = []; | ||
}; | ||
if (totalCount === 0 && rest === unknownSingleton) { | ||
if (totalCount === 0 && (rest === null || rest === void 0 ? void 0 : rest.name) === "unknown") { | ||
// A fast path for record(unknown()) | ||
@@ -451,2 +441,15 @@ return function (obj, _) { | ||
})); | ||
function set(obj, key, value) { | ||
if (key === "__proto__") { | ||
Object.defineProperty(obj, key, { | ||
value, | ||
writable: true, | ||
enumerable: true, | ||
configurable: true, | ||
}); | ||
} | ||
else { | ||
obj[key] = value; | ||
} | ||
} | ||
return function (obj, mode) { | ||
@@ -487,3 +490,3 @@ if (!isObject(obj)) { | ||
!copied) { | ||
output = Object.create(protoless); | ||
output = {}; | ||
copied = true; | ||
@@ -493,3 +496,3 @@ for (let m = 0; m < totalCount; m++) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
set(output, k, obj[k]); | ||
} | ||
@@ -502,3 +505,3 @@ } | ||
if (copied && issues === undefined) { | ||
output[key] = value; | ||
set(output, key, value); | ||
} | ||
@@ -511,3 +514,3 @@ } | ||
if (!copied) { | ||
output = Object.create(protoless); | ||
output = {}; | ||
copied = true; | ||
@@ -518,3 +521,3 @@ if (rest === undefined) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
set(output, k, obj[k]); | ||
} | ||
@@ -525,7 +528,9 @@ } | ||
for (const k in obj) { | ||
output[k] = obj[k]; | ||
if (k === "__proto__") | ||
output.__proto__ = null; | ||
set(output, k, obj[k]); | ||
} | ||
} | ||
} | ||
output[key] = r.value; | ||
set(output, key, r.value); | ||
} | ||
@@ -551,3 +556,3 @@ } | ||
if (copied && issues === undefined && value !== Nothing) { | ||
output[key] = value; | ||
set(output, key, value); | ||
} | ||
@@ -560,3 +565,3 @@ } | ||
if (!copied) { | ||
output = Object.create(protoless); | ||
output = {}; | ||
copied = true; | ||
@@ -567,3 +572,3 @@ if (rest === undefined) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
set(output, k, obj[k]); | ||
} | ||
@@ -574,3 +579,3 @@ } | ||
for (const k in obj) { | ||
output[k] = obj[k]; | ||
set(output, k, obj[k]); | ||
} | ||
@@ -580,3 +585,3 @@ for (let m = 0; m < i; m++) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
set(output, k, obj[k]); | ||
} | ||
@@ -586,3 +591,3 @@ } | ||
} | ||
output[key] = r.value; | ||
set(output, key, r.value); | ||
} | ||
@@ -633,5 +638,2 @@ } | ||
} | ||
toTerminals(func) { | ||
func(this); | ||
} | ||
func(arr, mode) { | ||
@@ -638,0 +640,0 @@ if (!Array.isArray(arr)) { |
@@ -188,3 +188,2 @@ type PrettyIntersection<V> = Extract<{ | ||
constructor(head: Head, rest?: Rest); | ||
toTerminals(func: (t: TerminalType) => void): void; | ||
func(arr: unknown, mode: FuncMode): RawResult<ArrayOutput<Head, Rest>>; | ||
@@ -191,0 +190,0 @@ } |
@@ -11,12 +11,13 @@ "use strict"; | ||
function cloneIssueWithPath(tree, path) { | ||
switch (tree.code) { | ||
const code = tree.code; | ||
switch (code) { | ||
case "invalid_type": | ||
return { code: "invalid_type", path, expected: tree.expected }; | ||
return { code, path, expected: tree.expected }; | ||
case "invalid_literal": | ||
return { code: "invalid_literal", path, expected: tree.expected }; | ||
return { code, path, expected: tree.expected }; | ||
case "missing_value": | ||
return { code: "missing_value", path }; | ||
return { code, path }; | ||
case "invalid_length": | ||
return { | ||
code: "invalid_length", | ||
code, | ||
path, | ||
@@ -27,7 +28,7 @@ minLength: tree.minLength, | ||
case "unrecognized_keys": | ||
return { code: "unrecognized_keys", path, keys: tree.keys }; | ||
return { code, path, keys: tree.keys }; | ||
case "invalid_union": | ||
return { code: "invalid_union", path, tree: tree.tree }; | ||
return { code, path, tree: tree.tree }; | ||
default: | ||
return { code: "custom_error", path, error: tree.error }; | ||
return { code, path, error: tree.error }; | ||
} | ||
@@ -390,13 +391,2 @@ } | ||
} | ||
// When an object matcher needs to create a copied version of the input, | ||
// it initializes the new objects with Object.create(protoless). | ||
// | ||
// Using Object.create(protoless) instead of just {} makes setting | ||
// "__proto__" key safe. Previously we set object properties with a helper | ||
// function that special-cased "__proto__". Now we can just do | ||
// `output[key] = value` directly. | ||
// | ||
// Using Object.create(protoless) instead of Object.create(null) seems to | ||
// be faster on V8 at the time of writing this (2023-08-07). | ||
const protoless = Object.freeze(Object.create(null)); | ||
function createObjectMatcher(shape, rest, checks) { | ||
@@ -424,3 +414,3 @@ const requiredKeys = []; | ||
}; | ||
if (totalCount === 0 && rest === unknownSingleton) { | ||
if (totalCount === 0 && (rest === null || rest === void 0 ? void 0 : rest.name) === "unknown") { | ||
// A fast path for record(unknown()) | ||
@@ -451,2 +441,15 @@ return function (obj, _) { | ||
})); | ||
function set(obj, key, value) { | ||
if (key === "__proto__") { | ||
Object.defineProperty(obj, key, { | ||
value, | ||
writable: true, | ||
enumerable: true, | ||
configurable: true, | ||
}); | ||
} | ||
else { | ||
obj[key] = value; | ||
} | ||
} | ||
return function (obj, mode) { | ||
@@ -487,3 +490,3 @@ if (!isObject(obj)) { | ||
!copied) { | ||
output = Object.create(protoless); | ||
output = {}; | ||
copied = true; | ||
@@ -493,3 +496,3 @@ for (let m = 0; m < totalCount; m++) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
set(output, k, obj[k]); | ||
} | ||
@@ -502,3 +505,3 @@ } | ||
if (copied && issues === undefined) { | ||
output[key] = value; | ||
set(output, key, value); | ||
} | ||
@@ -511,3 +514,3 @@ } | ||
if (!copied) { | ||
output = Object.create(protoless); | ||
output = {}; | ||
copied = true; | ||
@@ -518,3 +521,3 @@ if (rest === undefined) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
set(output, k, obj[k]); | ||
} | ||
@@ -525,7 +528,9 @@ } | ||
for (const k in obj) { | ||
output[k] = obj[k]; | ||
if (k === "__proto__") | ||
output.__proto__ = null; | ||
set(output, k, obj[k]); | ||
} | ||
} | ||
} | ||
output[key] = r.value; | ||
set(output, key, r.value); | ||
} | ||
@@ -551,3 +556,3 @@ } | ||
if (copied && issues === undefined && value !== Nothing) { | ||
output[key] = value; | ||
set(output, key, value); | ||
} | ||
@@ -560,3 +565,3 @@ } | ||
if (!copied) { | ||
output = Object.create(protoless); | ||
output = {}; | ||
copied = true; | ||
@@ -567,3 +572,3 @@ if (rest === undefined) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
set(output, k, obj[k]); | ||
} | ||
@@ -574,3 +579,3 @@ } | ||
for (const k in obj) { | ||
output[k] = obj[k]; | ||
set(output, k, obj[k]); | ||
} | ||
@@ -580,3 +585,3 @@ for (let m = 0; m < i; m++) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
set(output, k, obj[k]); | ||
} | ||
@@ -586,3 +591,3 @@ } | ||
} | ||
output[key] = r.value; | ||
set(output, key, r.value); | ||
} | ||
@@ -633,5 +638,2 @@ } | ||
} | ||
toTerminals(func) { | ||
func(this); | ||
} | ||
func(arr, mode) { | ||
@@ -638,0 +640,0 @@ if (!Array.isArray(arr)) { |
{ | ||
"name": "@badrap/valita", | ||
"version": "0.2.8", | ||
"version": "0.2.9", | ||
"description": "A validation & parsing library for TypeScript", | ||
@@ -5,0 +5,0 @@ "main": "./dist/cjs/index.js", |
@@ -5,4 +5,2 @@ # @badrap/valita [](https://github.com/badrap/valita/actions?query=workflow%3Atests) [](https://www.npmjs.com/package/@badrap/valita) | ||
We also pay special attention for providing descriptive validation error messages: | ||
```ts | ||
@@ -18,2 +16,17 @@ const vehicle = v.union( | ||
> [!NOTE] | ||
> While this package is still evolving, we're currently not accepting any new feature requests or suggestions. Please use the issue tracker for bug reports and security concerns, which we highly value and welcome. Thank you for your understanding ❤️ | ||
## Goals and Non-Goals | ||
### Goals | ||
1. **Input Validation & Parsing**: The fundamental goal of the library is to ensure that incoming data, which might not be from a trusted source, aligns with the predetermined format. | ||
2. **Minimalism**: Deliver a streamlined and concentrated library that offers just the essentials. | ||
3. **Extensibility**: Allow users to create their own validators and parsers that cater to specific validation scenarios. | ||
### Non-Goals: | ||
1. **Data Definition**: The library is designed to validate and parse input data as it enters the program, rather than serving as an exhaustive tool for defining all types within the program after obtaining input. | ||
2. **Extensive Built-In Formats**: The library does not prioritize having a large array of built-in validation formats out of the box. | ||
3. **Asynchronous Parsing**: Asynchronous operations are outside the scope for this library. | ||
## Installation | ||
@@ -306,2 +319,38 @@ | ||
### Type composition tips | ||
You can get access to the property validator with the `.shape` property. | ||
```js | ||
const userSchema = v.object({ | ||
name: v.string(), | ||
}); | ||
const name = userSchema.shape.name.parse("me"); | ||
``` | ||
However, if you use `userSchema.assert(...)`, then you lose the ability to validate properties. This is intentional because `.assert(...)` can change the output type, but `.shape` cannot reflect this at runtime. For example: | ||
```ts | ||
function isAdmin(x: unknown): x is { name: "admin"; weirdExtraKey: boolean } { | ||
return true; | ||
} | ||
const schema = userSchema.assert(isAdmin); | ||
// @ts-expect-error Property 'shape' does not exist on type 'Type<{ name: "admin"; weirdExtraKey: boolean; }> | ||
schema.shape === undefined; | ||
``` | ||
But that's not a problem! Just declare your property validator outside the object schema. | ||
```JS | ||
function isAdmin(x: unknown): boolean { | ||
return true; | ||
} | ||
const adminNameSchema = v.string().assert(isAdmin, 'error_message'); | ||
const useSchema = v.object({ | ||
name: adminNameSchema | ||
}); | ||
const name = adminNameSchema.parse('admin'); | ||
``` | ||
### Recursive Types | ||
@@ -308,0 +357,0 @@ |
@@ -63,12 +63,13 @@ // This is magic that turns object intersections to nicer-looking types. | ||
function cloneIssueWithPath(tree: IssueLeaf, path: Key[]): Issue { | ||
switch (tree.code) { | ||
const code = tree.code; | ||
switch (code) { | ||
case "invalid_type": | ||
return { code: "invalid_type", path, expected: tree.expected }; | ||
return { code, path, expected: tree.expected }; | ||
case "invalid_literal": | ||
return { code: "invalid_literal", path, expected: tree.expected }; | ||
return { code, path, expected: tree.expected }; | ||
case "missing_value": | ||
return { code: "missing_value", path }; | ||
return { code, path }; | ||
case "invalid_length": | ||
return { | ||
code: "invalid_length", | ||
code, | ||
path, | ||
@@ -79,7 +80,7 @@ minLength: tree.minLength, | ||
case "unrecognized_keys": | ||
return { code: "unrecognized_keys", path, keys: tree.keys }; | ||
return { code, path, keys: tree.keys }; | ||
case "invalid_union": | ||
return { code: "invalid_union", path, tree: tree.tree }; | ||
return { code, path, tree: tree.tree }; | ||
default: | ||
return { code: "custom_error", path, error: tree.error }; | ||
return { code, path, error: tree.error }; | ||
} | ||
@@ -569,14 +570,2 @@ } | ||
// When an object matcher needs to create a copied version of the input, | ||
// it initializes the new objects with Object.create(protoless). | ||
// | ||
// Using Object.create(protoless) instead of just {} makes setting | ||
// "__proto__" key safe. Previously we set object properties with a helper | ||
// function that special-cased "__proto__". Now we can just do | ||
// `output[key] = value` directly. | ||
// | ||
// Using Object.create(protoless) instead of Object.create(null) seems to | ||
// be faster on V8 at the time of writing this (2023-08-07). | ||
const protoless = Object.freeze(Object.create(null)); | ||
function createObjectMatcher( | ||
@@ -611,3 +600,3 @@ shape: ObjectShape, | ||
if (totalCount === 0 && rest === unknownSingleton) { | ||
if (totalCount === 0 && rest?.name === "unknown") { | ||
// A fast path for record(unknown()) | ||
@@ -643,2 +632,19 @@ return function (obj, _) { | ||
function set( | ||
obj: Record<string, unknown>, | ||
key: string, | ||
value: unknown, | ||
): void { | ||
if (key === "__proto__") { | ||
Object.defineProperty(obj, key, { | ||
value, | ||
writable: true, | ||
enumerable: true, | ||
configurable: true, | ||
}); | ||
} else { | ||
obj[key] = value; | ||
} | ||
} | ||
return function (obj, mode) { | ||
@@ -680,3 +686,3 @@ if (!isObject(obj)) { | ||
) { | ||
output = Object.create(protoless); | ||
output = {}; | ||
copied = true; | ||
@@ -686,3 +692,3 @@ for (let m = 0; m < totalCount; m++) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
set(output, k, obj[k]); | ||
} | ||
@@ -696,3 +702,3 @@ } | ||
if (copied && issues === undefined) { | ||
output[key] = value; | ||
set(output, key, value); | ||
} | ||
@@ -703,3 +709,3 @@ } else if (!r.ok) { | ||
if (!copied) { | ||
output = Object.create(protoless); | ||
output = {}; | ||
copied = true; | ||
@@ -710,3 +716,3 @@ if (rest === undefined) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
set(output, k, obj[k]); | ||
} | ||
@@ -716,7 +722,8 @@ } | ||
for (const k in obj) { | ||
output[k] = obj[k]; | ||
if (k === "__proto__") output.__proto__ = null; | ||
set(output, k, obj[k]); | ||
} | ||
} | ||
} | ||
output[key] = r.value; | ||
set(output, key, r.value); | ||
} | ||
@@ -743,3 +750,3 @@ } | ||
if (copied && issues === undefined && value !== Nothing) { | ||
output[key] = value; | ||
set(output, key, value); | ||
} | ||
@@ -750,3 +757,3 @@ } else if (!r.ok) { | ||
if (!copied) { | ||
output = Object.create(protoless); | ||
output = {}; | ||
copied = true; | ||
@@ -757,3 +764,3 @@ if (rest === undefined) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
set(output, k, obj[k]); | ||
} | ||
@@ -763,3 +770,3 @@ } | ||
for (const k in obj) { | ||
output[k] = obj[k]; | ||
set(output, k, obj[k]); | ||
} | ||
@@ -769,3 +776,3 @@ for (let m = 0; m < i; m++) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
set(output, k, obj[k]); | ||
} | ||
@@ -775,3 +782,3 @@ } | ||
} | ||
output[key] = r.value; | ||
set(output, key, r.value); | ||
} | ||
@@ -848,6 +855,2 @@ } | ||
toTerminals(func: (t: TerminalType) => void): void { | ||
func(this); | ||
} | ||
func(arr: unknown, mode: FuncMode): RawResult<ArrayOutput<Head, Rest>> { | ||
@@ -854,0 +857,0 @@ if (!Array.isArray(arr)) { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
368576
0.22%6474
0.15%393
14.24%