@skmtc/core 0.31.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.
@@ -1,5 +1,6 @@
1
1
  import type { OasParameter } from '../parameter/Parameter.js';
2
2
  import type { OasRef } from '../ref/Ref.js';
3
+ import type { OasServer } from '../server/Server.js';
3
4
  /**
4
5
  * Constructor fields for {@link OasPathItem}.
5
6
  */
@@ -10,6 +11,8 @@ export type PathItemFields = {
10
11
  description?: string | undefined;
11
12
  /** Parameters that are applicable for all operations on this path */
12
13
  parameters?: (OasParameter | OasRef<'parameter'>)[] | undefined;
14
+ /** Servers that replace the document's servers for all operations on this path */
15
+ servers?: OasServer[] | undefined;
13
16
  /** Custom extension fields (x-* properties) */
14
17
  extensionFields?: Record<string, unknown>;
15
18
  };
@@ -103,6 +106,8 @@ export declare class OasPathItem {
103
106
  summary: string | undefined;
104
107
  description: string | undefined;
105
108
  parameters: (OasParameter | OasRef<'parameter'>)[] | undefined;
109
+ /** Servers that replace the document's servers for all operations on this path */
110
+ servers: OasServer[] | undefined;
106
111
  extensionFields: Record<string, unknown> | undefined;
107
112
  constructor(fields?: PathItemFields);
108
113
  }
@@ -109,6 +109,13 @@ export class OasPathItem {
109
109
  writable: true,
110
110
  value: void 0
111
111
  });
112
+ /** Servers that replace the document's servers for all operations on this path */
113
+ Object.defineProperty(this, "servers", {
114
+ enumerable: true,
115
+ configurable: true,
116
+ writable: true,
117
+ value: void 0
118
+ });
112
119
  Object.defineProperty(this, "extensionFields", {
113
120
  enumerable: true,
114
121
  configurable: true,
@@ -118,6 +125,7 @@ export class OasPathItem {
118
125
  this.summary = fields.summary;
119
126
  this.description = fields.description;
120
127
  this.parameters = fields.parameters;
128
+ this.servers = fields.servers;
121
129
  this.extensionFields = fields.extensionFields;
122
130
  }
123
131
  }
@@ -1,10 +1,11 @@
1
1
  import type { ParseContextType } from '../../../context/parseTypes.js';
2
- import type { OasParameterLocation } from '../../../oas/parameter/parameter-types.js';
2
+ import type { OasParameterLocation, OasParameterStyle } from '../../../oas/parameter/parameter-types.js';
3
3
  import type { OpenAPIV3 } from 'openapi-types';
4
4
  import { OasParameter } from '../../../oas/parameter/Parameter.js';
5
5
  import type { OasRef } from '../../../oas/ref/Ref.js';
6
6
  import type { StackTrail } from '../../../context/StackTrail.js';
7
7
  type ParameterLocation = Exclude<OasParameterLocation, 'querystring'>;
8
+ type ParameterStyle = Exclude<OasParameterStyle, 'cookie'>;
8
9
  export type ToParameterListV3Args = {
9
10
  parameters: (OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject)[] | undefined;
10
11
  stackTrail: StackTrail;
@@ -35,7 +36,7 @@ export type ToStyleArgs = {
35
36
  };
36
37
  export type ToExplodeArgs = {
37
38
  explode: boolean | undefined;
38
- style: string | undefined;
39
+ style: ParameterStyle;
39
40
  stackTrail: StackTrail;
40
41
  };
41
42
  export {};
@@ -72,14 +72,15 @@ const toParameterV3 = ({ parameter, stackTrail, context }) => {
72
72
  }
73
73
  // Set missing 'required' to true for path parameters and false for others
74
74
  const defaultRequired = typeof required === 'undefined' ? (parsedLocation === 'path' ? true : false) : required;
75
+ const parsedStyle = stackTrail.trace('style', st => toStyle({ style, location: parsedLocation, stackTrail: st }));
75
76
  const fields = {
76
77
  name,
77
78
  location: parsedLocation,
78
79
  description,
79
80
  required: defaultRequired,
80
81
  deprecated,
81
- style: stackTrail.trace('style', st => toStyle({ style, location: parsedLocation, stackTrail: st })),
82
- explode: stackTrail.trace('explode', st => toExplode({ explode, style, stackTrail: st })),
82
+ style: parsedStyle,
83
+ explode: stackTrail.trace('explode', st => toExplode({ explode, style: parsedStyle, stackTrail: st })),
83
84
  allowEmptyValue,
84
85
  allowReserved,
85
86
  schema: stackTrail.trace('schema', st => toOptionalSchemaV3({ schema, stackTrail: st, context })),
@@ -1,12 +1,22 @@
1
1
  import { toParameterListV3 } from '../parameter/toParameterV3.js';
2
2
  import { OasPathItem } from '../../../oas/pathItem/PathItem.js';
3
+ import { toOptionalServersV3 } from '../server/toServerV3.js';
4
+ import { tryParseAt } from '../../../context/tryParseAt.js';
3
5
  import { toSpecificationExtensionsV3 } from '../specificationExtensions/toSpecificationExtensionsV3.js';
4
6
  export const toPathItemV3 = ({ pathItem, stackTrail, context }) => {
5
- const { summary, description, parameters, ...skipped } = pathItem;
7
+ const { summary, description, parameters, servers, ...skipped } = pathItem;
6
8
  return new OasPathItem({
7
9
  summary,
8
10
  description,
9
11
  parameters: stackTrail.trace('parameters', st => toParameterListV3({ parameters, stackTrail: st, context })),
12
+ servers: tryParseAt({
13
+ stackTrail,
14
+ key: 'servers',
15
+ context,
16
+ type: 'INVALID_OPERATION',
17
+ parent: pathItem,
18
+ fn: st => toOptionalServersV3({ servers, stackTrail: st, context })
19
+ }),
10
20
  extensionFields: toSpecificationExtensionsV3({
11
21
  skipped,
12
22
  parent: pathItem,
@@ -1,10 +1,11 @@
1
1
  import type { ParseContextType } from '../../../context/parseTypes.js';
2
- import type { OasParameterLocation } from '../../../oas/parameter/parameter-types.js';
2
+ import type { OasParameterLocation, OasParameterStyle } from '../../../oas/parameter/parameter-types.js';
3
3
  import type { OpenAPIV3 } from 'openapi-types';
4
4
  import { OasParameter } from '../../../oas/parameter/Parameter.js';
5
5
  import type { OasRef } from '../../../oas/ref/Ref.js';
6
6
  import type { StackTrail } from '../../../context/StackTrail.js';
7
7
  type ParameterLocation = Exclude<OasParameterLocation, 'querystring'>;
8
+ type ParameterStyle = Exclude<OasParameterStyle, 'cookie'>;
8
9
  export type ToParameterListV3Args = {
9
10
  parameters: (OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject)[] | undefined;
10
11
  stackTrail: StackTrail;
@@ -35,7 +36,7 @@ export type ToStyleArgs = {
35
36
  };
36
37
  export type ToExplodeArgs = {
37
38
  explode: boolean | undefined;
38
- style: string | undefined;
39
+ style: ParameterStyle;
39
40
  stackTrail: StackTrail;
40
41
  };
41
42
  export {};
@@ -72,14 +72,15 @@ const toParameterV3 = ({ parameter, stackTrail, context }) => {
72
72
  }
73
73
  // Set missing 'required' to true for path parameters and false for others
74
74
  const defaultRequired = typeof required === 'undefined' ? (parsedLocation === 'path' ? true : false) : required;
75
+ const parsedStyle = stackTrail.trace('style', st => toStyle({ style, location: parsedLocation, stackTrail: st }));
75
76
  const fields = {
76
77
  name,
77
78
  location: parsedLocation,
78
79
  description,
79
80
  required: defaultRequired,
80
81
  deprecated,
81
- style: stackTrail.trace('style', st => toStyle({ style, location: parsedLocation, stackTrail: st })),
82
- explode: stackTrail.trace('explode', st => toExplode({ explode, style, stackTrail: st })),
82
+ style: parsedStyle,
83
+ explode: stackTrail.trace('explode', st => toExplode({ explode, style: parsedStyle, stackTrail: st })),
83
84
  allowEmptyValue,
84
85
  allowReserved,
85
86
  schema: stackTrail.trace('schema', st => toOptionalSchemaV3({ schema, stackTrail: st, context })),
@@ -1,12 +1,22 @@
1
1
  import { toParameterListV3 } from '../parameter/toParameterV3.js';
2
2
  import { OasPathItem } from '../../../oas/pathItem/PathItem.js';
3
+ import { toOptionalServersV3 } from '../server/toServerV3.js';
4
+ import { tryParseAt } from '../../../context/tryParseAt.js';
3
5
  import { toSpecificationExtensionsV3 } from '../specificationExtensions/toSpecificationExtensionsV3.js';
4
6
  export const toPathItemV3 = ({ pathItem, stackTrail, context }) => {
5
- const { summary, description, parameters, ...skipped } = pathItem;
7
+ const { summary, description, parameters, servers, ...skipped } = pathItem;
6
8
  return new OasPathItem({
7
9
  summary,
8
10
  description,
9
11
  parameters: stackTrail.trace('parameters', st => toParameterListV3({ parameters, stackTrail: st, context })),
12
+ servers: tryParseAt({
13
+ stackTrail,
14
+ key: 'servers',
15
+ context,
16
+ type: 'INVALID_OPERATION',
17
+ parent: pathItem,
18
+ fn: st => toOptionalServersV3({ servers, stackTrail: st, context })
19
+ }),
10
20
  extensionFields: toSpecificationExtensionsV3({
11
21
  skipped,
12
22
  parent: pathItem,
@@ -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[]`, numeric exclusive bounds,
177
- * and binary/base64 content keywords. Each step returns its input unchanged
178
- * when it does not apply, so the composed result is referentially equal to
179
- * `schema` when nothing matched — letting the dispatcher re-dispatch only when
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 } = schema;
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(schema.oneOf ?? []);
369
+ const { members: oneOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.oneOf ?? []);
295
370
  const withoutNull = hasNullMember
296
- ? { ...schema, oneOf: oneOfWithoutNull, nullable: true }
297
- : schema;
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 (schema['x-expansionResources'] && Array.isArray(schema.anyOf)) {
344
- const { anyOf, ...value } = schema;
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(schema.anyOf ?? []);
430
+ const { members: anyOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.anyOf ?? []);
350
431
  const withoutNull = hasNullMember
351
- ? { ...schema, anyOf: anyOfWithoutNull, nullable: true }
352
- : schema;
432
+ ? { ...wrapper, anyOf: anyOfWithoutNull, nullable: true }
433
+ : wrapper;
353
434
  const merged = mergeUnion({
354
435
  schema: withoutNull,
355
436
  getRef: toGetRef(context.documentObject),
@@ -1,5 +1,5 @@
1
1
  import type { ParseContextType } from '../../../context/parseTypes.js';
2
- import { type OasParameterLocation } from '../../../oas/parameter/parameter-types.js';
2
+ import { type OasParameterLocation, type OasParameterStyle } from '../../../oas/parameter/parameter-types.js';
3
3
  import type { OpenAPIV3 } from 'openapi-types';
4
4
  import { OasParameter } from '../../../oas/parameter/Parameter.js';
5
5
  import type { OasRef } from '../../../oas/ref/Ref.js';
@@ -34,6 +34,6 @@ export type ToStyleArgs = {
34
34
  };
35
35
  export type ToExplodeArgs = {
36
36
  explode: boolean | undefined;
37
- style: string | undefined;
37
+ style: OasParameterStyle | undefined;
38
38
  stackTrail: StackTrail;
39
39
  };
@@ -91,14 +91,15 @@ const toParameterV3 = ({ parameter, stackTrail, context }) => {
91
91
  }
92
92
  // Set missing 'required' to true for path parameters and false for others
93
93
  const defaultRequired = typeof required === 'undefined' ? (parsedLocation === 'path' ? true : false) : required;
94
+ const parsedStyle = stackTrail.trace('style', st => toStyle({ style, location: parsedLocation, stackTrail: st }));
94
95
  const fields = {
95
96
  name,
96
97
  location: parsedLocation,
97
98
  description,
98
99
  required: defaultRequired,
99
100
  deprecated,
100
- style: stackTrail.trace('style', st => toStyle({ style, location: parsedLocation, stackTrail: st })),
101
- explode: stackTrail.trace('explode', st => toExplode({ explode, style, stackTrail: st })),
101
+ style: parsedStyle,
102
+ explode: stackTrail.trace('explode', st => toExplode({ explode, style: parsedStyle, stackTrail: st })),
102
103
  allowEmptyValue,
103
104
  allowReserved,
104
105
  schema: stackTrail.trace('schema', st => toOptionalSchemaV3({ schema, stackTrail: st, context })),
@@ -1,13 +1,23 @@
1
1
  import { toParameterListV3 } from '../parameter/toParameterV3.js';
2
2
  import { OasPathItem } from '../../../oas/pathItem/PathItem.js';
3
+ import { toOptionalServersV3 } from '../server/toServerV3.js';
4
+ import { tryParseAt } from '../../../context/tryParseAt.js';
3
5
  import { toSpecificationExtensionsV3 } from '../specificationExtensions/toSpecificationExtensionsV3.js';
4
6
  import { isEmpty } from '../../../helpers/isEmpty.js';
5
7
  export const toPathItemV3 = ({ pathItem, stackTrail, context }) => {
6
- const { summary, description, parameters, ...skipped } = pathItem;
8
+ const { summary, description, parameters, servers, ...skipped } = pathItem;
7
9
  return new OasPathItem({
8
10
  summary,
9
11
  description,
10
12
  parameters: stackTrail.trace('parameters', st => toParameterListV3({ parameters, stackTrail: st, context })),
13
+ servers: tryParseAt({
14
+ stackTrail,
15
+ key: 'servers',
16
+ context,
17
+ type: 'INVALID_OPERATION',
18
+ parent: pathItem,
19
+ fn: st => toOptionalServersV3({ servers, stackTrail: st, context })
20
+ }),
11
21
  extensionFields: toSpecificationExtensionsV3({
12
22
  skipped,
13
23
  parent: pathItem,
@@ -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[]`, numeric exclusive bounds,
177
- * and binary/base64 content keywords. Each step returns its input unchanged
178
- * when it does not apply, so the composed result is referentially equal to
179
- * `schema` when nothing matched — letting the dispatcher re-dispatch only when
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 } = schema;
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(schema.oneOf ?? []);
369
+ const { members: oneOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.oneOf ?? []);
295
370
  const withoutNull = hasNullMember
296
- ? { ...schema, oneOf: oneOfWithoutNull, nullable: true }
297
- : schema;
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 (schema['x-expansionResources'] && Array.isArray(schema.anyOf)) {
344
- const { anyOf, ...value } = schema;
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(schema.anyOf ?? []);
430
+ const { members: anyOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.anyOf ?? []);
350
431
  const withoutNull = hasNullMember
351
- ? { ...schema, anyOf: anyOfWithoutNull, nullable: true }
352
- : schema;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skmtc/core",
3
- "version": "0.31.0",
3
+ "version": "0.32.1",
4
4
  "description": "Skmtc is a declarative code generation framework",
5
5
  "keywords": [
6
6
  "openapi",
@@ -1,5 +1,6 @@
1
1
  import type { OasParameter } from '../parameter/Parameter.js';
2
2
  import type { OasRef } from '../ref/Ref.js';
3
+ import type { OasServer } from '../server/Server.js';
3
4
  /**
4
5
  * Constructor fields for {@link OasPathItem}.
5
6
  */
@@ -10,6 +11,8 @@ export type PathItemFields = {
10
11
  description?: string | undefined;
11
12
  /** Parameters that are applicable for all operations on this path */
12
13
  parameters?: (OasParameter | OasRef<'parameter'>)[] | undefined;
14
+ /** Servers that replace the document's servers for all operations on this path */
15
+ servers?: OasServer[] | undefined;
13
16
  /** Custom extension fields (x-* properties) */
14
17
  extensionFields?: Record<string, unknown>;
15
18
  };
@@ -103,6 +106,8 @@ export declare class OasPathItem {
103
106
  summary: string | undefined;
104
107
  description: string | undefined;
105
108
  parameters: (OasParameter | OasRef<'parameter'>)[] | undefined;
109
+ /** Servers that replace the document's servers for all operations on this path */
110
+ servers: OasServer[] | undefined;
106
111
  extensionFields: Record<string, unknown> | undefined;
107
112
  constructor(fields?: PathItemFields);
108
113
  }
@@ -112,6 +112,13 @@ class OasPathItem {
112
112
  writable: true,
113
113
  value: void 0
114
114
  });
115
+ /** Servers that replace the document's servers for all operations on this path */
116
+ Object.defineProperty(this, "servers", {
117
+ enumerable: true,
118
+ configurable: true,
119
+ writable: true,
120
+ value: void 0
121
+ });
115
122
  Object.defineProperty(this, "extensionFields", {
116
123
  enumerable: true,
117
124
  configurable: true,
@@ -121,6 +128,7 @@ class OasPathItem {
121
128
  this.summary = fields.summary;
122
129
  this.description = fields.description;
123
130
  this.parameters = fields.parameters;
131
+ this.servers = fields.servers;
124
132
  this.extensionFields = fields.extensionFields;
125
133
  }
126
134
  }
@@ -1,10 +1,11 @@
1
1
  import type { ParseContextType } from '../../../context/parseTypes.js';
2
- import type { OasParameterLocation } from '../../../oas/parameter/parameter-types.js';
2
+ import type { OasParameterLocation, OasParameterStyle } from '../../../oas/parameter/parameter-types.js';
3
3
  import type { OpenAPIV3 } from 'openapi-types';
4
4
  import { OasParameter } from '../../../oas/parameter/Parameter.js';
5
5
  import type { OasRef } from '../../../oas/ref/Ref.js';
6
6
  import type { StackTrail } from '../../../context/StackTrail.js';
7
7
  type ParameterLocation = Exclude<OasParameterLocation, 'querystring'>;
8
+ type ParameterStyle = Exclude<OasParameterStyle, 'cookie'>;
8
9
  export type ToParameterListV3Args = {
9
10
  parameters: (OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject)[] | undefined;
10
11
  stackTrail: StackTrail;
@@ -35,7 +36,7 @@ export type ToStyleArgs = {
35
36
  };
36
37
  export type ToExplodeArgs = {
37
38
  explode: boolean | undefined;
38
- style: string | undefined;
39
+ style: ParameterStyle;
39
40
  stackTrail: StackTrail;
40
41
  };
41
42
  export {};
@@ -111,14 +111,15 @@ const toParameterV3 = ({ parameter, stackTrail, context }) => {
111
111
  }
112
112
  // Set missing 'required' to true for path parameters and false for others
113
113
  const defaultRequired = typeof required === 'undefined' ? (parsedLocation === 'path' ? true : false) : required;
114
+ const parsedStyle = stackTrail.trace('style', st => toStyle({ style, location: parsedLocation, stackTrail: st }));
114
115
  const fields = {
115
116
  name,
116
117
  location: parsedLocation,
117
118
  description,
118
119
  required: defaultRequired,
119
120
  deprecated,
120
- style: stackTrail.trace('style', st => toStyle({ style, location: parsedLocation, stackTrail: st })),
121
- explode: stackTrail.trace('explode', st => toExplode({ explode, style, stackTrail: st })),
121
+ style: parsedStyle,
122
+ explode: stackTrail.trace('explode', st => toExplode({ explode, style: parsedStyle, stackTrail: st })),
122
123
  allowEmptyValue,
123
124
  allowReserved,
124
125
  schema: stackTrail.trace('schema', st => (0, toSchemasV3_js_1.toOptionalSchemaV3)({ schema, stackTrail: st, context })),
@@ -3,13 +3,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toPathItemV3 = void 0;
4
4
  const toParameterV3_js_1 = require("../parameter/toParameterV3.js");
5
5
  const PathItem_js_1 = require("../../../oas/pathItem/PathItem.js");
6
+ const toServerV3_js_1 = require("../server/toServerV3.js");
7
+ const tryParseAt_js_1 = require("../../../context/tryParseAt.js");
6
8
  const toSpecificationExtensionsV3_js_1 = require("../specificationExtensions/toSpecificationExtensionsV3.js");
7
9
  const toPathItemV3 = ({ pathItem, stackTrail, context }) => {
8
- const { summary, description, parameters, ...skipped } = pathItem;
10
+ const { summary, description, parameters, servers, ...skipped } = pathItem;
9
11
  return new PathItem_js_1.OasPathItem({
10
12
  summary,
11
13
  description,
12
14
  parameters: stackTrail.trace('parameters', st => (0, toParameterV3_js_1.toParameterListV3)({ parameters, stackTrail: st, context })),
15
+ servers: (0, tryParseAt_js_1.tryParseAt)({
16
+ stackTrail,
17
+ key: 'servers',
18
+ context,
19
+ type: 'INVALID_OPERATION',
20
+ parent: pathItem,
21
+ fn: st => (0, toServerV3_js_1.toOptionalServersV3)({ servers, stackTrail: st, context })
22
+ }),
13
23
  extensionFields: (0, toSpecificationExtensionsV3_js_1.toSpecificationExtensionsV3)({
14
24
  skipped,
15
25
  parent: pathItem,
@@ -1,10 +1,11 @@
1
1
  import type { ParseContextType } from '../../../context/parseTypes.js';
2
- import type { OasParameterLocation } from '../../../oas/parameter/parameter-types.js';
2
+ import type { OasParameterLocation, OasParameterStyle } from '../../../oas/parameter/parameter-types.js';
3
3
  import type { OpenAPIV3 } from 'openapi-types';
4
4
  import { OasParameter } from '../../../oas/parameter/Parameter.js';
5
5
  import type { OasRef } from '../../../oas/ref/Ref.js';
6
6
  import type { StackTrail } from '../../../context/StackTrail.js';
7
7
  type ParameterLocation = Exclude<OasParameterLocation, 'querystring'>;
8
+ type ParameterStyle = Exclude<OasParameterStyle, 'cookie'>;
8
9
  export type ToParameterListV3Args = {
9
10
  parameters: (OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject)[] | undefined;
10
11
  stackTrail: StackTrail;
@@ -35,7 +36,7 @@ export type ToStyleArgs = {
35
36
  };
36
37
  export type ToExplodeArgs = {
37
38
  explode: boolean | undefined;
38
- style: string | undefined;
39
+ style: ParameterStyle;
39
40
  stackTrail: StackTrail;
40
41
  };
41
42
  export {};
@@ -111,14 +111,15 @@ const toParameterV3 = ({ parameter, stackTrail, context }) => {
111
111
  }
112
112
  // Set missing 'required' to true for path parameters and false for others
113
113
  const defaultRequired = typeof required === 'undefined' ? (parsedLocation === 'path' ? true : false) : required;
114
+ const parsedStyle = stackTrail.trace('style', st => toStyle({ style, location: parsedLocation, stackTrail: st }));
114
115
  const fields = {
115
116
  name,
116
117
  location: parsedLocation,
117
118
  description,
118
119
  required: defaultRequired,
119
120
  deprecated,
120
- style: stackTrail.trace('style', st => toStyle({ style, location: parsedLocation, stackTrail: st })),
121
- explode: stackTrail.trace('explode', st => toExplode({ explode, style, stackTrail: st })),
121
+ style: parsedStyle,
122
+ explode: stackTrail.trace('explode', st => toExplode({ explode, style: parsedStyle, stackTrail: st })),
122
123
  allowEmptyValue,
123
124
  allowReserved,
124
125
  schema: stackTrail.trace('schema', st => (0, toSchemasV3_js_1.toOptionalSchemaV3)({ schema, stackTrail: st, context })),
@@ -3,13 +3,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toPathItemV3 = void 0;
4
4
  const toParameterV3_js_1 = require("../parameter/toParameterV3.js");
5
5
  const PathItem_js_1 = require("../../../oas/pathItem/PathItem.js");
6
+ const toServerV3_js_1 = require("../server/toServerV3.js");
7
+ const tryParseAt_js_1 = require("../../../context/tryParseAt.js");
6
8
  const toSpecificationExtensionsV3_js_1 = require("../specificationExtensions/toSpecificationExtensionsV3.js");
7
9
  const toPathItemV3 = ({ pathItem, stackTrail, context }) => {
8
- const { summary, description, parameters, ...skipped } = pathItem;
10
+ const { summary, description, parameters, servers, ...skipped } = pathItem;
9
11
  return new PathItem_js_1.OasPathItem({
10
12
  summary,
11
13
  description,
12
14
  parameters: stackTrail.trace('parameters', st => (0, toParameterV3_js_1.toParameterListV3)({ parameters, stackTrail: st, context })),
15
+ servers: (0, tryParseAt_js_1.tryParseAt)({
16
+ stackTrail,
17
+ key: 'servers',
18
+ context,
19
+ type: 'INVALID_OPERATION',
20
+ parent: pathItem,
21
+ fn: st => (0, toServerV3_js_1.toOptionalServersV3)({ servers, stackTrail: st, context })
22
+ }),
13
23
  extensionFields: (0, toSpecificationExtensionsV3_js_1.toSpecificationExtensionsV3)({
14
24
  skipped,
15
25
  parent: pathItem,
@@ -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[]`, numeric exclusive bounds,
182
- * and binary/base64 content keywords. Each step returns its input unchanged
183
- * when it does not apply, so the composed result is referentially equal to
184
- * `schema` when nothing matched — letting the dispatcher re-dispatch only when
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 } = schema;
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(schema.oneOf ?? []);
374
+ const { members: oneOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.oneOf ?? []);
300
375
  const withoutNull = hasNullMember
301
- ? { ...schema, oneOf: oneOfWithoutNull, nullable: true }
302
- : schema;
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 (schema['x-expansionResources'] && Array.isArray(schema.anyOf)) {
349
- const { anyOf, ...value } = schema;
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(schema.anyOf ?? []);
435
+ const { members: anyOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.anyOf ?? []);
355
436
  const withoutNull = hasNullMember
356
- ? { ...schema, anyOf: anyOfWithoutNull, nullable: true }
357
- : schema;
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),
@@ -1,5 +1,5 @@
1
1
  import type { ParseContextType } from '../../../context/parseTypes.js';
2
- import { type OasParameterLocation } from '../../../oas/parameter/parameter-types.js';
2
+ import { type OasParameterLocation, type OasParameterStyle } from '../../../oas/parameter/parameter-types.js';
3
3
  import type { OpenAPIV3 } from 'openapi-types';
4
4
  import { OasParameter } from '../../../oas/parameter/Parameter.js';
5
5
  import type { OasRef } from '../../../oas/ref/Ref.js';
@@ -34,6 +34,6 @@ export type ToStyleArgs = {
34
34
  };
35
35
  export type ToExplodeArgs = {
36
36
  explode: boolean | undefined;
37
- style: string | undefined;
37
+ style: OasParameterStyle | undefined;
38
38
  stackTrail: StackTrail;
39
39
  };
@@ -130,14 +130,15 @@ const toParameterV3 = ({ parameter, stackTrail, context }) => {
130
130
  }
131
131
  // Set missing 'required' to true for path parameters and false for others
132
132
  const defaultRequired = typeof required === 'undefined' ? (parsedLocation === 'path' ? true : false) : required;
133
+ const parsedStyle = stackTrail.trace('style', st => toStyle({ style, location: parsedLocation, stackTrail: st }));
133
134
  const fields = {
134
135
  name,
135
136
  location: parsedLocation,
136
137
  description,
137
138
  required: defaultRequired,
138
139
  deprecated,
139
- style: stackTrail.trace('style', st => toStyle({ style, location: parsedLocation, stackTrail: st })),
140
- explode: stackTrail.trace('explode', st => toExplode({ explode, style, stackTrail: st })),
140
+ style: parsedStyle,
141
+ explode: stackTrail.trace('explode', st => toExplode({ explode, style: parsedStyle, stackTrail: st })),
141
142
  allowEmptyValue,
142
143
  allowReserved,
143
144
  schema: stackTrail.trace('schema', st => (0, toSchemasV3_js_1.toOptionalSchemaV3)({ schema, stackTrail: st, context })),
@@ -3,14 +3,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.toPathItemResultV3 = exports.toPathItemV3 = void 0;
4
4
  const toParameterV3_js_1 = require("../parameter/toParameterV3.js");
5
5
  const PathItem_js_1 = require("../../../oas/pathItem/PathItem.js");
6
+ const toServerV3_js_1 = require("../server/toServerV3.js");
7
+ const tryParseAt_js_1 = require("../../../context/tryParseAt.js");
6
8
  const toSpecificationExtensionsV3_js_1 = require("../specificationExtensions/toSpecificationExtensionsV3.js");
7
9
  const isEmpty_js_1 = require("../../../helpers/isEmpty.js");
8
10
  const toPathItemV3 = ({ pathItem, stackTrail, context }) => {
9
- const { summary, description, parameters, ...skipped } = pathItem;
11
+ const { summary, description, parameters, servers, ...skipped } = pathItem;
10
12
  return new PathItem_js_1.OasPathItem({
11
13
  summary,
12
14
  description,
13
15
  parameters: stackTrail.trace('parameters', st => (0, toParameterV3_js_1.toParameterListV3)({ parameters, stackTrail: st, context })),
16
+ servers: (0, tryParseAt_js_1.tryParseAt)({
17
+ stackTrail,
18
+ key: 'servers',
19
+ context,
20
+ type: 'INVALID_OPERATION',
21
+ parent: pathItem,
22
+ fn: st => (0, toServerV3_js_1.toOptionalServersV3)({ servers, stackTrail: st, context })
23
+ }),
14
24
  extensionFields: (0, toSpecificationExtensionsV3_js_1.toSpecificationExtensionsV3)({
15
25
  skipped,
16
26
  parent: pathItem,
@@ -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[]`, numeric exclusive bounds,
182
- * and binary/base64 content keywords. Each step returns its input unchanged
183
- * when it does not apply, so the composed result is referentially equal to
184
- * `schema` when nothing matched — letting the dispatcher re-dispatch only when
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 } = schema;
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(schema.oneOf ?? []);
374
+ const { members: oneOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.oneOf ?? []);
300
375
  const withoutNull = hasNullMember
301
- ? { ...schema, oneOf: oneOfWithoutNull, nullable: true }
302
- : schema;
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 (schema['x-expansionResources'] && Array.isArray(schema.anyOf)) {
349
- const { anyOf, ...value } = schema;
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(schema.anyOf ?? []);
435
+ const { members: anyOfWithoutNull, nullable: hasNullMember } = partitionNullMember(wrapper.anyOf ?? []);
355
436
  const withoutNull = hasNullMember
356
- ? { ...schema, anyOf: anyOfWithoutNull, nullable: true }
357
- : schema;
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),