mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(schema): required undefined, huge bigint multipleOf and bigint/undefined coercion, as TypeBox does
- A required property set to undefined is valid when its schema takes undefined (a union with Type.Undefined(), Type.Unknown(), Type.Any()), as in TypeBox; before, it was always missing. A required Type.Undefined() property must be present (the previous commit let it be left out). An optional property set to undefined still counts as absent. check and errors() agree. - multipleOf with a bigint checks only the number side for being an integer: Number() of a huge bigint is Infinity, so 10n ** 400n was not a multiple of 5n. - coerce and parse convert to bigint and to undefined with TypeBox's Value.Convert rules (TryBigInt, TryUndefined), and to bigint literals; the ported Value.Convert tests for them run again.
This commit is contained in:
parent
63aeddc2e9
commit
207adccd04
9 changed files with 133 additions and 62 deletions
|
|
@ -8,7 +8,7 @@
|
|||
- `recursive<T>(id, build)` builds a schema that contains itself (a tree), with its type given as `T`.
|
||||
- `Validator.validate(value)` returns `{ ok: true, value }` or `{ ok: false, errors }` without coercing, on every validator.
|
||||
- `Static` types TypeBox's `Enum`, `Tuple`, `BigInt` and `Undefined` schemas.
|
||||
- TypeBox's JavaScript types validate as TypeBox does: `{ type: "bigint" }` (`Type.BigInt`, with bigint bounds) and `{ type: "undefined" }` (`Type.Undefined`; a required property with it may be left out). The `bigint()` builder builds the former.
|
||||
- TypeBox's JavaScript types validate and coerce as TypeBox does: `{ type: "bigint" }` (`Type.BigInt`) and `{ type: "undefined" }` (`Type.Undefined`), with `Value.Convert`'s rules for both; the number keywords apply to numbers and bigints alike, with bounds of either kind. The `bigint()` builder builds the former.
|
||||
- `equal(a, b)`: JSON equality as `const`, `enum` and `uniqueItems` compare, a drop-in for TypeBox's `Value.Equal`.
|
||||
|
||||
### Changed
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
### Fixed
|
||||
|
||||
- A required property set to `undefined` is valid when its schema takes `undefined` (a union with `Type.Undefined()`, `Type.Unknown()`, `Type.Any()`), as in TypeBox; before, it was always missing. An optional one set to `undefined` still counts as absent.
|
||||
- Fixed local `$ref`s in schemas whose root `$id` is a relative path with a directory, such as `schemas/tool.json`: the `$id` was applied twice, so `compile` threw and `unresolvedRefs: "reject"` rejected valid tool arguments. Relative `$id`s are now also applied once with `unevaluatedProperties`/`unevaluatedItems`, when explaining errors under nested resources, and on the first coercion of a schema with `$dynamicRef` ([#11697](https://github.com/earendil-works/pi/issues/11697))
|
||||
|
||||
## [1.5.0] - 2026-11-02
|
||||
|
|
|
|||
|
|
@ -129,8 +129,8 @@ import Type from "typebox";
|
|||
const user = compile(Type.Object({ name: Type.String(), tags: Type.Array(Type.String()) }));
|
||||
```
|
||||
|
||||
TypeBox's JavaScript-only types (`BigInt`, `Undefined`, `Function`, ...), refinements and codecs are not JSON Schema
|
||||
and are not supported.
|
||||
TypeBox's `BigInt` and `Undefined` are supported (see Values). Its other JavaScript-only types (`Function`, `Symbol`,
|
||||
...), refinements and codecs are not JSON Schema and are not supported.
|
||||
|
||||
### Coercion
|
||||
|
||||
|
|
@ -164,6 +164,9 @@ Three passes run in order:
|
|||
- `integer`: as `number`, then truncated (`"3.5"` and `3.5` become 3).
|
||||
- `boolean`: `"true"`/`"false"` (any case), `"1"`/`"0"`, 1/0, `null` to `false`.
|
||||
- `null`: `"null"`, `"undefined"`, `""`, `"0"`, 0 and `false`.
|
||||
- `bigint`: integer strings (`"12"`, `"12n"`; `"12.9"` truncated), numbers truncated, `"true"`/`"false"` and
|
||||
booleans to 1n/0n, `null` to 0n.
|
||||
- `undefined`: `"undefined"`, `"null"`, `""`, `"0"`, 0, 0n, `false` and `null`.
|
||||
- `array`: a single value becomes a one-item array, then each item converts; a tuple (`prefixItems` or an `items`
|
||||
array) converts its items and never wraps.
|
||||
- `object`: each property converts by `properties`, `patternProperties` or `additionalProperties`.
|
||||
|
|
@ -184,13 +187,15 @@ coerced by any pass.
|
|||
|
||||
### Values
|
||||
|
||||
- A property whose value is `undefined` counts as absent, as in TypeBox.
|
||||
- An optional property whose value is `undefined` counts as absent, as in TypeBox. A required property set to
|
||||
`undefined` is present: valid when its schema takes `undefined` (`Type.Undefined()` in it, `Type.Unknown()`,
|
||||
`Type.Any()`), else missing.
|
||||
- `{ type: "bytes", minLength, maxLength }` matches a `Uint8Array` of that length, an extension for values that never
|
||||
pass through JSON.
|
||||
- `{ type: "bigint" }` (TypeBox's `Type.BigInt`) matches a `bigint`, with bigint `minimum`, `maximum`,
|
||||
`exclusiveMinimum`, `exclusiveMaximum` and `multipleOf`.
|
||||
- `{ type: "undefined" }` (TypeBox's `Type.Undefined`) matches `undefined`. A required property with this schema may be
|
||||
left out, since a property set to `undefined` counts as absent.
|
||||
- `{ type: "bigint" }` (TypeBox's `Type.BigInt`) matches a `bigint`. The number keywords (`minimum`, `maximum`,
|
||||
`exclusiveMinimum`, `exclusiveMaximum`, `multipleOf`) apply to numbers and bigints alike, with bounds of either kind,
|
||||
as in TypeBox.
|
||||
- `{ type: "undefined" }` (TypeBox's `Type.Undefined`) matches `undefined`.
|
||||
- `equal(a, b)` is JSON equality as `const`, `enum` and `uniqueItems` compare (TypeBox's `Value.Equal`).
|
||||
|
||||
## Supported keywords
|
||||
|
|
|
|||
|
|
@ -308,8 +308,10 @@ export function numberCheck(
|
|||
*/
|
||||
export function isMultiple(v: Bound, d: Bound): boolean {
|
||||
if (typeof v === "bigint" || typeof d === "bigint") {
|
||||
if (!Number.isInteger(Number(v)) || !Number.isInteger(Number(d)) || BigInt(d) === 0n) return false;
|
||||
return BigInt(v) % BigInt(d) === 0n;
|
||||
if ((typeof v === "number" && !Number.isInteger(v)) || (typeof d === "number" && !Number.isInteger(d)))
|
||||
return false;
|
||||
const divisor = BigInt(d);
|
||||
return divisor !== 0n && BigInt(v) % divisor === 0n;
|
||||
}
|
||||
const q = v / d;
|
||||
if (Number.isInteger(q)) return true;
|
||||
|
|
@ -367,8 +369,10 @@ export function declaredObject(
|
|||
}
|
||||
next = i + 3;
|
||||
const x = o[k];
|
||||
if (x === undefined) continue;
|
||||
const f = entries[i + 2] as number;
|
||||
// An optional property set to undefined counts as absent; a required one is present, and its schema decides
|
||||
// whether undefined is a value of it (TypeBox's Type.Undefined, Unknown, Any).
|
||||
if (x === undefined && !(f & REQUIRED)) continue;
|
||||
const c = f & CODE;
|
||||
if (c !== 0 ? !leafOk(c, x, entries[i + 1]) : !(entries[i + 1] as Check)(x)) return false;
|
||||
if (f & REQUIRED) required++;
|
||||
|
|
@ -394,7 +398,8 @@ export function openObject(
|
|||
const k = entries[i] as string;
|
||||
const x = o[k];
|
||||
const f = entries[i + 2] as number;
|
||||
if (x === undefined || (f & INHERITED && !Object.hasOwn(o, k))) {
|
||||
// Absent, or an optional property set to undefined (which counts as absent); see declaredObject.
|
||||
if ((x === undefined && !(f & REQUIRED && Object.hasOwn(o, k))) || (f & INHERITED && !Object.hasOwn(o, k))) {
|
||||
if (f & REQUIRED) return false;
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,6 +206,10 @@ export class Coercer {
|
|||
return orValue(tryBoolean(value), value);
|
||||
case "null":
|
||||
return orValue(tryNull(value), value);
|
||||
case "bigint":
|
||||
return orValue(tryBigInt(value), value);
|
||||
case "undefined":
|
||||
return orValue(tryUndefined(value), value);
|
||||
case "array":
|
||||
return this.convertArray(node, b, value);
|
||||
case "object":
|
||||
|
|
@ -424,7 +428,9 @@ function convertLiteral(literal: unknown, value: unknown): unknown {
|
|||
? tryNumber(value)
|
||||
: typeof literal === "boolean"
|
||||
? tryBoolean(value)
|
||||
: undefined;
|
||||
: typeof literal === "bigint"
|
||||
? tryBigInt(value)
|
||||
: undefined;
|
||||
return result !== undefined && result.value === literal ? result.value : value;
|
||||
}
|
||||
|
||||
|
|
@ -464,6 +470,30 @@ function bigIntOfString(value: string): bigint | undefined {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
/** TypeBox's TryBigInt: booleans to 1n/0n, numbers truncated, null and undefined to 0n, bigint-like strings. */
|
||||
function tryBigInt(value: unknown): Converted<bigint> {
|
||||
if (typeof value === "bigint") return { value };
|
||||
if (typeof value === "boolean") return { value: value ? 1n : 0n };
|
||||
if (isFiniteNumber(value)) return { value: BigInt(Math.trunc(value)) };
|
||||
if (value === null || value === undefined) return { value: 0n };
|
||||
if (typeof value !== "string") return undefined;
|
||||
const big = bigIntOfString(value);
|
||||
if (big !== undefined) return { value: big };
|
||||
const lowercase = value.toLowerCase();
|
||||
return lowercase === "false" ? { value: 0n } : lowercase === "true" ? { value: 1n } : undefined;
|
||||
}
|
||||
|
||||
/** TypeBox's TryUndefined: 0n, false, 0, null, `"undefined"`, `"null"`, `""` and `"0"` to undefined. */
|
||||
function tryUndefined(value: unknown): Converted<undefined> {
|
||||
if (value === undefined || value === null || value === 0n || value === false || value === 0)
|
||||
return { value: undefined };
|
||||
if (typeof value !== "string") return undefined;
|
||||
const lowercase = value.toLowerCase();
|
||||
return lowercase === "undefined" || lowercase === "null" || value === "" || value === "0"
|
||||
? { value: undefined }
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function tryBoolean(value: unknown): Converted<boolean> {
|
||||
if (typeof value === "bigint") return value === 0n ? { value: false } : value === 1n ? { value: true } : undefined;
|
||||
if (typeof value === "boolean") return { value };
|
||||
|
|
@ -514,6 +544,10 @@ function matchesType(value: unknown, type: string): boolean {
|
|||
return typeof value === "string";
|
||||
case "null":
|
||||
return value === null;
|
||||
case "bigint":
|
||||
return typeof value === "bigint";
|
||||
case "undefined":
|
||||
return value === undefined;
|
||||
case "array":
|
||||
return Array.isArray(value);
|
||||
case "object":
|
||||
|
|
|
|||
|
|
@ -327,7 +327,7 @@ export class Compiler {
|
|||
|
||||
private objectCore(node: SchemaObject, base: string, guard: Guard): Check | undefined {
|
||||
const properties = isSchemaObject(node.properties) ? node.properties : {};
|
||||
const required = requiredKeys(node, properties);
|
||||
const required = Array.isArray(node.required) ? node.required.filter((k) => typeof k === "string") : [];
|
||||
const patterns = isSchemaObject(node.patternProperties) ? node.patternProperties : {};
|
||||
const additional = node.additionalProperties;
|
||||
const keys = Object.keys(properties);
|
||||
|
|
@ -602,18 +602,6 @@ function tagOf(branch: SchemaObject, key: string): unknown {
|
|||
return typeof tag.const === "object" && tag.const !== null ? undefined : tag.const;
|
||||
}
|
||||
|
||||
/**
|
||||
* The keys `node` requires. An `undefined` property counts as absent, so a required property whose schema accepts only
|
||||
* `undefined` (TypeBox's `Type.Undefined()`) is satisfied by `undefined` or by leaving it out.
|
||||
*/
|
||||
export function requiredKeys(node: SchemaObject, properties: SchemaObject): string[] {
|
||||
const required = Array.isArray(node.required) ? node.required.filter((k) => typeof k === "string") : [];
|
||||
return required.filter((k) => {
|
||||
const property = Object.hasOwn(properties, k) ? properties[k] : undefined;
|
||||
return !(isSchemaObject(property) && property.type === "undefined");
|
||||
});
|
||||
}
|
||||
|
||||
/** A number keyword's value: a number, or a bigint (TypeBox's `Type.BigInt` bounds). */
|
||||
function bound(value: unknown): Bound | undefined {
|
||||
return typeof value === "number" || typeof value === "bigint" ? value : undefined;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
TYPE_CHECKS,
|
||||
uniqueCheck,
|
||||
} from "./checks.ts";
|
||||
import { type Compiler, requiredKeys } from "./compiler.ts";
|
||||
import type { Compiler } from "./compiler.ts";
|
||||
import { isSchemaObject } from "./resolver.ts";
|
||||
import { sharedRegex } from "./shared.ts";
|
||||
import type { SchemaObject, ValidationError } from "./types.ts";
|
||||
|
|
@ -179,8 +179,12 @@ export class Explainer {
|
|||
push: Push,
|
||||
): void {
|
||||
const properties = isSchemaObject(node.properties) ? node.properties : {};
|
||||
for (const k of requiredKeys(node, properties)) {
|
||||
if (!Object.hasOwn(v, k) || v[k] === undefined) push("required", "missing", pointer(path, k));
|
||||
const required = Array.isArray(node.required) ? node.required : [];
|
||||
for (const k of required) {
|
||||
if (typeof k !== "string") continue;
|
||||
// Present and set to undefined: missing unless its schema takes undefined (see declaredObject).
|
||||
const accepted = v[k] === undefined && Object.hasOwn(properties, k) && this.ok(properties[k], b, undefined);
|
||||
if (!Object.hasOwn(v, k) || (v[k] === undefined && !accepted)) push("required", "missing", pointer(path, k));
|
||||
}
|
||||
for (const k of Object.keys(properties)) {
|
||||
if (Object.hasOwn(v, k) && v[k] !== undefined) this.walk(properties[k], b, v[k], pointer(path, k), out);
|
||||
|
|
|
|||
|
|
@ -123,8 +123,7 @@ describe("Value.Convert.Array", () => {
|
|||
const R = Value.Convert(T, undefined);
|
||||
Assert.IsEqual(R, [undefined]);
|
||||
});
|
||||
// Skipped: the schema or the value has no JSON form.
|
||||
it.skip("Should Convert 10", () => {
|
||||
it("Should Convert 10", () => {
|
||||
const T = Type.Array(Type.BigInt());
|
||||
const R = Value.Convert(T, 1n);
|
||||
Assert.IsEqual(R, [1n]);
|
||||
|
|
@ -655,44 +654,37 @@ describe("Value.Convert.Literal", () => {
|
|||
// ------------------------------------------------------------------
|
||||
// LiteralBigInt
|
||||
// ------------------------------------------------------------------
|
||||
// Skipped: the schema or the value has no JSON form.
|
||||
it.skip("LiteralBigInt: Should convert 1", () => {
|
||||
it("LiteralBigInt: Should convert 1", () => {
|
||||
const T = Type.Literal(100n);
|
||||
const R = Value.Convert(T, 100n);
|
||||
Assert.IsEqual(R, 100n);
|
||||
});
|
||||
// Skipped: the schema or the value has no JSON form.
|
||||
it.skip("LiteralBigInt: Should convert 2", () => {
|
||||
it("LiteralBigInt: Should convert 2", () => {
|
||||
const T = Type.Literal(100n);
|
||||
const R = Value.Convert(T, 100);
|
||||
Assert.IsEqual(R, 100n);
|
||||
});
|
||||
// Skipped: the schema or the value has no JSON form.
|
||||
it.skip("LiteralBigInt: Should convert 3", () => {
|
||||
it("LiteralBigInt: Should convert 3", () => {
|
||||
const T = Type.Literal(1n);
|
||||
const R = Value.Convert(T, "true");
|
||||
Assert.IsEqual(R, 1n);
|
||||
});
|
||||
// Skipped: the schema or the value has no JSON form.
|
||||
it.skip("LiteralBigInt: Should convert 4", () => {
|
||||
it("LiteralBigInt: Should convert 4", () => {
|
||||
const T = Type.Literal(0n);
|
||||
const R = Value.Convert(T, "false");
|
||||
Assert.IsEqual(R, 0n);
|
||||
});
|
||||
// Skipped: the schema or the value has no JSON form.
|
||||
it.skip("LiteralBigInt: Should convert 5", () => {
|
||||
it("LiteralBigInt: Should convert 5", () => {
|
||||
const T = Type.Literal(1n);
|
||||
const R = Value.Convert(T, "1");
|
||||
Assert.IsEqual(R, 1n);
|
||||
});
|
||||
// Skipped: the schema or the value has no JSON form.
|
||||
it.skip("LiteralBigInt: Should convert 6", () => {
|
||||
it("LiteralBigInt: Should convert 6", () => {
|
||||
const T = Type.Literal(0n);
|
||||
const R = Value.Convert(T, "0");
|
||||
Assert.IsEqual(R, 0n);
|
||||
});
|
||||
// Skipped: the schema or the value has no JSON form.
|
||||
it.skip("LiteralBigInt: Should convert 7", () => {
|
||||
it("LiteralBigInt: Should convert 7", () => {
|
||||
const T = Type.Literal(100n);
|
||||
const R = Value.Convert(T, "2");
|
||||
Assert.IsEqual(R, "2");
|
||||
|
|
@ -700,44 +692,37 @@ describe("Value.Convert.Literal", () => {
|
|||
// ------------------------------------------------------------------
|
||||
// LiteralBigInt
|
||||
// ------------------------------------------------------------------
|
||||
// Skipped: the schema or the value has no JSON form.
|
||||
it.skip("LiteralBigInt: Should convert 1", () => {
|
||||
it("LiteralBigInt: Should convert 1", () => {
|
||||
const T = Type.Literal(100n);
|
||||
const R = Value.Convert(T, 100n);
|
||||
Assert.IsEqual(R, 100n);
|
||||
});
|
||||
// Skipped: the schema or the value has no JSON form.
|
||||
it.skip("LiteralBigInt: Should convert 2", () => {
|
||||
it("LiteralBigInt: Should convert 2", () => {
|
||||
const T = Type.Literal(100n);
|
||||
const R = Value.Convert(T, 100);
|
||||
Assert.IsEqual(R, 100n);
|
||||
});
|
||||
// Skipped: the schema or the value has no JSON form.
|
||||
it.skip("LiteralBigInt: Should convert 3", () => {
|
||||
it("LiteralBigInt: Should convert 3", () => {
|
||||
const T = Type.Literal(1n);
|
||||
const R = Value.Convert(T, "true");
|
||||
Assert.IsEqual(R, 1n);
|
||||
});
|
||||
// Skipped: the schema or the value has no JSON form.
|
||||
it.skip("LiteralBigInt: Should convert 4", () => {
|
||||
it("LiteralBigInt: Should convert 4", () => {
|
||||
const T = Type.Literal(0n);
|
||||
const R = Value.Convert(T, "false");
|
||||
Assert.IsEqual(R, 0n);
|
||||
});
|
||||
// Skipped: the schema or the value has no JSON form.
|
||||
it.skip("LiteralBigInt: Should convert 5", () => {
|
||||
it("LiteralBigInt: Should convert 5", () => {
|
||||
const T = Type.Literal(1n);
|
||||
const R = Value.Convert(T, "1");
|
||||
Assert.IsEqual(R, 1n);
|
||||
});
|
||||
// Skipped: the schema or the value has no JSON form.
|
||||
it.skip("LiteralBigInt: Should convert 6", () => {
|
||||
it("LiteralBigInt: Should convert 6", () => {
|
||||
const T = Type.Literal(0n);
|
||||
const R = Value.Convert(T, "0");
|
||||
Assert.IsEqual(R, 0n);
|
||||
});
|
||||
// Skipped: the schema or the value has no JSON form.
|
||||
it.skip("LiteralBigInt: Should convert 7", () => {
|
||||
it("LiteralBigInt: Should convert 7", () => {
|
||||
const T = Type.Literal(100n);
|
||||
const R = Value.Convert(T, "2");
|
||||
Assert.IsEqual(R, "2");
|
||||
|
|
|
|||
|
|
@ -138,6 +138,25 @@ describe("validate", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("bigint and undefined, as TypeBox's Value.Convert", () => {
|
||||
it("converts to a bigint like TypeBox's TryBigInt", () => {
|
||||
const schema = Type.Object({ n: Type.BigInt({ minimum: 0n }) });
|
||||
expect(compile(schema, { coerce: true }).parse({ n: "42" })).toEqual({ ok: true, value: { n: 42n } });
|
||||
expect(coerceValue(Type.BigInt(), "12n")).toBe(12n);
|
||||
expect(coerceValue(Type.BigInt(), "12.9")).toBe(12n);
|
||||
expect(coerceValue(Type.BigInt(), 3.7)).toBe(3n);
|
||||
expect(coerceValue(Type.BigInt(), true)).toBe(1n);
|
||||
expect(coerceValue(Type.BigInt(), "x")).toBe("x");
|
||||
});
|
||||
|
||||
it("converts to undefined like TypeBox's TryUndefined", () => {
|
||||
for (const value of [null, 0, 0n, false, "", "0", "null", "undefined"]) {
|
||||
expect(coerceValue(Type.Undefined(), value)).toBeUndefined();
|
||||
}
|
||||
expect(coerceValue(Type.Undefined(), "x")).toBe("x");
|
||||
});
|
||||
});
|
||||
|
||||
describe("pass 1: optional nulls", () => {
|
||||
it("drops null in optional non-nullable properties, also nested", () => {
|
||||
const schema = obj(
|
||||
|
|
|
|||
|
|
@ -460,18 +460,48 @@ describe("TypeBox's JavaScript types", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it("check Type.Undefined: only undefined, and a required undefined property may be left out", () => {
|
||||
it("check Type.Undefined: only undefined, and a required property with it must be present", () => {
|
||||
expect(compile(Type.Undefined()).check(undefined)).toBe(true);
|
||||
expect(compile(Type.Undefined()).check(null)).toBe(false);
|
||||
// A property set to undefined counts as absent, so a required one is satisfied by leaving it out too.
|
||||
const schema = Type.Object({ selection: Type.Literal("average"), principalId: Type.Undefined() });
|
||||
const validator = compile(schema);
|
||||
expect(validator.check({ selection: "average", principalId: undefined })).toBe(true);
|
||||
expect(validator.check({ selection: "average" })).toBe(true);
|
||||
expect(validator.check({ selection: "average" })).toBe(false);
|
||||
expect(validator.check({ selection: "average", principalId: "x" })).toBe(false);
|
||||
expect(validator.check({ selection: "average", principalId: null })).toBe(false);
|
||||
expect(validator.errors({ selection: "average" })).toEqual([
|
||||
{ path: "/principalId", keyword: "required", message: "missing" },
|
||||
]);
|
||||
expect(validator.errors({ selection: "average", principalId: "x" })).toEqual([
|
||||
{ path: "/principalId", keyword: "type", message: "expected undefined, got string" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("check a required property set to undefined by its schema, as TypeBox does", () => {
|
||||
// Present and undefined: valid when the schema takes undefined (Undefined in a union, Unknown, Any).
|
||||
for (const property of [Type.Union([Type.String(), Type.Undefined()]), Type.Unknown(), Type.Any()]) {
|
||||
for (const additionalProperties of [undefined, false]) {
|
||||
const validator = compile(
|
||||
Type.Object({ a: property }, additionalProperties === false ? { additionalProperties } : {}),
|
||||
);
|
||||
expect(validator.check({ a: undefined })).toBe(true);
|
||||
expect(validator.check({})).toBe(false);
|
||||
}
|
||||
}
|
||||
// A required property whose schema does not take undefined stays missing when set to undefined.
|
||||
const name = compile(Type.Object({ name: Type.String() }));
|
||||
expect(name.check({ name: undefined })).toBe(false);
|
||||
expect(name.errors({ name: undefined })).toEqual([{ path: "/name", keyword: "required", message: "missing" }]);
|
||||
// An optional property set to undefined still counts as absent.
|
||||
expect(compile(Type.Object({ name: Type.Optional(Type.String()) })).check({ name: undefined })).toBe(true);
|
||||
});
|
||||
|
||||
it("check multipleOf of bigints exactly, however large", () => {
|
||||
const five = compile(Type.BigInt({ multipleOf: 5n }));
|
||||
expect(five.check(10n ** 400n)).toBe(true);
|
||||
expect(five.check(10n ** 400n + 1n)).toBe(false);
|
||||
// A bigint is no multiple of 0 or of a fraction.
|
||||
expect(compile({ multipleOf: 0n }).check(4n)).toBe(false);
|
||||
expect(compile({ multipleOf: 0.5 }).check(3n)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue