Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ark/docs/public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1386,7 +1386,7 @@ Add a type-only symbol to an existing type so that the only values that satisfy
const Even = type("(number % 2)#even")
type Even = typeof Even.infer

const good: Even = even.assert(2)
const good: Even = Even.assert(2)
// TypeScript: Type 'number' is not assignable to type 'Brand<number, "even">'
const bad: Even = 5
```
Expand All @@ -1397,10 +1397,10 @@ const bad: Even = 5

```ts
// @noErrors
const even = type.number.divisibleBy(2).brand("even")
const Even = type.number.divisibleBy(2).brand("even")
type Even = typeof Even.infer

const good: Even = even.assert(2)
const good: Even = Even.assert(2)
// TypeScript: Type 'number' is not assignable to type 'Brand<number, "even">'
const bad: Even = 5
```
Expand Down
71 changes: 50 additions & 21 deletions ark/json-schema/__tests__/array.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
contextualize(() => {
it("type array", () => {
const t = jsonSchemaToType({ type: "array" })
attest<unknown[]>(t.infer)
attest(t.expression).snap("Array")
})

Expand All @@ -16,12 +17,14 @@ contextualize(() => {
type: "array",
items: { type: "string" }
})
attest<string[]>(tItems.infer)
attest(tItems.expression).snap("string[]")

const tItemsArr = jsonSchemaToType({
type: "array",
items: [{ type: "string" }, { type: "number" }]
})
attest<[string, number]>(tItemsArr.infer)
attest(tItemsArr.expression).snap("[string, number]")
})

Expand All @@ -30,17 +33,33 @@ contextualize(() => {
type: "array",
prefixItems: [{ type: "string" }, { type: "number" }]
})
attest<[string, number, ...unknown[]]>(tPrefixItems.infer)
attest(tPrefixItems.expression).snap("[string, number, ...unknown[]]")
})

it("items & prefixItems", () => {
const tItemsFalseAndPrefixItems = jsonSchemaToType({
type: "array",
prefixItems: [{ type: "string" }, { type: "number" }],
items: false
})
attest<[string, number]>(tItemsFalseAndPrefixItems.infer)

const tItemsAndPrefixItems = jsonSchemaToType({
type: "array",
prefixItems: [{ type: "string" }, { type: "number" }],
items: { type: "boolean" }
})
attest(tItemsAndPrefixItems.expression).snap(
"[string, number, ...boolean[]]"
attest<[string, number, ...boolean[]]>(tItemsAndPrefixItems.infer)

const tItemsArrayAndPrefixItems = jsonSchemaToType({
type: "array",
prefixItems: [{ type: "string" }, { type: "number" }],
items: [{ type: "boolean" }, { type: "null" }]
})
attest<[string, number, boolean, null]>(tItemsArrayAndPrefixItems.infer)
attest(tItemsArrayAndPrefixItems.expression).snap(
"[string, number, boolean, null]"
)
})

Expand All @@ -49,6 +68,7 @@ contextualize(() => {
type: "array",
additionalItems: { type: "string" }
})
attest<string[]>(tAdditionalItems.infer)
attest(tAdditionalItems.expression).snap("string[]")
})

Expand All @@ -58,21 +78,25 @@ contextualize(() => {
additionalItems: { type: "boolean" },
items: [{ type: "string" }, { type: "number" }]
})
attest<[string, number, ...boolean[]]>(tItemsVariadic.infer)
attest(tItemsVariadic.expression).snap("[string, number, ...boolean[]]")

const tItemsFalseAdditional = jsonSchemaToType({
type: "array",
additionalItems: false,
items: [{ type: "string" }]
})
attest<[string]>(tItemsFalseAdditional.infer)
attest(tItemsFalseAdditional.expression).snap("[string]")

attest(() =>
jsonSchemaToType({
type: "array",
additionalItems: { type: "string" },
items: { type: "string" }
})
attest(
() =>
// @ts-ignore Suppress 'excessively deep and possibly infinite' error
jsonSchemaToType({
type: "array",
additionalItems: { type: "string" },
items: { type: "string" }
}) as never
).throws(writeJsonSchemaArrayNonArrayItemsAndAdditionalItemsMessage())
})

Expand All @@ -88,13 +112,14 @@ contextualize(() => {
})

it("additionalItems & items & prefixItems", () => {
attest(() =>
jsonSchemaToType({
type: "array",
additionalItems: { type: "boolean" },
items: { type: "null" },
prefixItems: [{ type: "string" }, { type: "number" }]
})
attest(
() =>
jsonSchemaToType({
type: "array",
additionalItems: { type: "boolean" },
items: { type: "null" },
prefixItems: [{ type: "string" }, { type: "number" }]
}) as never
).throws(writeJsonSchemaArrayAdditionalItemsAndItemsAndPrefixItemsMessage())
})

Expand All @@ -103,6 +128,7 @@ contextualize(() => {
type: "array",
contains: { type: "number" }
})
attest<unknown[]>(tContains.infer)
attest(tContains.json).snap({
proto: "Array",
predicate: ["$ark.jsonSchemaArrayContainsValidator"]
Expand All @@ -117,27 +143,29 @@ contextualize(() => {
type: "array",
maxItems: 5
})
attest<unknown[]>(tMaxItems.infer)
attest(tMaxItems.expression).snap("Array <= 5")
})

it("maxItems (negative)", () => {
attest(() => jsonSchemaToType({ type: "array", maxItems: -1 })).throws(
"TraversalError: maxItems must be non-negative"
)
attest(
() => jsonSchemaToType({ type: "array", maxItems: -1 }) as never
).throws("TraversalError: maxItems must be non-negative")
})

it("minItems (positive)", () => {
const tMinItems = jsonSchemaToType({
type: "array",
minItems: 5
})
attest<unknown[]>(tMinItems.infer)
attest(tMinItems.expression).snap("Array >= 5")
})

it("minItems (negative)", () => {
attest(() => jsonSchemaToType({ type: "array", minItems: -1 })).throws(
"TraversalError: minItems must be non-negative"
)
attest(
() => jsonSchemaToType({ type: "array", minItems: -1 }) as never
).throws("TraversalError: minItems must be non-negative")
})

it("minItems (0)", () => {
Expand All @@ -155,6 +183,7 @@ contextualize(() => {
type: "array",
uniqueItems: true
})
attest<unknown[]>(tUniqueItems.infer)
attest(tUniqueItems.json).snap({
proto: "Array",
predicate: ["$ark.jsonSchemaArrayUniqueItemsValidator"]
Expand Down
10 changes: 7 additions & 3 deletions ark/json-schema/__tests__/composition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,24 @@ contextualize(() => {
{ type: "string", maxLength: 10 }
]
})
attest<string>(tAllOf.infer)
attest(tAllOf.expression).snap("string <= 10 & >= 1")
})

it("anyOf", () => {
const tAnyOf = jsonSchemaToType({
anyOf: [
{ type: "string", minLength: 1 },
{ type: "string", maxLength: 10 }
{ type: "string", minLength: 1, maxLength: 1 },
{ type: "number", maximum: 9 }
]
})
attest(tAnyOf.expression).snap("string <= 10 | string >= 1")
attest<string | number>(tAnyOf.infer)
attest(tAnyOf.expression).snap("number <= 9 | string == 1")
})

it("not", () => {
const tNot = jsonSchemaToType({ not: { type: "string", maxLength: 3 } })
attest<unknown>(tNot.infer)
attest(tNot.json).snap({
predicate: ["$ark.jsonSchemaNotValidator"]
})
Expand All @@ -39,6 +42,7 @@ contextualize(() => {
const tOneOf = jsonSchemaToType({
oneOf: [{ type: "string", minLength: 10 }, { const: "foo" }]
})
attest<string | "foo">(tOneOf.infer)
attest(tOneOf.json).snap({
predicate: ["$ark.jsonSchemaOneOfValidator"]
})
Expand Down
38 changes: 23 additions & 15 deletions ark/json-schema/__tests__/object.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import {
writeJsonSchemaObjectNonConformingPatternAndPropertyNamesMessage
} from "@ark/json-schema"
import { writeDuplicateKeyMessage } from "@ark/schema"
import type { Json } from "@ark/util"

contextualize(() => {
it("type object", () => {
const t = jsonSchemaToType({ type: "object" })
attest<Record<string, Json>>(t.infer)
attest(t.expression).snap("{}")
attest(t.allows({ foo: 3 }))
})
Expand All @@ -18,6 +20,7 @@ contextualize(() => {
type: "object",
maxProperties: 1
})
attest<Record<string, Json>>(tMaxProperties.infer)
attest(tMaxProperties.json).snap({
domain: "object",
predicate: ["$ark.jsonSchemaObjectMaxPropertiesValidator"]
Expand All @@ -33,6 +36,7 @@ contextualize(() => {
type: "object",
minProperties: 2
})
attest<Record<string, Json>>(tMinProperties.infer)
attest(tMinProperties.json).snap({
domain: "object",
predicate: ["$ark.jsonSchemaObjectMinPropertiesValidator"]
Expand All @@ -52,28 +56,31 @@ contextualize(() => {
},
required: ["foo"]
})
attest<{ foo: string; bar?: number }>(tRequired.infer)
attest(tRequired.expression).snap("{ foo: string, bar?: number }")

attest(() =>
jsonSchemaToType({ type: "object", required: ["foo"] })
attest(
() => jsonSchemaToType({ type: "object", required: ["foo"] }) as never
).throws(
"TraversalError: must be a valid object JSON Schema (was an object JSON Schema with 'required' array but no 'properties' object)"
)
attest(() =>
jsonSchemaToType({
type: "object",
properties: { foo: { type: "string" } },
required: ["bar"]
})
attest(
() =>
jsonSchemaToType({
type: "object",
properties: { foo: { type: "string" } },
required: ["bar"]
}) as never
).throws(
`TraversalError: required must be a key from the 'properties' object, i.e. foo (was bar)`
)
attest(() =>
jsonSchemaToType({
type: "object",
properties: { foo: { type: "string" } },
required: ["foo", "foo"]
})
attest(
() =>
jsonSchemaToType({
type: "object",
properties: { foo: { type: "string" } },
required: ["foo", "foo"]
}) as never
).throws(writeDuplicateKeyMessage("foo"))
})

Expand All @@ -83,6 +90,7 @@ contextualize(() => {
additionalProperties: { type: "number" },
properties: { bar: { type: "string" } }
})
attest<{ bar?: string } & Record<string, Json>>(tAdditionalProperties.infer)
attest(tAdditionalProperties.json).snap({
domain: "object",
optional: [{ key: "bar", value: "string" }],
Expand All @@ -101,6 +109,7 @@ contextualize(() => {
"^[a-z]+$": { type: "string" }
}
})
attest<Record<string, Json>>(tPatternProperties.infer)
attest(tPatternProperties.expression).snap("{ [/^[a-z]+$/]: string }")
attest(tPatternProperties.allows({})).equals(true)
attest(tPatternProperties.allows({ foo: "bar" })).equals(true)
Expand All @@ -118,7 +127,6 @@ contextualize(() => {
)

attest(() =>
// @ts-expect-error
jsonSchemaToType({
type: "object",
propertyNames: { type: "number" }
Expand Down
26 changes: 16 additions & 10 deletions ark/json-schema/__tests__/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { JsonSchemaOrBoolean } from "@ark/schema"
contextualize(() => {
it("type string", () => {
const t = jsonSchemaToType({ type: "string" })
attest<string>(t.infer)
attest(t.expression).snap("string")
})

Expand All @@ -13,16 +14,18 @@ contextualize(() => {
type: "string",
maxLength: 5
})
attest<string>(tMaxLength.infer)
attest(tMaxLength.expression).snap("string <= 5")
})

it("maxLength (negative)", () => {
const maxLength = -5
attest(() =>
jsonSchemaToType({
type: "string",
maxLength
})
attest(
() =>
jsonSchemaToType({
type: "string",
maxLength
}) as never
).throws(
`TraversalError: maxLength must be non-negative (was ${maxLength})`
)
Expand All @@ -33,16 +36,18 @@ contextualize(() => {
type: "string",
minLength: 5
})
attest<string>(tMinLength.infer)
attest(tMinLength.expression).snap("string >= 5")
})

it("minLength (negative)", () => {
const minLength = -1
attest(() =>
jsonSchemaToType({
type: "string",
minLength
})
attest(
() =>
jsonSchemaToType({
type: "string",
minLength
}) as never
).throws(
`TraversalError: minLength must be non-negative (was ${minLength})`
)
Expand All @@ -53,6 +58,7 @@ contextualize(() => {
type: "string",
pattern: "es"
})
attest<string>(tPatternString.infer)
attest(tPatternString.expression).snap("/es/")
// JSON Schema explicitly specifies that regexes MUST NOT be implicitly anchored
// https://json-schema.org/draft-07/draft-handrews-json-schema-validation-01#rfc.section.4.3
Expand Down
Loading
Loading