@takeshape/json-schema 11.143.2 → 11.154.1
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/package.json +2 -2
- package/dist/converters/index.d.ts +0 -1
- package/dist/converters/index.js +0 -1
- package/dist/converters/schema-converter.d.ts +0 -64
- package/dist/converters/schema-converter.js +0 -558
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/schema-validator.d.ts +0 -36
- package/dist/schema-validator.js +0 -207
- package/dist/utils/constants.d.ts +0 -3
- package/dist/utils/constants.js +0 -56
- package/dist/utils/index.d.ts +0 -4
- package/dist/utils/index.js +0 -4
- package/dist/utils/keys.d.ts +0 -5
- package/dist/utils/keys.js +0 -8
- package/dist/utils/references.d.ts +0 -145
- package/dist/utils/references.js +0 -113
- package/dist/utils/type-utils.d.ts +0 -18
- package/dist/utils/type-utils.js +0 -55
- package/dist/utils/types.d.ts +0 -76
- package/dist/utils/types.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takeshape/json-schema",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.154.1",
|
|
4
4
|
"description": "JSON Schema validator",
|
|
5
5
|
"homepage": "https://www.takeshape.io",
|
|
6
6
|
"repository": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"ajv-formats": "3.0.1",
|
|
39
39
|
"lodash": "4.17.21",
|
|
40
40
|
"minimatch": "3.1.2",
|
|
41
|
-
"@takeshape/util": "11.
|
|
41
|
+
"@takeshape/util": "11.154.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/json-schema": "7.0.15",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './schema-converter.ts';
|
package/dist/converters/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./schema-converter.js";
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import type { JSONSchema7 } from 'json-schema';
|
|
2
|
-
export type SchemaConversionStats = {
|
|
3
|
-
propertiesCount: number;
|
|
4
|
-
stringLength: number;
|
|
5
|
-
enumValuesCount: number;
|
|
6
|
-
};
|
|
7
|
-
export type SchemaConversionResult = {
|
|
8
|
-
schema: JSONSchema7;
|
|
9
|
-
warnings: string[];
|
|
10
|
-
stats: SchemaConversionStats;
|
|
11
|
-
};
|
|
12
|
-
export declare enum SchemaConversionTarget {
|
|
13
|
-
OpenAI = "OPENAI",
|
|
14
|
-
JSONSchema = "JSON_SCHEMA"
|
|
15
|
-
}
|
|
16
|
-
export type SchemaConversionOptions = {
|
|
17
|
-
/**
|
|
18
|
-
* A target flavor.
|
|
19
|
-
*/
|
|
20
|
-
target?: SchemaConversionTarget;
|
|
21
|
-
/**
|
|
22
|
-
* Keys to include
|
|
23
|
-
*/
|
|
24
|
-
propertyFilter?: (key: string) => boolean;
|
|
25
|
-
/**
|
|
26
|
-
* @default false
|
|
27
|
-
*/
|
|
28
|
-
inlineDefinitions?: boolean;
|
|
29
|
-
/**
|
|
30
|
-
* @default 5
|
|
31
|
-
*/
|
|
32
|
-
maxDepth?: number;
|
|
33
|
-
/**
|
|
34
|
-
* Allow unknown keys when processing for the JSONSchema target.
|
|
35
|
-
*
|
|
36
|
-
* @default false
|
|
37
|
-
*/
|
|
38
|
-
allowUnknownKeys?: boolean;
|
|
39
|
-
};
|
|
40
|
-
export declare class SchemaConverter {
|
|
41
|
-
#private;
|
|
42
|
-
warnings: string[];
|
|
43
|
-
stats: SchemaConversionStats;
|
|
44
|
-
static convert(schema: JSONSchema7, options?: SchemaConversionOptions): SchemaConversionResult;
|
|
45
|
-
protected constructor(originalSchema: JSONSchema7, options?: SchemaConversionOptions);
|
|
46
|
-
private run;
|
|
47
|
-
private processSchema;
|
|
48
|
-
private processDefinitions;
|
|
49
|
-
private countDefinitions;
|
|
50
|
-
private getUsedDefinitions;
|
|
51
|
-
private addDefinition;
|
|
52
|
-
private addNullable;
|
|
53
|
-
private processRefSchema;
|
|
54
|
-
private initializeNewSchema;
|
|
55
|
-
private processObjectSchema;
|
|
56
|
-
private processArraySchema;
|
|
57
|
-
private getUnsupportedArrayKeywords;
|
|
58
|
-
private processOneOfSchema;
|
|
59
|
-
private processAnyOfSchema;
|
|
60
|
-
private processAllOfSchema;
|
|
61
|
-
private mergeAllOfSchema;
|
|
62
|
-
private processEnumSchema;
|
|
63
|
-
private checkUnsupportedKeywords;
|
|
64
|
-
}
|
|
@@ -1,558 +0,0 @@
|
|
|
1
|
-
import { assert, ensureArray } from '@takeshape/util';
|
|
2
|
-
import intersection from 'lodash/intersection.js';
|
|
3
|
-
import uniq from 'lodash/uniq.js';
|
|
4
|
-
import { getReferenceMap, isAllOfSchema, isAnyOfSchema, isArraySchema, isEnumSchema, isListSchema, isObjectSchema, isObjectSchemaWithProperties, isOneOfSchema, isRefSchema, isTupleSchema, pickJSONSchema7 } from "../utils/index.js";
|
|
5
|
-
export var SchemaConversionTarget;
|
|
6
|
-
(function (SchemaConversionTarget) {
|
|
7
|
-
SchemaConversionTarget["OpenAI"] = "OPENAI";
|
|
8
|
-
SchemaConversionTarget["JSONSchema"] = "JSON_SCHEMA";
|
|
9
|
-
})(SchemaConversionTarget || (SchemaConversionTarget = {}));
|
|
10
|
-
var SchemaProcessingMode;
|
|
11
|
-
(function (SchemaProcessingMode) {
|
|
12
|
-
SchemaProcessingMode["Schema"] = "SCHEMA";
|
|
13
|
-
SchemaProcessingMode["Definition"] = "DEFINITION";
|
|
14
|
-
SchemaProcessingMode["Count"] = "COUNT";
|
|
15
|
-
})(SchemaProcessingMode || (SchemaProcessingMode = {}));
|
|
16
|
-
export class SchemaConverter {
|
|
17
|
-
warnings = [];
|
|
18
|
-
stats = {
|
|
19
|
-
propertiesCount: 0,
|
|
20
|
-
stringLength: 0,
|
|
21
|
-
enumValuesCount: 0
|
|
22
|
-
};
|
|
23
|
-
#schema;
|
|
24
|
-
#config;
|
|
25
|
-
#usedDefinitions = new Set();
|
|
26
|
-
#definitions = {};
|
|
27
|
-
#definitionsReferences = new Map();
|
|
28
|
-
static convert(schema, options = {}) {
|
|
29
|
-
return new SchemaConverter(schema, options).run();
|
|
30
|
-
}
|
|
31
|
-
constructor(originalSchema, options = {}) {
|
|
32
|
-
const { propertyFilter, target = SchemaConversionTarget.JSONSchema, inlineDefinitions = false, maxDepth = 5, allowUnknownKeys = false } = options;
|
|
33
|
-
this.#config = {
|
|
34
|
-
target,
|
|
35
|
-
maxDepth,
|
|
36
|
-
allowUnknownKeys,
|
|
37
|
-
propertyFilter,
|
|
38
|
-
inlineDefinitions
|
|
39
|
-
};
|
|
40
|
-
const { $defs, definitions, ...schema } = originalSchema;
|
|
41
|
-
this.#schema = schema;
|
|
42
|
-
if ($defs || definitions) {
|
|
43
|
-
const defs = { ...$defs, ...definitions };
|
|
44
|
-
this.#definitionsReferences = getReferenceMap(defs);
|
|
45
|
-
this.#definitions = this.processDefinitions(defs);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
run() {
|
|
49
|
-
const { target, inlineDefinitions } = this.#config;
|
|
50
|
-
// Process the schema
|
|
51
|
-
const result = this.processSchema({
|
|
52
|
-
processingMode: SchemaProcessingMode.Schema,
|
|
53
|
-
isRequired: true,
|
|
54
|
-
path: [],
|
|
55
|
-
depth: 0
|
|
56
|
-
}, this.#schema);
|
|
57
|
-
if (!inlineDefinitions && this.#usedDefinitions.size) {
|
|
58
|
-
const defs = this.getUsedDefinitions();
|
|
59
|
-
if (Object.keys(defs).length) {
|
|
60
|
-
result.$defs = defs;
|
|
61
|
-
this.countDefinitions(defs);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
// Check the total property count
|
|
65
|
-
if (target === SchemaConversionTarget.OpenAI && this.stats.propertiesCount > 100) {
|
|
66
|
-
this.warnings.push(`Schema has ${this.stats.propertiesCount} total object properties, exceeding the 100 limit`);
|
|
67
|
-
}
|
|
68
|
-
// Check string length total
|
|
69
|
-
if (target === SchemaConversionTarget.OpenAI && this.stats.stringLength > 15000) {
|
|
70
|
-
this.warnings.push(`Total string length of property names, definition names, enum values exceeds the 15,000 limit (${this.stats.stringLength})`);
|
|
71
|
-
}
|
|
72
|
-
// Check enum values count
|
|
73
|
-
if (target === SchemaConversionTarget.OpenAI && this.stats.enumValuesCount > 500) {
|
|
74
|
-
this.warnings.push(`Schema has ${this.stats.enumValuesCount} enum values across all properties, exceeding the 500 limit`);
|
|
75
|
-
}
|
|
76
|
-
// Check if root is an object
|
|
77
|
-
if (target === SchemaConversionTarget.OpenAI && !isObjectSchemaWithProperties(result)) {
|
|
78
|
-
this.warnings.push('Root level must be an object type, wrapping in an object');
|
|
79
|
-
// Attempt to fix by wrapping in an object if possible
|
|
80
|
-
return {
|
|
81
|
-
schema: {
|
|
82
|
-
type: 'object',
|
|
83
|
-
properties: {
|
|
84
|
-
root: result
|
|
85
|
-
},
|
|
86
|
-
required: ['root'],
|
|
87
|
-
additionalProperties: false
|
|
88
|
-
},
|
|
89
|
-
warnings: this.warnings,
|
|
90
|
-
stats: this.stats
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
schema: result,
|
|
95
|
-
warnings: this.warnings,
|
|
96
|
-
stats: this.stats
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
processSchema(context, schema) {
|
|
100
|
-
assert(typeof schema === 'object', 'Schema must be an object');
|
|
101
|
-
const { depth, path, isRequired, processingMode } = context;
|
|
102
|
-
const { target, maxDepth } = this.#config;
|
|
103
|
-
// Clone the schema to avoid modifying the original
|
|
104
|
-
const schemaCopy = { ...schema };
|
|
105
|
-
const currentPath = path.join('.') || 'root';
|
|
106
|
-
// Check for unsupported keywords
|
|
107
|
-
this.checkUnsupportedKeywords(schemaCopy, currentPath);
|
|
108
|
-
// Check depth limit
|
|
109
|
-
if (depth > maxDepth) {
|
|
110
|
-
this.warnings.push(`Nesting depth exceeds limit (max 5) at ${currentPath}`);
|
|
111
|
-
// Simplify the schema at this level
|
|
112
|
-
return this.addNullable({ type: 'string', description: 'Simplified due to excessive nesting' }, isRequired);
|
|
113
|
-
}
|
|
114
|
-
// Handle $ref
|
|
115
|
-
if (isRefSchema(schemaCopy)) {
|
|
116
|
-
return this.processRefSchema(context, schemaCopy);
|
|
117
|
-
}
|
|
118
|
-
// Process specific types
|
|
119
|
-
if (isObjectSchema(schemaCopy)) {
|
|
120
|
-
return this.addNullable(this.processObjectSchema(context, schemaCopy), isRequired);
|
|
121
|
-
}
|
|
122
|
-
if (isArraySchema(schemaCopy)) {
|
|
123
|
-
return this.addNullable(this.processArraySchema(context, schemaCopy), isRequired);
|
|
124
|
-
}
|
|
125
|
-
if (isAnyOfSchema(schemaCopy)) {
|
|
126
|
-
return this.addNullable(this.processAnyOfSchema(context, schemaCopy), isRequired);
|
|
127
|
-
}
|
|
128
|
-
if (isOneOfSchema(schemaCopy)) {
|
|
129
|
-
if (target === SchemaConversionTarget.OpenAI) {
|
|
130
|
-
this.warnings.push(`oneOf at ${currentPath} converted to anyOf (OpenAI only supports anyOf)`);
|
|
131
|
-
const { oneOf, ...rest } = schemaCopy;
|
|
132
|
-
return this.addNullable(this.processAnyOfSchema(context, { ...rest, anyOf: oneOf }), isRequired);
|
|
133
|
-
}
|
|
134
|
-
return this.addNullable(this.processOneOfSchema(context, schemaCopy), isRequired);
|
|
135
|
-
}
|
|
136
|
-
if (isAllOfSchema(schemaCopy)) {
|
|
137
|
-
if (target === SchemaConversionTarget.OpenAI) {
|
|
138
|
-
this.warnings.push(`allOf at ${currentPath} is not directly supported, attempting to merge schemas`);
|
|
139
|
-
return this.addNullable(this.mergeAllOfSchema(context, schemaCopy), isRequired);
|
|
140
|
-
}
|
|
141
|
-
return this.addNullable(this.processAllOfSchema(context, schemaCopy), isRequired);
|
|
142
|
-
}
|
|
143
|
-
if (isEnumSchema(schemaCopy)) {
|
|
144
|
-
return this.addNullable(this.processEnumSchema(context, schemaCopy), isRequired);
|
|
145
|
-
}
|
|
146
|
-
if (target === SchemaConversionTarget.OpenAI && schemaCopy.default) {
|
|
147
|
-
const defaultText = `Default value: ${typeof schemaCopy.default === 'object' ? JSON.stringify(schemaCopy.default) : schemaCopy.default}`;
|
|
148
|
-
schemaCopy.description = schema.description ? `${schema.description} ${defaultText}` : defaultText;
|
|
149
|
-
// biome-ignore lint/performance/noDelete: don't want to leave cruft
|
|
150
|
-
delete schemaCopy.default;
|
|
151
|
-
}
|
|
152
|
-
// Update string length total for const values
|
|
153
|
-
if (schemaCopy.const &&
|
|
154
|
-
typeof schemaCopy.const === 'string' &&
|
|
155
|
-
(processingMode === SchemaProcessingMode.Schema || processingMode === SchemaProcessingMode.Count)) {
|
|
156
|
-
this.stats.stringLength += schemaCopy.const.length;
|
|
157
|
-
}
|
|
158
|
-
const newSchema = this.initializeNewSchema(schemaCopy, {
|
|
159
|
-
type: schemaCopy.type
|
|
160
|
-
});
|
|
161
|
-
return this.addNullable(newSchema, isRequired);
|
|
162
|
-
}
|
|
163
|
-
processDefinitions(defs) {
|
|
164
|
-
return Object.entries(defs).reduce((acc, [defName, def]) => {
|
|
165
|
-
acc[defName] = this.processSchema({
|
|
166
|
-
processingMode: SchemaProcessingMode.Definition,
|
|
167
|
-
isRequired: true,
|
|
168
|
-
path: [],
|
|
169
|
-
depth: 0
|
|
170
|
-
}, def);
|
|
171
|
-
return acc;
|
|
172
|
-
}, {});
|
|
173
|
-
}
|
|
174
|
-
countDefinitions(defs) {
|
|
175
|
-
for (const def of Object.values(defs)) {
|
|
176
|
-
this.processSchema({
|
|
177
|
-
processingMode: SchemaProcessingMode.Count,
|
|
178
|
-
isRequired: true,
|
|
179
|
-
path: [],
|
|
180
|
-
depth: 0
|
|
181
|
-
}, def);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
getUsedDefinitions() {
|
|
185
|
-
const defs = {};
|
|
186
|
-
for (const usedDef of this.#usedDefinitions) {
|
|
187
|
-
this.addDefinition(defs, usedDef);
|
|
188
|
-
// Add any definitions that are referenced by the used definition
|
|
189
|
-
const defRefs = this.#definitionsReferences.get(usedDef);
|
|
190
|
-
if (defRefs) {
|
|
191
|
-
for (const ref of defRefs) {
|
|
192
|
-
this.addDefinition(defs, ref);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return defs;
|
|
197
|
-
}
|
|
198
|
-
addDefinition(defs, key) {
|
|
199
|
-
const definition = this.#definitions[key];
|
|
200
|
-
if (definition) {
|
|
201
|
-
this.stats.stringLength += key.length;
|
|
202
|
-
defs[key] = definition;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
addNullable(schema, isRequired) {
|
|
206
|
-
if (this.#config.target === SchemaConversionTarget.JSONSchema) {
|
|
207
|
-
return schema;
|
|
208
|
-
}
|
|
209
|
-
if (isRequired) {
|
|
210
|
-
return schema;
|
|
211
|
-
}
|
|
212
|
-
if (schema.type && !isObjectSchema(schema) && !isArraySchema(schema)) {
|
|
213
|
-
return {
|
|
214
|
-
...schema,
|
|
215
|
-
type: uniq([...ensureArray(schema.type), 'null'])
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
return {
|
|
219
|
-
anyOf: [...(schema.anyOf ? schema.anyOf : [schema]), { type: 'null' }]
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
processRefSchema(context, schema) {
|
|
223
|
-
const { isRequired, processingMode } = context;
|
|
224
|
-
const newSchema = {
|
|
225
|
-
$ref: schema.$ref
|
|
226
|
-
};
|
|
227
|
-
// Normalize definitions path
|
|
228
|
-
if (newSchema.$ref.startsWith('#/definitions/')) {
|
|
229
|
-
newSchema.$ref = newSchema.$ref.replace('#/definitions/', '#/$defs/');
|
|
230
|
-
}
|
|
231
|
-
const defName = newSchema.$ref.replace('#/$defs/', '');
|
|
232
|
-
if (defName && this.#config.inlineDefinitions && this.#definitions[defName]) {
|
|
233
|
-
return this.processSchema(context, this.#definitions[defName]);
|
|
234
|
-
}
|
|
235
|
-
if (processingMode === SchemaProcessingMode.Schema) {
|
|
236
|
-
this.#usedDefinitions.add(defName);
|
|
237
|
-
}
|
|
238
|
-
return this.addNullable(newSchema, isRequired);
|
|
239
|
-
}
|
|
240
|
-
initializeNewSchema(schema, newSchema) {
|
|
241
|
-
// OpenAI will only allow some very specific properties, so start fairly clean
|
|
242
|
-
if (this.#config.target === SchemaConversionTarget.OpenAI) {
|
|
243
|
-
let base = {
|
|
244
|
-
title: schema.title,
|
|
245
|
-
description: schema.description
|
|
246
|
-
};
|
|
247
|
-
if (newSchema.type === 'object') {
|
|
248
|
-
base = {
|
|
249
|
-
...base,
|
|
250
|
-
required: Object.keys(newSchema.properties ?? schema.properties ?? {}),
|
|
251
|
-
additionalProperties: false
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
else if (newSchema.type === 'array') {
|
|
255
|
-
base = {
|
|
256
|
-
...base,
|
|
257
|
-
items: []
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
else if (newSchema.type === 'number' ||
|
|
261
|
-
newSchema.type === 'integer' ||
|
|
262
|
-
newSchema.type === 'string' ||
|
|
263
|
-
newSchema.type === 'boolean') {
|
|
264
|
-
base = {
|
|
265
|
-
...base,
|
|
266
|
-
const: schema.const
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
return {
|
|
270
|
-
...base,
|
|
271
|
-
...newSchema
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
return {
|
|
275
|
-
// We may be converting a schema with non-spec properties
|
|
276
|
-
...(this.#config.allowUnknownKeys ? schema : pickJSONSchema7(schema)),
|
|
277
|
-
...newSchema
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
processObjectSchema(context, schema) {
|
|
281
|
-
const { depth, path, processingMode } = context;
|
|
282
|
-
const { target, propertyFilter } = this.#config;
|
|
283
|
-
const newSchema = this.initializeNewSchema(schema, {
|
|
284
|
-
type: 'object'
|
|
285
|
-
});
|
|
286
|
-
const uniqueRequired = new Set(schema.required ?? []);
|
|
287
|
-
const newSchemaProperties = {};
|
|
288
|
-
// Process properties
|
|
289
|
-
if (schema.properties) {
|
|
290
|
-
// Process each property
|
|
291
|
-
for (const [key, property] of Object.entries(schema.properties)) {
|
|
292
|
-
// Skip properties that are not in propertyKeys
|
|
293
|
-
if (propertyFilter && !propertyFilter(key)) {
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
newSchemaProperties[key] = this.processSchema({
|
|
297
|
-
...context,
|
|
298
|
-
path: [...path, key],
|
|
299
|
-
depth: depth + 1,
|
|
300
|
-
isRequired: uniqueRequired.has(key)
|
|
301
|
-
}, property);
|
|
302
|
-
// Update string length total for property names
|
|
303
|
-
if (processingMode === SchemaProcessingMode.Schema || processingMode === SchemaProcessingMode.Count) {
|
|
304
|
-
this.stats.stringLength += key.length;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
const newPropertyKeys = Object.keys(newSchemaProperties);
|
|
309
|
-
// Update total property count
|
|
310
|
-
if (processingMode === SchemaProcessingMode.Schema || processingMode === SchemaProcessingMode.Count) {
|
|
311
|
-
this.stats.propertiesCount += newPropertyKeys.length;
|
|
312
|
-
}
|
|
313
|
-
let required = schema.required;
|
|
314
|
-
if (target === SchemaConversionTarget.OpenAI) {
|
|
315
|
-
// OpenAI requires all properties to be required
|
|
316
|
-
required = newPropertyKeys;
|
|
317
|
-
}
|
|
318
|
-
else if (required) {
|
|
319
|
-
required = intersection([...uniqueRequired], newPropertyKeys);
|
|
320
|
-
}
|
|
321
|
-
return {
|
|
322
|
-
...newSchema,
|
|
323
|
-
properties: newSchemaProperties,
|
|
324
|
-
// Make all properties required for openai target, for json-schema some may have been removed
|
|
325
|
-
required
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
processArraySchema(context, schema) {
|
|
329
|
-
const { depth, path } = context;
|
|
330
|
-
const newSchema = this.initializeNewSchema(schema, {
|
|
331
|
-
type: 'array'
|
|
332
|
-
});
|
|
333
|
-
// Process items schema
|
|
334
|
-
if (schema.items) {
|
|
335
|
-
if (isTupleSchema(schema)) {
|
|
336
|
-
newSchema.items = schema.items.map((item, index) => this.processSchema({
|
|
337
|
-
...context,
|
|
338
|
-
path: [...path, 'items', index],
|
|
339
|
-
depth: depth + 1
|
|
340
|
-
}, item));
|
|
341
|
-
}
|
|
342
|
-
if (isListSchema(schema)) {
|
|
343
|
-
newSchema.items = this.processSchema({
|
|
344
|
-
...context,
|
|
345
|
-
path: [...path, 'items'],
|
|
346
|
-
depth: depth + 1
|
|
347
|
-
}, schema.items);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
this.warnings.push(`Array missing items definition at ${path.join('.') || 'root'}, defaulting to string items`);
|
|
352
|
-
newSchema.items = { type: 'string' };
|
|
353
|
-
}
|
|
354
|
-
for (const keyword of this.getUnsupportedArrayKeywords()) {
|
|
355
|
-
if (schema[keyword] !== undefined) {
|
|
356
|
-
this.warnings.push(`Unsupported array keyword "${keyword}" at ${path.join('.') || 'root'} will be ignored`);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
return newSchema;
|
|
360
|
-
}
|
|
361
|
-
getUnsupportedArrayKeywords() {
|
|
362
|
-
if (this.#config.target === SchemaConversionTarget.OpenAI) {
|
|
363
|
-
return [
|
|
364
|
-
// 'unevaluatedItems',
|
|
365
|
-
'contains',
|
|
366
|
-
// 'minContains',
|
|
367
|
-
// 'maxContains',
|
|
368
|
-
'minItems',
|
|
369
|
-
'maxItems',
|
|
370
|
-
'uniqueItems'
|
|
371
|
-
];
|
|
372
|
-
}
|
|
373
|
-
return [];
|
|
374
|
-
}
|
|
375
|
-
processOneOfSchema(context, schema) {
|
|
376
|
-
const { depth, path } = context;
|
|
377
|
-
const newSchema = this.initializeNewSchema(schema, {
|
|
378
|
-
oneOf: []
|
|
379
|
-
});
|
|
380
|
-
// Process each oneOf schema
|
|
381
|
-
newSchema.oneOf = schema.oneOf.map((subSchema, index) => {
|
|
382
|
-
return this.processSchema({ ...context, path: [...path, 'oneOf', index], depth: depth + 1 }, subSchema);
|
|
383
|
-
});
|
|
384
|
-
return newSchema;
|
|
385
|
-
}
|
|
386
|
-
processAnyOfSchema(context, schema) {
|
|
387
|
-
const { target } = this.#config;
|
|
388
|
-
const { depth, path } = context;
|
|
389
|
-
const newSchema = this.initializeNewSchema(schema, {
|
|
390
|
-
anyOf: []
|
|
391
|
-
});
|
|
392
|
-
// Check if this is at the root level
|
|
393
|
-
if (target === SchemaConversionTarget.OpenAI && path.length === 0) {
|
|
394
|
-
this.warnings.push('anyOf at root level is not supported by OpenAI Structured Outputs');
|
|
395
|
-
}
|
|
396
|
-
// Process each anyOf schema
|
|
397
|
-
newSchema.anyOf = schema.anyOf.map((subSchema, index) => {
|
|
398
|
-
return this.processSchema({ ...context, path: [...path, 'anyOf', index], depth: depth + 1 }, subSchema);
|
|
399
|
-
});
|
|
400
|
-
return newSchema;
|
|
401
|
-
}
|
|
402
|
-
processAllOfSchema(context, schema) {
|
|
403
|
-
const { depth, path } = context;
|
|
404
|
-
const newSchema = this.initializeNewSchema(schema, {
|
|
405
|
-
allOf: []
|
|
406
|
-
});
|
|
407
|
-
// Process each anyOf schema
|
|
408
|
-
newSchema.allOf = schema.allOf.map((subSchema, index) => {
|
|
409
|
-
return this.processSchema({ ...context, path: [...path, 'allOf', index], depth: depth + 1 }, subSchema);
|
|
410
|
-
});
|
|
411
|
-
return newSchema;
|
|
412
|
-
}
|
|
413
|
-
mergeAllOfSchema(context, schema) {
|
|
414
|
-
const { depth, path } = context;
|
|
415
|
-
const newSchema = this.initializeNewSchema(schema, {
|
|
416
|
-
type: 'object'
|
|
417
|
-
});
|
|
418
|
-
const allOfSchemas = schema.allOf ?? [];
|
|
419
|
-
// Try to merge properties from all schemas
|
|
420
|
-
for (const [index, value] of allOfSchemas.entries()) {
|
|
421
|
-
const subSchema = this.processSchema({ ...context, path: [...path, 'allOf', index] }, value);
|
|
422
|
-
if (isObjectSchemaWithProperties(subSchema)) {
|
|
423
|
-
// Merge properties
|
|
424
|
-
newSchema.properties = {
|
|
425
|
-
...newSchema.properties,
|
|
426
|
-
...subSchema.properties
|
|
427
|
-
};
|
|
428
|
-
// Merge required fields
|
|
429
|
-
if (Array.isArray(subSchema.required)) {
|
|
430
|
-
newSchema.required = [...(newSchema.required ?? []), ...subSchema.required];
|
|
431
|
-
}
|
|
432
|
-
// Preserve description if we don't have one yet
|
|
433
|
-
if (!newSchema.description && subSchema.description) {
|
|
434
|
-
newSchema.description = subSchema.description;
|
|
435
|
-
}
|
|
436
|
-
// Preserve title if we don't have one yet
|
|
437
|
-
if (!newSchema.title && subSchema.title) {
|
|
438
|
-
newSchema.title = subSchema.title;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
else {
|
|
442
|
-
this.warnings.push(`Non-object schema in allOf at ${path.join('.') || 'root'} cannot be merged properly`);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
// Remove duplicate required fields
|
|
446
|
-
newSchema.required = [...new Set(newSchema.required)];
|
|
447
|
-
return this.processObjectSchema({ ...context, depth: depth + 1 }, newSchema);
|
|
448
|
-
}
|
|
449
|
-
processEnumSchema(context, schema) {
|
|
450
|
-
const { target } = this.#config;
|
|
451
|
-
const { path, processingMode } = context;
|
|
452
|
-
const newSchema = this.initializeNewSchema(schema, {
|
|
453
|
-
enum: schema.enum,
|
|
454
|
-
type: schema.type
|
|
455
|
-
});
|
|
456
|
-
if (Array.isArray(newSchema.enum)) {
|
|
457
|
-
// Update enum values count
|
|
458
|
-
if (processingMode === SchemaProcessingMode.Schema || processingMode === SchemaProcessingMode.Count) {
|
|
459
|
-
this.stats.enumValuesCount += newSchema.enum.length;
|
|
460
|
-
}
|
|
461
|
-
// Check enum size
|
|
462
|
-
if (target === SchemaConversionTarget.OpenAI && newSchema.enum.length > 500) {
|
|
463
|
-
this.warnings.push(`Enum at ${path.join('.') || 'root'} has ${newSchema.enum.length} values, exceeding the 500 limit`);
|
|
464
|
-
newSchema.enum = newSchema.enum.slice(0, 500);
|
|
465
|
-
}
|
|
466
|
-
// Check string length for large enums
|
|
467
|
-
if (newSchema.type === 'string') {
|
|
468
|
-
let totalLength = 0;
|
|
469
|
-
for (const val of newSchema.enum) {
|
|
470
|
-
if (typeof val === 'string') {
|
|
471
|
-
totalLength += val.length;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
if (target === SchemaConversionTarget.OpenAI && totalLength > 7500) {
|
|
475
|
-
this.warnings.push(`Enum strings at ${path.join('.') || 'root'} exceed 7500 characters (${totalLength}) with ${newSchema.enum.length} values`);
|
|
476
|
-
// Truncate enum values to fit within limits
|
|
477
|
-
let currentLength = 0;
|
|
478
|
-
const truncatedEnum = [];
|
|
479
|
-
for (const val of newSchema.enum) {
|
|
480
|
-
if (typeof val === 'string') {
|
|
481
|
-
if (currentLength + val.length <= 7500) {
|
|
482
|
-
truncatedEnum.push(val);
|
|
483
|
-
currentLength += val.length;
|
|
484
|
-
}
|
|
485
|
-
else {
|
|
486
|
-
break;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
else {
|
|
490
|
-
truncatedEnum.push(val);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
newSchema.enum = truncatedEnum;
|
|
494
|
-
}
|
|
495
|
-
if (processingMode === SchemaProcessingMode.Schema || processingMode === SchemaProcessingMode.Count) {
|
|
496
|
-
this.stats.stringLength += totalLength;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
return newSchema;
|
|
501
|
-
}
|
|
502
|
-
checkUnsupportedKeywords(schema, path) {
|
|
503
|
-
if (this.#config.target === SchemaConversionTarget.OpenAI) {
|
|
504
|
-
// String-specific unsupported keywords
|
|
505
|
-
if (schema.type === 'string' ||
|
|
506
|
-
(!schema.type &&
|
|
507
|
-
(schema.minLength !== undefined ||
|
|
508
|
-
schema.maxLength !== undefined ||
|
|
509
|
-
schema.pattern !== undefined ||
|
|
510
|
-
schema.format !== undefined))) {
|
|
511
|
-
const unsupportedStringKeywords = ['minLength', 'maxLength', 'pattern', 'format'];
|
|
512
|
-
for (const keyword of unsupportedStringKeywords) {
|
|
513
|
-
if (schema[keyword] !== undefined) {
|
|
514
|
-
this.warnings.push(`Unsupported string keyword "${keyword}" at ${path || 'root'} will be ignored`);
|
|
515
|
-
delete schema[keyword];
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
// Number-specific unsupported keywords
|
|
520
|
-
if (schema.type === 'number' ||
|
|
521
|
-
schema.type === 'integer' ||
|
|
522
|
-
(!schema.type &&
|
|
523
|
-
(schema.minimum !== undefined || schema.maximum !== undefined || schema.multipleOf !== undefined))) {
|
|
524
|
-
const unsupportedNumberKeywords = ['minimum', 'maximum', 'multipleOf'];
|
|
525
|
-
for (const keyword of unsupportedNumberKeywords) {
|
|
526
|
-
if (schema[keyword] !== undefined) {
|
|
527
|
-
this.warnings.push(`Unsupported number keyword "${keyword}" at ${path || 'root'} will be ignored`);
|
|
528
|
-
delete schema[keyword];
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
// Object-specific unsupported keywords
|
|
533
|
-
if (schema.type === 'object' || (!schema.type && schema.properties !== undefined)) {
|
|
534
|
-
const unsupportedObjectKeywords = [
|
|
535
|
-
'patternProperties',
|
|
536
|
-
// 'unevaluatedProperties',
|
|
537
|
-
'propertyNames',
|
|
538
|
-
'minProperties',
|
|
539
|
-
'maxProperties'
|
|
540
|
-
];
|
|
541
|
-
for (const keyword of unsupportedObjectKeywords) {
|
|
542
|
-
if (schema[keyword] !== undefined) {
|
|
543
|
-
this.warnings.push(`Unsupported object keyword "${keyword}" at ${path || 'root'} will be ignored`);
|
|
544
|
-
delete schema[keyword];
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
// Check Draft 7 keywords that aren't supported in OpenAI schema
|
|
549
|
-
const draft7Keywords = ['if', 'then', 'else', 'not', 'dependencies'];
|
|
550
|
-
for (const keyword of draft7Keywords) {
|
|
551
|
-
if (schema[keyword] !== undefined) {
|
|
552
|
-
this.warnings.push(`JSON Schema Draft 7 keyword "${keyword}" at ${path || 'root'} is not supported and will be ignored`);
|
|
553
|
-
delete schema[keyword];
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
}
|
package/dist/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './schema-validator.ts';
|
package/dist/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./schema-validator.js";
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { ErrorObject, Options, SchemaObject } from 'ajv';
|
|
2
|
-
import { Ajv } from 'ajv';
|
|
3
|
-
import type { JSONSchema7 } from 'json-schema';
|
|
4
|
-
export type Data = any;
|
|
5
|
-
export type ValidateParams = {
|
|
6
|
-
ignoreMissing: boolean;
|
|
7
|
-
ignoreNulls: boolean;
|
|
8
|
-
errorsText: boolean;
|
|
9
|
-
};
|
|
10
|
-
export type ValidateResult = {
|
|
11
|
-
valid: boolean;
|
|
12
|
-
errorsText: string;
|
|
13
|
-
errors: ErrorObject[];
|
|
14
|
-
};
|
|
15
|
-
export type Validator = (data: Data, options?: Partial<ValidateParams>) => ValidateResult;
|
|
16
|
-
export declare function parseDataPath(instancePath: string): string[];
|
|
17
|
-
export declare function isInvalidPropertyRequired(topLevelSchema: SchemaObject, error: ErrorObject): boolean;
|
|
18
|
-
export declare function refToPath(ref: string): string[];
|
|
19
|
-
/**
|
|
20
|
-
* Given a schema object traverse it using a "instancePath" and return the schema at that path
|
|
21
|
-
*/
|
|
22
|
-
export declare function followSchemaPath(topLevelSchema: SchemaObject, instancePath: string[]): SchemaObject | undefined;
|
|
23
|
-
export type MinimalAjv = Pick<Ajv, 'validate' | 'errors' | 'getSchema' | 'errorsText'>;
|
|
24
|
-
export declare function validate(ajv: MinimalAjv, id: string, data: Data, options?: Partial<ValidateParams>): ValidateResult;
|
|
25
|
-
export { Ajv, type ErrorObject };
|
|
26
|
-
export declare function createAjv(options?: Options): Ajv;
|
|
27
|
-
/**
|
|
28
|
-
* Apply various fixes to the schema to work around AJV issues and bugs.
|
|
29
|
-
* See inline comments for more.
|
|
30
|
-
*/
|
|
31
|
-
export declare function fixSchema(schema: SchemaObject): SchemaObject;
|
|
32
|
-
export declare function createSchemaValidator(schema: SchemaObject | SchemaObject[], metaSchemas?: SchemaObject[], options?: Options): Validator;
|
|
33
|
-
/**
|
|
34
|
-
* Returns a compiled validator from a schema which can act as a type guard.
|
|
35
|
-
*/
|
|
36
|
-
export declare function createTypedValidator<T>(schema: JSONSchema7, options?: Options): import("ajv").ValidateFunction<T>;
|