@fgv/ts-json-base 5.1.0-4 → 5.1.0-40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.js +2 -1
- package/dist/index.browser.js.map +1 -0
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -0
- package/dist/packlets/converters/converters.js +11 -0
- package/dist/packlets/converters/converters.js.map +1 -0
- package/dist/packlets/converters/index.js.map +1 -0
- package/dist/packlets/file-tree/directoryItem.js.map +1 -0
- package/dist/packlets/file-tree/fileItem.js +11 -4
- package/dist/packlets/file-tree/fileItem.js.map +1 -0
- package/dist/packlets/file-tree/fileTree.js.map +1 -0
- package/dist/packlets/file-tree/fileTreeAccessors.js.map +1 -0
- package/dist/packlets/file-tree/fileTreeHelpers.inMemory.js.map +1 -0
- package/dist/packlets/file-tree/fileTreeHelpers.js.map +1 -0
- package/dist/packlets/file-tree/filterSpec.js.map +1 -0
- package/dist/packlets/file-tree/fsTree.js +44 -13
- package/dist/packlets/file-tree/fsTree.js.map +1 -0
- package/dist/packlets/file-tree/in-memory/inMemoryTree.js +44 -13
- package/dist/packlets/file-tree/in-memory/inMemoryTree.js.map +1 -0
- package/dist/packlets/file-tree/in-memory/index.js.map +1 -0
- package/dist/packlets/file-tree/in-memory/treeBuilder.js.map +1 -0
- package/dist/packlets/file-tree/index.browser.js.map +1 -0
- package/dist/packlets/file-tree/index.js.map +1 -0
- package/dist/packlets/json/common.js.map +1 -0
- package/dist/packlets/json/index.js.map +1 -0
- package/dist/packlets/json-compatible/common.js.map +1 -0
- package/dist/packlets/json-compatible/converters.js.map +1 -0
- package/dist/packlets/json-compatible/index.js.map +1 -0
- package/dist/packlets/json-compatible/validators.js.map +1 -0
- package/dist/packlets/json-file/file.js.map +1 -0
- package/dist/packlets/json-file/index.browser.js.map +1 -0
- package/dist/packlets/json-file/index.js.map +1 -0
- package/dist/packlets/json-file/jsonFsHelper.js.map +1 -0
- package/dist/packlets/json-file/jsonLike.js.map +1 -0
- package/dist/packlets/json-file/jsonTreeHelper.js.map +1 -0
- package/dist/packlets/json-schema-builder/factories.js +342 -0
- package/dist/packlets/json-schema-builder/factories.js.map +1 -0
- package/dist/packlets/json-schema-builder/fromJson.js +371 -0
- package/dist/packlets/json-schema-builder/fromJson.js.map +1 -0
- package/dist/packlets/json-schema-builder/index.js +36 -0
- package/dist/packlets/json-schema-builder/index.js.map +1 -0
- package/dist/{test/unit/json-compatible/helpers.js → packlets/json-schema-builder/types.js} +3 -12
- package/dist/packlets/json-schema-builder/types.js.map +1 -0
- package/dist/packlets/validators/index.js.map +1 -0
- package/dist/packlets/validators/validators.js.map +1 -0
- package/dist/ts-json-base.d.ts +473 -36
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/index.browser.d.ts +2 -1
- package/lib/index.browser.d.ts.map +1 -0
- package/lib/index.browser.js +3 -1
- package/lib/index.browser.js.map +1 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +3 -1
- package/lib/index.js.map +1 -0
- package/lib/packlets/converters/converters.d.ts +27 -1
- package/lib/packlets/converters/converters.d.ts.map +1 -0
- package/lib/packlets/converters/converters.js +12 -0
- package/lib/packlets/converters/converters.js.map +1 -0
- package/lib/packlets/converters/index.d.ts.map +1 -0
- package/lib/packlets/converters/index.js.map +1 -0
- package/lib/packlets/file-tree/directoryItem.d.ts.map +1 -0
- package/lib/packlets/file-tree/directoryItem.js.map +1 -0
- package/lib/packlets/file-tree/fileItem.d.ts +11 -4
- package/lib/packlets/file-tree/fileItem.d.ts.map +1 -0
- package/lib/packlets/file-tree/fileItem.js +11 -4
- package/lib/packlets/file-tree/fileItem.js.map +1 -0
- package/lib/packlets/file-tree/fileTree.d.ts.map +1 -0
- package/lib/packlets/file-tree/fileTree.js.map +1 -0
- package/lib/packlets/file-tree/fileTreeAccessors.d.ts.map +1 -0
- package/lib/packlets/file-tree/fileTreeAccessors.js.map +1 -0
- package/lib/packlets/file-tree/fileTreeHelpers.d.ts.map +1 -0
- package/lib/packlets/file-tree/fileTreeHelpers.inMemory.d.ts.map +1 -0
- package/lib/packlets/file-tree/fileTreeHelpers.inMemory.js.map +1 -0
- package/lib/packlets/file-tree/fileTreeHelpers.js.map +1 -0
- package/lib/packlets/file-tree/filterSpec.d.ts.map +1 -0
- package/lib/packlets/file-tree/filterSpec.js.map +1 -0
- package/lib/packlets/file-tree/fsTree.d.ts +44 -13
- package/lib/packlets/file-tree/fsTree.d.ts.map +1 -0
- package/lib/packlets/file-tree/fsTree.js +44 -13
- package/lib/packlets/file-tree/fsTree.js.map +1 -0
- package/lib/packlets/file-tree/in-memory/inMemoryTree.d.ts +44 -13
- package/lib/packlets/file-tree/in-memory/inMemoryTree.d.ts.map +1 -0
- package/lib/packlets/file-tree/in-memory/inMemoryTree.js +44 -13
- package/lib/packlets/file-tree/in-memory/inMemoryTree.js.map +1 -0
- package/lib/packlets/file-tree/in-memory/index.d.ts.map +1 -0
- package/lib/packlets/file-tree/in-memory/index.js.map +1 -0
- package/lib/packlets/file-tree/in-memory/treeBuilder.d.ts.map +1 -0
- package/lib/packlets/file-tree/in-memory/treeBuilder.js.map +1 -0
- package/lib/packlets/file-tree/index.browser.d.ts.map +1 -0
- package/lib/packlets/file-tree/index.browser.js.map +1 -0
- package/lib/packlets/file-tree/index.d.ts.map +1 -0
- package/lib/packlets/file-tree/index.js.map +1 -0
- package/lib/packlets/json/common.d.ts.map +1 -0
- package/lib/packlets/json/common.js.map +1 -0
- package/lib/packlets/json/index.d.ts.map +1 -0
- package/lib/packlets/json/index.js.map +1 -0
- package/lib/packlets/json-compatible/common.d.ts.map +1 -0
- package/lib/packlets/json-compatible/common.js.map +1 -0
- package/lib/packlets/json-compatible/converters.d.ts.map +1 -0
- package/lib/packlets/json-compatible/converters.js.map +1 -0
- package/lib/packlets/json-compatible/index.d.ts.map +1 -0
- package/lib/packlets/json-compatible/index.js.map +1 -0
- package/lib/packlets/json-compatible/validators.d.ts.map +1 -0
- package/lib/packlets/json-compatible/validators.js.map +1 -0
- package/lib/packlets/json-file/file.d.ts.map +1 -0
- package/lib/packlets/json-file/file.js.map +1 -0
- package/lib/packlets/json-file/index.browser.d.ts.map +1 -0
- package/lib/packlets/json-file/index.browser.js.map +1 -0
- package/lib/packlets/json-file/index.d.ts.map +1 -0
- package/lib/packlets/json-file/index.js.map +1 -0
- package/lib/packlets/json-file/jsonFsHelper.d.ts.map +1 -0
- package/lib/packlets/json-file/jsonFsHelper.js.map +1 -0
- package/lib/packlets/json-file/jsonLike.d.ts.map +1 -0
- package/lib/packlets/json-file/jsonLike.js.map +1 -0
- package/lib/packlets/json-file/jsonTreeHelper.d.ts.map +1 -0
- package/lib/packlets/json-file/jsonTreeHelper.js.map +1 -0
- package/lib/packlets/json-schema-builder/factories.d.ts +95 -0
- package/lib/packlets/json-schema-builder/factories.d.ts.map +1 -0
- package/lib/packlets/json-schema-builder/factories.js +352 -0
- package/lib/packlets/json-schema-builder/factories.js.map +1 -0
- package/lib/packlets/json-schema-builder/fromJson.d.ts +45 -0
- package/lib/packlets/json-schema-builder/fromJson.d.ts.map +1 -0
- package/lib/packlets/json-schema-builder/fromJson.js +375 -0
- package/lib/packlets/json-schema-builder/fromJson.js.map +1 -0
- package/lib/packlets/json-schema-builder/index.d.ts +15 -0
- package/lib/packlets/json-schema-builder/index.d.ts.map +1 -0
- package/lib/packlets/json-schema-builder/index.js +52 -0
- package/lib/packlets/json-schema-builder/index.js.map +1 -0
- package/lib/packlets/json-schema-builder/types.d.ts +142 -0
- package/lib/packlets/json-schema-builder/types.d.ts.map +1 -0
- package/lib/packlets/json-schema-builder/types.js +24 -0
- package/lib/packlets/json-schema-builder/types.js.map +1 -0
- package/lib/packlets/validators/index.d.ts.map +1 -0
- package/lib/packlets/validators/index.js.map +1 -0
- package/lib/packlets/validators/validators.d.ts.map +1 -0
- package/lib/packlets/validators/validators.js.map +1 -0
- package/package.json +6 -6
- package/dist/test/fixtures/file-tree/config.json +0 -1
- package/dist/test/fixtures/file-tree/data/items.json +0 -1
- package/dist/test/fixtures/file-tree/docs/api/reference.json +0 -1
- package/dist/test/unit/data/file/bad/bad3.json +0 -3
- package/dist/test/unit/data/file/bad/thing1.json +0 -4
- package/dist/test/unit/data/file/bad/thing2.json +0 -3
- package/dist/test/unit/data/file/good/thing1.json +0 -4
- package/dist/test/unit/data/file/good/thing2.json +0 -3
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Erik Fortune
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
import { Converters, fail, mapResults, succeed } from '@fgv/ts-utils';
|
|
23
|
+
import { array, boolean, enumOf, integer, number, object, optional, string } from './factories';
|
|
24
|
+
/**
|
|
25
|
+
* Compositional / assertive keywords outside the LLM-tool subset. Their presence cannot be honored
|
|
26
|
+
* faithfully — silently dropping them would produce a converter looser than the schema describes.
|
|
27
|
+
* Pure annotations (`title`, `default`, `examples`, draft-07 `format`) carry no validation semantics
|
|
28
|
+
* and are intentionally ignored. `description` IS preserved on every node (see `_descriptionField`).
|
|
29
|
+
*/
|
|
30
|
+
const FORBIDDEN_KEYWORDS = [
|
|
31
|
+
'$ref',
|
|
32
|
+
'oneOf',
|
|
33
|
+
'anyOf',
|
|
34
|
+
'allOf',
|
|
35
|
+
'not',
|
|
36
|
+
'if',
|
|
37
|
+
'then',
|
|
38
|
+
'else',
|
|
39
|
+
'pattern'
|
|
40
|
+
];
|
|
41
|
+
/** The type values we can dispatch to; used for early error detection. */
|
|
42
|
+
const _SUPPORTED_TYPES = new Set([
|
|
43
|
+
'string',
|
|
44
|
+
'number',
|
|
45
|
+
'integer',
|
|
46
|
+
'boolean',
|
|
47
|
+
'array',
|
|
48
|
+
'object'
|
|
49
|
+
]);
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Field-level converters — extract individual fields declaratively, eliminating
|
|
52
|
+
// `from as Record<string, unknown>` in the arm bodies.
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
/**
|
|
55
|
+
* Extracts an optional `description` string. Absent values succeed as `undefined`.
|
|
56
|
+
* When present, `description` must be a string — a non-string value (e.g. a number or object)
|
|
57
|
+
* produces a descriptive failure. Pure annotations with no validation semantics are accepted;
|
|
58
|
+
* non-string values that cannot be used as a description are rejected with a clear error.
|
|
59
|
+
*/
|
|
60
|
+
const _descriptionField = Converters.optionalField('description', Converters.string);
|
|
61
|
+
/**
|
|
62
|
+
* Extracts and validates the `enum` field: must be a non-empty array of strings.
|
|
63
|
+
* Non-array input or non-string element → descriptive failure; empty array → failure.
|
|
64
|
+
*/
|
|
65
|
+
const _enumValuesField = Converters.field('enum', Converters.arrayOf(Converters.string).withConstraint((values) => values.length > 0 || fail("'enum' must be a non-empty array")));
|
|
66
|
+
/**
|
|
67
|
+
* Extracts an optional `type` field as a raw string. Used by the enum arm to detect
|
|
68
|
+
* conflicting type declarations; the pre-flight in `jsonSchemaConverter` validates type
|
|
69
|
+
* values for non-enum nodes.
|
|
70
|
+
*/
|
|
71
|
+
const _typeOptionalField = Converters.optionalField('type', Converters.string);
|
|
72
|
+
/**
|
|
73
|
+
* Checks that the input is a non-null, non-array object and returns it as
|
|
74
|
+
* `Record<string, unknown>`. This is a safe narrowing after explicit runtime guards —
|
|
75
|
+
* not an unsafe cast.
|
|
76
|
+
*/
|
|
77
|
+
const _plainObjectField = Converters.generic((v) => {
|
|
78
|
+
if (typeof v === 'object' && !Array.isArray(v) && v !== null) {
|
|
79
|
+
return succeed(v);
|
|
80
|
+
}
|
|
81
|
+
return fail('expected an object');
|
|
82
|
+
});
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Private helpers
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
/**
|
|
87
|
+
* Checks for forbidden keywords in a raw schema object (already validated as non-null object).
|
|
88
|
+
* Returns `succeed(true)` if clean; returns a `Failure` if a forbidden keyword is found.
|
|
89
|
+
*/
|
|
90
|
+
function _checkForbidden(raw) {
|
|
91
|
+
for (const keyword of FORBIDDEN_KEYWORDS) {
|
|
92
|
+
if (keyword in raw) {
|
|
93
|
+
return fail(`unsupported JSON Schema keyword '${keyword}'`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return succeed(true);
|
|
97
|
+
}
|
|
98
|
+
/** Converts an optional description string to `ISchemaOptions` form. */
|
|
99
|
+
function _descriptionOpts(description) {
|
|
100
|
+
return description !== undefined ? { description } : {};
|
|
101
|
+
}
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Per-arm converter functions — defined as function declarations so they are
|
|
104
|
+
// hoisted and can be referenced before their textual position. Each arm uses
|
|
105
|
+
// Converters.field / Converters.optionalField to extract fields declaratively
|
|
106
|
+
// rather than casting `from` to `Record<string, unknown>` and reading properties
|
|
107
|
+
// manually (the anti-pattern called out in CODING_STANDARDS §Type-Safe Validation).
|
|
108
|
+
//
|
|
109
|
+
// Arms are typed as `Converter<ISchemaValidator<JsonValue>, string>` where the
|
|
110
|
+
// context string is the current JSON Pointer path. `discriminatedObject` and `oneOf`
|
|
111
|
+
// thread the context through to each arm automatically.
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
/** String arm: extracts `description?` and delegates to the `string` factory. */
|
|
114
|
+
function _convertString(from, __self, context) {
|
|
115
|
+
/* c8 ignore next 1 - defensive default; jsonSchemaConverter always supplies the path */
|
|
116
|
+
const path = context !== null && context !== void 0 ? context : '#';
|
|
117
|
+
return _descriptionField
|
|
118
|
+
.convert(from)
|
|
119
|
+
.withErrorFormat((msg) => `${path}: ${msg}`)
|
|
120
|
+
.onSuccess((description) => succeed(string(_descriptionOpts(description))));
|
|
121
|
+
}
|
|
122
|
+
/** Number arm: extracts `description?` and delegates to the `number` factory. */
|
|
123
|
+
function _convertNumber(from, __self, context) {
|
|
124
|
+
/* c8 ignore next 1 - defensive default; jsonSchemaConverter always supplies the path */
|
|
125
|
+
const path = context !== null && context !== void 0 ? context : '#';
|
|
126
|
+
return _descriptionField
|
|
127
|
+
.convert(from)
|
|
128
|
+
.withErrorFormat((msg) => `${path}: ${msg}`)
|
|
129
|
+
.onSuccess((description) => succeed(number(_descriptionOpts(description))));
|
|
130
|
+
}
|
|
131
|
+
/** Integer arm: extracts `description?` and delegates to the `integer` factory. */
|
|
132
|
+
function _convertInteger(from, __self, context) {
|
|
133
|
+
/* c8 ignore next 1 - defensive default; jsonSchemaConverter always supplies the path */
|
|
134
|
+
const path = context !== null && context !== void 0 ? context : '#';
|
|
135
|
+
return _descriptionField
|
|
136
|
+
.convert(from)
|
|
137
|
+
.withErrorFormat((msg) => `${path}: ${msg}`)
|
|
138
|
+
.onSuccess((description) => succeed(integer(_descriptionOpts(description))));
|
|
139
|
+
}
|
|
140
|
+
/** Boolean arm: extracts `description?` and delegates to the `boolean` factory. */
|
|
141
|
+
function _convertBoolean(from, __self, context) {
|
|
142
|
+
/* c8 ignore next 1 - defensive default; jsonSchemaConverter always supplies the path */
|
|
143
|
+
const path = context !== null && context !== void 0 ? context : '#';
|
|
144
|
+
return _descriptionField
|
|
145
|
+
.convert(from)
|
|
146
|
+
.withErrorFormat((msg) => `${path}: ${msg}`)
|
|
147
|
+
.onSuccess((description) => succeed(boolean(_descriptionOpts(description))));
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Array arm — uses `Converters.field` to extract `items` as a raw unknown value (no cast),
|
|
151
|
+
* then recurses for the `items` sub-schema via `jsonSchemaConverter`.
|
|
152
|
+
* Receives the current JSON Pointer path via `context`.
|
|
153
|
+
*/
|
|
154
|
+
function _convertArray(from, __self, context) {
|
|
155
|
+
/* c8 ignore next 1 - defensive default; jsonSchemaConverter always supplies the path */
|
|
156
|
+
const path = context !== null && context !== void 0 ? context : '#';
|
|
157
|
+
// Extract `items` as an opaque unknown value — the field extractor verifies only that
|
|
158
|
+
// the key exists and that `from` is an object; type validation happens via jsonSchemaConverter.
|
|
159
|
+
const itemsResult = Converters.field('items', Converters.generic((v) => succeed(v))).convert(from);
|
|
160
|
+
if (itemsResult.isFailure()) {
|
|
161
|
+
return fail(`${path}: 'array' requires an 'items' schema`);
|
|
162
|
+
}
|
|
163
|
+
const items = itemsResult.value;
|
|
164
|
+
if (Array.isArray(items)) {
|
|
165
|
+
return fail(`${path}: tuple-form 'items' arrays are not supported`);
|
|
166
|
+
}
|
|
167
|
+
return _descriptionField
|
|
168
|
+
.convert(from)
|
|
169
|
+
.withErrorFormat((msg) => `${path}: ${msg}`)
|
|
170
|
+
.onSuccess((description) =>
|
|
171
|
+
// Forward reference to jsonSchemaConverter; safe — called at runtime, not module load.
|
|
172
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
173
|
+
jsonSchemaConverter
|
|
174
|
+
.convert(items, `${path}/items`)
|
|
175
|
+
.onSuccess((inner) => succeed(array(inner, _descriptionOpts(description)))));
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Object arm — delegates to `_parseObjectBody` for recursive property processing.
|
|
179
|
+
* Receives the current JSON Pointer path via `context`.
|
|
180
|
+
*/
|
|
181
|
+
function _convertObject(from, __self, context) {
|
|
182
|
+
/* c8 ignore next 1 - defensive default; jsonSchemaConverter always supplies the path */
|
|
183
|
+
return _parseObjectBody(from, context !== null && context !== void 0 ? context : '#');
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Enum arm — extracts `type?`, `enum`, and `description?` declaratively via field converters.
|
|
187
|
+
*
|
|
188
|
+
* L1 rejection: rejects a `type` field that conflicts with enum semantics. An enum schema's only
|
|
189
|
+
* valid type declarations are absent or `'string'`; any other value — including a union array like
|
|
190
|
+
* `['string', 'null']` that would have been caught by the union-type pre-flight for non-enum nodes —
|
|
191
|
+
* produces a descriptive failure.
|
|
192
|
+
*
|
|
193
|
+
* Receives the current JSON Pointer path via `context`.
|
|
194
|
+
*/
|
|
195
|
+
function _convertEnum(from, __self, context) {
|
|
196
|
+
/* c8 ignore next 1 - defensive default; jsonSchemaConverter always supplies the path */
|
|
197
|
+
const path = context !== null && context !== void 0 ? context : '#';
|
|
198
|
+
// Extract enum values declaratively — fails if not an array, not all strings, or empty.
|
|
199
|
+
const enumResult = _enumValuesField.convert(from);
|
|
200
|
+
if (enumResult.isFailure()) {
|
|
201
|
+
return fail(`${path}: ${enumResult.message}`);
|
|
202
|
+
}
|
|
203
|
+
const values = enumResult.value;
|
|
204
|
+
// L1: reject conflicting `type`. For enum nodes, `jsonSchemaConverter`'s union-type pre-flight
|
|
205
|
+
// is skipped (the `!('enum' in raw)` gate). Validate here instead.
|
|
206
|
+
const typeResult = _typeOptionalField.convert(from);
|
|
207
|
+
if (typeResult.isFailure()) {
|
|
208
|
+
// e.g. type: 123 — the field exists but is not a string.
|
|
209
|
+
return fail(`${path}: enum schema 'type' field must be a string or absent`);
|
|
210
|
+
}
|
|
211
|
+
if (typeResult.value !== undefined && typeResult.value !== 'string') {
|
|
212
|
+
return fail(`${path}: enum schema declares conflicting 'type' '${typeResult.value}' (must be 'string' or absent)`);
|
|
213
|
+
}
|
|
214
|
+
return _descriptionField
|
|
215
|
+
.convert(from)
|
|
216
|
+
.withErrorFormat((msg) => `${path}: ${msg}`)
|
|
217
|
+
.onSuccess((description) => succeed(enumOf(values, _descriptionOpts(description))));
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Parses the body of an `object`-type schema using field converters, then recurses into
|
|
221
|
+
* property sub-schemas via `jsonSchemaConverter`.
|
|
222
|
+
* Called after pre-flight guarantees `from` is a non-null, non-array object.
|
|
223
|
+
*/
|
|
224
|
+
function _parseObjectBody(from, path) {
|
|
225
|
+
// Extract `properties` — must be a non-array object if present.
|
|
226
|
+
const propsResult = Converters.optionalField('properties', _plainObjectField).convert(from);
|
|
227
|
+
if (propsResult.isFailure()) {
|
|
228
|
+
return fail(`${path}: 'properties' must be an object`);
|
|
229
|
+
}
|
|
230
|
+
const rawProps = propsResult.value;
|
|
231
|
+
// Extract `required` — must be an array of strings if present.
|
|
232
|
+
const requiredResult = Converters.optionalField('required', Converters.arrayOf(Converters.string)).convert(from);
|
|
233
|
+
if (requiredResult.isFailure()) {
|
|
234
|
+
return fail(`${path}: 'required' must be an array of strings`);
|
|
235
|
+
}
|
|
236
|
+
const rawRequired = requiredResult.value;
|
|
237
|
+
// Extract `additionalProperties` — must be boolean if present (schema-valued not supported).
|
|
238
|
+
const addlPropsResult = Converters.optionalField('additionalProperties', Converters.boolean).convert(from);
|
|
239
|
+
if (addlPropsResult.isFailure()) {
|
|
240
|
+
return fail(`${path}: schema-valued 'additionalProperties' is not supported`);
|
|
241
|
+
}
|
|
242
|
+
const additionalProperties = addlPropsResult.value;
|
|
243
|
+
// Extract optional description; _descriptionField always succeeds (optional string).
|
|
244
|
+
const descResult = _descriptionField.convert(from);
|
|
245
|
+
/* c8 ignore next 3 - _descriptionField always succeeds */
|
|
246
|
+
if (descResult.isFailure()) {
|
|
247
|
+
return fail(`${path}: ${descResult.message}`);
|
|
248
|
+
}
|
|
249
|
+
const description = descResult.value;
|
|
250
|
+
const requiredSet = new Set(rawRequired !== null && rawRequired !== void 0 ? rawRequired : []);
|
|
251
|
+
const propEntries = rawProps !== undefined ? Object.entries(rawProps) : [];
|
|
252
|
+
// Reject `required` keys with no matching property schema.
|
|
253
|
+
const declared = new Set(propEntries.map(([k]) => k));
|
|
254
|
+
for (const key of requiredSet) {
|
|
255
|
+
if (!declared.has(key)) {
|
|
256
|
+
return fail(`${path}: 'required' key '${key}' has no matching entry in 'properties'`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return mapResults(propEntries.map(([key, child]) =>
|
|
260
|
+
// Forward reference to jsonSchemaConverter: safe because this lambda executes only
|
|
261
|
+
// after the module is fully initialized (at parse time, not at module load).
|
|
262
|
+
// Thread the JSON Pointer path as context so nested errors are correctly attributed.
|
|
263
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
264
|
+
jsonSchemaConverter
|
|
265
|
+
.convert(child, `${path}/properties/${key}`)
|
|
266
|
+
.onSuccess((node) => succeed([key, requiredSet.has(key) ? node : optional(node)])))).onSuccess((built) => {
|
|
267
|
+
const properties = {};
|
|
268
|
+
for (const [key, node] of built) {
|
|
269
|
+
properties[key] = node;
|
|
270
|
+
}
|
|
271
|
+
return succeed(object(properties, Object.assign({
|
|
272
|
+
// JSON Schema's default (absent additionalProperties) permits extra fields;
|
|
273
|
+
// only an explicit `false` produces a strict validator.
|
|
274
|
+
additionalProperties: additionalProperties !== false }, _descriptionOpts(description))));
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
// Arm converter instances (built once, referenced by jsonSchemaConverter's dispatch).
|
|
279
|
+
// Typed as `Converter<ISchemaValidator<JsonValue>, string>` so the JSON Pointer path
|
|
280
|
+
// context flows from jsonSchemaConverter through oneOf/discriminatedObject to each arm.
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
const _stringArm = Converters.generic(_convertString);
|
|
283
|
+
const _numberArm = Converters.generic(_convertNumber);
|
|
284
|
+
const _integerArm = Converters.generic(_convertInteger);
|
|
285
|
+
const _booleanArm = Converters.generic(_convertBoolean);
|
|
286
|
+
const _arrayArm = Converters.generic(_convertArray);
|
|
287
|
+
const _objectArm = Converters.generic(_convertObject);
|
|
288
|
+
const _enumArm = Converters.generic(_convertEnum);
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
// Type-dispatched converter (non-enum nodes only).
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
const _typeDispatchConverter = Converters.discriminatedObject('type', {
|
|
293
|
+
string: _stringArm,
|
|
294
|
+
number: _numberArm,
|
|
295
|
+
integer: _integerArm,
|
|
296
|
+
boolean: _booleanArm,
|
|
297
|
+
array: _arrayArm,
|
|
298
|
+
object: _objectArm
|
|
299
|
+
});
|
|
300
|
+
/**
|
|
301
|
+
* The main converter. Parses a raw JSON Schema object into a typed schema validator
|
|
302
|
+
* for the LLM-tool subset.
|
|
303
|
+
*
|
|
304
|
+
* @remarks
|
|
305
|
+
* Performs pre-flight checks (non-object root, union type arrays, forbidden keywords,
|
|
306
|
+
* unknown types) before dispatching to the per-type arm converters.
|
|
307
|
+
*
|
|
308
|
+
* The conversion context (`TC = string`) carries the current JSON Pointer path so that
|
|
309
|
+
* error messages from nested nodes name the actual failing node (e.g.
|
|
310
|
+
* `#/properties/config/properties/inner: 'required' key '...'`) rather than always
|
|
311
|
+
* reporting `#:`. The context defaults to `'#'` when absent (top-level call).
|
|
312
|
+
*
|
|
313
|
+
* Array and object arms reference `jsonSchemaConverter` by name from inside function
|
|
314
|
+
* declarations (hoisted). By the time any arm is called at runtime, `jsonSchemaConverter`
|
|
315
|
+
* is fully initialized, so recursive sub-schema calls also go through the pre-flight checks
|
|
316
|
+
* and produce meaningful error messages.
|
|
317
|
+
*
|
|
318
|
+
* @public
|
|
319
|
+
*/
|
|
320
|
+
export const jsonSchemaConverter = Converters.generic((from, __self, context) => {
|
|
321
|
+
const path = context !== null && context !== void 0 ? context : '#';
|
|
322
|
+
// Guard: root must be a non-array object.
|
|
323
|
+
if (typeof from !== 'object' || Array.isArray(from) || from === null) {
|
|
324
|
+
return fail(`${path}: expected a JSON Schema object`);
|
|
325
|
+
}
|
|
326
|
+
const raw = from;
|
|
327
|
+
// Union type arrays: give a better error than discriminatedObject's generic message.
|
|
328
|
+
if (Array.isArray(raw.type)) {
|
|
329
|
+
return fail(`${path}: union 'type' arrays are not supported`);
|
|
330
|
+
}
|
|
331
|
+
// Forbidden keywords: check before dispatching so inputs with no `type` (e.g. just
|
|
332
|
+
// `{ $ref: '...' }`) get a specific error rather than a generic "no matching converter".
|
|
333
|
+
const forbidden = _checkForbidden(raw);
|
|
334
|
+
if (forbidden.isFailure()) {
|
|
335
|
+
return fail(`${path}: ${forbidden.message}`);
|
|
336
|
+
}
|
|
337
|
+
// Missing/unknown type (not enum): give a better error than a generic "no matching converter".
|
|
338
|
+
if (!('enum' in raw) && (typeof raw.type !== 'string' || !_SUPPORTED_TYPES.has(raw.type))) {
|
|
339
|
+
return fail(`${path}: unsupported or missing 'type'`);
|
|
340
|
+
}
|
|
341
|
+
// Enum nodes route directly to the enum arm so that validation failures (invalid enum values,
|
|
342
|
+
// conflicting type) propagate immediately — not through oneOf, which would silently try the
|
|
343
|
+
// type-dispatched arm and produce a confusing "no matching converter" message.
|
|
344
|
+
if ('enum' in raw) {
|
|
345
|
+
return _enumArm.convert(from, path);
|
|
346
|
+
}
|
|
347
|
+
return _typeDispatchConverter.convert(from, path);
|
|
348
|
+
});
|
|
349
|
+
/**
|
|
350
|
+
* Parses a raw JSON Schema object (e.g. one discovered at an MCP tool boundary) into a typed schema
|
|
351
|
+
* value within the LLM-tool subset.
|
|
352
|
+
*
|
|
353
|
+
* @remarks
|
|
354
|
+
* Because the static type cannot be recovered from a runtime value, the result is typed as the
|
|
355
|
+
* opaque supertype `ISchemaValidator<JsonValue>` — the honest type when a schema arrives at runtime,
|
|
356
|
+
* since schemas may validate strings, numbers, booleans, arrays, or objects. The `validate()` method
|
|
357
|
+
* performs real runtime validation; the derived static type is the opaque `JsonValue`.
|
|
358
|
+
*
|
|
359
|
+
* Consumers who need a narrower derived type must author the schema via the factories.
|
|
360
|
+
*
|
|
361
|
+
* Out-of-subset features fail loudly (see `FORBIDDEN_KEYWORDS`); `description` is preserved on every
|
|
362
|
+
* node; other annotations (`title`, `default`, `format`, `examples`) are silently ignored.
|
|
363
|
+
*
|
|
364
|
+
* @param json - The raw JSON Schema object to parse.
|
|
365
|
+
* @returns `Success` with the parsed schema, or `Failure` describing the first out-of-subset feature.
|
|
366
|
+
* @public
|
|
367
|
+
*/
|
|
368
|
+
export function fromJson(json) {
|
|
369
|
+
return jsonSchemaConverter.convert(json);
|
|
370
|
+
}
|
|
371
|
+
//# sourceMappingURL=fromJson.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fromJson.js","sourceRoot":"","sources":["../../../src/packlets/json-schema-builder/fromJson.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { Converter, Converters, Result, fail, mapResults, succeed } from '@fgv/ts-utils';\nimport { JsonObject, JsonValue } from '../json';\nimport { array, boolean, enumOf, integer, number, object, optional, string } from './factories';\nimport { ILlmProperties, ISchemaValidator } from './types';\n\n/**\n * Compositional / assertive keywords outside the LLM-tool subset. Their presence cannot be honored\n * faithfully — silently dropping them would produce a converter looser than the schema describes.\n * Pure annotations (`title`, `default`, `examples`, draft-07 `format`) carry no validation semantics\n * and are intentionally ignored. `description` IS preserved on every node (see `_descriptionField`).\n */\nconst FORBIDDEN_KEYWORDS: readonly string[] = [\n '$ref',\n 'oneOf',\n 'anyOf',\n 'allOf',\n 'not',\n 'if',\n 'then',\n 'else',\n 'pattern'\n];\n\n/** The type values we can dispatch to; used for early error detection. */\nconst _SUPPORTED_TYPES: ReadonlySet<string> = new Set([\n 'string',\n 'number',\n 'integer',\n 'boolean',\n 'array',\n 'object'\n]);\n\n// ---------------------------------------------------------------------------\n// Field-level converters — extract individual fields declaratively, eliminating\n// `from as Record<string, unknown>` in the arm bodies.\n// ---------------------------------------------------------------------------\n\n/**\n * Extracts an optional `description` string. Absent values succeed as `undefined`.\n * When present, `description` must be a string — a non-string value (e.g. a number or object)\n * produces a descriptive failure. Pure annotations with no validation semantics are accepted;\n * non-string values that cannot be used as a description are rejected with a clear error.\n */\nconst _descriptionField: Converter<string | undefined> = Converters.optionalField(\n 'description',\n Converters.string\n);\n\n/**\n * Extracts and validates the `enum` field: must be a non-empty array of strings.\n * Non-array input or non-string element → descriptive failure; empty array → failure.\n */\nconst _enumValuesField: Converter<string[]> = Converters.field(\n 'enum',\n Converters.arrayOf(Converters.string).withConstraint(\n (values) => values.length > 0 || fail(\"'enum' must be a non-empty array\")\n )\n);\n\n/**\n * Extracts an optional `type` field as a raw string. Used by the enum arm to detect\n * conflicting type declarations; the pre-flight in `jsonSchemaConverter` validates type\n * values for non-enum nodes.\n */\nconst _typeOptionalField: Converter<string | undefined> = Converters.optionalField('type', Converters.string);\n\n/**\n * Checks that the input is a non-null, non-array object and returns it as\n * `Record<string, unknown>`. This is a safe narrowing after explicit runtime guards —\n * not an unsafe cast.\n */\nconst _plainObjectField: Converter<Record<string, unknown>> = Converters.generic(\n (v: unknown): Result<Record<string, unknown>> => {\n if (typeof v === 'object' && !Array.isArray(v) && v !== null) {\n return succeed(v as Record<string, unknown>);\n }\n return fail('expected an object');\n }\n);\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Checks for forbidden keywords in a raw schema object (already validated as non-null object).\n * Returns `succeed(true)` if clean; returns a `Failure` if a forbidden keyword is found.\n */\nfunction _checkForbidden(raw: Record<string, unknown>): Result<true> {\n for (const keyword of FORBIDDEN_KEYWORDS) {\n if (keyword in raw) {\n return fail(`unsupported JSON Schema keyword '${keyword}'`);\n }\n }\n return succeed(true as const);\n}\n\n/** Converts an optional description string to `ISchemaOptions` form. */\nfunction _descriptionOpts(description: string | undefined): { description?: string } {\n return description !== undefined ? { description } : {};\n}\n\n// ---------------------------------------------------------------------------\n// Per-arm converter functions — defined as function declarations so they are\n// hoisted and can be referenced before their textual position. Each arm uses\n// Converters.field / Converters.optionalField to extract fields declaratively\n// rather than casting `from` to `Record<string, unknown>` and reading properties\n// manually (the anti-pattern called out in CODING_STANDARDS §Type-Safe Validation).\n//\n// Arms are typed as `Converter<ISchemaValidator<JsonValue>, string>` where the\n// context string is the current JSON Pointer path. `discriminatedObject` and `oneOf`\n// thread the context through to each arm automatically.\n// ---------------------------------------------------------------------------\n\n/** String arm: extracts `description?` and delegates to the `string` factory. */\nfunction _convertString(\n from: unknown,\n __self: Converter<ISchemaValidator<JsonValue>, string>,\n context?: string\n): Result<ISchemaValidator<JsonValue>> {\n /* c8 ignore next 1 - defensive default; jsonSchemaConverter always supplies the path */\n const path = context ?? '#';\n return _descriptionField\n .convert(from)\n .withErrorFormat((msg) => `${path}: ${msg}`)\n .onSuccess((description) =>\n succeed(string(_descriptionOpts(description)) as unknown as ISchemaValidator<JsonValue>)\n );\n}\n\n/** Number arm: extracts `description?` and delegates to the `number` factory. */\nfunction _convertNumber(\n from: unknown,\n __self: Converter<ISchemaValidator<JsonValue>, string>,\n context?: string\n): Result<ISchemaValidator<JsonValue>> {\n /* c8 ignore next 1 - defensive default; jsonSchemaConverter always supplies the path */\n const path = context ?? '#';\n return _descriptionField\n .convert(from)\n .withErrorFormat((msg) => `${path}: ${msg}`)\n .onSuccess((description) =>\n succeed(number(_descriptionOpts(description)) as unknown as ISchemaValidator<JsonValue>)\n );\n}\n\n/** Integer arm: extracts `description?` and delegates to the `integer` factory. */\nfunction _convertInteger(\n from: unknown,\n __self: Converter<ISchemaValidator<JsonValue>, string>,\n context?: string\n): Result<ISchemaValidator<JsonValue>> {\n /* c8 ignore next 1 - defensive default; jsonSchemaConverter always supplies the path */\n const path = context ?? '#';\n return _descriptionField\n .convert(from)\n .withErrorFormat((msg) => `${path}: ${msg}`)\n .onSuccess((description) =>\n succeed(integer(_descriptionOpts(description)) as unknown as ISchemaValidator<JsonValue>)\n );\n}\n\n/** Boolean arm: extracts `description?` and delegates to the `boolean` factory. */\nfunction _convertBoolean(\n from: unknown,\n __self: Converter<ISchemaValidator<JsonValue>, string>,\n context?: string\n): Result<ISchemaValidator<JsonValue>> {\n /* c8 ignore next 1 - defensive default; jsonSchemaConverter always supplies the path */\n const path = context ?? '#';\n return _descriptionField\n .convert(from)\n .withErrorFormat((msg) => `${path}: ${msg}`)\n .onSuccess((description) =>\n succeed(boolean(_descriptionOpts(description)) as unknown as ISchemaValidator<JsonValue>)\n );\n}\n\n/**\n * Array arm — uses `Converters.field` to extract `items` as a raw unknown value (no cast),\n * then recurses for the `items` sub-schema via `jsonSchemaConverter`.\n * Receives the current JSON Pointer path via `context`.\n */\nfunction _convertArray(\n from: unknown,\n __self: Converter<ISchemaValidator<JsonValue>, string>,\n context?: string\n): Result<ISchemaValidator<JsonValue>> {\n /* c8 ignore next 1 - defensive default; jsonSchemaConverter always supplies the path */\n const path = context ?? '#';\n\n // Extract `items` as an opaque unknown value — the field extractor verifies only that\n // the key exists and that `from` is an object; type validation happens via jsonSchemaConverter.\n const itemsResult = Converters.field(\n 'items',\n Converters.generic((v: unknown): Result<unknown> => succeed(v))\n ).convert(from);\n if (itemsResult.isFailure()) {\n return fail(`${path}: 'array' requires an 'items' schema`);\n }\n const items = itemsResult.value;\n if (Array.isArray(items)) {\n return fail(`${path}: tuple-form 'items' arrays are not supported`);\n }\n\n return _descriptionField\n .convert(from)\n .withErrorFormat((msg) => `${path}: ${msg}`)\n .onSuccess((description) =>\n // Forward reference to jsonSchemaConverter; safe — called at runtime, not module load.\n // eslint-disable-next-line @typescript-eslint/no-use-before-define\n jsonSchemaConverter\n .convert(items, `${path}/items`)\n .onSuccess((inner) =>\n succeed(array(inner, _descriptionOpts(description)) as unknown as ISchemaValidator<JsonValue>)\n )\n );\n}\n\n/**\n * Object arm — delegates to `_parseObjectBody` for recursive property processing.\n * Receives the current JSON Pointer path via `context`.\n */\nfunction _convertObject(\n from: unknown,\n __self: Converter<ISchemaValidator<JsonValue>, string>,\n context?: string\n): Result<ISchemaValidator<JsonValue>> {\n /* c8 ignore next 1 - defensive default; jsonSchemaConverter always supplies the path */\n return _parseObjectBody(from, context ?? '#');\n}\n\n/**\n * Enum arm — extracts `type?`, `enum`, and `description?` declaratively via field converters.\n *\n * L1 rejection: rejects a `type` field that conflicts with enum semantics. An enum schema's only\n * valid type declarations are absent or `'string'`; any other value — including a union array like\n * `['string', 'null']` that would have been caught by the union-type pre-flight for non-enum nodes —\n * produces a descriptive failure.\n *\n * Receives the current JSON Pointer path via `context`.\n */\nfunction _convertEnum(\n from: unknown,\n __self: Converter<ISchemaValidator<JsonValue>, string>,\n context?: string\n): Result<ISchemaValidator<JsonValue>> {\n /* c8 ignore next 1 - defensive default; jsonSchemaConverter always supplies the path */\n const path = context ?? '#';\n\n // Extract enum values declaratively — fails if not an array, not all strings, or empty.\n const enumResult = _enumValuesField.convert(from);\n if (enumResult.isFailure()) {\n return fail(`${path}: ${enumResult.message}`);\n }\n const values = enumResult.value;\n\n // L1: reject conflicting `type`. For enum nodes, `jsonSchemaConverter`'s union-type pre-flight\n // is skipped (the `!('enum' in raw)` gate). Validate here instead.\n const typeResult = _typeOptionalField.convert(from);\n if (typeResult.isFailure()) {\n // e.g. type: 123 — the field exists but is not a string.\n return fail(`${path}: enum schema 'type' field must be a string or absent`);\n }\n if (typeResult.value !== undefined && typeResult.value !== 'string') {\n return fail(\n `${path}: enum schema declares conflicting 'type' '${typeResult.value}' (must be 'string' or absent)`\n );\n }\n\n return _descriptionField\n .convert(from)\n .withErrorFormat((msg) => `${path}: ${msg}`)\n .onSuccess((description) =>\n succeed(enumOf(values, _descriptionOpts(description)) as unknown as ISchemaValidator<JsonValue>)\n );\n}\n\n/**\n * Parses the body of an `object`-type schema using field converters, then recurses into\n * property sub-schemas via `jsonSchemaConverter`.\n * Called after pre-flight guarantees `from` is a non-null, non-array object.\n */\nfunction _parseObjectBody(from: unknown, path: string): Result<ISchemaValidator<JsonValue>> {\n // Extract `properties` — must be a non-array object if present.\n const propsResult = Converters.optionalField('properties', _plainObjectField).convert(from);\n if (propsResult.isFailure()) {\n return fail(`${path}: 'properties' must be an object`);\n }\n const rawProps = propsResult.value;\n\n // Extract `required` — must be an array of strings if present.\n const requiredResult = Converters.optionalField('required', Converters.arrayOf(Converters.string)).convert(\n from\n );\n if (requiredResult.isFailure()) {\n return fail(`${path}: 'required' must be an array of strings`);\n }\n const rawRequired = requiredResult.value;\n\n // Extract `additionalProperties` — must be boolean if present (schema-valued not supported).\n const addlPropsResult = Converters.optionalField('additionalProperties', Converters.boolean).convert(from);\n if (addlPropsResult.isFailure()) {\n return fail(`${path}: schema-valued 'additionalProperties' is not supported`);\n }\n const additionalProperties = addlPropsResult.value;\n\n // Extract optional description; _descriptionField always succeeds (optional string).\n const descResult = _descriptionField.convert(from);\n /* c8 ignore next 3 - _descriptionField always succeeds */\n if (descResult.isFailure()) {\n return fail(`${path}: ${descResult.message}`);\n }\n const description = descResult.value;\n\n const requiredSet = new Set<string>(rawRequired ?? []);\n const propEntries: [string, unknown][] = rawProps !== undefined ? Object.entries(rawProps) : [];\n\n // Reject `required` keys with no matching property schema.\n const declared = new Set(propEntries.map(([k]) => k));\n for (const key of requiredSet) {\n if (!declared.has(key)) {\n return fail(`${path}: 'required' key '${key}' has no matching entry in 'properties'`);\n }\n }\n\n return mapResults(\n propEntries.map(([key, child]) =>\n // Forward reference to jsonSchemaConverter: safe because this lambda executes only\n // after the module is fully initialized (at parse time, not at module load).\n // Thread the JSON Pointer path as context so nested errors are correctly attributed.\n // eslint-disable-next-line @typescript-eslint/no-use-before-define\n jsonSchemaConverter\n .convert(child, `${path}/properties/${key}`)\n .onSuccess((node) => succeed([key, requiredSet.has(key) ? node : optional(node)] as const))\n )\n ).onSuccess((built) => {\n const properties: ILlmProperties = {};\n for (const [key, node] of built) {\n properties[key] = node;\n }\n return succeed(\n object(properties, {\n // JSON Schema's default (absent additionalProperties) permits extra fields;\n // only an explicit `false` produces a strict validator.\n additionalProperties: additionalProperties !== false,\n ..._descriptionOpts(description)\n }) as unknown as ISchemaValidator<JsonValue>\n );\n });\n}\n\n// ---------------------------------------------------------------------------\n// Arm converter instances (built once, referenced by jsonSchemaConverter's dispatch).\n// Typed as `Converter<ISchemaValidator<JsonValue>, string>` so the JSON Pointer path\n// context flows from jsonSchemaConverter through oneOf/discriminatedObject to each arm.\n// ---------------------------------------------------------------------------\n\nconst _stringArm: Converter<ISchemaValidator<JsonValue>, string> = Converters.generic(_convertString);\nconst _numberArm: Converter<ISchemaValidator<JsonValue>, string> = Converters.generic(_convertNumber);\nconst _integerArm: Converter<ISchemaValidator<JsonValue>, string> = Converters.generic(_convertInteger);\nconst _booleanArm: Converter<ISchemaValidator<JsonValue>, string> = Converters.generic(_convertBoolean);\nconst _arrayArm: Converter<ISchemaValidator<JsonValue>, string> = Converters.generic(_convertArray);\nconst _objectArm: Converter<ISchemaValidator<JsonValue>, string> = Converters.generic(_convertObject);\nconst _enumArm: Converter<ISchemaValidator<JsonValue>, string> = Converters.generic(_convertEnum);\n\n// ---------------------------------------------------------------------------\n// Type-dispatched converter (non-enum nodes only).\n// ---------------------------------------------------------------------------\nconst _typeDispatchConverter: Converter<ISchemaValidator<JsonValue>, string> = Converters.discriminatedObject<\n ISchemaValidator<JsonValue>,\n string,\n string\n>('type', {\n string: _stringArm,\n number: _numberArm,\n integer: _integerArm,\n boolean: _booleanArm,\n array: _arrayArm,\n object: _objectArm\n});\n\n/**\n * The main converter. Parses a raw JSON Schema object into a typed schema validator\n * for the LLM-tool subset.\n *\n * @remarks\n * Performs pre-flight checks (non-object root, union type arrays, forbidden keywords,\n * unknown types) before dispatching to the per-type arm converters.\n *\n * The conversion context (`TC = string`) carries the current JSON Pointer path so that\n * error messages from nested nodes name the actual failing node (e.g.\n * `#/properties/config/properties/inner: 'required' key '...'`) rather than always\n * reporting `#:`. The context defaults to `'#'` when absent (top-level call).\n *\n * Array and object arms reference `jsonSchemaConverter` by name from inside function\n * declarations (hoisted). By the time any arm is called at runtime, `jsonSchemaConverter`\n * is fully initialized, so recursive sub-schema calls also go through the pre-flight checks\n * and produce meaningful error messages.\n *\n * @public\n */\nexport const jsonSchemaConverter: Converter<ISchemaValidator<JsonValue>, string> = Converters.generic(\n (\n from: unknown,\n __self: Converter<ISchemaValidator<JsonValue>, string>,\n context?: string\n ): Result<ISchemaValidator<JsonValue>> => {\n const path = context ?? '#';\n\n // Guard: root must be a non-array object.\n if (typeof from !== 'object' || Array.isArray(from) || from === null) {\n return fail(`${path}: expected a JSON Schema object`);\n }\n const raw = from as Record<string, unknown>;\n\n // Union type arrays: give a better error than discriminatedObject's generic message.\n if (Array.isArray(raw.type)) {\n return fail(`${path}: union 'type' arrays are not supported`);\n }\n\n // Forbidden keywords: check before dispatching so inputs with no `type` (e.g. just\n // `{ $ref: '...' }`) get a specific error rather than a generic \"no matching converter\".\n const forbidden = _checkForbidden(raw);\n if (forbidden.isFailure()) {\n return fail(`${path}: ${forbidden.message}`);\n }\n\n // Missing/unknown type (not enum): give a better error than a generic \"no matching converter\".\n if (!('enum' in raw) && (typeof raw.type !== 'string' || !_SUPPORTED_TYPES.has(raw.type))) {\n return fail(`${path}: unsupported or missing 'type'`);\n }\n\n // Enum nodes route directly to the enum arm so that validation failures (invalid enum values,\n // conflicting type) propagate immediately — not through oneOf, which would silently try the\n // type-dispatched arm and produce a confusing \"no matching converter\" message.\n if ('enum' in raw) {\n return _enumArm.convert(from, path);\n }\n return _typeDispatchConverter.convert(from, path);\n }\n);\n\n/**\n * Parses a raw JSON Schema object (e.g. one discovered at an MCP tool boundary) into a typed schema\n * value within the LLM-tool subset.\n *\n * @remarks\n * Because the static type cannot be recovered from a runtime value, the result is typed as the\n * opaque supertype `ISchemaValidator<JsonValue>` — the honest type when a schema arrives at runtime,\n * since schemas may validate strings, numbers, booleans, arrays, or objects. The `validate()` method\n * performs real runtime validation; the derived static type is the opaque `JsonValue`.\n *\n * Consumers who need a narrower derived type must author the schema via the factories.\n *\n * Out-of-subset features fail loudly (see `FORBIDDEN_KEYWORDS`); `description` is preserved on every\n * node; other annotations (`title`, `default`, `format`, `examples`) are silently ignored.\n *\n * @param json - The raw JSON Schema object to parse.\n * @returns `Success` with the parsed schema, or `Failure` describing the first out-of-subset feature.\n * @public\n */\nexport function fromJson(json: JsonObject): Result<ISchemaValidator<JsonValue>> {\n return jsonSchemaConverter.convert(json);\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Erik Fortune
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Typed JSON Schema for the LLM-tool subset: author a schema with the factories, derive its
|
|
24
|
+
* static TypeScript type with {@link Static}, and call `schema.validate(input)` directly or
|
|
25
|
+
* emit the wire JSON Schema with `schema.toJson()` — all from the single declaration.
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* Surface change from the first-pass implementation:
|
|
29
|
+
* - `toConverter(schema)` removed — schemas ARE Validators; call `schema.validate(input)`.
|
|
30
|
+
* - `toJson(schema)` removed — call `schema.toJson()` instead.
|
|
31
|
+
* - `fromJson(json)` returns `ISchemaValidator<JsonValue>` (honest supertype — a runtime-parsed schema may validate strings, numbers, booleans, arrays, or objects, not just objects).
|
|
32
|
+
*/
|
|
33
|
+
export * from './types';
|
|
34
|
+
export * from './factories';
|
|
35
|
+
export * from './fromJson';
|
|
36
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/json-schema-builder/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;;;;;;GAUG;AACH,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n/**\n * Typed JSON Schema for the LLM-tool subset: author a schema with the factories, derive its\n * static TypeScript type with {@link Static}, and call `schema.validate(input)` directly or\n * emit the wire JSON Schema with `schema.toJson()` — all from the single declaration.\n *\n * @remarks\n * Surface change from the first-pass implementation:\n * - `toConverter(schema)` removed — schemas ARE Validators; call `schema.validate(input)`.\n * - `toJson(schema)` removed — call `schema.toJson()` instead.\n * - `fromJson(json)` returns `ISchemaValidator<JsonValue>` (honest supertype — a runtime-parsed schema may validate strings, numbers, booleans, arrays, or objects, not just objects).\n */\nexport * from './types';\nexport * from './factories';\nexport * from './fromJson';\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright (c)
|
|
2
|
+
* Copyright (c) 2026 Erik Fortune
|
|
3
3
|
*
|
|
4
4
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
5
|
* of this software and associated documentation files (the "Software"), to deal
|
|
@@ -19,14 +19,5 @@
|
|
|
19
19
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
20
|
* SOFTWARE.
|
|
21
21
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return Object.assign({ id: 'u1', name: 'Test User', tags: ['a', 'b'], meta: { active: true, score: 10 } }, overrides);
|
|
25
|
-
}
|
|
26
|
-
export function makeAlpha() {
|
|
27
|
-
return { kind: 'alpha', value: 'ok' };
|
|
28
|
-
}
|
|
29
|
-
export function makeBeta() {
|
|
30
|
-
return { kind: 'beta', count: 3 };
|
|
31
|
-
}
|
|
32
|
-
//# sourceMappingURL=helpers.js.map
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/packlets/json-schema-builder/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { Validator } from '@fgv/ts-utils';\nimport { JsonObject } from '../json';\n\n/**\n * Discriminant tag carried by every schema node.\n * @public\n */\nexport type SchemaNodeType =\n | 'string'\n | 'number'\n | 'integer'\n | 'boolean'\n | 'enum'\n | 'optional'\n | 'array'\n | 'object';\n\n/**\n * A typed JSON Schema node for the LLM-tool subset. Every value returned by the\n * schema factories implements this interface — it IS a `Validator<T>` and also\n * carries a `toJson()` method for wire-format emission.\n *\n * @remarks\n * The `__staticType` property is a phantom: it exists only in the type system to carry\n * the derived static type `T` and is never assigned at runtime (declared optional so\n * factory return values need not populate it). `Static<S>` reads this slot to recover\n * `T` from a schema value without requiring a user-supplied type assertion. A plain\n * optional property is used rather than a module-private `unique symbol` because this is\n * a published package surface — a private-symbol-keyed property cannot be named in the\n * emitted `.d.ts` (TypeBox issue #679), which would break declaration emission and API\n * Extractor for downstream consumers.\n *\n * Consumers call `schema.validate(input)` directly; no separate adapter step is required.\n * @public\n */\nexport interface ISchemaValidator<T> extends Validator<T> {\n /**\n * Phantom type carrier — type-level only, never present at runtime.\n */\n readonly __staticType?: T;\n /**\n * Runtime discriminant. Identifies the schema node kind.\n */\n readonly _type: SchemaNodeType;\n /**\n * Optional human-readable description emitted into the wire JSON Schema.\n */\n readonly description?: string;\n /**\n * Emits the standard JSON Schema (draft-07 LLM-tool subset) wire form for this schema.\n * @returns The JSON Schema object describing this schema node.\n */\n toJson(): JsonObject;\n}\n\n/**\n * Recover the derived static type `T` from a schema value.\n *\n * @remarks\n * `Static<typeof MySchema>` resolves to the TypeScript type that `schema.validate()` produces\n * and that `schema.toJson()` describes — derived, never asserted. No user-supplied `T` is ever\n * required.\n * @public\n */\nexport type Static<S extends ISchemaValidator<unknown>> = S extends ISchemaValidator<infer T> ? T : never;\n\n/**\n * A record of property schemas, as accepted by the `object` factory.\n * @public\n */\nexport type ILlmProperties = Record<string, ISchemaValidator<unknown>>;\n\n/**\n * The keys of `P` whose schemas are optional (wrapped with the `optional` modifier).\n * @public\n */\nexport type OptionalKeys<P extends ILlmProperties> = {\n [K in keyof P]: P[K] extends ISchemaValidator<infer U> ? (undefined extends U ? K : never) : never;\n}[keyof P];\n\n/**\n * The keys of `P` whose schemas are required (i.e. not optional).\n * @public\n */\nexport type RequiredKeys<P extends ILlmProperties> = Exclude<keyof P, OptionalKeys<P>>;\n\n/**\n * The static value type carried by an optional property (the inner schema's static type,\n * without the `| undefined` that optionality adds — the `?` modifier conveys that separately).\n * @public\n */\nexport type OptionalPropertyStatic<V> = V extends ISchemaValidator<infer U> ? Exclude<U, undefined> : never;\n\n/**\n * Flattens an intersection of object types into a single object type for readable derived types.\n * @public\n */\nexport type Simplify<T> = { [K in keyof T]: T[K] } & {};\n\n/**\n * The derived static type for an object built from properties `P`: required keys carry their\n * static type directly, optional keys carry theirs under a `?` modifier.\n * @public\n */\nexport type ObjectStatic<P extends ILlmProperties> = Simplify<\n { [K in RequiredKeys<P>]: Static<P[K]> } & { [K in OptionalKeys<P>]?: OptionalPropertyStatic<P[K]> }\n>;\n\n// ---------------------------------------------------------------------------\n// Legacy type aliases kept for backwards compatibility with the first-pass API.\n// These names were in the original public surface; the canonical interface is\n// now ISchemaValidator<T>. New code should use ISchemaValidator<T> directly.\n// ---------------------------------------------------------------------------\n\n/**\n * @deprecated Use `ISchemaValidator` instead.\n * @public\n */\nexport type ILlmSchema<T> = ISchemaValidator<T>;\n\n/**\n * Schema node for a JSON `string`.\n * @deprecated Use `ISchemaValidator` instead.\n * @public\n */\nexport type ILlmStringSchema = ISchemaValidator<string>;\n\n/**\n * Schema node for a JSON `number` or `integer`.\n * @deprecated Use `ISchemaValidator` instead.\n * @public\n */\nexport type ILlmNumberSchema = ISchemaValidator<number>;\n\n/**\n * Schema node for a JSON `boolean`.\n * @deprecated Use `ISchemaValidator` instead.\n * @public\n */\nexport type ILlmBooleanSchema = ISchemaValidator<boolean>;\n\n/**\n * Schema node for a closed set of string literals (`enum`).\n * @deprecated Use `ISchemaValidator` instead.\n * @public\n */\nexport type ILlmEnumSchema<T extends string> = ISchemaValidator<T>;\n\n/**\n * Wrapper marking a property as optional within an object schema.\n * @deprecated Use `ISchemaValidator` instead.\n * @public\n */\nexport type ILlmOptional<S extends ISchemaValidator<unknown>> = ISchemaValidator<Static<S> | undefined>;\n\n/**\n * Schema node for a JSON `array`.\n * @deprecated Use `ISchemaValidator` instead.\n * @public\n */\nexport type ILlmArraySchema<S extends ISchemaValidator<unknown>> = ISchemaValidator<Static<S>[]>;\n\n/**\n * Schema node for a JSON `object`.\n * @deprecated Use `ISchemaValidator` instead.\n * @public\n */\nexport type ILlmObjectSchema<P extends ILlmProperties> = ISchemaValidator<ObjectStatic<P>>;\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/validators/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,cAAc,cAAc,CAAC","sourcesContent":["/*\n * Copyright (c) 2020 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nexport * from './validators';\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validators.js","sourceRoot":"","sources":["../../../src/packlets/validators/validators.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright (c) 2023 Erik Fortune\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport { Failure, Validation, Validator, fail } from '@fgv/ts-utils';\nimport { JsonArray, JsonObject, JsonPrimitive, JsonValue, isJsonArray, isJsonObject } from '../json';\n\n/* eslint-disable @typescript-eslint/no-use-before-define */\n\n/**\n * Validation context for in-place JSON validators.\n * @public\n */\nexport interface IJsonValidatorContext {\n ignoreUndefinedProperties?: boolean;\n}\n\n/**\n * An in-place validator which validates that a supplied `unknown` value is\n * a valid {@link JsonPrimitive | JsonPrimitive}.\n * @public\n */\nexport const jsonPrimitive: Validator<JsonPrimitive, IJsonValidatorContext> =\n new Validation.Base.GenericValidator({\n validator: (\n from: unknown,\n ctx?: IJsonValidatorContext,\n self?: Validator<JsonPrimitive, IJsonValidatorContext>\n ): boolean | Failure<JsonPrimitive> => {\n if (from === null) {\n return true;\n }\n switch (typeof from) {\n case 'boolean':\n case 'string':\n return true;\n case 'number':\n if (!Number.isNaN(from)) {\n return true;\n }\n break;\n }\n if (from === undefined && ctx?.ignoreUndefinedProperties === true) {\n return true;\n }\n return fail(`\"${String(from)}\": invalid JSON primitive.`);\n }\n });\n\n/**\n * An in-place validator which validates that a supplied `unknown` value is\n * a valid {@link JsonObject | JsonObject}. Fails by default if any properties or array elements\n * are `undefined` - this default behavior can be overridden by supplying an appropriate\n * {@link Validators.IJsonValidatorContext | context} at runtime.\n * @public\n */\nexport const jsonObject: Validator<JsonObject, IJsonValidatorContext> = new Validation.Base.GenericValidator({\n validator: (\n from: unknown,\n ctx?: IJsonValidatorContext,\n self?: Validator<JsonObject, IJsonValidatorContext>\n ) => {\n if (!isJsonObject(from)) {\n return fail('invalid JSON object.');\n }\n const errors: string[] = [];\n for (const [name, value] of Object.entries(from)) {\n jsonValue.validate(value, ctx).onFailure((m) => {\n errors.push(`${name}: ${m}`);\n return fail(m);\n });\n }\n if (errors.length > 0) {\n return fail(`invalid JSON object:\\n${errors.join('\\n')}`);\n }\n return true;\n }\n});\n\n/**\n * An in-place validator which validates that a supplied `unknown` value is\n * a valid {@link JsonArray | JsonArray}. Fails by default if any properties or array elements\n * are `undefined` - this default behavior can be overridden by supplying an appropriate\n * {@link Validators.IJsonValidatorContext | context} at runtime.\n * @public\n */\nexport const jsonArray: Validator<JsonArray, IJsonValidatorContext> = new Validation.Base.GenericValidator({\n validator: (\n from: unknown,\n ctx?: IJsonValidatorContext,\n self?: Validator<JsonArray, IJsonValidatorContext>\n ) => {\n if (!isJsonArray(from)) {\n return fail('not an array');\n }\n const errors: string[] = [];\n for (let i = 0; i < from.length; i++) {\n const value = from[i];\n jsonValue.validate(value, ctx).onFailure((m) => {\n errors.push(`${i}: ${m}`);\n return fail(m);\n });\n }\n if (errors.length > 0) {\n return fail(`array contains non-json elements:\\n${errors.join('\\n')}`);\n }\n return true;\n }\n});\n\n/**\n * An in-place validator which validates that a supplied `unknown` value is\n * a valid {@link JsonValue | JsonValue}. Fails by default if any properties or array elements\n * are `undefined` - this default behavior can be overridden by supplying an appropriate\n * {@link Validators.IJsonValidatorContext | context} at runtime.\n * @public\n */\nexport const jsonValue: Validator<JsonValue, IJsonValidatorContext> = new Validation.Base.GenericValidator<\n JsonValue,\n IJsonValidatorContext\n>({\n validator: (\n from: unknown,\n ctx?: IJsonValidatorContext,\n self?: Validator<JsonValue, IJsonValidatorContext>\n ) => {\n if (isJsonArray(from)) {\n const result = jsonArray.validate(from, ctx);\n return result.success === true ? true : result;\n } else if (isJsonObject(from)) {\n const result = jsonObject.validate(from, ctx);\n return result.success === true ? true : result;\n }\n const result = jsonPrimitive.validate(from, ctx);\n return result.success === true ? true : result;\n }\n});\n\n/**\n * A `StringValidator` which validates a string in place.\n * Accepts {@link Validators.IJsonValidatorContext | IJsonValidatorContext} but ignores it.\n * @public\n */\nexport const string: Validation.Classes.StringValidator<string, IJsonValidatorContext> =\n new Validation.Classes.StringValidator<string, IJsonValidatorContext>();\n\n/**\n * A `NumberValidator` which validates a number in place.\n * Accepts {@link Validators.IJsonValidatorContext | IJsonValidatorContext} but ignores it.\n * @public\n */\nexport const number: Validation.Classes.NumberValidator<number, IJsonValidatorContext> =\n new Validation.Classes.NumberValidator<number, IJsonValidatorContext>();\n\n/**\n * A `BooleanValidator` which validates a boolean in place.\n * Accepts `IJsonValidatorContext` but ignores it.\n * @public\n */\nexport const boolean: Validator<boolean, IJsonValidatorContext> =\n new Validation.Classes.BooleanValidator<IJsonValidatorContext>();\n\n/**\n * Helper to create a validator for a literal value.\n * Accepts `IJsonValidatorContext` but ignores it.\n * Mirrors the behavior of `@fgv/ts-utils`.\n * @public\n */\nexport function literal<T>(value: T): Validator<T, IJsonValidatorContext> {\n return new Validation.Base.GenericValidator<T, IJsonValidatorContext>({\n validator: (from: unknown): boolean | Failure<T> => {\n return from === value ? true : fail(`Expected literal ${String(value)}, found ${JSON.stringify(from)}`);\n }\n });\n}\n\n/**\n * Helper function to create a `Validator` which validates `unknown` to one of a set of\n * supplied enumerated values. Anything else fails.\n *\n * @remarks\n * This JSON variant accepts an `IJsonValidatorContext` OR\n * a `ReadonlyArray<T>` as its validation context. If the context is an array, it is used to override the\n * allowed values for that validation; otherwise, the original `values` supplied at creation time are used.\n *\n * @param values - Array of allowed values.\n * @param message - Optional custom failure message.\n * @returns A new `Validator` returning `<T>`.\n * @public\n */\nexport function enumeratedValue<T>(\n values: ReadonlyArray<T>,\n message?: string\n): Validator<T, IJsonValidatorContext | ReadonlyArray<T>> {\n return new Validation.Base.GenericValidator<T, IJsonValidatorContext | ReadonlyArray<T>>({\n validator: (from: unknown, context?: IJsonValidatorContext | ReadonlyArray<T>): boolean | Failure<T> => {\n const effectiveValues = Array.isArray(context) ? (context as ReadonlyArray<T>) : values;\n const index = effectiveValues.indexOf(from as T);\n if (index >= 0) {\n return true;\n }\n return fail(message ?? `Invalid enumerated value ${JSON.stringify(from)}`);\n }\n });\n}\n"]}
|