Skip to content

Commit 0eb6ccf

Browse files
committed
Specification
1 parent 735e9c5 commit 0eb6ccf

File tree

554 files changed

+42837
-17359
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

554 files changed

+42837
-17359
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
node_modules
22
target
3+
4+
# spec
5+
spec-cases-directory
6+
spec-clone-directory

deno.jsonc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"publish": "deno run -A tasks.ts publish", // Publish Package
1212
"report": "deno run -A tasks.ts report", // Test Converage
1313
"range": "deno run -A tasks.ts range", // TypeScript Compiler Ranges
14+
"spec": "deno run -A tasks.ts spec", // Refresh JSON Schema Spec
1415
"start": "deno run -A tasks.ts start", // Run Example
1516
"syntax": "deno run -A tasks.ts syntax", // Build Parsers
1617
"test": "deno run -A tasks.ts test", // Test Project

design/website/docs/type/overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ JSON Schema Type Builder with Static Type Resolution for TypeScript
44

55
## Overview
66

7-
TypeBox types are JSON Schema fragments that can compose into more complex types. The library offers a set of types used to construct JSON Schema compliant schematics as well as a set of extended types used to model constructs native to the JavaScript language. The schematics produced by TypeBox can be passed directly to any JSON Schema compliant validator.
7+
TypeBox types are JSON Schema fragments that compose into more complex types. The library offers a set of types used to construct JSON Schema compliant schematics as well as a set of extended types used to model constructs native to the JavaScript language. The schematics produced by TypeBox can be passed directly to any JSON Schema compliant validator.
88

99
## Example
1010

docs/docs/type/overview.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<h1>Type</h1>
22
<p>JSON Schema Type Builder with Static Type Resolution for TypeScript</p>
33
<h2>Overview</h2>
4-
<p>TypeBox types are JSON Schema fragments that can compose into more complex types. The library offers a set of types used to construct JSON Schema compliant schematics as well as a set of extended types used to model constructs native to the JavaScript language. The schematics produced by TypeBox can be passed directly to any JSON Schema compliant validator.</p>
4+
<p>TypeBox types are JSON Schema fragments that compose into more complex types. The library offers a set of types used to construct JSON Schema compliant schematics as well as a set of extended types used to model constructs native to the JavaScript language. The schematics produced by TypeBox can be passed directly to any JSON Schema compliant validator.</p>
55
<h2>Example</h2>
66
<p>The following creates a User type and infers with Static.</p>
77
<pre><code class="language-typescript">import Type from &#39;typebox&#39;

readme.md

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ License: MIT
7070
7171
[Documentation](https://sinclairzx81.github.io/typebox/#/docs/type/overview) | [Example](https://www.typescriptlang.org/play/?#code/JYWwDg9gTgLgBAFQJ5gKZwGZQiOByGFVAIwgA88AoSgehrgFonmXW32POvueHb7kafo16ix4ic2oBjCADsAzvACqC1FDgBeREQB0AeWIArVNJgAKAN5wbtu-Yc26cWYpVqN2y5RvAAJgBcOmi6AMowUMByAObmAJQANI7JKfbONoRoQXgQxqYweAk+cHIAhiCoQYKoYRFRsYmpTQ7pcGDYaLDAqApB3jaoIKXAADZVeuGRMVaY0EMw2YPDI3hwAL5xya2+gXDWmZX4SlPRq2tFG81X11v0dmUVfXAH2cf1Z0U3X99w23BLoyexR+INSfwyRFedRihTgwNBCLSd3sGDmpQW+ABKzhiNxdj+a3heIRrUJxLxrSgqAAjgBXYBU3YAbSJ5J+fzw-lhrLZXw5D1Q3N5JORNjwWKowtBrQAujypc1nIThJJVWr1XxnOF0cBpCqNQbDZxqAc4Kp1FpgjVtTBdQAeA4QDBmjwAPmuzlN5s8e3lCqarX8QTeMU+-u+rQFweh0TD4ZurSx0ZOfvjjiVQA)
7272
73-
TypeBox types are JSON Schema fragments that can compose into more complex types. The library offers a set of types used to construct JSON Schema compliant schematics as well as a set of extended types used to model constructs native to the JavaScript language. The schematics produced by TypeBox can be passed directly to any JSON Schema compliant validator.
73+
TypeBox types are JSON Schema fragments that compose into more complex types. The library offers a set of types used to construct JSON Schema compliant schematics as well as a set of extended types used to model constructs native to the JavaScript language. The schematics produced by TypeBox can be passed directly to any JSON Schema compliant validator.
7474
7575
## Example
7676
@@ -211,11 +211,19 @@ import Schema from 'typebox/schema'
211211
// Compile
212212
// -------------------------------------------------------------------------------
213213

214-
const User = Schema.Compile(Type.Object({
215-
id: Type.String(),
216-
name: Type.String(),
217-
email: Type.String({ format: 'email' })
218-
}))
214+
const User = Schema.Compile({
215+
type: 'object',
216+
properties: {
217+
id: { type: 'string' },
218+
name: { type: 'string' },
219+
email: { type: 'string', format: 'email' }
220+
},
221+
required: [
222+
'id',
223+
'name',
224+
'email'
225+
]
226+
})
219227

220228
// -------------------------------------------------------------------------------
221229
// Parse
@@ -228,6 +236,60 @@ const user = User.Parse({ // const user: {
228236
}) // } = ...
229237
```
230238

239+
### Specification
240+
241+
TypeBox has support for all major JSON Schema versions. It uses a progressive specification adoption strategy where it implements the latest validation semantics for any given keyword and offers backward-compatible support for legacy drafts if the keyword can be supported in a forward-compatible way.
242+
243+
Specification Matrix: [Official JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite)
244+
245+
| Spec | 3 | 4 | 6 | 7 | 2019-09 | 2020-12 | v1 |
246+
|:-----|:--|:--|:--|:--|:--|:--|:--|
247+
| additionalItems |||||| - | - |
248+
| additionalProperties ||||||||
249+
| allOf | - |||||||
250+
| anchor | - | - | - | - ||||
251+
| anyOf | - |||||||
252+
| boolean_schema | - | - ||||||
253+
| const | - | - ||||||
254+
| contains | - | - ||||||
255+
| content | - | - | - | - ||||
256+
| default ||||||||
257+
| dependencies | 17/18 |||| - | - | - |
258+
| dependentRequired | - | - | - | - ||||
259+
| dependentSchemas | - | - | - | - ||||
260+
| enum | 14/16 |||||||
261+
| exclusiveMaximum | - | - ||||||
262+
| exclusiveMinimum | - | - ||||||
263+
| format |||||| 114/133 | - |
264+
| if-then-else | - | - | - |||||
265+
| infinite-loop-detection ||||||||
266+
| items ||||||||
267+
| maxContains | - | - | - | - ||||
268+
| maximum | 13/14 | 13/14 ||||||
269+
| maxItems ||||||||
270+
| maxLength ||||||||
271+
| maxProperties | - |||||||
272+
| minContains | - | - | - | - ||||
273+
| minimum | 12/13 | 16/17 ||||||
274+
| minItems ||||||||
275+
| minLength ||||||||
276+
| minProperties | - |||||||
277+
| multipleOf | - |||||||
278+
| not | - |||||||
279+
| oneOf | - |||||||
280+
| pattern |||||| 10/12 | 10/12 |
281+
| patternProperties ||||||| 25/26 |
282+
| prefixItems | - | - | - | - | - |||
283+
| properties ||||||||
284+
| propertyNames | - | - ||||||
285+
| recursiveRef | - | - | - | - || - | - |
286+
| ref | 23/27 | 37/45 | 67/70 | 75/78 | 79/81 | 77/79 | 77/79 |
287+
| required | 3/4 |||||||
288+
| type | 73/80 |||||||
289+
| unevaluatedItems | - | - | - | - | 55/56 | 63/71 | 63/71 |
290+
| unevaluatedProperties | - | - | - | - | 124/125 | 123/125 | 123/125 |
291+
| uniqueItems ||||||||
292+
231293
<a name="Versions"></a>
232294

233295
## Versions

task/spec/index.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*--------------------------------------------------------------------------
2+
3+
TypeBox
4+
5+
The MIT License (MIT)
6+
7+
Copyright (c) 2017-2026 Haydn Paterson
8+
9+
Permission is hereby granted, free of charge, to any person obtaining a copy
10+
of this software and associated documentation files (the "Software"), to deal
11+
in the Software without restriction, including without limitation the rights
12+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
copies of the Software, and to permit persons to whom the Software is
14+
furnished to do so, subject to the following conditions:
15+
16+
The above copyright notice and this permission notice shall be included in
17+
all copies or substantial portions of the Software.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.
26+
27+
---------------------------------------------------------------------------*/
28+
29+
export * as Spec from './refresh.ts'

task/spec/process.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*--------------------------------------------------------------------------
2+
3+
TypeBox
4+
5+
The MIT License (MIT)
6+
7+
Copyright (c) 2017-2026 Haydn Paterson
8+
9+
Permission is hereby granted, free of charge, to any person obtaining a copy
10+
of this software and associated documentation files (the "Software"), to deal
11+
in the Software without restriction, including without limitation the rights
12+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
copies of the Software, and to permit persons to whom the Software is
14+
furnished to do so, subject to the following conditions:
15+
16+
The above copyright notice and this permission notice shall be included in
17+
all copies or substantial portions of the Software.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.
26+
27+
---------------------------------------------------------------------------*/
28+
29+
import type { JSONSchemaTestFile, JSONSchemaTestGroup, JSONSchemaTestSuite } from './types.ts'
30+
31+
export type ProcessCallback = (draft: string, schema: Record<string, unknown> | boolean, value: unknown) => boolean | null
32+
33+
// ------------------------------------------------------------------
34+
// CollectJsonFiles
35+
// ------------------------------------------------------------------
36+
function collectJsonFiles(directory: string): string[] {
37+
const results: string[] = []
38+
for (const entry of Deno.readDirSync(directory)) {
39+
const full = `${directory}/${entry.name}`
40+
if (entry.isDirectory) results.push(...collectJsonFiles(full))
41+
else if (entry.isFile && entry.name.endsWith('.json')) results.push(full)
42+
}
43+
return results
44+
}
45+
function createAccumulator(input: JSONSchemaTestGroup[]): JSONSchemaTestGroup[] {
46+
return input.map((group) => ({ ...group, tests: [] }))
47+
}
48+
// ------------------------------------------------------------------
49+
// RunTest
50+
// ------------------------------------------------------------------
51+
function runTest(callback: ProcessCallback, draft: string, schema: Record<string, unknown> | boolean, data: unknown): boolean | null {
52+
try {
53+
return callback(draft, schema, data)
54+
} catch {
55+
return null
56+
}
57+
}
58+
// ------------------------------------------------------------------
59+
// ResolveDraftAndKeyword
60+
// ------------------------------------------------------------------
61+
function resolveDraftAndKeyword(sourcePath: string, rootDirectory: string): { draft: string; keyword: string } | null {
62+
const root = rootDirectory.replace(/\\/g, '/').replace(/\/$/, '')
63+
const normalized = sourcePath.replace(/\\/g, '/')
64+
if (!normalized.startsWith(root + '/')) return null
65+
const relative = normalized.slice(root.length + 1) // e.g. "draft7/optional/email.json"
66+
const slashIndex = relative.indexOf('/')
67+
if (slashIndex === -1) return null // no subdirectory — not a valid test path
68+
const draft = relative.slice(0, slashIndex)
69+
const keyword = relative.slice(slashIndex + 1).replace(/\.json$/, '')
70+
return { draft, keyword }
71+
}
72+
function resolveFailingPath(sourcePath: string): string {
73+
return sourcePath.replace(/\\/g, '/').split('/').map((s, i, a) => i === a.length - 1 ? '_' + s : s).join('/')
74+
}
75+
// ------------------------------------------------------------------
76+
// Assert: Verify processed test counts match source, and log counts
77+
// ------------------------------------------------------------------
78+
function assertCounts(suite: JSONSchemaTestSuite): void {
79+
const stats = Object.values(suite.report)
80+
const total = stats.reduce((n, s) => n + s.total, 0)
81+
const passed = stats.reduce((n, s) => n + s.passed, 0)
82+
const failed = stats.reduce((n, s) => n + s.failed, 0)
83+
console.log(`spec: total ${total}, passed ${passed}, failed ${failed}`)
84+
if (passed + failed !== total) {
85+
throw new Error(`Test count mismatch: source has ${total} tests but processed ${passed + failed}`)
86+
}
87+
}
88+
// ------------------------------------------------------------------
89+
// Process: Run all tests and split into passing/failing files
90+
// ------------------------------------------------------------------
91+
export function process(directory: string, callback: ProcessCallback = () => true): JSONSchemaTestSuite {
92+
const files: JSONSchemaTestFile[] = []
93+
const report: JSONSchemaTestSuite['report'] = {}
94+
for (const sourcePath of collectJsonFiles(directory)) {
95+
const resolved = resolveDraftAndKeyword(sourcePath, directory)
96+
if (resolved === null) continue
97+
const { draft, keyword } = resolved
98+
const source = JSON.parse(Deno.readTextFileSync(sourcePath)) as JSONSchemaTestGroup[]
99+
const passed = createAccumulator(source)
100+
const failed = createAccumulator(source)
101+
for (let i = 0; i < source.length; i++) {
102+
const schema = source[i].schema
103+
for (const test of source[i].tests) {
104+
const actual = runTest(callback, draft, schema, test.data)
105+
if (actual !== null && test.valid === actual) {
106+
passed[i].tests.push(test)
107+
} else {
108+
failed[i].tests.push(test)
109+
}
110+
}
111+
}
112+
const passedGroups = passed.filter((group) => group.tests.length > 0)
113+
const failedGroups = failed.filter((group) => group.tests.length > 0)
114+
const passedCount = passedGroups.reduce((n, g) => n + g.tests.length, 0)
115+
const failedCount = failedGroups.reduce((n, g) => n + g.tests.length, 0)
116+
const root = directory.replace(/\\/g, '/').replace(/\/$/, '')
117+
const relativePath = sourcePath.replace(/\\/g, '/').slice(root.length + 1)
118+
const relativeFailingPath = resolveFailingPath(relativePath)
119+
if (passedGroups.length > 0) {
120+
files.push({ path: relativePath, draft, keyword, failing: false, groups: passedGroups })
121+
}
122+
if (failedGroups.length > 0) {
123+
files.push({ path: relativeFailingPath, draft, keyword, failing: true, groups: failedGroups })
124+
}
125+
report[`${draft}/${keyword}`] = {
126+
passed: passedCount,
127+
failed: failedCount,
128+
total: passedCount + failedCount
129+
}
130+
}
131+
files.sort((a, b) => a.path.localeCompare(b.path))
132+
const suite = { files, report }
133+
assertCounts(suite)
134+
return suite
135+
}

0 commit comments

Comments
 (0)