@badrap/valita
Advanced tools
Comparing version 0.2.5 to 0.2.7
@@ -11,32 +11,33 @@ type PrettyIntersection<V> = Extract<{ | ||
}; | ||
type IssueNode<PathType = Key | undefined> = Readonly<{ | ||
type IssueNode = Readonly<{ | ||
ok: false; | ||
code: "custom_error"; | ||
error: CustomError; | ||
} | { | ||
ok: false; | ||
code: "invalid_type"; | ||
path: PathType; | ||
expected: InputType[]; | ||
} | { | ||
ok: false; | ||
code: "missing_value"; | ||
path: PathType; | ||
} | { | ||
ok: false; | ||
code: "invalid_literal"; | ||
path: PathType; | ||
expected: Literal[]; | ||
} | { | ||
code: "invalid_length"; | ||
path: PathType; | ||
minLength: number; | ||
maxLength: number; | ||
} | { | ||
ok: false; | ||
code: "unrecognized_keys"; | ||
path: PathType; | ||
keys: Key[]; | ||
} | { | ||
ok: false; | ||
code: "invalid_union"; | ||
path: PathType; | ||
tree: IssueTree; | ||
} | { | ||
code: "custom_error"; | ||
path: PathType; | ||
error: CustomError; | ||
ok: false; | ||
code: "invalid_length"; | ||
minLength: number; | ||
maxLength: number; | ||
}>; | ||
type IssueTree = Readonly<{ | ||
ok: false; | ||
code: "prepend"; | ||
@@ -46,2 +47,3 @@ key: Key; | ||
}> | Readonly<{ | ||
ok: false; | ||
code: "join"; | ||
@@ -51,3 +53,31 @@ left: IssueTree; | ||
}> | IssueNode; | ||
type Issue = IssueNode<Key[]>; | ||
type Issue = Readonly<{ | ||
code: "custom_error"; | ||
path: Key[]; | ||
error: CustomError; | ||
} | { | ||
code: "invalid_type"; | ||
path: Key[]; | ||
expected: InputType[]; | ||
} | { | ||
code: "missing_value"; | ||
path: Key[]; | ||
} | { | ||
code: "invalid_literal"; | ||
path: Key[]; | ||
expected: Literal[]; | ||
} | { | ||
code: "unrecognized_keys"; | ||
path: Key[]; | ||
keys: Key[]; | ||
} | { | ||
code: "invalid_union"; | ||
path: Key[]; | ||
tree: IssueTree; | ||
} | { | ||
code: "invalid_length"; | ||
path: Key[]; | ||
minLength: number; | ||
maxLength: number; | ||
}>; | ||
export declare class ValitaError extends Error { | ||
@@ -78,6 +108,3 @@ private readonly issueTree; | ||
export type ValitaResult<V> = Ok<V> | Err; | ||
type RawResult<T> = true | Readonly<{ | ||
code: "ok"; | ||
value: T; | ||
}> | IssueTree; | ||
type RawResult<T> = undefined | Ok<T> | IssueTree; | ||
declare const enum FuncMode { | ||
@@ -129,3 +156,2 @@ PASS = 0, | ||
}> : unknown)>; | ||
type Obj = Record<string, unknown>; | ||
declare class ObjectType<Shape extends ObjectShape = ObjectShape, Rest extends AbstractType | undefined = AbstractType | undefined> extends Type<ObjectOutput<Shape, Rest>> { | ||
@@ -142,3 +168,3 @@ readonly shape: Shape; | ||
check(func: (v: ObjectOutput<Shape, Rest>) => boolean, error?: CustomError): ObjectType<Shape, Rest>; | ||
func(obj: Obj, mode: FuncMode): RawResult<ObjectOutput<Shape, Rest>>; | ||
func(v: unknown, mode: FuncMode): RawResult<ObjectOutput<Shape, Rest>>; | ||
rest<R extends Type>(restType: R): ObjectType<Shape, R>; | ||
@@ -145,0 +171,0 @@ extend<S extends ObjectShape>(shape: S): ObjectType<Omit<Shape, keyof S> & S, Rest>; |
@@ -5,6 +5,6 @@ "use strict"; | ||
function joinIssues(left, right) { | ||
return left ? { code: "join", left, right } : right; | ||
return left ? { ok: false, code: "join", left, right } : right; | ||
} | ||
function prependPath(key, tree) { | ||
return { code: "prepend", key, tree }; | ||
return { ok: false, code: "prepend", key, tree }; | ||
} | ||
@@ -44,5 +44,2 @@ function cloneIssueWithPath(tree, path) { | ||
else { | ||
if (tree.path !== undefined) { | ||
path.push(tree.path); | ||
} | ||
if (tree.code === "custom_error" && | ||
@@ -79,5 +76,2 @@ typeof tree.error === "object" && | ||
else { | ||
if (tree.path !== undefined) { | ||
path.push(tree.path); | ||
} | ||
if (tree.code === "custom_error" && | ||
@@ -201,3 +195,3 @@ typeof tree.error === "object" && | ||
function err(error) { | ||
return new Err({ code: "custom_error", path: undefined, error }); | ||
return new Err({ ok: false, code: "custom_error", error }); | ||
} | ||
@@ -208,15 +202,2 @@ exports.err = err; | ||
} | ||
function safeSet(obj, key, value) { | ||
if (key === "__proto__") { | ||
Object.defineProperty(obj, key, { | ||
value, | ||
writable: true, | ||
enumerable: true, | ||
configurable: true, | ||
}); | ||
} | ||
else { | ||
obj[key] = value; | ||
} | ||
} | ||
const Nothing = Symbol.for("valita.Nothing"); | ||
@@ -228,14 +209,14 @@ class AbstractType { | ||
default(defaultValue) { | ||
const defaultResult = { code: "ok", value: defaultValue }; | ||
const defaultResult = ok(defaultValue); | ||
return new TransformType(this.optional(), (v) => { | ||
return v === undefined ? defaultResult : true; | ||
return v === undefined ? defaultResult : undefined; | ||
}); | ||
} | ||
assert(func, error) { | ||
const err = { code: "custom_error", path: undefined, error }; | ||
return new TransformType(this, (v) => (func(v) ? true : err)); | ||
const err = { ok: false, code: "custom_error", error }; | ||
return new TransformType(this, (v) => func(v) ? undefined : err); | ||
} | ||
map(func) { | ||
return new TransformType(this, (v) => ({ | ||
code: "ok", | ||
ok: true, | ||
value: func(v), | ||
@@ -247,8 +228,3 @@ })); | ||
const r = func(v); | ||
if (r.ok) { | ||
return { code: "ok", value: r.value }; | ||
} | ||
else { | ||
return r.issueTree; | ||
} | ||
return r.ok ? r : r.issueTree; | ||
}); | ||
@@ -266,13 +242,15 @@ } | ||
let mode = 1 /* FuncMode.STRICT */; | ||
if (options && options.mode === "passthrough") { | ||
mode = 0 /* FuncMode.PASS */; | ||
if (options !== undefined) { | ||
if (options.mode === "passthrough") { | ||
mode = 0 /* FuncMode.PASS */; | ||
} | ||
else if (options.mode === "strip") { | ||
mode = 2 /* FuncMode.STRIP */; | ||
} | ||
} | ||
else if (options && options.mode === "strip") { | ||
mode = 2 /* FuncMode.STRIP */; | ||
} | ||
const r = this.func(v, mode); | ||
if (r === true) { | ||
if (r === undefined) { | ||
return { ok: true, value: v }; | ||
} | ||
else if (r.code === "ok") { | ||
else if (r.ok) { | ||
return { ok: true, value: r.value }; | ||
@@ -286,13 +264,15 @@ } | ||
let mode = 1 /* FuncMode.STRICT */; | ||
if (options && options.mode === "passthrough") { | ||
mode = 0 /* FuncMode.PASS */; | ||
if (options !== undefined) { | ||
if (options.mode === "passthrough") { | ||
mode = 0 /* FuncMode.PASS */; | ||
} | ||
else if (options.mode === "strip") { | ||
mode = 2 /* FuncMode.STRIP */; | ||
} | ||
} | ||
else if (options && options.mode === "strip") { | ||
mode = 2 /* FuncMode.STRIP */; | ||
} | ||
const r = this.func(v, mode); | ||
if (r === true) { | ||
if (r === undefined) { | ||
return v; | ||
} | ||
else if (r.code === "ok") { | ||
else if (r.ok) { | ||
return r.value; | ||
@@ -312,3 +292,5 @@ } | ||
func(v, mode) { | ||
return v === undefined || v === Nothing ? true : this.type.func(v, mode); | ||
return v === undefined || v === Nothing | ||
? undefined | ||
: this.type.func(v, mode); | ||
} | ||
@@ -324,38 +306,2 @@ toTerminals(func) { | ||
} | ||
function prependIssue(issue, result) { | ||
return result === true || result.code === "ok" | ||
? issue | ||
: joinIssues(issue, result); | ||
} | ||
function assignEnumerable(to, from) { | ||
for (const key in from) { | ||
safeSet(to, key, from[key]); | ||
} | ||
return to; | ||
} | ||
function addResult(objResult, obj, key, value, keyResult, assign) { | ||
if (keyResult === true) { | ||
if (objResult !== true && objResult.code === "ok" && value !== Nothing) { | ||
safeSet(objResult.value, key, value); | ||
} | ||
return objResult; | ||
} | ||
else if (keyResult.code === "ok") { | ||
if (objResult === true) { | ||
const copy = assign({}, obj); | ||
safeSet(copy, key, keyResult.value); | ||
return { code: "ok", value: copy }; | ||
} | ||
else if (objResult.code === "ok") { | ||
safeSet(objResult.value, key, keyResult.value); | ||
return objResult; | ||
} | ||
else { | ||
return objResult; | ||
} | ||
} | ||
else { | ||
return prependIssue(prependPath(key, keyResult), objResult); | ||
} | ||
} | ||
// Set a bit in position `index` to one and return the updated bitset. | ||
@@ -398,3 +344,3 @@ // This function may or may not mutate `bits` in-place. | ||
var _a; | ||
const issue = { code: "custom_error", path: undefined, error }; | ||
const issue = { ok: false, code: "custom_error", error }; | ||
return new ObjectType(this.shape, this.restType, [ | ||
@@ -408,3 +354,3 @@ ...((_a = this.checks) !== null && _a !== void 0 ? _a : []), | ||
} | ||
func(obj, mode) { | ||
func(v, mode) { | ||
let func = this._func; | ||
@@ -415,3 +361,3 @@ if (func === undefined) { | ||
} | ||
return func(obj, mode); | ||
return func(v, mode); | ||
} | ||
@@ -448,3 +394,14 @@ rest(restType) { | ||
} | ||
function createObjectMatcher(shape, restType, checks) { | ||
// 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) { | ||
const requiredKeys = []; | ||
@@ -464,7 +421,27 @@ const optionalKeys = []; | ||
} | ||
const requiredCount = requiredKeys.length | 0; | ||
const optionalCount = optionalKeys.length | 0; | ||
const totalCount = (requiredCount + optionalCount) | 0; | ||
const keys = [...requiredKeys, ...optionalKeys]; | ||
const totalCount = keys.length; | ||
const invalidType = { | ||
ok: false, | ||
code: "invalid_type", | ||
expected: ["object"], | ||
}; | ||
if (totalCount === 0 && rest === unknownSingleton) { | ||
// A fast path for record(unknown()) | ||
return function (obj, _) { | ||
if (!isObject(obj)) { | ||
return invalidType; | ||
} | ||
if (checks !== undefined) { | ||
for (let i = 0; i < checks.length; i++) { | ||
if (!checks[i].func(obj)) { | ||
return checks[i].issue; | ||
} | ||
} | ||
} | ||
return undefined; | ||
}; | ||
} | ||
const types = keys.map((key) => shape[key]); | ||
const requiredCount = requiredKeys.length; | ||
const invertedIndexes = Object.create(null); | ||
@@ -474,129 +451,143 @@ keys.forEach((key, index) => { | ||
}); | ||
const invalidType = { | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["object"], | ||
}; | ||
const missingValues = requiredKeys.map((key) => ({ | ||
const missingValues = requiredKeys.map((key) => prependPath(key, { | ||
ok: false, | ||
code: "missing_value", | ||
path: key, | ||
})); | ||
function assignKnown(to, from) { | ||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i]; | ||
const value = from[key]; | ||
if (i < requiredCount || value !== undefined || key in from) { | ||
safeSet(to, key, value); | ||
} | ||
return function (obj, mode) { | ||
if (!isObject(obj)) { | ||
return invalidType; | ||
} | ||
return to; | ||
} | ||
function assignAll(to, from) { | ||
return assignKnown(assignEnumerable(to, from), from); | ||
} | ||
function checkRemainingKeys(initialResult, obj, mode, bits, assign) { | ||
let result = initialResult; | ||
for (let i = 0; i < totalCount; i++) { | ||
if (!getBit(bits, i)) { | ||
const key = keys[i]; | ||
const value = key in obj ? obj[key] : Nothing; | ||
if (i < requiredCount && value === Nothing) { | ||
result = prependIssue(missingValues[i], result); | ||
let copied = false; | ||
let output = obj; | ||
let issues; | ||
let unrecognized = undefined; | ||
let seenBits = 0; | ||
let seenCount = 0; | ||
if (mode !== 0 /* FuncMode.PASS */ || rest !== undefined) { | ||
for (const key in obj) { | ||
const value = obj[key]; | ||
const index = ~invertedIndexes[key]; | ||
let r; | ||
if (index >= 0) { | ||
seenCount++; | ||
seenBits = setBit(seenBits, index); | ||
r = types[index].func(value, mode); | ||
} | ||
else if (rest !== undefined) { | ||
r = rest.func(value, mode); | ||
} | ||
else { | ||
result = addResult(result, obj, key, value, types[i].func(value, mode), assign); | ||
if (mode === 1 /* FuncMode.STRICT */) { | ||
if (unrecognized === undefined) { | ||
unrecognized = [key]; | ||
} | ||
else { | ||
unrecognized.push(key); | ||
} | ||
} | ||
else if (mode === 2 /* FuncMode.STRIP */ && | ||
issues === undefined && | ||
!copied) { | ||
output = Object.create(protoless); | ||
copied = true; | ||
for (let m = 0; m < totalCount; m++) { | ||
if (getBit(seenBits, m)) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
} | ||
} | ||
} | ||
continue; | ||
} | ||
if (r === undefined) { | ||
if (copied && issues === undefined) { | ||
output[key] = value; | ||
} | ||
} | ||
else if (!r.ok) { | ||
issues = joinIssues(issues, prependPath(key, r)); | ||
} | ||
else if (issues === undefined) { | ||
if (!copied) { | ||
output = Object.create(protoless); | ||
copied = true; | ||
if (rest === undefined) { | ||
for (let m = 0; m < totalCount; m++) { | ||
if (m !== index && getBit(seenBits, m)) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
} | ||
} | ||
} | ||
else { | ||
for (const k in obj) { | ||
output[k] = obj[k]; | ||
} | ||
} | ||
} | ||
output[key] = r.value; | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
function pass(obj, mode) { | ||
let result = true; | ||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i]; | ||
let value = obj[key]; | ||
if (value === undefined && !(key in obj)) { | ||
if (i < requiredCount) { | ||
result = prependIssue(missingValues[i], result); | ||
if (seenCount < totalCount) { | ||
for (let i = 0; i < totalCount; i++) { | ||
if (getBit(seenBits, i)) { | ||
continue; | ||
} | ||
value = Nothing; | ||
const key = keys[i]; | ||
let value = obj[key]; | ||
if (value === undefined && !(key in obj)) { | ||
if (i < requiredCount) { | ||
issues = joinIssues(issues, missingValues[i]); | ||
continue; | ||
} | ||
value = Nothing; | ||
} | ||
const r = types[i].func(value, mode); | ||
if (r === undefined) { | ||
if (copied && issues === undefined && value !== Nothing) { | ||
output[key] = value; | ||
} | ||
} | ||
else if (!r.ok) { | ||
issues = joinIssues(issues, prependPath(key, r)); | ||
} | ||
else if (issues === undefined) { | ||
if (!copied) { | ||
output = Object.create(protoless); | ||
copied = true; | ||
if (rest === undefined) { | ||
for (let m = 0; m < totalCount; m++) { | ||
if (m < i || getBit(seenBits, m)) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
} | ||
} | ||
} | ||
else { | ||
for (const k in obj) { | ||
output[k] = obj[k]; | ||
} | ||
for (let m = 0; m < i; m++) { | ||
if (!getBit(seenBits, m)) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
} | ||
} | ||
} | ||
} | ||
output[key] = r.value; | ||
} | ||
} | ||
result = addResult(result, obj, key, value, types[i].func(value, mode), assignKnown); | ||
} | ||
return result; | ||
} | ||
function strict(obj, mode) { | ||
let result = true; | ||
let unrecognized = undefined; | ||
let seenBits = 0; | ||
let seenCount = 0; | ||
for (const key in obj) { | ||
const value = obj[key]; | ||
const index = ~invertedIndexes[key]; | ||
if (index >= 0) { | ||
seenCount++; | ||
seenBits = setBit(seenBits, index); | ||
result = addResult(result, obj, key, value, types[index].func(value, mode), assignKnown); | ||
} | ||
else if (mode === 2 /* FuncMode.STRIP */) { | ||
result = | ||
result === true | ||
? { code: "ok", value: assignKnown({}, obj) } | ||
: result; | ||
} | ||
else if (unrecognized === undefined) { | ||
unrecognized = [key]; | ||
} | ||
else { | ||
unrecognized.push(key); | ||
} | ||
} | ||
if (seenCount < totalCount) { | ||
result = checkRemainingKeys(result, obj, mode, seenBits, assignKnown); | ||
} | ||
return unrecognized === undefined | ||
? result | ||
: prependIssue({ | ||
if (unrecognized !== undefined) { | ||
issues = joinIssues(issues, { | ||
ok: false, | ||
code: "unrecognized_keys", | ||
path: undefined, | ||
keys: unrecognized, | ||
}, result); | ||
} | ||
function withRest(rest, obj, mode) { | ||
if (totalCount === 0) { | ||
if (rest.name === "unknown") { | ||
return true; | ||
} | ||
let result = true; | ||
for (const key in obj) { | ||
const value = obj[key]; | ||
result = addResult(result, obj, key, value, rest.func(value, mode), assignEnumerable); | ||
} | ||
}); | ||
} | ||
let result = true; | ||
let seenBits = 0; | ||
let seenCount = 0; | ||
for (const key in obj) { | ||
const value = obj[key]; | ||
const index = ~invertedIndexes[key]; | ||
if (index >= 0) { | ||
seenCount++; | ||
seenBits = setBit(seenBits, index); | ||
result = addResult(result, obj, key, value, types[index].func(value, mode), assignEnumerable); | ||
} | ||
else { | ||
result = addResult(result, obj, key, value, rest.func(value, mode), assignEnumerable); | ||
} | ||
} | ||
if (seenCount < totalCount) { | ||
result = checkRemainingKeys(result, obj, mode, seenBits, assignAll); | ||
} | ||
return result; | ||
} | ||
function runChecks(obj, result) { | ||
if ((result === true || result.code === "ok") && checks) { | ||
const value = result === true ? obj : result.value; | ||
if (issues === undefined && checks !== undefined) { | ||
for (let i = 0; i < checks.length; i++) { | ||
if (!checks[i].func(value)) { | ||
if (!checks[i].func(output)) { | ||
return checks[i].issue; | ||
@@ -606,19 +597,9 @@ } | ||
} | ||
return result; | ||
} | ||
function func(obj, mode) { | ||
if (!isObject(obj)) { | ||
return invalidType; | ||
if (issues === undefined && copied) { | ||
return { ok: true, value: output }; | ||
} | ||
if (restType) { | ||
return runChecks(obj, withRest(restType, obj, mode)); | ||
} | ||
else if (mode === 0 /* FuncMode.PASS */) { | ||
return runChecks(obj, pass(obj, mode)); | ||
} | ||
else { | ||
return runChecks(obj, strict(obj, mode)); | ||
return issues; | ||
} | ||
} | ||
return func; | ||
}; | ||
} | ||
@@ -634,9 +615,9 @@ class ArrayType extends Type { | ||
this.invalidType = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["array"], | ||
}; | ||
this.invalidLength = { | ||
ok: false, | ||
code: "invalid_length", | ||
path: undefined, | ||
minLength: this.minLength, | ||
@@ -664,4 +645,4 @@ maxLength: this.maxLength, | ||
const r = type.func(arr[i], mode); | ||
if (r !== true) { | ||
if (r.code === "ok") { | ||
if (r !== undefined) { | ||
if (r.ok) { | ||
if (output === arr) { | ||
@@ -681,6 +662,6 @@ output = arr.slice(); | ||
else if (arr === output) { | ||
return true; | ||
return undefined; | ||
} | ||
else { | ||
return { code: "ok", value: output }; | ||
return { ok: true, value: output }; | ||
} | ||
@@ -794,14 +775,14 @@ } | ||
} | ||
const missingValue = { code: "missing_value", path: key }; | ||
const issue = types.size === 0 | ||
const missingValue = prependPath(key, { ok: false, code: "missing_value" }); | ||
const issue = prependPath(key, types.size === 0 | ||
? { | ||
ok: false, | ||
code: "invalid_literal", | ||
path: key, | ||
expected: Array.from(literals.keys()), | ||
} | ||
: { | ||
ok: false, | ||
code: "invalid_type", | ||
path: key, | ||
expected: expectedTypes, | ||
}; | ||
}); | ||
const litMap = literals.size > 0 ? new Map() : undefined; | ||
@@ -849,9 +830,9 @@ for (const [literal, options] of literals) { | ||
? { | ||
ok: false, | ||
code: "invalid_literal", | ||
path: undefined, | ||
expected: Array.from(literals.keys()), | ||
} | ||
: { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: expectedTypes, | ||
@@ -880,3 +861,3 @@ }; | ||
const r = options[i].func(value, mode); | ||
if (r === true || r.code === "ok") { | ||
if (r === undefined || r.ok) { | ||
return r; | ||
@@ -888,3 +869,3 @@ } | ||
if (count > 1) { | ||
return { code: "invalid_union", path: undefined, tree: issueTree }; | ||
return { ok: false, code: "invalid_union", tree: issueTree }; | ||
} | ||
@@ -934,3 +915,3 @@ return issueTree; | ||
this.name = "transform"; | ||
this.undef = { code: "ok", value: undefined }; | ||
this.undef = ok(undefined); | ||
this.transformChain = undefined; | ||
@@ -955,7 +936,7 @@ this.transformRoot = undefined; | ||
let result = this.transformRoot.func(v, mode); | ||
if (result !== true && result.code !== "ok") { | ||
if (result !== undefined && !result.ok) { | ||
return result; | ||
} | ||
let current; | ||
if (result !== true) { | ||
if (result !== undefined) { | ||
current = result.value; | ||
@@ -972,4 +953,4 @@ } | ||
const r = chain[i](current, mode); | ||
if (r !== true) { | ||
if (r.code !== "ok") { | ||
if (r !== undefined) { | ||
if (!r.ok) { | ||
return r; | ||
@@ -1021,4 +1002,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: [], | ||
@@ -1042,3 +1023,3 @@ }; | ||
func(_, __) { | ||
return true; | ||
return undefined; | ||
} | ||
@@ -1056,4 +1037,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["undefined"], | ||
@@ -1063,3 +1044,3 @@ }; | ||
func(v, _) { | ||
return v === undefined ? true : this.issue; | ||
return v === undefined ? undefined : this.issue; | ||
} | ||
@@ -1077,4 +1058,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["null"], | ||
@@ -1084,3 +1065,3 @@ }; | ||
func(v, _) { | ||
return v === null ? true : this.issue; | ||
return v === null ? undefined : this.issue; | ||
} | ||
@@ -1098,4 +1079,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["number"], | ||
@@ -1105,3 +1086,3 @@ }; | ||
func(v, _) { | ||
return typeof v === "number" ? true : this.issue; | ||
return typeof v === "number" ? undefined : this.issue; | ||
} | ||
@@ -1119,4 +1100,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["bigint"], | ||
@@ -1126,3 +1107,3 @@ }; | ||
func(v, _) { | ||
return typeof v === "bigint" ? true : this.issue; | ||
return typeof v === "bigint" ? undefined : this.issue; | ||
} | ||
@@ -1140,4 +1121,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["string"], | ||
@@ -1147,3 +1128,3 @@ }; | ||
func(v, _) { | ||
return typeof v === "string" ? true : this.issue; | ||
return typeof v === "string" ? undefined : this.issue; | ||
} | ||
@@ -1161,4 +1142,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["boolean"], | ||
@@ -1168,3 +1149,3 @@ }; | ||
func(v, _) { | ||
return typeof v === "boolean" ? true : this.issue; | ||
return typeof v === "boolean" ? undefined : this.issue; | ||
} | ||
@@ -1183,4 +1164,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_literal", | ||
path: undefined, | ||
expected: [value], | ||
@@ -1190,3 +1171,3 @@ }; | ||
func(v, _) { | ||
return v === this.value ? true : this.issue; | ||
return v === this.value ? undefined : this.issue; | ||
} | ||
@@ -1193,0 +1174,0 @@ } |
@@ -11,32 +11,33 @@ type PrettyIntersection<V> = Extract<{ | ||
}; | ||
type IssueNode<PathType = Key | undefined> = Readonly<{ | ||
type IssueNode = Readonly<{ | ||
ok: false; | ||
code: "custom_error"; | ||
error: CustomError; | ||
} | { | ||
ok: false; | ||
code: "invalid_type"; | ||
path: PathType; | ||
expected: InputType[]; | ||
} | { | ||
ok: false; | ||
code: "missing_value"; | ||
path: PathType; | ||
} | { | ||
ok: false; | ||
code: "invalid_literal"; | ||
path: PathType; | ||
expected: Literal[]; | ||
} | { | ||
code: "invalid_length"; | ||
path: PathType; | ||
minLength: number; | ||
maxLength: number; | ||
} | { | ||
ok: false; | ||
code: "unrecognized_keys"; | ||
path: PathType; | ||
keys: Key[]; | ||
} | { | ||
ok: false; | ||
code: "invalid_union"; | ||
path: PathType; | ||
tree: IssueTree; | ||
} | { | ||
code: "custom_error"; | ||
path: PathType; | ||
error: CustomError; | ||
ok: false; | ||
code: "invalid_length"; | ||
minLength: number; | ||
maxLength: number; | ||
}>; | ||
type IssueTree = Readonly<{ | ||
ok: false; | ||
code: "prepend"; | ||
@@ -46,2 +47,3 @@ key: Key; | ||
}> | Readonly<{ | ||
ok: false; | ||
code: "join"; | ||
@@ -51,3 +53,31 @@ left: IssueTree; | ||
}> | IssueNode; | ||
type Issue = IssueNode<Key[]>; | ||
type Issue = Readonly<{ | ||
code: "custom_error"; | ||
path: Key[]; | ||
error: CustomError; | ||
} | { | ||
code: "invalid_type"; | ||
path: Key[]; | ||
expected: InputType[]; | ||
} | { | ||
code: "missing_value"; | ||
path: Key[]; | ||
} | { | ||
code: "invalid_literal"; | ||
path: Key[]; | ||
expected: Literal[]; | ||
} | { | ||
code: "unrecognized_keys"; | ||
path: Key[]; | ||
keys: Key[]; | ||
} | { | ||
code: "invalid_union"; | ||
path: Key[]; | ||
tree: IssueTree; | ||
} | { | ||
code: "invalid_length"; | ||
path: Key[]; | ||
minLength: number; | ||
maxLength: number; | ||
}>; | ||
export declare class ValitaError extends Error { | ||
@@ -78,6 +108,3 @@ private readonly issueTree; | ||
export type ValitaResult<V> = Ok<V> | Err; | ||
type RawResult<T> = true | Readonly<{ | ||
code: "ok"; | ||
value: T; | ||
}> | IssueTree; | ||
type RawResult<T> = undefined | Ok<T> | IssueTree; | ||
declare const enum FuncMode { | ||
@@ -129,3 +156,2 @@ PASS = 0, | ||
}> : unknown)>; | ||
type Obj = Record<string, unknown>; | ||
declare class ObjectType<Shape extends ObjectShape = ObjectShape, Rest extends AbstractType | undefined = AbstractType | undefined> extends Type<ObjectOutput<Shape, Rest>> { | ||
@@ -142,3 +168,3 @@ readonly shape: Shape; | ||
check(func: (v: ObjectOutput<Shape, Rest>) => boolean, error?: CustomError): ObjectType<Shape, Rest>; | ||
func(obj: Obj, mode: FuncMode): RawResult<ObjectOutput<Shape, Rest>>; | ||
func(v: unknown, mode: FuncMode): RawResult<ObjectOutput<Shape, Rest>>; | ||
rest<R extends Type>(restType: R): ObjectType<Shape, R>; | ||
@@ -145,0 +171,0 @@ extend<S extends ObjectShape>(shape: S): ObjectType<Omit<Shape, keyof S> & S, Rest>; |
@@ -5,6 +5,6 @@ "use strict"; | ||
function joinIssues(left, right) { | ||
return left ? { code: "join", left, right } : right; | ||
return left ? { ok: false, code: "join", left, right } : right; | ||
} | ||
function prependPath(key, tree) { | ||
return { code: "prepend", key, tree }; | ||
return { ok: false, code: "prepend", key, tree }; | ||
} | ||
@@ -44,5 +44,2 @@ function cloneIssueWithPath(tree, path) { | ||
else { | ||
if (tree.path !== undefined) { | ||
path.push(tree.path); | ||
} | ||
if (tree.code === "custom_error" && | ||
@@ -79,5 +76,2 @@ typeof tree.error === "object" && | ||
else { | ||
if (tree.path !== undefined) { | ||
path.push(tree.path); | ||
} | ||
if (tree.code === "custom_error" && | ||
@@ -201,3 +195,3 @@ typeof tree.error === "object" && | ||
function err(error) { | ||
return new Err({ code: "custom_error", path: undefined, error }); | ||
return new Err({ ok: false, code: "custom_error", error }); | ||
} | ||
@@ -208,15 +202,2 @@ exports.err = err; | ||
} | ||
function safeSet(obj, key, value) { | ||
if (key === "__proto__") { | ||
Object.defineProperty(obj, key, { | ||
value, | ||
writable: true, | ||
enumerable: true, | ||
configurable: true, | ||
}); | ||
} | ||
else { | ||
obj[key] = value; | ||
} | ||
} | ||
const Nothing = Symbol.for("valita.Nothing"); | ||
@@ -228,14 +209,14 @@ class AbstractType { | ||
default(defaultValue) { | ||
const defaultResult = { code: "ok", value: defaultValue }; | ||
const defaultResult = ok(defaultValue); | ||
return new TransformType(this.optional(), (v) => { | ||
return v === undefined ? defaultResult : true; | ||
return v === undefined ? defaultResult : undefined; | ||
}); | ||
} | ||
assert(func, error) { | ||
const err = { code: "custom_error", path: undefined, error }; | ||
return new TransformType(this, (v) => (func(v) ? true : err)); | ||
const err = { ok: false, code: "custom_error", error }; | ||
return new TransformType(this, (v) => func(v) ? undefined : err); | ||
} | ||
map(func) { | ||
return new TransformType(this, (v) => ({ | ||
code: "ok", | ||
ok: true, | ||
value: func(v), | ||
@@ -247,8 +228,3 @@ })); | ||
const r = func(v); | ||
if (r.ok) { | ||
return { code: "ok", value: r.value }; | ||
} | ||
else { | ||
return r.issueTree; | ||
} | ||
return r.ok ? r : r.issueTree; | ||
}); | ||
@@ -266,13 +242,15 @@ } | ||
let mode = 1 /* FuncMode.STRICT */; | ||
if (options && options.mode === "passthrough") { | ||
mode = 0 /* FuncMode.PASS */; | ||
if (options !== undefined) { | ||
if (options.mode === "passthrough") { | ||
mode = 0 /* FuncMode.PASS */; | ||
} | ||
else if (options.mode === "strip") { | ||
mode = 2 /* FuncMode.STRIP */; | ||
} | ||
} | ||
else if (options && options.mode === "strip") { | ||
mode = 2 /* FuncMode.STRIP */; | ||
} | ||
const r = this.func(v, mode); | ||
if (r === true) { | ||
if (r === undefined) { | ||
return { ok: true, value: v }; | ||
} | ||
else if (r.code === "ok") { | ||
else if (r.ok) { | ||
return { ok: true, value: r.value }; | ||
@@ -286,13 +264,15 @@ } | ||
let mode = 1 /* FuncMode.STRICT */; | ||
if (options && options.mode === "passthrough") { | ||
mode = 0 /* FuncMode.PASS */; | ||
if (options !== undefined) { | ||
if (options.mode === "passthrough") { | ||
mode = 0 /* FuncMode.PASS */; | ||
} | ||
else if (options.mode === "strip") { | ||
mode = 2 /* FuncMode.STRIP */; | ||
} | ||
} | ||
else if (options && options.mode === "strip") { | ||
mode = 2 /* FuncMode.STRIP */; | ||
} | ||
const r = this.func(v, mode); | ||
if (r === true) { | ||
if (r === undefined) { | ||
return v; | ||
} | ||
else if (r.code === "ok") { | ||
else if (r.ok) { | ||
return r.value; | ||
@@ -312,3 +292,5 @@ } | ||
func(v, mode) { | ||
return v === undefined || v === Nothing ? true : this.type.func(v, mode); | ||
return v === undefined || v === Nothing | ||
? undefined | ||
: this.type.func(v, mode); | ||
} | ||
@@ -324,38 +306,2 @@ toTerminals(func) { | ||
} | ||
function prependIssue(issue, result) { | ||
return result === true || result.code === "ok" | ||
? issue | ||
: joinIssues(issue, result); | ||
} | ||
function assignEnumerable(to, from) { | ||
for (const key in from) { | ||
safeSet(to, key, from[key]); | ||
} | ||
return to; | ||
} | ||
function addResult(objResult, obj, key, value, keyResult, assign) { | ||
if (keyResult === true) { | ||
if (objResult !== true && objResult.code === "ok" && value !== Nothing) { | ||
safeSet(objResult.value, key, value); | ||
} | ||
return objResult; | ||
} | ||
else if (keyResult.code === "ok") { | ||
if (objResult === true) { | ||
const copy = assign({}, obj); | ||
safeSet(copy, key, keyResult.value); | ||
return { code: "ok", value: copy }; | ||
} | ||
else if (objResult.code === "ok") { | ||
safeSet(objResult.value, key, keyResult.value); | ||
return objResult; | ||
} | ||
else { | ||
return objResult; | ||
} | ||
} | ||
else { | ||
return prependIssue(prependPath(key, keyResult), objResult); | ||
} | ||
} | ||
// Set a bit in position `index` to one and return the updated bitset. | ||
@@ -398,3 +344,3 @@ // This function may or may not mutate `bits` in-place. | ||
var _a; | ||
const issue = { code: "custom_error", path: undefined, error }; | ||
const issue = { ok: false, code: "custom_error", error }; | ||
return new ObjectType(this.shape, this.restType, [ | ||
@@ -408,3 +354,3 @@ ...((_a = this.checks) !== null && _a !== void 0 ? _a : []), | ||
} | ||
func(obj, mode) { | ||
func(v, mode) { | ||
let func = this._func; | ||
@@ -415,3 +361,3 @@ if (func === undefined) { | ||
} | ||
return func(obj, mode); | ||
return func(v, mode); | ||
} | ||
@@ -448,3 +394,14 @@ rest(restType) { | ||
} | ||
function createObjectMatcher(shape, restType, checks) { | ||
// 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) { | ||
const requiredKeys = []; | ||
@@ -464,7 +421,27 @@ const optionalKeys = []; | ||
} | ||
const requiredCount = requiredKeys.length | 0; | ||
const optionalCount = optionalKeys.length | 0; | ||
const totalCount = (requiredCount + optionalCount) | 0; | ||
const keys = [...requiredKeys, ...optionalKeys]; | ||
const totalCount = keys.length; | ||
const invalidType = { | ||
ok: false, | ||
code: "invalid_type", | ||
expected: ["object"], | ||
}; | ||
if (totalCount === 0 && rest === unknownSingleton) { | ||
// A fast path for record(unknown()) | ||
return function (obj, _) { | ||
if (!isObject(obj)) { | ||
return invalidType; | ||
} | ||
if (checks !== undefined) { | ||
for (let i = 0; i < checks.length; i++) { | ||
if (!checks[i].func(obj)) { | ||
return checks[i].issue; | ||
} | ||
} | ||
} | ||
return undefined; | ||
}; | ||
} | ||
const types = keys.map((key) => shape[key]); | ||
const requiredCount = requiredKeys.length; | ||
const invertedIndexes = Object.create(null); | ||
@@ -474,129 +451,143 @@ keys.forEach((key, index) => { | ||
}); | ||
const invalidType = { | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["object"], | ||
}; | ||
const missingValues = requiredKeys.map((key) => ({ | ||
const missingValues = requiredKeys.map((key) => prependPath(key, { | ||
ok: false, | ||
code: "missing_value", | ||
path: key, | ||
})); | ||
function assignKnown(to, from) { | ||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i]; | ||
const value = from[key]; | ||
if (i < requiredCount || value !== undefined || key in from) { | ||
safeSet(to, key, value); | ||
} | ||
return function (obj, mode) { | ||
if (!isObject(obj)) { | ||
return invalidType; | ||
} | ||
return to; | ||
} | ||
function assignAll(to, from) { | ||
return assignKnown(assignEnumerable(to, from), from); | ||
} | ||
function checkRemainingKeys(initialResult, obj, mode, bits, assign) { | ||
let result = initialResult; | ||
for (let i = 0; i < totalCount; i++) { | ||
if (!getBit(bits, i)) { | ||
const key = keys[i]; | ||
const value = key in obj ? obj[key] : Nothing; | ||
if (i < requiredCount && value === Nothing) { | ||
result = prependIssue(missingValues[i], result); | ||
let copied = false; | ||
let output = obj; | ||
let issues; | ||
let unrecognized = undefined; | ||
let seenBits = 0; | ||
let seenCount = 0; | ||
if (mode !== 0 /* FuncMode.PASS */ || rest !== undefined) { | ||
for (const key in obj) { | ||
const value = obj[key]; | ||
const index = ~invertedIndexes[key]; | ||
let r; | ||
if (index >= 0) { | ||
seenCount++; | ||
seenBits = setBit(seenBits, index); | ||
r = types[index].func(value, mode); | ||
} | ||
else if (rest !== undefined) { | ||
r = rest.func(value, mode); | ||
} | ||
else { | ||
result = addResult(result, obj, key, value, types[i].func(value, mode), assign); | ||
if (mode === 1 /* FuncMode.STRICT */) { | ||
if (unrecognized === undefined) { | ||
unrecognized = [key]; | ||
} | ||
else { | ||
unrecognized.push(key); | ||
} | ||
} | ||
else if (mode === 2 /* FuncMode.STRIP */ && | ||
issues === undefined && | ||
!copied) { | ||
output = Object.create(protoless); | ||
copied = true; | ||
for (let m = 0; m < totalCount; m++) { | ||
if (getBit(seenBits, m)) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
} | ||
} | ||
} | ||
continue; | ||
} | ||
if (r === undefined) { | ||
if (copied && issues === undefined) { | ||
output[key] = value; | ||
} | ||
} | ||
else if (!r.ok) { | ||
issues = joinIssues(issues, prependPath(key, r)); | ||
} | ||
else if (issues === undefined) { | ||
if (!copied) { | ||
output = Object.create(protoless); | ||
copied = true; | ||
if (rest === undefined) { | ||
for (let m = 0; m < totalCount; m++) { | ||
if (m !== index && getBit(seenBits, m)) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
} | ||
} | ||
} | ||
else { | ||
for (const k in obj) { | ||
output[k] = obj[k]; | ||
} | ||
} | ||
} | ||
output[key] = r.value; | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
function pass(obj, mode) { | ||
let result = true; | ||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i]; | ||
let value = obj[key]; | ||
if (value === undefined && !(key in obj)) { | ||
if (i < requiredCount) { | ||
result = prependIssue(missingValues[i], result); | ||
if (seenCount < totalCount) { | ||
for (let i = 0; i < totalCount; i++) { | ||
if (getBit(seenBits, i)) { | ||
continue; | ||
} | ||
value = Nothing; | ||
const key = keys[i]; | ||
let value = obj[key]; | ||
if (value === undefined && !(key in obj)) { | ||
if (i < requiredCount) { | ||
issues = joinIssues(issues, missingValues[i]); | ||
continue; | ||
} | ||
value = Nothing; | ||
} | ||
const r = types[i].func(value, mode); | ||
if (r === undefined) { | ||
if (copied && issues === undefined && value !== Nothing) { | ||
output[key] = value; | ||
} | ||
} | ||
else if (!r.ok) { | ||
issues = joinIssues(issues, prependPath(key, r)); | ||
} | ||
else if (issues === undefined) { | ||
if (!copied) { | ||
output = Object.create(protoless); | ||
copied = true; | ||
if (rest === undefined) { | ||
for (let m = 0; m < totalCount; m++) { | ||
if (m < i || getBit(seenBits, m)) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
} | ||
} | ||
} | ||
else { | ||
for (const k in obj) { | ||
output[k] = obj[k]; | ||
} | ||
for (let m = 0; m < i; m++) { | ||
if (!getBit(seenBits, m)) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
} | ||
} | ||
} | ||
} | ||
output[key] = r.value; | ||
} | ||
} | ||
result = addResult(result, obj, key, value, types[i].func(value, mode), assignKnown); | ||
} | ||
return result; | ||
} | ||
function strict(obj, mode) { | ||
let result = true; | ||
let unrecognized = undefined; | ||
let seenBits = 0; | ||
let seenCount = 0; | ||
for (const key in obj) { | ||
const value = obj[key]; | ||
const index = ~invertedIndexes[key]; | ||
if (index >= 0) { | ||
seenCount++; | ||
seenBits = setBit(seenBits, index); | ||
result = addResult(result, obj, key, value, types[index].func(value, mode), assignKnown); | ||
} | ||
else if (mode === 2 /* FuncMode.STRIP */) { | ||
result = | ||
result === true | ||
? { code: "ok", value: assignKnown({}, obj) } | ||
: result; | ||
} | ||
else if (unrecognized === undefined) { | ||
unrecognized = [key]; | ||
} | ||
else { | ||
unrecognized.push(key); | ||
} | ||
} | ||
if (seenCount < totalCount) { | ||
result = checkRemainingKeys(result, obj, mode, seenBits, assignKnown); | ||
} | ||
return unrecognized === undefined | ||
? result | ||
: prependIssue({ | ||
if (unrecognized !== undefined) { | ||
issues = joinIssues(issues, { | ||
ok: false, | ||
code: "unrecognized_keys", | ||
path: undefined, | ||
keys: unrecognized, | ||
}, result); | ||
} | ||
function withRest(rest, obj, mode) { | ||
if (totalCount === 0) { | ||
if (rest.name === "unknown") { | ||
return true; | ||
} | ||
let result = true; | ||
for (const key in obj) { | ||
const value = obj[key]; | ||
result = addResult(result, obj, key, value, rest.func(value, mode), assignEnumerable); | ||
} | ||
}); | ||
} | ||
let result = true; | ||
let seenBits = 0; | ||
let seenCount = 0; | ||
for (const key in obj) { | ||
const value = obj[key]; | ||
const index = ~invertedIndexes[key]; | ||
if (index >= 0) { | ||
seenCount++; | ||
seenBits = setBit(seenBits, index); | ||
result = addResult(result, obj, key, value, types[index].func(value, mode), assignEnumerable); | ||
} | ||
else { | ||
result = addResult(result, obj, key, value, rest.func(value, mode), assignEnumerable); | ||
} | ||
} | ||
if (seenCount < totalCount) { | ||
result = checkRemainingKeys(result, obj, mode, seenBits, assignAll); | ||
} | ||
return result; | ||
} | ||
function runChecks(obj, result) { | ||
if ((result === true || result.code === "ok") && checks) { | ||
const value = result === true ? obj : result.value; | ||
if (issues === undefined && checks !== undefined) { | ||
for (let i = 0; i < checks.length; i++) { | ||
if (!checks[i].func(value)) { | ||
if (!checks[i].func(output)) { | ||
return checks[i].issue; | ||
@@ -606,19 +597,9 @@ } | ||
} | ||
return result; | ||
} | ||
function func(obj, mode) { | ||
if (!isObject(obj)) { | ||
return invalidType; | ||
if (issues === undefined && copied) { | ||
return { ok: true, value: output }; | ||
} | ||
if (restType) { | ||
return runChecks(obj, withRest(restType, obj, mode)); | ||
} | ||
else if (mode === 0 /* FuncMode.PASS */) { | ||
return runChecks(obj, pass(obj, mode)); | ||
} | ||
else { | ||
return runChecks(obj, strict(obj, mode)); | ||
return issues; | ||
} | ||
} | ||
return func; | ||
}; | ||
} | ||
@@ -634,9 +615,9 @@ class ArrayType extends Type { | ||
this.invalidType = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["array"], | ||
}; | ||
this.invalidLength = { | ||
ok: false, | ||
code: "invalid_length", | ||
path: undefined, | ||
minLength: this.minLength, | ||
@@ -664,4 +645,4 @@ maxLength: this.maxLength, | ||
const r = type.func(arr[i], mode); | ||
if (r !== true) { | ||
if (r.code === "ok") { | ||
if (r !== undefined) { | ||
if (r.ok) { | ||
if (output === arr) { | ||
@@ -681,6 +662,6 @@ output = arr.slice(); | ||
else if (arr === output) { | ||
return true; | ||
return undefined; | ||
} | ||
else { | ||
return { code: "ok", value: output }; | ||
return { ok: true, value: output }; | ||
} | ||
@@ -794,14 +775,14 @@ } | ||
} | ||
const missingValue = { code: "missing_value", path: key }; | ||
const issue = types.size === 0 | ||
const missingValue = prependPath(key, { ok: false, code: "missing_value" }); | ||
const issue = prependPath(key, types.size === 0 | ||
? { | ||
ok: false, | ||
code: "invalid_literal", | ||
path: key, | ||
expected: Array.from(literals.keys()), | ||
} | ||
: { | ||
ok: false, | ||
code: "invalid_type", | ||
path: key, | ||
expected: expectedTypes, | ||
}; | ||
}); | ||
const litMap = literals.size > 0 ? new Map() : undefined; | ||
@@ -849,9 +830,9 @@ for (const [literal, options] of literals) { | ||
? { | ||
ok: false, | ||
code: "invalid_literal", | ||
path: undefined, | ||
expected: Array.from(literals.keys()), | ||
} | ||
: { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: expectedTypes, | ||
@@ -880,3 +861,3 @@ }; | ||
const r = options[i].func(value, mode); | ||
if (r === true || r.code === "ok") { | ||
if (r === undefined || r.ok) { | ||
return r; | ||
@@ -888,3 +869,3 @@ } | ||
if (count > 1) { | ||
return { code: "invalid_union", path: undefined, tree: issueTree }; | ||
return { ok: false, code: "invalid_union", tree: issueTree }; | ||
} | ||
@@ -934,3 +915,3 @@ return issueTree; | ||
this.name = "transform"; | ||
this.undef = { code: "ok", value: undefined }; | ||
this.undef = ok(undefined); | ||
this.transformChain = undefined; | ||
@@ -955,7 +936,7 @@ this.transformRoot = undefined; | ||
let result = this.transformRoot.func(v, mode); | ||
if (result !== true && result.code !== "ok") { | ||
if (result !== undefined && !result.ok) { | ||
return result; | ||
} | ||
let current; | ||
if (result !== true) { | ||
if (result !== undefined) { | ||
current = result.value; | ||
@@ -972,4 +953,4 @@ } | ||
const r = chain[i](current, mode); | ||
if (r !== true) { | ||
if (r.code !== "ok") { | ||
if (r !== undefined) { | ||
if (!r.ok) { | ||
return r; | ||
@@ -1021,4 +1002,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: [], | ||
@@ -1042,3 +1023,3 @@ }; | ||
func(_, __) { | ||
return true; | ||
return undefined; | ||
} | ||
@@ -1056,4 +1037,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["undefined"], | ||
@@ -1063,3 +1044,3 @@ }; | ||
func(v, _) { | ||
return v === undefined ? true : this.issue; | ||
return v === undefined ? undefined : this.issue; | ||
} | ||
@@ -1077,4 +1058,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["null"], | ||
@@ -1084,3 +1065,3 @@ }; | ||
func(v, _) { | ||
return v === null ? true : this.issue; | ||
return v === null ? undefined : this.issue; | ||
} | ||
@@ -1098,4 +1079,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["number"], | ||
@@ -1105,3 +1086,3 @@ }; | ||
func(v, _) { | ||
return typeof v === "number" ? true : this.issue; | ||
return typeof v === "number" ? undefined : this.issue; | ||
} | ||
@@ -1119,4 +1100,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["bigint"], | ||
@@ -1126,3 +1107,3 @@ }; | ||
func(v, _) { | ||
return typeof v === "bigint" ? true : this.issue; | ||
return typeof v === "bigint" ? undefined : this.issue; | ||
} | ||
@@ -1140,4 +1121,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["string"], | ||
@@ -1147,3 +1128,3 @@ }; | ||
func(v, _) { | ||
return typeof v === "string" ? true : this.issue; | ||
return typeof v === "string" ? undefined : this.issue; | ||
} | ||
@@ -1161,4 +1142,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["boolean"], | ||
@@ -1168,3 +1149,3 @@ }; | ||
func(v, _) { | ||
return typeof v === "boolean" ? true : this.issue; | ||
return typeof v === "boolean" ? undefined : this.issue; | ||
} | ||
@@ -1183,4 +1164,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_literal", | ||
path: undefined, | ||
expected: [value], | ||
@@ -1190,3 +1171,3 @@ }; | ||
func(v, _) { | ||
return v === this.value ? true : this.issue; | ||
return v === this.value ? undefined : this.issue; | ||
} | ||
@@ -1193,0 +1174,0 @@ } |
{ | ||
"name": "@badrap/valita", | ||
"version": "0.2.5", | ||
"version": "0.2.7", | ||
"description": "A validation & parsing library for TypeScript", | ||
@@ -31,11 +31,11 @@ "main": "./dist/cjs/index.js", | ||
"devDependencies": { | ||
"@typescript-eslint/eslint-plugin": "^6.2.0", | ||
"@typescript-eslint/parser": "^6.2.0", | ||
"eslint": "^8.45.0", | ||
"eslint-config-prettier": "^8.9.0", | ||
"@typescript-eslint/eslint-plugin": "^6.2.1", | ||
"@typescript-eslint/parser": "^6.2.1", | ||
"eslint": "^8.46.0", | ||
"eslint-config-prettier": "^9.0.0", | ||
"eslint-plugin-prettier": "^5.0.0", | ||
"prettier": "^3.0.0", | ||
"prettier": "^3.0.1", | ||
"ts-expect": "^1.3.0", | ||
"typescript": "^5.1.6", | ||
"vitest": "^0.33.0" | ||
"vitest": "^0.34.1" | ||
}, | ||
@@ -42,0 +42,0 @@ "files": [ |
616
src/index.ts
@@ -24,30 +24,38 @@ // This is magic that turns object intersections to nicer-looking types. | ||
type IssueNode<PathType = Key | undefined> = Readonly< | ||
| { code: "invalid_type"; path: PathType; expected: InputType[] } | ||
| { code: "missing_value"; path: PathType } | ||
| { code: "invalid_literal"; path: PathType; expected: Literal[] } | ||
type IssueNode = Readonly< | ||
| { ok: false; code: "custom_error"; error: CustomError } | ||
| { ok: false; code: "invalid_type"; expected: InputType[] } | ||
| { ok: false; code: "missing_value" } | ||
| { ok: false; code: "invalid_literal"; expected: Literal[] } | ||
| { ok: false; code: "unrecognized_keys"; keys: Key[] } | ||
| { ok: false; code: "invalid_union"; tree: IssueTree } | ||
| { ok: false; code: "invalid_length"; minLength: number; maxLength: number } | ||
>; | ||
type IssueTree = | ||
| Readonly<{ ok: false; code: "prepend"; key: Key; tree: IssueTree }> | ||
| Readonly<{ ok: false; code: "join"; left: IssueTree; right: IssueTree }> | ||
| IssueNode; | ||
type Issue = Readonly< | ||
| { code: "custom_error"; path: Key[]; error: CustomError } | ||
| { code: "invalid_type"; path: Key[]; expected: InputType[] } | ||
| { code: "missing_value"; path: Key[] } | ||
| { code: "invalid_literal"; path: Key[]; expected: Literal[] } | ||
| { code: "unrecognized_keys"; path: Key[]; keys: Key[] } | ||
| { code: "invalid_union"; path: Key[]; tree: IssueTree } | ||
| { | ||
code: "invalid_length"; | ||
path: PathType; | ||
path: Key[]; | ||
minLength: number; | ||
maxLength: number; | ||
} | ||
| { code: "unrecognized_keys"; path: PathType; keys: Key[] } | ||
| { code: "invalid_union"; path: PathType; tree: IssueTree } | ||
| { code: "custom_error"; path: PathType; error: CustomError } | ||
>; | ||
type IssueTree = | ||
| Readonly<{ code: "prepend"; key: Key; tree: IssueTree }> | ||
| Readonly<{ code: "join"; left: IssueTree; right: IssueTree }> | ||
| IssueNode; | ||
type Issue = IssueNode<Key[]>; | ||
function joinIssues(left: IssueTree | undefined, right: IssueTree): IssueTree { | ||
return left ? { code: "join", left, right } : right; | ||
return left ? { ok: false, code: "join", left, right } : right; | ||
} | ||
function prependPath(key: Key, tree: IssueTree): IssueTree { | ||
return { code: "prepend", key, tree }; | ||
return { ok: false, code: "prepend", key, tree }; | ||
} | ||
@@ -91,5 +99,2 @@ | ||
} else { | ||
if (tree.path !== undefined) { | ||
path.push(tree.path); | ||
} | ||
if ( | ||
@@ -129,5 +134,2 @@ tree.code === "custom_error" && | ||
} else { | ||
if (tree.path !== undefined) { | ||
path.push(tree.path); | ||
} | ||
if ( | ||
@@ -266,3 +268,3 @@ tree.code === "custom_error" && | ||
function err(error?: CustomError): Err { | ||
return new Err({ code: "custom_error", path: undefined, error }); | ||
return new Err({ ok: false, code: "custom_error", error }); | ||
} | ||
@@ -273,3 +275,3 @@ | ||
type RawResult<T> = true | Readonly<{ code: "ok"; value: T }> | IssueTree; | ||
type RawResult<T> = undefined | Ok<T> | IssueTree; | ||
@@ -280,19 +282,2 @@ function isObject(v: unknown): v is Record<string, unknown> { | ||
function safeSet( | ||
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; | ||
} | ||
} | ||
const Nothing = Symbol.for("valita.Nothing"); | ||
@@ -325,7 +310,5 @@ | ||
default<T>(defaultValue: T): Type<Exclude<Output, undefined> | T> { | ||
const defaultResult = { code: "ok", value: defaultValue } as RawResult< | ||
Exclude<Output, undefined> | T | ||
>; | ||
const defaultResult = ok(defaultValue); | ||
return new TransformType(this.optional(), (v) => { | ||
return v === undefined ? defaultResult : true; | ||
return v === undefined ? defaultResult : undefined; | ||
}); | ||
@@ -338,4 +321,6 @@ } | ||
): Type<T> { | ||
const err: IssueNode = { code: "custom_error", path: undefined, error }; | ||
return new TransformType(this, (v) => (func(v as Output) ? true : err)); | ||
const err: IssueNode = { ok: false, code: "custom_error", error }; | ||
return new TransformType(this, (v) => | ||
func(v as Output) ? undefined : err, | ||
); | ||
} | ||
@@ -347,3 +332,3 @@ | ||
return new TransformType(this, (v) => ({ | ||
code: "ok", | ||
ok: true, | ||
value: func(v as Output), | ||
@@ -358,7 +343,3 @@ })); | ||
const r = func(v as Output); | ||
if (r.ok) { | ||
return { code: "ok", value: r.value }; | ||
} else { | ||
return (r as unknown as { issueTree: IssueTree }).issueTree; | ||
} | ||
return r.ok ? r : (r as unknown as { issueTree: IssueTree }).issueTree; | ||
}); | ||
@@ -383,12 +364,14 @@ } | ||
let mode: FuncMode = FuncMode.STRICT; | ||
if (options && options.mode === "passthrough") { | ||
mode = FuncMode.PASS; | ||
} else if (options && options.mode === "strip") { | ||
mode = FuncMode.STRIP; | ||
if (options !== undefined) { | ||
if (options.mode === "passthrough") { | ||
mode = FuncMode.PASS; | ||
} else if (options.mode === "strip") { | ||
mode = FuncMode.STRIP; | ||
} | ||
} | ||
const r = this.func(v, mode); | ||
if (r === true) { | ||
if (r === undefined) { | ||
return { ok: true, value: v as Infer<this> }; | ||
} else if (r.code === "ok") { | ||
} else if (r.ok) { | ||
return { ok: true, value: r.value as Infer<this> }; | ||
@@ -402,12 +385,14 @@ } else { | ||
let mode: FuncMode = FuncMode.STRICT; | ||
if (options && options.mode === "passthrough") { | ||
mode = FuncMode.PASS; | ||
} else if (options && options.mode === "strip") { | ||
mode = FuncMode.STRIP; | ||
if (options !== undefined) { | ||
if (options.mode === "passthrough") { | ||
mode = FuncMode.PASS; | ||
} else if (options.mode === "strip") { | ||
mode = FuncMode.STRIP; | ||
} | ||
} | ||
const r = this.func(v, mode); | ||
if (r === true) { | ||
if (r === undefined) { | ||
return v as Infer<this>; | ||
} else if (r.code === "ok") { | ||
} else if (r.ok) { | ||
return r.value as Infer<this>; | ||
@@ -428,3 +413,5 @@ } else { | ||
func(v: unknown, mode: FuncMode): RawResult<Output | undefined> { | ||
return v === undefined || v === Nothing ? true : this.type.func(v, mode); | ||
return v === undefined || v === Nothing | ||
? undefined | ||
: this.type.func(v, mode); | ||
} | ||
@@ -460,46 +447,2 @@ | ||
function prependIssue(issue: IssueTree, result: RawResult<unknown>): IssueTree { | ||
return result === true || result.code === "ok" | ||
? issue | ||
: joinIssues(issue, result); | ||
} | ||
type Obj = Record<string, unknown>; | ||
function assignEnumerable(to: Obj, from: Obj): Obj { | ||
for (const key in from) { | ||
safeSet(to, key, from[key]); | ||
} | ||
return to; | ||
} | ||
function addResult( | ||
objResult: RawResult<Obj>, | ||
obj: Obj, | ||
key: string, | ||
value: unknown, | ||
keyResult: RawResult<unknown>, | ||
assign: (to: Obj, from: Obj) => Obj, | ||
): RawResult<Obj> { | ||
if (keyResult === true) { | ||
if (objResult !== true && objResult.code === "ok" && value !== Nothing) { | ||
safeSet(objResult.value, key, value); | ||
} | ||
return objResult; | ||
} else if (keyResult.code === "ok") { | ||
if (objResult === true) { | ||
const copy = assign({}, obj); | ||
safeSet(copy, key, keyResult.value); | ||
return { code: "ok", value: copy }; | ||
} else if (objResult.code === "ok") { | ||
safeSet(objResult.value, key, keyResult.value); | ||
return objResult; | ||
} else { | ||
return objResult; | ||
} | ||
} else { | ||
return prependIssue(prependPath(key, keyResult), objResult); | ||
} | ||
} | ||
// A bitset type, used for keeping track which known (required & optional) keys | ||
@@ -550,3 +493,3 @@ // the object validator has seen. Basically, when key `knownKey` is encountered, | ||
private _func?: Func<ObjectOutput<Shape, Rest>>; | ||
private _func?: Func<unknown>; | ||
@@ -568,3 +511,3 @@ constructor( | ||
): ObjectType<Shape, Rest> { | ||
const issue: IssueNode = { code: "custom_error", path: undefined, error }; | ||
const issue: IssueNode = { ok: false, code: "custom_error", error }; | ||
return new ObjectType(this.shape, this.restType, [ | ||
@@ -579,3 +522,3 @@ ...(this.checks ?? []), | ||
func(obj: Obj, mode: FuncMode): RawResult<ObjectOutput<Shape, Rest>> { | ||
func(v: unknown, mode: FuncMode): RawResult<ObjectOutput<Shape, Rest>> { | ||
let func = this._func; | ||
@@ -586,3 +529,3 @@ if (func === undefined) { | ||
} | ||
return func(obj, mode); | ||
return func(v, mode) as RawResult<ObjectOutput<Shape, Rest>>; | ||
} | ||
@@ -639,8 +582,17 @@ | ||
function createObjectMatcher< | ||
Shape extends ObjectShape = ObjectShape, | ||
Rest extends AbstractType | undefined = AbstractType | undefined, | ||
>( | ||
shape: Shape, | ||
restType: Rest, | ||
// 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: ObjectShape, | ||
rest?: AbstractType, | ||
checks?: { | ||
@@ -650,3 +602,3 @@ func: (v: unknown) => boolean; | ||
}[], | ||
): Func<ObjectOutput<Shape, Rest>> { | ||
): Func<unknown> { | ||
const requiredKeys: string[] = []; | ||
@@ -665,97 +617,49 @@ const optionalKeys: string[] = []; | ||
} | ||
const requiredCount = requiredKeys.length | 0; | ||
const optionalCount = optionalKeys.length | 0; | ||
const totalCount = (requiredCount + optionalCount) | 0; | ||
const keys = [...requiredKeys, ...optionalKeys]; | ||
const types = keys.map((key) => shape[key]); | ||
const invertedIndexes = Object.create(null); | ||
keys.forEach((key, index) => { | ||
invertedIndexes[key] = ~index; | ||
}); | ||
const totalCount = keys.length; | ||
const invalidType: IssueNode = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["object"], | ||
}; | ||
const missingValues: IssueNode[] = requiredKeys.map((key) => ({ | ||
code: "missing_value", | ||
path: key, | ||
})); | ||
function assignKnown(to: Obj, from: Obj): Obj { | ||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i]; | ||
const value = from[key]; | ||
if (i < requiredCount || value !== undefined || key in from) { | ||
safeSet(to, key, value); | ||
if (totalCount === 0 && rest === unknownSingleton) { | ||
// A fast path for record(unknown()) | ||
return function (obj, _) { | ||
if (!isObject(obj)) { | ||
return invalidType; | ||
} | ||
} | ||
return to; | ||
} | ||
function assignAll(to: Obj, from: Obj): Obj { | ||
return assignKnown(assignEnumerable(to, from), from); | ||
} | ||
function checkRemainingKeys( | ||
initialResult: RawResult<Obj>, | ||
obj: Obj, | ||
mode: FuncMode, | ||
bits: BitSet, | ||
assign: (to: Obj, from: Obj) => Obj, | ||
): RawResult<Obj> { | ||
let result = initialResult; | ||
for (let i = 0; i < totalCount; i++) { | ||
if (!getBit(bits, i)) { | ||
const key = keys[i]; | ||
const value = key in obj ? obj[key] : Nothing; | ||
if (i < requiredCount && value === Nothing) { | ||
result = prependIssue(missingValues[i], result); | ||
} else { | ||
result = addResult( | ||
result, | ||
obj, | ||
key, | ||
value, | ||
types[i].func(value, mode), | ||
assign, | ||
); | ||
if (checks !== undefined) { | ||
for (let i = 0; i < checks.length; i++) { | ||
if (!checks[i].func(obj)) { | ||
return checks[i].issue; | ||
} | ||
} | ||
} | ||
} | ||
return result; | ||
return undefined; | ||
}; | ||
} | ||
function pass(obj: Obj, mode: FuncMode): RawResult<Obj> { | ||
let result: RawResult<Obj> = true; | ||
for (let i = 0; i < keys.length; i++) { | ||
const key = keys[i]; | ||
const types = keys.map((key) => shape[key]); | ||
const requiredCount = requiredKeys.length; | ||
const invertedIndexes = Object.create(null); | ||
keys.forEach((key, index) => { | ||
invertedIndexes[key] = ~index; | ||
}); | ||
const missingValues = requiredKeys.map((key) => | ||
prependPath(key, { | ||
ok: false, | ||
code: "missing_value", | ||
}), | ||
); | ||
let value: unknown = obj[key]; | ||
if (value === undefined && !(key in obj)) { | ||
if (i < requiredCount) { | ||
result = prependIssue(missingValues[i], result); | ||
continue; | ||
} | ||
value = Nothing; | ||
} | ||
result = addResult( | ||
result, | ||
obj, | ||
key, | ||
value, | ||
types[i].func(value, mode), | ||
assignKnown, | ||
); | ||
return function (obj, mode) { | ||
if (!isObject(obj)) { | ||
return invalidType; | ||
} | ||
return result; | ||
} | ||
function strict(obj: Obj, mode: FuncMode): RawResult<Obj> { | ||
let result: RawResult<Obj> = true; | ||
let copied = false; | ||
let output = obj; | ||
let issues: IssueTree | undefined; | ||
let unrecognized: Key[] | undefined = undefined; | ||
@@ -765,95 +669,63 @@ let seenBits: BitSet = 0; | ||
for (const key in obj) { | ||
const value = obj[key]; | ||
const index = ~invertedIndexes[key]; | ||
if (index >= 0) { | ||
seenCount++; | ||
seenBits = setBit(seenBits, index); | ||
result = addResult( | ||
result, | ||
obj, | ||
key, | ||
value, | ||
types[index].func(value, mode), | ||
assignKnown, | ||
); | ||
} else if (mode === FuncMode.STRIP) { | ||
result = | ||
result === true | ||
? { code: "ok", value: assignKnown({}, obj) } | ||
: result; | ||
} else if (unrecognized === undefined) { | ||
unrecognized = [key]; | ||
} else { | ||
unrecognized.push(key); | ||
} | ||
} | ||
if (seenCount < totalCount) { | ||
result = checkRemainingKeys(result, obj, mode, seenBits, assignKnown); | ||
} | ||
return unrecognized === undefined | ||
? result | ||
: prependIssue( | ||
{ | ||
code: "unrecognized_keys", | ||
path: undefined, | ||
keys: unrecognized, | ||
}, | ||
result, | ||
); | ||
} | ||
function withRest( | ||
rest: AbstractType, | ||
obj: Obj, | ||
mode: FuncMode, | ||
): RawResult<Obj> { | ||
if (totalCount === 0) { | ||
if (rest.name === "unknown") { | ||
return true; | ||
} | ||
let result: RawResult<Obj> = true; | ||
if (mode !== FuncMode.PASS || rest !== undefined) { | ||
for (const key in obj) { | ||
const value = obj[key]; | ||
result = addResult( | ||
result, | ||
obj, | ||
key, | ||
value, | ||
rest.func(value, mode), | ||
assignEnumerable, | ||
); | ||
} | ||
} | ||
const index = ~invertedIndexes[key]; | ||
let result: RawResult<Obj> = true; | ||
let seenBits: BitSet = 0; | ||
let seenCount = 0; | ||
let r: RawResult<unknown>; | ||
if (index >= 0) { | ||
seenCount++; | ||
seenBits = setBit(seenBits, index); | ||
r = types[index].func(value, mode); | ||
} else if (rest !== undefined) { | ||
r = rest.func(value, mode); | ||
} else { | ||
if (mode === FuncMode.STRICT) { | ||
if (unrecognized === undefined) { | ||
unrecognized = [key]; | ||
} else { | ||
unrecognized.push(key); | ||
} | ||
} else if ( | ||
mode === FuncMode.STRIP && | ||
issues === undefined && | ||
!copied | ||
) { | ||
output = Object.create(protoless); | ||
copied = true; | ||
for (let m = 0; m < totalCount; m++) { | ||
if (getBit(seenBits, m)) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
} | ||
} | ||
} | ||
continue; | ||
} | ||
for (const key in obj) { | ||
const value = obj[key]; | ||
const index = ~invertedIndexes[key]; | ||
if (index >= 0) { | ||
seenCount++; | ||
seenBits = setBit(seenBits, index); | ||
result = addResult( | ||
result, | ||
obj, | ||
key, | ||
value, | ||
types[index].func(value, mode), | ||
assignEnumerable, | ||
); | ||
} else { | ||
result = addResult( | ||
result, | ||
obj, | ||
key, | ||
value, | ||
rest.func(value, mode), | ||
assignEnumerable, | ||
); | ||
if (r === undefined) { | ||
if (copied && issues === undefined) { | ||
output[key] = value; | ||
} | ||
} else if (!r.ok) { | ||
issues = joinIssues(issues, prependPath(key, r)); | ||
} else if (issues === undefined) { | ||
if (!copied) { | ||
output = Object.create(protoless); | ||
copied = true; | ||
if (rest === undefined) { | ||
for (let m = 0; m < totalCount; m++) { | ||
if (m !== index && getBit(seenBits, m)) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
} | ||
} | ||
} else { | ||
for (const k in obj) { | ||
output[k] = obj[k]; | ||
} | ||
} | ||
} | ||
output[key] = r.value; | ||
} | ||
} | ||
@@ -863,15 +735,61 @@ } | ||
if (seenCount < totalCount) { | ||
result = checkRemainingKeys(result, obj, mode, seenBits, assignAll); | ||
for (let i = 0; i < totalCount; i++) { | ||
if (getBit(seenBits, i)) { | ||
continue; | ||
} | ||
const key = keys[i]; | ||
let value = obj[key]; | ||
if (value === undefined && !(key in obj)) { | ||
if (i < requiredCount) { | ||
issues = joinIssues(issues, missingValues[i]); | ||
continue; | ||
} | ||
value = Nothing; | ||
} | ||
const r = types[i].func(value, mode); | ||
if (r === undefined) { | ||
if (copied && issues === undefined && value !== Nothing) { | ||
output[key] = value; | ||
} | ||
} else if (!r.ok) { | ||
issues = joinIssues(issues, prependPath(key, r)); | ||
} else if (issues === undefined) { | ||
if (!copied) { | ||
output = Object.create(protoless); | ||
copied = true; | ||
if (rest === undefined) { | ||
for (let m = 0; m < totalCount; m++) { | ||
if (m < i || getBit(seenBits, m)) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
} | ||
} | ||
} else { | ||
for (const k in obj) { | ||
output[k] = obj[k]; | ||
} | ||
for (let m = 0; m < i; m++) { | ||
if (!getBit(seenBits, m)) { | ||
const k = keys[m]; | ||
output[k] = obj[k]; | ||
} | ||
} | ||
} | ||
} | ||
output[key] = r.value; | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
function runChecks( | ||
obj: Record<string, unknown>, | ||
result: RawResult<Obj>, | ||
): RawResult<ObjectOutput<Shape, Rest>> { | ||
if ((result === true || result.code === "ok") && checks) { | ||
const value = result === true ? obj : result.value; | ||
if (unrecognized !== undefined) { | ||
issues = joinIssues(issues, { | ||
ok: false, | ||
code: "unrecognized_keys", | ||
keys: unrecognized, | ||
}); | ||
} | ||
if (issues === undefined && checks !== undefined) { | ||
for (let i = 0; i < checks.length; i++) { | ||
if (!checks[i].func(value)) { | ||
if (!checks[i].func(output)) { | ||
return checks[i].issue; | ||
@@ -881,23 +799,9 @@ } | ||
} | ||
return result as RawResult<ObjectOutput<Shape, Rest>>; | ||
} | ||
function func( | ||
obj: unknown, | ||
mode: FuncMode, | ||
): RawResult<ObjectOutput<Shape, Rest>> { | ||
if (!isObject(obj)) { | ||
return invalidType; | ||
} | ||
if (restType) { | ||
return runChecks(obj, withRest(restType, obj, mode)); | ||
} else if (mode === FuncMode.PASS) { | ||
return runChecks(obj, pass(obj, mode)); | ||
if (issues === undefined && copied) { | ||
return { ok: true, value: output }; | ||
} else { | ||
return runChecks(obj, strict(obj, mode)); | ||
return issues; | ||
} | ||
} | ||
return func; | ||
}; | ||
} | ||
@@ -936,9 +840,9 @@ | ||
this.invalidType = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["array"], | ||
}; | ||
this.invalidLength = { | ||
ok: false, | ||
code: "invalid_length", | ||
path: undefined, | ||
minLength: this.minLength, | ||
@@ -970,4 +874,4 @@ maxLength: this.maxLength, | ||
const r = type.func(arr[i], mode); | ||
if (r !== true) { | ||
if (r.code === "ok") { | ||
if (r !== undefined) { | ||
if (r.ok) { | ||
if (output === arr) { | ||
@@ -985,5 +889,5 @@ output = arr.slice(); | ||
} else if (arr === output) { | ||
return true; | ||
return undefined; | ||
} else { | ||
return { code: "ok", value: output as ArrayOutput<Head, Rest> }; | ||
return { ok: true, value: output as ArrayOutput<Head, Rest> }; | ||
} | ||
@@ -1114,15 +1018,17 @@ } | ||
const missingValue: IssueNode = { code: "missing_value", path: key }; | ||
const issue: IssueNode = | ||
const missingValue = prependPath(key, { ok: false, code: "missing_value" }); | ||
const issue = prependPath( | ||
key, | ||
types.size === 0 | ||
? { | ||
ok: false, | ||
code: "invalid_literal", | ||
path: key, | ||
expected: Array.from(literals.keys()) as Literal[], | ||
} | ||
: { | ||
ok: false, | ||
code: "invalid_type", | ||
path: key, | ||
expected: expectedTypes, | ||
}; | ||
}, | ||
); | ||
@@ -1186,9 +1092,9 @@ const litMap = | ||
? { | ||
ok: false, | ||
code: "invalid_literal", | ||
path: undefined, | ||
expected: Array.from(literals.keys()) as Literal[], | ||
} | ||
: { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: expectedTypes, | ||
@@ -1219,3 +1125,3 @@ }; | ||
const r = options[i].func(value, mode); | ||
if (r === true || r.code === "ok") { | ||
if (r === undefined || r.ok) { | ||
return r; | ||
@@ -1227,3 +1133,3 @@ } | ||
if (count > 1) { | ||
return { code: "invalid_union", path: undefined, tree: issueTree }; | ||
return { ok: false, code: "invalid_union", tree: issueTree }; | ||
} | ||
@@ -1278,3 +1184,3 @@ return issueTree; | ||
private transformRoot?: AbstractType; | ||
private readonly undef: RawResult<unknown> = { code: "ok", value: undefined }; | ||
private readonly undef = ok(undefined); | ||
@@ -1308,3 +1214,3 @@ constructor( | ||
let result = this.transformRoot!.func(v, mode); | ||
if (result !== true && result.code !== "ok") { | ||
if (result !== undefined && !result.ok) { | ||
return result; | ||
@@ -1314,3 +1220,3 @@ } | ||
let current: unknown; | ||
if (result !== true) { | ||
if (result !== undefined) { | ||
current = result.value; | ||
@@ -1326,4 +1232,4 @@ } else if (v === Nothing) { | ||
const r = chain[i](current, mode); | ||
if (r !== true) { | ||
if (r.code !== "ok") { | ||
if (r !== undefined) { | ||
if (!r.ok) { | ||
return r; | ||
@@ -1378,4 +1284,4 @@ } | ||
private readonly issue: IssueNode = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: [], | ||
@@ -1395,3 +1301,3 @@ }; | ||
func(_: unknown, __: FuncMode): RawResult<unknown> { | ||
return true; | ||
return undefined; | ||
} | ||
@@ -1407,8 +1313,8 @@ } | ||
private readonly issue: IssueNode = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["undefined"], | ||
}; | ||
func(v: unknown, _: FuncMode): RawResult<undefined> { | ||
return v === undefined ? true : this.issue; | ||
return v === undefined ? undefined : this.issue; | ||
} | ||
@@ -1424,8 +1330,8 @@ } | ||
private readonly issue: IssueNode = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["null"], | ||
}; | ||
func(v: unknown, _: FuncMode): RawResult<null> { | ||
return v === null ? true : this.issue; | ||
return v === null ? undefined : this.issue; | ||
} | ||
@@ -1441,8 +1347,8 @@ } | ||
private readonly issue: IssueNode = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["number"], | ||
}; | ||
func(v: unknown, _: FuncMode): RawResult<number> { | ||
return typeof v === "number" ? true : this.issue; | ||
return typeof v === "number" ? undefined : this.issue; | ||
} | ||
@@ -1458,8 +1364,8 @@ } | ||
private readonly issue: IssueNode = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["bigint"], | ||
}; | ||
func(v: unknown, _: FuncMode): RawResult<bigint> { | ||
return typeof v === "bigint" ? true : this.issue; | ||
return typeof v === "bigint" ? undefined : this.issue; | ||
} | ||
@@ -1475,8 +1381,8 @@ } | ||
private readonly issue: IssueNode = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["string"], | ||
}; | ||
func(v: unknown, _: FuncMode): RawResult<string> { | ||
return typeof v === "string" ? true : this.issue; | ||
return typeof v === "string" ? undefined : this.issue; | ||
} | ||
@@ -1492,8 +1398,8 @@ } | ||
private readonly issue: IssueNode = { | ||
ok: false, | ||
code: "invalid_type", | ||
path: undefined, | ||
expected: ["boolean"], | ||
}; | ||
func(v: unknown, _: FuncMode): RawResult<boolean> { | ||
return typeof v === "boolean" ? true : this.issue; | ||
return typeof v === "boolean" ? undefined : this.issue; | ||
} | ||
@@ -1512,4 +1418,4 @@ } | ||
this.issue = { | ||
ok: false, | ||
code: "invalid_literal", | ||
path: undefined, | ||
expected: [value], | ||
@@ -1519,3 +1425,3 @@ }; | ||
func(v: unknown, _: FuncMode): RawResult<Out> { | ||
return v === this.value ? true : this.issue; | ||
return v === this.value ? undefined : this.issue; | ||
} | ||
@@ -1522,0 +1428,0 @@ } |
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
368459
6428