@skmtc/core 0.32.0 → 0.32.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.
|
@@ -171,16 +171,79 @@ const normalizeContentFormat = (schema) => {
|
|
|
171
171
|
const { contentMediaType: _contentMediaType, contentEncoding: _contentEncoding, ...rest } = view;
|
|
172
172
|
return 'format' in rest ? rest : { ...rest, format: derivedFormat };
|
|
173
173
|
};
|
|
174
|
+
/**
|
|
175
|
+
* In-place applicators that evaluate keys `unevaluatedProperties` would
|
|
176
|
+
* otherwise see. `$ref` and `not` never reach `normalizeSchemaShape`.
|
|
177
|
+
*/
|
|
178
|
+
const inPlaceApplicators = [
|
|
179
|
+
'allOf',
|
|
180
|
+
'oneOf',
|
|
181
|
+
'anyOf',
|
|
182
|
+
'if',
|
|
183
|
+
'then',
|
|
184
|
+
'else',
|
|
185
|
+
'dependentSchemas',
|
|
186
|
+
'$dynamicRef'
|
|
187
|
+
];
|
|
188
|
+
/**
|
|
189
|
+
* With no in-place applicator on the same schema, `unevaluatedProperties`
|
|
190
|
+
* means the same as `additionalProperties` (JSON Schema 2020-12 §11.3,
|
|
191
|
+
* §10.3.2.3), so rename it. When `additionalProperties` is also present it
|
|
192
|
+
* evaluates every remaining key, so `unevaluatedProperties` is dropped.
|
|
193
|
+
*/
|
|
194
|
+
const normalizeUnevaluatedProperties = (schema) => {
|
|
195
|
+
if (!('unevaluatedProperties' in schema)) {
|
|
196
|
+
return schema;
|
|
197
|
+
}
|
|
198
|
+
if (inPlaceApplicators.some(keyword => keyword in schema)) {
|
|
199
|
+
return schema;
|
|
200
|
+
}
|
|
201
|
+
// `unevaluatedProperties` is a 3.1 keyword the 3.0-typed SchemaObject does
|
|
202
|
+
// not model; read it via an annotated destructure.
|
|
203
|
+
const { unevaluatedProperties, ...rest } = schema;
|
|
204
|
+
if ('additionalProperties' in rest) {
|
|
205
|
+
return rest;
|
|
206
|
+
}
|
|
207
|
+
if (!isAdditionalPropertiesValue(unevaluatedProperties)) {
|
|
208
|
+
return schema;
|
|
209
|
+
}
|
|
210
|
+
return { ...rest, additionalProperties: unevaluatedProperties };
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Is this a value `additionalProperties` accepts: a boolean, or a schema or
|
|
214
|
+
* `$ref` object?
|
|
215
|
+
*/
|
|
216
|
+
const isAdditionalPropertiesValue = (value) => typeof value === 'boolean' ||
|
|
217
|
+
(typeof value === 'object' && value !== null && !Array.isArray(value));
|
|
218
|
+
/**
|
|
219
|
+
* Remove `unevaluatedProperties` from an `allOf`/`oneOf`/`anyOf` wrapper and
|
|
220
|
+
* log it as skipped. Left in place, it would be renamed to
|
|
221
|
+
* `additionalProperties` when the merged or collapsed schema is dispatched
|
|
222
|
+
* again with no composition keyword left.
|
|
223
|
+
*/
|
|
224
|
+
const withoutWrapperUnevaluatedProperties = ({ schema, parentType, stackTrail, context }) => {
|
|
225
|
+
if (!('unevaluatedProperties' in schema)) {
|
|
226
|
+
return schema;
|
|
227
|
+
}
|
|
228
|
+
const { unevaluatedProperties, ...rest } = schema;
|
|
229
|
+
context.logSkippedFields({
|
|
230
|
+
skipped: { unevaluatedProperties },
|
|
231
|
+
parent: schema,
|
|
232
|
+
parentType,
|
|
233
|
+
stackTrail
|
|
234
|
+
});
|
|
235
|
+
return rest;
|
|
236
|
+
};
|
|
174
237
|
/**
|
|
175
238
|
* Map the 3.1-only schema-shape encodings onto the shared IR shape before
|
|
176
|
-
* dispatching: type arrays, `const`, `examples[]`,
|
|
177
|
-
* and binary/base64 content keywords. Each step
|
|
178
|
-
* when it does not apply, so the composed result
|
|
179
|
-
* `schema` when nothing matched — letting the
|
|
180
|
-
* needed.
|
|
239
|
+
* dispatching: `unevaluatedProperties`, type arrays, `const`, `examples[]`,
|
|
240
|
+
* numeric exclusive bounds, and binary/base64 content keywords. Each step
|
|
241
|
+
* returns its input unchanged when it does not apply, so the composed result
|
|
242
|
+
* is referentially equal to `schema` when nothing matched — letting the
|
|
243
|
+
* dispatcher re-dispatch only when needed.
|
|
181
244
|
*/
|
|
182
245
|
const normalizeSchemaShape = (schema) => {
|
|
183
|
-
return normalizeExclusiveBounds(normalizeExamples(normalizeConst(normalizeContentFormat(normalizeTypeArray(schema)))));
|
|
246
|
+
return normalizeExclusiveBounds(normalizeExamples(normalizeConst(normalizeContentFormat(normalizeTypeArray(normalizeUnevaluatedProperties(schema))))));
|
|
184
247
|
};
|
|
185
248
|
/**
|
|
186
249
|
* Is this a 3.1 `{ type: 'null' }` schema member? (`'null'` is a 3.1 type
|
|
@@ -252,6 +315,12 @@ export const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
252
315
|
return toSchemaV3({ schema: normalized, stackTrail, context });
|
|
253
316
|
}
|
|
254
317
|
if ('allOf' in schema && Array.isArray(schema.allOf)) {
|
|
318
|
+
const wrapper = withoutWrapperUnevaluatedProperties({
|
|
319
|
+
schema,
|
|
320
|
+
parentType: 'allOf',
|
|
321
|
+
stackTrail,
|
|
322
|
+
context
|
|
323
|
+
});
|
|
255
324
|
return stackTrail.trace('allOf', st => {
|
|
256
325
|
// A single-member allOf collapses to its sole member — the value must
|
|
257
326
|
// satisfy exactly that one schema. When the member is a `$ref`, keep it
|
|
@@ -261,7 +330,7 @@ export const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
261
330
|
// self-referential, and eagerly resolving it would not terminate. The
|
|
262
331
|
// ref resolves at use time like every other ref. Mirrors the
|
|
263
332
|
// single-member oneOf/anyOf handling below.
|
|
264
|
-
const { allOf, ...value } =
|
|
333
|
+
const { allOf, ...value } = wrapper;
|
|
265
334
|
const members = allOf ?? [];
|
|
266
335
|
if (members.length === 1) {
|
|
267
336
|
const [member] = members;
|
|
@@ -281,20 +350,26 @@ export const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
281
350
|
});
|
|
282
351
|
}
|
|
283
352
|
const merged = mergeIntersection({
|
|
284
|
-
schema,
|
|
353
|
+
schema: wrapper,
|
|
285
354
|
getRef: toGetRef(context.documentObject)
|
|
286
355
|
});
|
|
287
356
|
return toSchemaV3({ schema: merged, stackTrail: st, context });
|
|
288
357
|
});
|
|
289
358
|
}
|
|
290
359
|
if ('oneOf' in schema && Array.isArray(schema.oneOf)) {
|
|
360
|
+
const wrapper = withoutWrapperUnevaluatedProperties({
|
|
361
|
+
schema,
|
|
362
|
+
parentType: 'oneOf',
|
|
363
|
+
stackTrail,
|
|
364
|
+
context
|
|
365
|
+
});
|
|
291
366
|
return stackTrail.trace('oneOf', st => {
|
|
292
367
|
// 3.1: a `{ type: 'null' }` member makes the union nullable. Fold it
|
|
293
368
|
// into a `nullable` flag the collapse/union logic already understands.
|
|
294
|
-
const { members: oneOfWithoutNull, nullable: hasNullMember } = partitionNullMember(
|
|
369
|
+
const { members: oneOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.oneOf ?? []);
|
|
295
370
|
const withoutNull = hasNullMember
|
|
296
|
-
? { ...
|
|
297
|
-
:
|
|
371
|
+
? { ...wrapper, oneOf: oneOfWithoutNull, nullable: true }
|
|
372
|
+
: wrapper;
|
|
298
373
|
const merged = mergeUnion({
|
|
299
374
|
schema: withoutNull,
|
|
300
375
|
getRef: toGetRef(context.documentObject),
|
|
@@ -337,19 +412,25 @@ export const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
337
412
|
});
|
|
338
413
|
}
|
|
339
414
|
if ('anyOf' in schema && Array.isArray(schema.anyOf)) {
|
|
415
|
+
const wrapper = withoutWrapperUnevaluatedProperties({
|
|
416
|
+
schema,
|
|
417
|
+
parentType: 'anyOf',
|
|
418
|
+
stackTrail,
|
|
419
|
+
context
|
|
420
|
+
});
|
|
340
421
|
return stackTrail.trace('anyOf', st => {
|
|
341
422
|
// deno-lint-ignore ban-ts-comment
|
|
342
423
|
// @ts-expect-error
|
|
343
|
-
if (
|
|
344
|
-
const { anyOf, ...value } =
|
|
424
|
+
if (wrapper['x-expansionResources'] && Array.isArray(wrapper.anyOf)) {
|
|
425
|
+
const { anyOf, ...value } = wrapper;
|
|
345
426
|
return toUnion({ value, members: anyOf, parentType: 'anyOf', stackTrail: st, context });
|
|
346
427
|
}
|
|
347
428
|
// 3.1: a `{ type: 'null' }` member makes the union nullable (mirrors
|
|
348
429
|
// the oneOf branch above).
|
|
349
|
-
const { members: anyOfWithoutNull, nullable: hasNullMember } = partitionNullMember(
|
|
430
|
+
const { members: anyOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.anyOf ?? []);
|
|
350
431
|
const withoutNull = hasNullMember
|
|
351
|
-
? { ...
|
|
352
|
-
:
|
|
432
|
+
? { ...wrapper, anyOf: anyOfWithoutNull, nullable: true }
|
|
433
|
+
: wrapper;
|
|
353
434
|
const merged = mergeUnion({
|
|
354
435
|
schema: withoutNull,
|
|
355
436
|
getRef: toGetRef(context.documentObject),
|
|
@@ -171,16 +171,79 @@ const normalizeContentFormat = (schema) => {
|
|
|
171
171
|
const { contentMediaType: _contentMediaType, contentEncoding: _contentEncoding, ...rest } = view;
|
|
172
172
|
return 'format' in rest ? rest : { ...rest, format: derivedFormat };
|
|
173
173
|
};
|
|
174
|
+
/**
|
|
175
|
+
* In-place applicators that evaluate keys `unevaluatedProperties` would
|
|
176
|
+
* otherwise see. `$ref` and `not` never reach `normalizeSchemaShape`.
|
|
177
|
+
*/
|
|
178
|
+
const inPlaceApplicators = [
|
|
179
|
+
'allOf',
|
|
180
|
+
'oneOf',
|
|
181
|
+
'anyOf',
|
|
182
|
+
'if',
|
|
183
|
+
'then',
|
|
184
|
+
'else',
|
|
185
|
+
'dependentSchemas',
|
|
186
|
+
'$dynamicRef'
|
|
187
|
+
];
|
|
188
|
+
/**
|
|
189
|
+
* With no in-place applicator on the same schema, `unevaluatedProperties`
|
|
190
|
+
* means the same as `additionalProperties` (JSON Schema 2020-12 §11.3,
|
|
191
|
+
* §10.3.2.3), so rename it. When `additionalProperties` is also present it
|
|
192
|
+
* evaluates every remaining key, so `unevaluatedProperties` is dropped.
|
|
193
|
+
*/
|
|
194
|
+
const normalizeUnevaluatedProperties = (schema) => {
|
|
195
|
+
if (!('unevaluatedProperties' in schema)) {
|
|
196
|
+
return schema;
|
|
197
|
+
}
|
|
198
|
+
if (inPlaceApplicators.some(keyword => keyword in schema)) {
|
|
199
|
+
return schema;
|
|
200
|
+
}
|
|
201
|
+
// `unevaluatedProperties` is a 3.1 keyword the 3.0-typed SchemaObject does
|
|
202
|
+
// not model; read it via an annotated destructure.
|
|
203
|
+
const { unevaluatedProperties, ...rest } = schema;
|
|
204
|
+
if ('additionalProperties' in rest) {
|
|
205
|
+
return rest;
|
|
206
|
+
}
|
|
207
|
+
if (!isAdditionalPropertiesValue(unevaluatedProperties)) {
|
|
208
|
+
return schema;
|
|
209
|
+
}
|
|
210
|
+
return { ...rest, additionalProperties: unevaluatedProperties };
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Is this a value `additionalProperties` accepts: a boolean, or a schema or
|
|
214
|
+
* `$ref` object?
|
|
215
|
+
*/
|
|
216
|
+
const isAdditionalPropertiesValue = (value) => typeof value === 'boolean' ||
|
|
217
|
+
(typeof value === 'object' && value !== null && !Array.isArray(value));
|
|
218
|
+
/**
|
|
219
|
+
* Remove `unevaluatedProperties` from an `allOf`/`oneOf`/`anyOf` wrapper and
|
|
220
|
+
* log it as skipped. Left in place, it would be renamed to
|
|
221
|
+
* `additionalProperties` when the merged or collapsed schema is dispatched
|
|
222
|
+
* again with no composition keyword left.
|
|
223
|
+
*/
|
|
224
|
+
const withoutWrapperUnevaluatedProperties = ({ schema, parentType, stackTrail, context }) => {
|
|
225
|
+
if (!('unevaluatedProperties' in schema)) {
|
|
226
|
+
return schema;
|
|
227
|
+
}
|
|
228
|
+
const { unevaluatedProperties, ...rest } = schema;
|
|
229
|
+
context.logSkippedFields({
|
|
230
|
+
skipped: { unevaluatedProperties },
|
|
231
|
+
parent: schema,
|
|
232
|
+
parentType,
|
|
233
|
+
stackTrail
|
|
234
|
+
});
|
|
235
|
+
return rest;
|
|
236
|
+
};
|
|
174
237
|
/**
|
|
175
238
|
* Map the 3.1-only schema-shape encodings onto the shared IR shape before
|
|
176
|
-
* dispatching: type arrays, `const`, `examples[]`,
|
|
177
|
-
* and binary/base64 content keywords. Each step
|
|
178
|
-
* when it does not apply, so the composed result
|
|
179
|
-
* `schema` when nothing matched — letting the
|
|
180
|
-
* needed.
|
|
239
|
+
* dispatching: `unevaluatedProperties`, type arrays, `const`, `examples[]`,
|
|
240
|
+
* numeric exclusive bounds, and binary/base64 content keywords. Each step
|
|
241
|
+
* returns its input unchanged when it does not apply, so the composed result
|
|
242
|
+
* is referentially equal to `schema` when nothing matched — letting the
|
|
243
|
+
* dispatcher re-dispatch only when needed.
|
|
181
244
|
*/
|
|
182
245
|
const normalizeSchemaShape = (schema) => {
|
|
183
|
-
return normalizeExclusiveBounds(normalizeExamples(normalizeConst(normalizeContentFormat(normalizeTypeArray(schema)))));
|
|
246
|
+
return normalizeExclusiveBounds(normalizeExamples(normalizeConst(normalizeContentFormat(normalizeTypeArray(normalizeUnevaluatedProperties(schema))))));
|
|
184
247
|
};
|
|
185
248
|
/**
|
|
186
249
|
* Is this a 3.1 `{ type: 'null' }` schema member? (`'null'` is a 3.1 type
|
|
@@ -252,6 +315,12 @@ export const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
252
315
|
return toSchemaV3({ schema: normalized, stackTrail, context });
|
|
253
316
|
}
|
|
254
317
|
if ('allOf' in schema && Array.isArray(schema.allOf)) {
|
|
318
|
+
const wrapper = withoutWrapperUnevaluatedProperties({
|
|
319
|
+
schema,
|
|
320
|
+
parentType: 'allOf',
|
|
321
|
+
stackTrail,
|
|
322
|
+
context
|
|
323
|
+
});
|
|
255
324
|
return stackTrail.trace('allOf', st => {
|
|
256
325
|
// A single-member allOf collapses to its sole member — the value must
|
|
257
326
|
// satisfy exactly that one schema. When the member is a `$ref`, keep it
|
|
@@ -261,7 +330,7 @@ export const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
261
330
|
// self-referential, and eagerly resolving it would not terminate. The
|
|
262
331
|
// ref resolves at use time like every other ref. Mirrors the
|
|
263
332
|
// single-member oneOf/anyOf handling below.
|
|
264
|
-
const { allOf, ...value } =
|
|
333
|
+
const { allOf, ...value } = wrapper;
|
|
265
334
|
const members = allOf ?? [];
|
|
266
335
|
if (members.length === 1) {
|
|
267
336
|
const [member] = members;
|
|
@@ -281,20 +350,26 @@ export const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
281
350
|
});
|
|
282
351
|
}
|
|
283
352
|
const merged = mergeIntersection({
|
|
284
|
-
schema,
|
|
353
|
+
schema: wrapper,
|
|
285
354
|
getRef: toGetRef(context.documentObject)
|
|
286
355
|
});
|
|
287
356
|
return toSchemaV3({ schema: merged, stackTrail: st, context });
|
|
288
357
|
});
|
|
289
358
|
}
|
|
290
359
|
if ('oneOf' in schema && Array.isArray(schema.oneOf)) {
|
|
360
|
+
const wrapper = withoutWrapperUnevaluatedProperties({
|
|
361
|
+
schema,
|
|
362
|
+
parentType: 'oneOf',
|
|
363
|
+
stackTrail,
|
|
364
|
+
context
|
|
365
|
+
});
|
|
291
366
|
return stackTrail.trace('oneOf', st => {
|
|
292
367
|
// 3.1: a `{ type: 'null' }` member makes the union nullable. Fold it
|
|
293
368
|
// into a `nullable` flag the collapse/union logic already understands.
|
|
294
|
-
const { members: oneOfWithoutNull, nullable: hasNullMember } = partitionNullMember(
|
|
369
|
+
const { members: oneOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.oneOf ?? []);
|
|
295
370
|
const withoutNull = hasNullMember
|
|
296
|
-
? { ...
|
|
297
|
-
:
|
|
371
|
+
? { ...wrapper, oneOf: oneOfWithoutNull, nullable: true }
|
|
372
|
+
: wrapper;
|
|
298
373
|
const merged = mergeUnion({
|
|
299
374
|
schema: withoutNull,
|
|
300
375
|
getRef: toGetRef(context.documentObject),
|
|
@@ -337,19 +412,25 @@ export const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
337
412
|
});
|
|
338
413
|
}
|
|
339
414
|
if ('anyOf' in schema && Array.isArray(schema.anyOf)) {
|
|
415
|
+
const wrapper = withoutWrapperUnevaluatedProperties({
|
|
416
|
+
schema,
|
|
417
|
+
parentType: 'anyOf',
|
|
418
|
+
stackTrail,
|
|
419
|
+
context
|
|
420
|
+
});
|
|
340
421
|
return stackTrail.trace('anyOf', st => {
|
|
341
422
|
// deno-lint-ignore ban-ts-comment
|
|
342
423
|
// @ts-expect-error
|
|
343
|
-
if (
|
|
344
|
-
const { anyOf, ...value } =
|
|
424
|
+
if (wrapper['x-expansionResources'] && Array.isArray(wrapper.anyOf)) {
|
|
425
|
+
const { anyOf, ...value } = wrapper;
|
|
345
426
|
return toUnion({ value, members: anyOf, parentType: 'anyOf', stackTrail: st, context });
|
|
346
427
|
}
|
|
347
428
|
// 3.1: a `{ type: 'null' }` member makes the union nullable (mirrors
|
|
348
429
|
// the oneOf branch above).
|
|
349
|
-
const { members: anyOfWithoutNull, nullable: hasNullMember } = partitionNullMember(
|
|
430
|
+
const { members: anyOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.anyOf ?? []);
|
|
350
431
|
const withoutNull = hasNullMember
|
|
351
|
-
? { ...
|
|
352
|
-
:
|
|
432
|
+
? { ...wrapper, anyOf: anyOfWithoutNull, nullable: true }
|
|
433
|
+
: wrapper;
|
|
353
434
|
const merged = mergeUnion({
|
|
354
435
|
schema: withoutNull,
|
|
355
436
|
getRef: toGetRef(context.documentObject),
|
package/package.json
CHANGED
|
@@ -176,16 +176,79 @@ const normalizeContentFormat = (schema) => {
|
|
|
176
176
|
const { contentMediaType: _contentMediaType, contentEncoding: _contentEncoding, ...rest } = view;
|
|
177
177
|
return 'format' in rest ? rest : { ...rest, format: derivedFormat };
|
|
178
178
|
};
|
|
179
|
+
/**
|
|
180
|
+
* In-place applicators that evaluate keys `unevaluatedProperties` would
|
|
181
|
+
* otherwise see. `$ref` and `not` never reach `normalizeSchemaShape`.
|
|
182
|
+
*/
|
|
183
|
+
const inPlaceApplicators = [
|
|
184
|
+
'allOf',
|
|
185
|
+
'oneOf',
|
|
186
|
+
'anyOf',
|
|
187
|
+
'if',
|
|
188
|
+
'then',
|
|
189
|
+
'else',
|
|
190
|
+
'dependentSchemas',
|
|
191
|
+
'$dynamicRef'
|
|
192
|
+
];
|
|
193
|
+
/**
|
|
194
|
+
* With no in-place applicator on the same schema, `unevaluatedProperties`
|
|
195
|
+
* means the same as `additionalProperties` (JSON Schema 2020-12 §11.3,
|
|
196
|
+
* §10.3.2.3), so rename it. When `additionalProperties` is also present it
|
|
197
|
+
* evaluates every remaining key, so `unevaluatedProperties` is dropped.
|
|
198
|
+
*/
|
|
199
|
+
const normalizeUnevaluatedProperties = (schema) => {
|
|
200
|
+
if (!('unevaluatedProperties' in schema)) {
|
|
201
|
+
return schema;
|
|
202
|
+
}
|
|
203
|
+
if (inPlaceApplicators.some(keyword => keyword in schema)) {
|
|
204
|
+
return schema;
|
|
205
|
+
}
|
|
206
|
+
// `unevaluatedProperties` is a 3.1 keyword the 3.0-typed SchemaObject does
|
|
207
|
+
// not model; read it via an annotated destructure.
|
|
208
|
+
const { unevaluatedProperties, ...rest } = schema;
|
|
209
|
+
if ('additionalProperties' in rest) {
|
|
210
|
+
return rest;
|
|
211
|
+
}
|
|
212
|
+
if (!isAdditionalPropertiesValue(unevaluatedProperties)) {
|
|
213
|
+
return schema;
|
|
214
|
+
}
|
|
215
|
+
return { ...rest, additionalProperties: unevaluatedProperties };
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* Is this a value `additionalProperties` accepts: a boolean, or a schema or
|
|
219
|
+
* `$ref` object?
|
|
220
|
+
*/
|
|
221
|
+
const isAdditionalPropertiesValue = (value) => typeof value === 'boolean' ||
|
|
222
|
+
(typeof value === 'object' && value !== null && !Array.isArray(value));
|
|
223
|
+
/**
|
|
224
|
+
* Remove `unevaluatedProperties` from an `allOf`/`oneOf`/`anyOf` wrapper and
|
|
225
|
+
* log it as skipped. Left in place, it would be renamed to
|
|
226
|
+
* `additionalProperties` when the merged or collapsed schema is dispatched
|
|
227
|
+
* again with no composition keyword left.
|
|
228
|
+
*/
|
|
229
|
+
const withoutWrapperUnevaluatedProperties = ({ schema, parentType, stackTrail, context }) => {
|
|
230
|
+
if (!('unevaluatedProperties' in schema)) {
|
|
231
|
+
return schema;
|
|
232
|
+
}
|
|
233
|
+
const { unevaluatedProperties, ...rest } = schema;
|
|
234
|
+
context.logSkippedFields({
|
|
235
|
+
skipped: { unevaluatedProperties },
|
|
236
|
+
parent: schema,
|
|
237
|
+
parentType,
|
|
238
|
+
stackTrail
|
|
239
|
+
});
|
|
240
|
+
return rest;
|
|
241
|
+
};
|
|
179
242
|
/**
|
|
180
243
|
* Map the 3.1-only schema-shape encodings onto the shared IR shape before
|
|
181
|
-
* dispatching: type arrays, `const`, `examples[]`,
|
|
182
|
-
* and binary/base64 content keywords. Each step
|
|
183
|
-
* when it does not apply, so the composed result
|
|
184
|
-
* `schema` when nothing matched — letting the
|
|
185
|
-
* needed.
|
|
244
|
+
* dispatching: `unevaluatedProperties`, type arrays, `const`, `examples[]`,
|
|
245
|
+
* numeric exclusive bounds, and binary/base64 content keywords. Each step
|
|
246
|
+
* returns its input unchanged when it does not apply, so the composed result
|
|
247
|
+
* is referentially equal to `schema` when nothing matched — letting the
|
|
248
|
+
* dispatcher re-dispatch only when needed.
|
|
186
249
|
*/
|
|
187
250
|
const normalizeSchemaShape = (schema) => {
|
|
188
|
-
return normalizeExclusiveBounds(normalizeExamples(normalizeConst(normalizeContentFormat(normalizeTypeArray(schema)))));
|
|
251
|
+
return normalizeExclusiveBounds(normalizeExamples(normalizeConst(normalizeContentFormat(normalizeTypeArray(normalizeUnevaluatedProperties(schema))))));
|
|
189
252
|
};
|
|
190
253
|
/**
|
|
191
254
|
* Is this a 3.1 `{ type: 'null' }` schema member? (`'null'` is a 3.1 type
|
|
@@ -257,6 +320,12 @@ const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
257
320
|
return (0, exports.toSchemaV3)({ schema: normalized, stackTrail, context });
|
|
258
321
|
}
|
|
259
322
|
if ('allOf' in schema && Array.isArray(schema.allOf)) {
|
|
323
|
+
const wrapper = withoutWrapperUnevaluatedProperties({
|
|
324
|
+
schema,
|
|
325
|
+
parentType: 'allOf',
|
|
326
|
+
stackTrail,
|
|
327
|
+
context
|
|
328
|
+
});
|
|
260
329
|
return stackTrail.trace('allOf', st => {
|
|
261
330
|
// A single-member allOf collapses to its sole member — the value must
|
|
262
331
|
// satisfy exactly that one schema. When the member is a `$ref`, keep it
|
|
@@ -266,7 +335,7 @@ const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
266
335
|
// self-referential, and eagerly resolving it would not terminate. The
|
|
267
336
|
// ref resolves at use time like every other ref. Mirrors the
|
|
268
337
|
// single-member oneOf/anyOf handling below.
|
|
269
|
-
const { allOf, ...value } =
|
|
338
|
+
const { allOf, ...value } = wrapper;
|
|
270
339
|
const members = allOf ?? [];
|
|
271
340
|
if (members.length === 1) {
|
|
272
341
|
const [member] = members;
|
|
@@ -286,20 +355,26 @@ const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
286
355
|
});
|
|
287
356
|
}
|
|
288
357
|
const merged = (0, merge_intersection_js_1.mergeIntersection)({
|
|
289
|
-
schema,
|
|
358
|
+
schema: wrapper,
|
|
290
359
|
getRef: (0, refFns_js_2.toGetRef)(context.documentObject)
|
|
291
360
|
});
|
|
292
361
|
return (0, exports.toSchemaV3)({ schema: merged, stackTrail: st, context });
|
|
293
362
|
});
|
|
294
363
|
}
|
|
295
364
|
if ('oneOf' in schema && Array.isArray(schema.oneOf)) {
|
|
365
|
+
const wrapper = withoutWrapperUnevaluatedProperties({
|
|
366
|
+
schema,
|
|
367
|
+
parentType: 'oneOf',
|
|
368
|
+
stackTrail,
|
|
369
|
+
context
|
|
370
|
+
});
|
|
296
371
|
return stackTrail.trace('oneOf', st => {
|
|
297
372
|
// 3.1: a `{ type: 'null' }` member makes the union nullable. Fold it
|
|
298
373
|
// into a `nullable` flag the collapse/union logic already understands.
|
|
299
|
-
const { members: oneOfWithoutNull, nullable: hasNullMember } = partitionNullMember(
|
|
374
|
+
const { members: oneOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.oneOf ?? []);
|
|
300
375
|
const withoutNull = hasNullMember
|
|
301
|
-
? { ...
|
|
302
|
-
:
|
|
376
|
+
? { ...wrapper, oneOf: oneOfWithoutNull, nullable: true }
|
|
377
|
+
: wrapper;
|
|
303
378
|
const merged = (0, merge_union_js_1.mergeUnion)({
|
|
304
379
|
schema: withoutNull,
|
|
305
380
|
getRef: (0, refFns_js_2.toGetRef)(context.documentObject),
|
|
@@ -342,19 +417,25 @@ const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
342
417
|
});
|
|
343
418
|
}
|
|
344
419
|
if ('anyOf' in schema && Array.isArray(schema.anyOf)) {
|
|
420
|
+
const wrapper = withoutWrapperUnevaluatedProperties({
|
|
421
|
+
schema,
|
|
422
|
+
parentType: 'anyOf',
|
|
423
|
+
stackTrail,
|
|
424
|
+
context
|
|
425
|
+
});
|
|
345
426
|
return stackTrail.trace('anyOf', st => {
|
|
346
427
|
// deno-lint-ignore ban-ts-comment
|
|
347
428
|
// @ts-expect-error
|
|
348
|
-
if (
|
|
349
|
-
const { anyOf, ...value } =
|
|
429
|
+
if (wrapper['x-expansionResources'] && Array.isArray(wrapper.anyOf)) {
|
|
430
|
+
const { anyOf, ...value } = wrapper;
|
|
350
431
|
return (0, toUnion_js_1.toUnion)({ value, members: anyOf, parentType: 'anyOf', stackTrail: st, context });
|
|
351
432
|
}
|
|
352
433
|
// 3.1: a `{ type: 'null' }` member makes the union nullable (mirrors
|
|
353
434
|
// the oneOf branch above).
|
|
354
|
-
const { members: anyOfWithoutNull, nullable: hasNullMember } = partitionNullMember(
|
|
435
|
+
const { members: anyOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.anyOf ?? []);
|
|
355
436
|
const withoutNull = hasNullMember
|
|
356
|
-
? { ...
|
|
357
|
-
:
|
|
437
|
+
? { ...wrapper, anyOf: anyOfWithoutNull, nullable: true }
|
|
438
|
+
: wrapper;
|
|
358
439
|
const merged = (0, merge_union_js_1.mergeUnion)({
|
|
359
440
|
schema: withoutNull,
|
|
360
441
|
getRef: (0, refFns_js_2.toGetRef)(context.documentObject),
|
|
@@ -176,16 +176,79 @@ const normalizeContentFormat = (schema) => {
|
|
|
176
176
|
const { contentMediaType: _contentMediaType, contentEncoding: _contentEncoding, ...rest } = view;
|
|
177
177
|
return 'format' in rest ? rest : { ...rest, format: derivedFormat };
|
|
178
178
|
};
|
|
179
|
+
/**
|
|
180
|
+
* In-place applicators that evaluate keys `unevaluatedProperties` would
|
|
181
|
+
* otherwise see. `$ref` and `not` never reach `normalizeSchemaShape`.
|
|
182
|
+
*/
|
|
183
|
+
const inPlaceApplicators = [
|
|
184
|
+
'allOf',
|
|
185
|
+
'oneOf',
|
|
186
|
+
'anyOf',
|
|
187
|
+
'if',
|
|
188
|
+
'then',
|
|
189
|
+
'else',
|
|
190
|
+
'dependentSchemas',
|
|
191
|
+
'$dynamicRef'
|
|
192
|
+
];
|
|
193
|
+
/**
|
|
194
|
+
* With no in-place applicator on the same schema, `unevaluatedProperties`
|
|
195
|
+
* means the same as `additionalProperties` (JSON Schema 2020-12 §11.3,
|
|
196
|
+
* §10.3.2.3), so rename it. When `additionalProperties` is also present it
|
|
197
|
+
* evaluates every remaining key, so `unevaluatedProperties` is dropped.
|
|
198
|
+
*/
|
|
199
|
+
const normalizeUnevaluatedProperties = (schema) => {
|
|
200
|
+
if (!('unevaluatedProperties' in schema)) {
|
|
201
|
+
return schema;
|
|
202
|
+
}
|
|
203
|
+
if (inPlaceApplicators.some(keyword => keyword in schema)) {
|
|
204
|
+
return schema;
|
|
205
|
+
}
|
|
206
|
+
// `unevaluatedProperties` is a 3.1 keyword the 3.0-typed SchemaObject does
|
|
207
|
+
// not model; read it via an annotated destructure.
|
|
208
|
+
const { unevaluatedProperties, ...rest } = schema;
|
|
209
|
+
if ('additionalProperties' in rest) {
|
|
210
|
+
return rest;
|
|
211
|
+
}
|
|
212
|
+
if (!isAdditionalPropertiesValue(unevaluatedProperties)) {
|
|
213
|
+
return schema;
|
|
214
|
+
}
|
|
215
|
+
return { ...rest, additionalProperties: unevaluatedProperties };
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* Is this a value `additionalProperties` accepts: a boolean, or a schema or
|
|
219
|
+
* `$ref` object?
|
|
220
|
+
*/
|
|
221
|
+
const isAdditionalPropertiesValue = (value) => typeof value === 'boolean' ||
|
|
222
|
+
(typeof value === 'object' && value !== null && !Array.isArray(value));
|
|
223
|
+
/**
|
|
224
|
+
* Remove `unevaluatedProperties` from an `allOf`/`oneOf`/`anyOf` wrapper and
|
|
225
|
+
* log it as skipped. Left in place, it would be renamed to
|
|
226
|
+
* `additionalProperties` when the merged or collapsed schema is dispatched
|
|
227
|
+
* again with no composition keyword left.
|
|
228
|
+
*/
|
|
229
|
+
const withoutWrapperUnevaluatedProperties = ({ schema, parentType, stackTrail, context }) => {
|
|
230
|
+
if (!('unevaluatedProperties' in schema)) {
|
|
231
|
+
return schema;
|
|
232
|
+
}
|
|
233
|
+
const { unevaluatedProperties, ...rest } = schema;
|
|
234
|
+
context.logSkippedFields({
|
|
235
|
+
skipped: { unevaluatedProperties },
|
|
236
|
+
parent: schema,
|
|
237
|
+
parentType,
|
|
238
|
+
stackTrail
|
|
239
|
+
});
|
|
240
|
+
return rest;
|
|
241
|
+
};
|
|
179
242
|
/**
|
|
180
243
|
* Map the 3.1-only schema-shape encodings onto the shared IR shape before
|
|
181
|
-
* dispatching: type arrays, `const`, `examples[]`,
|
|
182
|
-
* and binary/base64 content keywords. Each step
|
|
183
|
-
* when it does not apply, so the composed result
|
|
184
|
-
* `schema` when nothing matched — letting the
|
|
185
|
-
* needed.
|
|
244
|
+
* dispatching: `unevaluatedProperties`, type arrays, `const`, `examples[]`,
|
|
245
|
+
* numeric exclusive bounds, and binary/base64 content keywords. Each step
|
|
246
|
+
* returns its input unchanged when it does not apply, so the composed result
|
|
247
|
+
* is referentially equal to `schema` when nothing matched — letting the
|
|
248
|
+
* dispatcher re-dispatch only when needed.
|
|
186
249
|
*/
|
|
187
250
|
const normalizeSchemaShape = (schema) => {
|
|
188
|
-
return normalizeExclusiveBounds(normalizeExamples(normalizeConst(normalizeContentFormat(normalizeTypeArray(schema)))));
|
|
251
|
+
return normalizeExclusiveBounds(normalizeExamples(normalizeConst(normalizeContentFormat(normalizeTypeArray(normalizeUnevaluatedProperties(schema))))));
|
|
189
252
|
};
|
|
190
253
|
/**
|
|
191
254
|
* Is this a 3.1 `{ type: 'null' }` schema member? (`'null'` is a 3.1 type
|
|
@@ -257,6 +320,12 @@ const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
257
320
|
return (0, exports.toSchemaV3)({ schema: normalized, stackTrail, context });
|
|
258
321
|
}
|
|
259
322
|
if ('allOf' in schema && Array.isArray(schema.allOf)) {
|
|
323
|
+
const wrapper = withoutWrapperUnevaluatedProperties({
|
|
324
|
+
schema,
|
|
325
|
+
parentType: 'allOf',
|
|
326
|
+
stackTrail,
|
|
327
|
+
context
|
|
328
|
+
});
|
|
260
329
|
return stackTrail.trace('allOf', st => {
|
|
261
330
|
// A single-member allOf collapses to its sole member — the value must
|
|
262
331
|
// satisfy exactly that one schema. When the member is a `$ref`, keep it
|
|
@@ -266,7 +335,7 @@ const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
266
335
|
// self-referential, and eagerly resolving it would not terminate. The
|
|
267
336
|
// ref resolves at use time like every other ref. Mirrors the
|
|
268
337
|
// single-member oneOf/anyOf handling below.
|
|
269
|
-
const { allOf, ...value } =
|
|
338
|
+
const { allOf, ...value } = wrapper;
|
|
270
339
|
const members = allOf ?? [];
|
|
271
340
|
if (members.length === 1) {
|
|
272
341
|
const [member] = members;
|
|
@@ -286,20 +355,26 @@ const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
286
355
|
});
|
|
287
356
|
}
|
|
288
357
|
const merged = (0, merge_intersection_js_1.mergeIntersection)({
|
|
289
|
-
schema,
|
|
358
|
+
schema: wrapper,
|
|
290
359
|
getRef: (0, refFns_js_2.toGetRef)(context.documentObject)
|
|
291
360
|
});
|
|
292
361
|
return (0, exports.toSchemaV3)({ schema: merged, stackTrail: st, context });
|
|
293
362
|
});
|
|
294
363
|
}
|
|
295
364
|
if ('oneOf' in schema && Array.isArray(schema.oneOf)) {
|
|
365
|
+
const wrapper = withoutWrapperUnevaluatedProperties({
|
|
366
|
+
schema,
|
|
367
|
+
parentType: 'oneOf',
|
|
368
|
+
stackTrail,
|
|
369
|
+
context
|
|
370
|
+
});
|
|
296
371
|
return stackTrail.trace('oneOf', st => {
|
|
297
372
|
// 3.1: a `{ type: 'null' }` member makes the union nullable. Fold it
|
|
298
373
|
// into a `nullable` flag the collapse/union logic already understands.
|
|
299
|
-
const { members: oneOfWithoutNull, nullable: hasNullMember } = partitionNullMember(
|
|
374
|
+
const { members: oneOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.oneOf ?? []);
|
|
300
375
|
const withoutNull = hasNullMember
|
|
301
|
-
? { ...
|
|
302
|
-
:
|
|
376
|
+
? { ...wrapper, oneOf: oneOfWithoutNull, nullable: true }
|
|
377
|
+
: wrapper;
|
|
303
378
|
const merged = (0, merge_union_js_1.mergeUnion)({
|
|
304
379
|
schema: withoutNull,
|
|
305
380
|
getRef: (0, refFns_js_2.toGetRef)(context.documentObject),
|
|
@@ -342,19 +417,25 @@ const toSchemaV3 = ({ schema, stackTrail, context }) => {
|
|
|
342
417
|
});
|
|
343
418
|
}
|
|
344
419
|
if ('anyOf' in schema && Array.isArray(schema.anyOf)) {
|
|
420
|
+
const wrapper = withoutWrapperUnevaluatedProperties({
|
|
421
|
+
schema,
|
|
422
|
+
parentType: 'anyOf',
|
|
423
|
+
stackTrail,
|
|
424
|
+
context
|
|
425
|
+
});
|
|
345
426
|
return stackTrail.trace('anyOf', st => {
|
|
346
427
|
// deno-lint-ignore ban-ts-comment
|
|
347
428
|
// @ts-expect-error
|
|
348
|
-
if (
|
|
349
|
-
const { anyOf, ...value } =
|
|
429
|
+
if (wrapper['x-expansionResources'] && Array.isArray(wrapper.anyOf)) {
|
|
430
|
+
const { anyOf, ...value } = wrapper;
|
|
350
431
|
return (0, toUnion_js_1.toUnion)({ value, members: anyOf, parentType: 'anyOf', stackTrail: st, context });
|
|
351
432
|
}
|
|
352
433
|
// 3.1: a `{ type: 'null' }` member makes the union nullable (mirrors
|
|
353
434
|
// the oneOf branch above).
|
|
354
|
-
const { members: anyOfWithoutNull, nullable: hasNullMember } = partitionNullMember(
|
|
435
|
+
const { members: anyOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.anyOf ?? []);
|
|
355
436
|
const withoutNull = hasNullMember
|
|
356
|
-
? { ...
|
|
357
|
-
:
|
|
437
|
+
? { ...wrapper, anyOf: anyOfWithoutNull, nullable: true }
|
|
438
|
+
: wrapper;
|
|
358
439
|
const merged = (0, merge_union_js_1.mergeUnion)({
|
|
359
440
|
schema: withoutNull,
|
|
360
441
|
getRef: (0, refFns_js_2.toGetRef)(context.documentObject),
|