@skmtc/core 0.30.1 → 0.31.0

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.
Files changed (57) hide show
  1. package/esm/context/generateTypes.d.ts +14 -2
  2. package/esm/context/generateTypes.js +3 -1
  3. package/esm/mod.d.ts +1 -0
  4. package/esm/mod.js +1 -0
  5. package/esm/oas/header/Header.d.ts +4 -2
  6. package/esm/oas/header/Header.js +12 -5
  7. package/esm/oas/mediaType/jsonMediaType.d.ts +50 -0
  8. package/esm/oas/mediaType/jsonMediaType.js +64 -0
  9. package/esm/oas/mediaType/toContentSchema.d.ts +8 -0
  10. package/esm/oas/mediaType/toContentSchema.js +6 -0
  11. package/esm/oas/operation/Operation.d.ts +10 -2
  12. package/esm/oas/operation/Operation.js +20 -7
  13. package/esm/oas/parameter/Parameter.d.ts +1 -1
  14. package/esm/oas/parameter/Parameter.js +7 -4
  15. package/esm/oas/requestBody/RequestBody.d.ts +11 -0
  16. package/esm/oas/requestBody/RequestBody.js +16 -2
  17. package/esm/oas/response/Response.d.ts +11 -0
  18. package/esm/oas/response/Response.js +16 -2
  19. package/esm/oas/webhook/Webhook.d.ts +4 -4
  20. package/esm/oas/webhook/Webhook.js +6 -6
  21. package/esm/parse/logBodyMediaTypes.d.ts +27 -0
  22. package/esm/parse/logBodyMediaTypes.js +50 -0
  23. package/esm/parse/v3-0/requestBody/toRequestBodiesV3.js +4 -1
  24. package/esm/parse/v3-0/response/toResponseV3.js +4 -1
  25. package/esm/parse/v3-1/requestBody/toRequestBodiesV3.js +4 -1
  26. package/esm/parse/v3-1/response/toResponseV3.js +4 -1
  27. package/esm/parse/v3-2/requestBody/toRequestBodiesV3.js +4 -1
  28. package/esm/parse/v3-2/response/toResponseV3.js +4 -1
  29. package/package.json +1 -1
  30. package/script/context/generateTypes.d.ts +14 -2
  31. package/script/context/generateTypes.js +3 -1
  32. package/script/mod.d.ts +1 -0
  33. package/script/mod.js +1 -0
  34. package/script/oas/header/Header.d.ts +4 -2
  35. package/script/oas/header/Header.js +12 -5
  36. package/script/oas/mediaType/jsonMediaType.d.ts +50 -0
  37. package/script/oas/mediaType/jsonMediaType.js +71 -0
  38. package/script/oas/mediaType/toContentSchema.d.ts +8 -0
  39. package/script/oas/mediaType/toContentSchema.js +10 -0
  40. package/script/oas/operation/Operation.d.ts +10 -2
  41. package/script/oas/operation/Operation.js +20 -7
  42. package/script/oas/parameter/Parameter.d.ts +1 -1
  43. package/script/oas/parameter/Parameter.js +7 -4
  44. package/script/oas/requestBody/RequestBody.d.ts +11 -0
  45. package/script/oas/requestBody/RequestBody.js +16 -2
  46. package/script/oas/response/Response.d.ts +11 -0
  47. package/script/oas/response/Response.js +16 -2
  48. package/script/oas/webhook/Webhook.d.ts +4 -4
  49. package/script/oas/webhook/Webhook.js +6 -6
  50. package/script/parse/logBodyMediaTypes.d.ts +27 -0
  51. package/script/parse/logBodyMediaTypes.js +54 -0
  52. package/script/parse/v3-0/requestBody/toRequestBodiesV3.js +4 -1
  53. package/script/parse/v3-0/response/toResponseV3.js +4 -1
  54. package/script/parse/v3-1/requestBody/toRequestBodiesV3.js +4 -1
  55. package/script/parse/v3-1/response/toResponseV3.js +4 -1
  56. package/script/parse/v3-2/requestBody/toRequestBodiesV3.js +4 -1
  57. package/script/parse/v3-2/response/toResponseV3.js +4 -1
@@ -154,8 +154,20 @@ export type AnyOasDocument = OpenAPIV2.Document | OpenAPIV3.Document | OpenAPIV3
154
154
  * The TS type and the valibot schema below stay in sync via the
155
155
  * `v.GenericSchema<OasIssueType>` annotation — adding a variant in one
156
156
  * place without the other fails to type-check.
157
+ *
158
+ * Media type issues, at most one per response or request body, from the
159
+ * choice `toJsonContent` makes on its parsed `content`:
160
+ *
161
+ * - `WILDCARD_MEDIA_TYPE` (`warning`) — the body has no JSON media type
162
+ * (see `isJsonMediaType`) and is read from `*\/*` as JSON, since its schema
163
+ * there is an object, array, union or `$ref`. The document does not say the
164
+ * format; springdoc writes `*\/*` for every response.
165
+ * - `NO_JSON_MEDIA_TYPE` (`debug`) — no lookup without a media type reads the
166
+ * body, and its keys include a range: `application/*`, or `*\/*` with a
167
+ * scalar schema (a text body or a file). A body with only specific
168
+ * non-JSON types (`multipart/form-data`, `text/plain`) logs nothing.
157
169
  */
158
- export type OasIssueType = 'UNEXPECTED_PROPERTY' | 'MISSING_OBJECT_TYPE' | 'MISSING_STRING_TYPE' | 'MISSING_ARRAY_TYPE' | 'MISSING_BOOLEAN_TYPE' | 'INVALID_EXAMPLE' | 'INVALID_ENUM' | 'INVALID_DEFAULT' | 'INVALID_NULLABLE' | 'UNEXPECTED_FORMAT' | 'INVALID_RESPONSE' | 'INVALID_FORMAT' | 'INVALID_OPERATION' | 'INVALID_SCHEMA' | 'INVALID_PARAMETER' | 'INVALID_SECURITY_SCHEME' | 'INVALID_DEPENDENCY_REF' | 'EXAMPLE_AND_EXAMPLES_DEFINED' | 'INVALID_TAG_PARENT' | 'UNSUPPORTED_OPERATION_METHOD';
170
+ export type OasIssueType = 'UNEXPECTED_PROPERTY' | 'MISSING_OBJECT_TYPE' | 'MISSING_STRING_TYPE' | 'MISSING_ARRAY_TYPE' | 'MISSING_BOOLEAN_TYPE' | 'INVALID_EXAMPLE' | 'INVALID_ENUM' | 'INVALID_DEFAULT' | 'INVALID_NULLABLE' | 'UNEXPECTED_FORMAT' | 'INVALID_RESPONSE' | 'INVALID_FORMAT' | 'INVALID_OPERATION' | 'INVALID_SCHEMA' | 'INVALID_PARAMETER' | 'INVALID_SECURITY_SCHEME' | 'INVALID_DEPENDENCY_REF' | 'EXAMPLE_AND_EXAMPLES_DEFINED' | 'INVALID_TAG_PARENT' | 'UNSUPPORTED_OPERATION_METHOD' | 'WILDCARD_MEDIA_TYPE' | 'NO_JSON_MEDIA_TYPE';
159
171
  /**
160
172
  * Valibot schema for {@link OasIssueType}. Annotation deliberately omitted
161
173
  * so the precise literal-union output type flows through to consumers
@@ -163,7 +175,7 @@ export type OasIssueType = 'UNEXPECTED_PROPERTY' | 'MISSING_OBJECT_TYPE' | 'MISS
163
175
  * this list and {@link OasIssueType} is caught by `assertSchemaMatchesType`
164
176
  * below.
165
177
  */
166
- export declare const oasIssueType: v.UnionSchema<[v.LiteralSchema<"UNEXPECTED_PROPERTY", undefined>, v.LiteralSchema<"MISSING_OBJECT_TYPE", undefined>, v.LiteralSchema<"MISSING_STRING_TYPE", undefined>, v.LiteralSchema<"MISSING_ARRAY_TYPE", undefined>, v.LiteralSchema<"MISSING_BOOLEAN_TYPE", undefined>, v.LiteralSchema<"INVALID_EXAMPLE", undefined>, v.LiteralSchema<"INVALID_ENUM", undefined>, v.LiteralSchema<"INVALID_DEFAULT", undefined>, v.LiteralSchema<"INVALID_NULLABLE", undefined>, v.LiteralSchema<"UNEXPECTED_FORMAT", undefined>, v.LiteralSchema<"INVALID_RESPONSE", undefined>, v.LiteralSchema<"INVALID_FORMAT", undefined>, v.LiteralSchema<"INVALID_OPERATION", undefined>, v.LiteralSchema<"INVALID_SCHEMA", undefined>, v.LiteralSchema<"INVALID_PARAMETER", undefined>, v.LiteralSchema<"INVALID_SECURITY_SCHEME", undefined>, v.LiteralSchema<"INVALID_DEPENDENCY_REF", undefined>, v.LiteralSchema<"EXAMPLE_AND_EXAMPLES_DEFINED", undefined>, v.LiteralSchema<"INVALID_TAG_PARENT", undefined>, v.LiteralSchema<"UNSUPPORTED_OPERATION_METHOD", undefined>], undefined>;
178
+ export declare const oasIssueType: v.UnionSchema<[v.LiteralSchema<"UNEXPECTED_PROPERTY", undefined>, v.LiteralSchema<"MISSING_OBJECT_TYPE", undefined>, v.LiteralSchema<"MISSING_STRING_TYPE", undefined>, v.LiteralSchema<"MISSING_ARRAY_TYPE", undefined>, v.LiteralSchema<"MISSING_BOOLEAN_TYPE", undefined>, v.LiteralSchema<"INVALID_EXAMPLE", undefined>, v.LiteralSchema<"INVALID_ENUM", undefined>, v.LiteralSchema<"INVALID_DEFAULT", undefined>, v.LiteralSchema<"INVALID_NULLABLE", undefined>, v.LiteralSchema<"UNEXPECTED_FORMAT", undefined>, v.LiteralSchema<"INVALID_RESPONSE", undefined>, v.LiteralSchema<"INVALID_FORMAT", undefined>, v.LiteralSchema<"INVALID_OPERATION", undefined>, v.LiteralSchema<"INVALID_SCHEMA", undefined>, v.LiteralSchema<"INVALID_PARAMETER", undefined>, v.LiteralSchema<"INVALID_SECURITY_SCHEME", undefined>, v.LiteralSchema<"INVALID_DEPENDENCY_REF", undefined>, v.LiteralSchema<"EXAMPLE_AND_EXAMPLES_DEFINED", undefined>, v.LiteralSchema<"INVALID_TAG_PARENT", undefined>, v.LiteralSchema<"UNSUPPORTED_OPERATION_METHOD", undefined>, v.LiteralSchema<"WILDCARD_MEDIA_TYPE", undefined>, v.LiteralSchema<"NO_JSON_MEDIA_TYPE", undefined>], undefined>;
167
179
  export type GenerateResult = {
168
180
  files: Map<string, FileBase>;
169
181
  previews: Record<string, Preview>;
@@ -26,7 +26,9 @@ export const oasIssueType = v.union([
26
26
  v.literal('INVALID_DEPENDENCY_REF'),
27
27
  v.literal('EXAMPLE_AND_EXAMPLES_DEFINED'),
28
28
  v.literal('INVALID_TAG_PARENT'),
29
- v.literal('UNSUPPORTED_OPERATION_METHOD')
29
+ v.literal('UNSUPPORTED_OPERATION_METHOD'),
30
+ v.literal('WILDCARD_MEDIA_TYPE'),
31
+ v.literal('NO_JSON_MEDIA_TYPE')
30
32
  ]);
31
33
  // Compile-time drift detector: this binding fails to type-check if
32
34
  // `OasIssueType` and the literal list above disagree in either
package/esm/mod.d.ts CHANGED
@@ -146,6 +146,7 @@ export * from './oas/info/info-types.js';
146
146
  export * from './oas/license/License.js';
147
147
  export * from './oas/license/license-types.js';
148
148
  export * from './oas/mediaType/MediaType.js';
149
+ export * from './oas/mediaType/jsonMediaType.js';
149
150
  export * from './oas/integer/Integer.js';
150
151
  export * from './oas/integer/integer-types.js';
151
152
  export * from './parse/v3-0/integer/toInteger.js';
package/esm/mod.js CHANGED
@@ -146,6 +146,7 @@ export * from './oas/info/info-types.js';
146
146
  export * from './oas/license/License.js';
147
147
  export * from './oas/license/license-types.js';
148
148
  export * from './oas/mediaType/MediaType.js';
149
+ export * from './oas/mediaType/jsonMediaType.js';
149
150
  export * from './oas/integer/Integer.js';
150
151
  export * from './oas/integer/integer-types.js';
151
152
  export * from './parse/v3-0/integer/toInteger.js';
@@ -77,9 +77,11 @@ export declare class OasHeader {
77
77
  resolve(): OasHeader;
78
78
  resolveOnce(): OasHeader;
79
79
  /** Returns schema for the header. Either, `schema` property if
80
- * definedor value matching `mediaType` from `content` property.
80
+ * defined or value matching `mediaType` from `content` property. When
81
+ * `content` has exactly one entry (the spec requires one), its schema is
82
+ * used whatever the media type, as in `OasParameter.toSchema`.
81
83
  *
82
- * @param mediaType - Optional media type to get schema for. Defaults to `application/json`
84
+ * @param mediaType - Optional media type to get schema for, an exact lookup. Omitted: the JSON media type (`application/json`, else the first JSON-like type, else `*\/*`).
83
85
  */
84
86
  toSchema(mediaType?: string): OasSchema | OasRef<'schema'>;
85
87
  toJsonSchema(options: ToJsonSchemaOptions): OpenAPIV3.HeaderObject;
@@ -1,3 +1,4 @@
1
+ import { toContentSchema } from '../mediaType/toContentSchema.js';
1
2
  /**
2
3
  * Represents a Header Object in the OpenAPI Specification.
3
4
  *
@@ -106,17 +107,23 @@ export class OasHeader {
106
107
  return this;
107
108
  }
108
109
  /** Returns schema for the header. Either, `schema` property if
109
- * definedor value matching `mediaType` from `content` property.
110
+ * defined or value matching `mediaType` from `content` property. When
111
+ * `content` has exactly one entry (the spec requires one), its schema is
112
+ * used whatever the media type, as in `OasParameter.toSchema`.
110
113
  *
111
- * @param mediaType - Optional media type to get schema for. Defaults to `application/json`
114
+ * @param mediaType - Optional media type to get schema for, an exact lookup. Omitted: the JSON media type (`application/json`, else the first JSON-like type, else `*\/*`).
112
115
  */
113
- toSchema(mediaType = 'application/json') {
116
+ toSchema(mediaType) {
114
117
  if (this.schema) {
115
118
  return this.schema;
116
119
  }
117
- const schema = this.content?.[mediaType]?.schema;
120
+ const contentEntries = Object.values(this.content ?? {});
121
+ const onlyEntry = contentEntries.length === 1 ? contentEntries[0] : undefined;
122
+ const schema = toContentSchema(this.content, mediaType) ?? onlyEntry?.schema;
118
123
  if (!schema) {
119
- throw new Error(`Schema not found for media type ${mediaType}`);
124
+ throw new Error(mediaType === undefined
125
+ ? 'Schema not found for a JSON media type'
126
+ : `Schema not found for media type "${mediaType}"`);
120
127
  }
121
128
  return schema;
122
129
  }
@@ -0,0 +1,50 @@
1
+ import type { OasRef } from '../ref/Ref.js';
2
+ import type { OasSchema } from '../schema/Schema.js';
3
+ import type { OasMediaType } from './MediaType.js';
4
+ /**
5
+ * The media type without its parameters, trimmed and lowercased:
6
+ * `Application/JSON; charset=utf-8` → `application/json`.
7
+ */
8
+ export declare const toMediaTypeEssence: (mediaType: string) => string;
9
+ /**
10
+ * True when a body of this media type is one JSON value: `application/json`,
11
+ * or any `application/…+json` (the RFC 6839 structured syntax suffix, e.g.
12
+ * `application/vnd.api+json`, `application/ld+json`,
13
+ * `application/problem+json`). Parameters and case are ignored, so
14
+ * `application/json;v=1.0` is JSON.
15
+ *
16
+ * False for everything else, including:
17
+ * - `text/json`: not registered; RFC 8259 registers only `application/json`.
18
+ * - `application/x-ndjson`, `application/jsonl`: a stream of JSON values, not
19
+ * one value.
20
+ * - ranges such as `*\/*`, `application/*` and `application/*+json`: they do
21
+ * not name one type.
22
+ * - names without the suffix, such as `application/vnd.goa.error`, even when
23
+ * the API sends JSON under them.
24
+ */
25
+ export declare const isJsonMediaType: (mediaType: string) => boolean;
26
+ /** True when the media type is the full range `*\/*`, parameters and case ignored. */
27
+ export declare const isAnyMediaType: (mediaType: string) => boolean;
28
+ /** The media type a body is read from, and its schema. */
29
+ export type JsonContent = {
30
+ /** The key in `content`: a JSON-like media type, or `*\/*` (with any parameters). */
31
+ mediaType: string;
32
+ /**
33
+ * The media type to send or expect the body as: `mediaType` when it is
34
+ * JSON-like, `application/json` when the body is read from `*\/*`.
35
+ */
36
+ contentType: string;
37
+ schema: OasSchema | OasRef<'schema'> | undefined;
38
+ };
39
+ /**
40
+ * Choose the media type a body is read from: `application/json` exactly when
41
+ * it is a key, else the first JSON-like key in document order (see
42
+ * {@link isJsonMediaType}), else the `*\/*` key when its schema is an object,
43
+ * array, union or `$ref`. `*\/*` admits JSON, and documents use it for JSON
44
+ * bodies (springdoc writes it for every response); the parser logs
45
+ * `WILDCARD_MEDIA_TYPE` for every body read this way. `undefined` otherwise:
46
+ * there is no fall back to a specific non-JSON type, to a narrower range such
47
+ * as `application/*`, or to a scalar schema under `*\/*`, since a consumer
48
+ * would read that body as JSON.
49
+ */
50
+ export declare const toJsonContent: (content: Record<string, OasMediaType> | undefined) => JsonContent | undefined;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The media type without its parameters, trimmed and lowercased:
3
+ * `Application/JSON; charset=utf-8` → `application/json`.
4
+ */
5
+ export const toMediaTypeEssence = (mediaType) => (mediaType.split(';')[0] ?? '').trim().toLowerCase();
6
+ /**
7
+ * True when a body of this media type is one JSON value: `application/json`,
8
+ * or any `application/…+json` (the RFC 6839 structured syntax suffix, e.g.
9
+ * `application/vnd.api+json`, `application/ld+json`,
10
+ * `application/problem+json`). Parameters and case are ignored, so
11
+ * `application/json;v=1.0` is JSON.
12
+ *
13
+ * False for everything else, including:
14
+ * - `text/json`: not registered; RFC 8259 registers only `application/json`.
15
+ * - `application/x-ndjson`, `application/jsonl`: a stream of JSON values, not
16
+ * one value.
17
+ * - ranges such as `*\/*`, `application/*` and `application/*+json`: they do
18
+ * not name one type.
19
+ * - names without the suffix, such as `application/vnd.goa.error`, even when
20
+ * the API sends JSON under them.
21
+ */
22
+ export const isJsonMediaType = (mediaType) => {
23
+ const essence = toMediaTypeEssence(mediaType);
24
+ return essence === 'application/json' || /^application\/[^/*\s]+\+json$/.test(essence);
25
+ };
26
+ /** True when the media type is the full range `*\/*`, parameters and case ignored. */
27
+ export const isAnyMediaType = (mediaType) => toMediaTypeEssence(mediaType) === '*/*';
28
+ /**
29
+ * True when a schema under `*\/*` can be read as one JSON value: an object,
30
+ * array, union or `$ref`. A scalar is not: under `*\/*` a `string` is usually
31
+ * a text body, and `string` with `format: binary` a file.
32
+ */
33
+ const isJsonShaped = (schema) => schema !== undefined &&
34
+ (schema.isRef() || schema.type === 'object' || schema.type === 'array' || schema.type === 'union');
35
+ /**
36
+ * Choose the media type a body is read from: `application/json` exactly when
37
+ * it is a key, else the first JSON-like key in document order (see
38
+ * {@link isJsonMediaType}), else the `*\/*` key when its schema is an object,
39
+ * array, union or `$ref`. `*\/*` admits JSON, and documents use it for JSON
40
+ * bodies (springdoc writes it for every response); the parser logs
41
+ * `WILDCARD_MEDIA_TYPE` for every body read this way. `undefined` otherwise:
42
+ * there is no fall back to a specific non-JSON type, to a narrower range such
43
+ * as `application/*`, or to a scalar schema under `*\/*`, since a consumer
44
+ * would read that body as JSON.
45
+ */
46
+ export const toJsonContent = (content) => {
47
+ if (!content) {
48
+ return undefined;
49
+ }
50
+ const mediaTypes = Object.keys(content);
51
+ const jsonMediaType = 'application/json' in content ? 'application/json' : mediaTypes.find(isJsonMediaType);
52
+ if (jsonMediaType !== undefined) {
53
+ return {
54
+ mediaType: jsonMediaType,
55
+ contentType: jsonMediaType,
56
+ schema: content[jsonMediaType]?.schema
57
+ };
58
+ }
59
+ const anyMediaType = mediaTypes.find(isAnyMediaType);
60
+ const schema = anyMediaType === undefined ? undefined : content[anyMediaType]?.schema;
61
+ return anyMediaType !== undefined && isJsonShaped(schema)
62
+ ? { mediaType: anyMediaType, contentType: 'application/json', schema }
63
+ : undefined;
64
+ };
@@ -0,0 +1,8 @@
1
+ import type { OasRef } from '../ref/Ref.js';
2
+ import type { OasSchema } from '../schema/Schema.js';
3
+ import type { OasMediaType } from './MediaType.js';
4
+ /**
5
+ * The schema a `toSchema(mediaType?)` lookup returns: an exact key lookup when
6
+ * `mediaType` is given, else the schema of `toJsonContent`.
7
+ */
8
+ export declare const toContentSchema: (content: Record<string, OasMediaType> | undefined, mediaType: string | undefined) => OasSchema | OasRef<"schema"> | undefined;
@@ -0,0 +1,6 @@
1
+ import { toJsonContent } from './jsonMediaType.js';
2
+ /**
3
+ * The schema a `toSchema(mediaType?)` lookup returns: an exact key lookup when
4
+ * `mediaType` is given, else the schema of `toJsonContent`.
5
+ */
6
+ export const toContentSchema = (content, mediaType) => mediaType === undefined ? toJsonContent(content)?.schema : content?.[mediaType]?.schema;
@@ -60,6 +60,14 @@ export type OperationFields = {
60
60
  export type ToRequestBodyMapArgs = {
61
61
  schema: OasSchema | OasRef<'schema'>;
62
62
  requestBody: OasRequestBody;
63
+ /** The `content` key the schema was read from. */
64
+ mediaType: string;
65
+ /**
66
+ * The media type to send the body as: the explicit media type when one was
67
+ * passed, else `contentType` of `OasRequestBody.toJsonContent` (the
68
+ * JSON-like key, or `application/json` for a body read from `*\/*`).
69
+ */
70
+ contentType: string;
63
71
  };
64
72
  export declare class OasOperation extends OasBase {
65
73
  /** Type identifier for OAS operation objects */
@@ -120,7 +128,7 @@ export declare class OasOperation extends OasBase {
120
128
  * Maps the request body schema to a custom value using the provided mapping function.
121
129
  *
122
130
  * @param map - Function to transform the request body schema and metadata
123
- * @param mediaType - Media type to extract schema from (default: 'application/json')
131
+ * @param mediaType - Media type to extract schema from, an exact lookup. Omitted: the media type `OasRequestBody.toJsonContent` picks.
124
132
  * @returns Mapped value or undefined if no request body schema found
125
133
  */
126
134
  toRequestBody<V>(map: ({ schema, requestBody }: ToRequestBodyMapArgs) => V, mediaType?: string): V | undefined;
@@ -133,7 +141,7 @@ export declare class OasOperation extends OasBase {
133
141
  * {@link traverseSchema}.
134
142
  *
135
143
  * @param path - Schema path; `path[0]` must be `'SuccessResponse'` or `'RequestBody'`.
136
- * @param mediaType - Media type the body schema is read from (default `application/json`).
144
+ * @param mediaType - Media type the body schema is read from, an exact lookup. Omitted: the JSON media type (`application/json`, else the first JSON-like type, else `*\/*`).
137
145
  * @returns The schema at the path (may be an unresolved `$ref`).
138
146
  * @throws Error on an unknown root, a missing response/request body, or a non-navigable segment.
139
147
  */
@@ -164,13 +164,25 @@ export class OasOperation extends OasBase {
164
164
  * Maps the request body schema to a custom value using the provided mapping function.
165
165
  *
166
166
  * @param map - Function to transform the request body schema and metadata
167
- * @param mediaType - Media type to extract schema from (default: 'application/json')
167
+ * @param mediaType - Media type to extract schema from, an exact lookup. Omitted: the media type `OasRequestBody.toJsonContent` picks.
168
168
  * @returns Mapped value or undefined if no request body schema found
169
169
  */
170
- toRequestBody(map, mediaType = 'application/json') {
170
+ toRequestBody(map, mediaType) {
171
171
  const requestBody = this.requestBody?.resolve();
172
- const schema = requestBody?.content[mediaType]?.schema;
173
- return schema ? map({ schema, requestBody }) : undefined;
172
+ if (!requestBody) {
173
+ return undefined;
174
+ }
175
+ const chosen = mediaType === undefined
176
+ ? requestBody.toJsonContent()
177
+ : { mediaType, contentType: mediaType, schema: requestBody.toSchema(mediaType) };
178
+ return chosen?.schema
179
+ ? map({
180
+ schema: chosen.schema,
181
+ requestBody,
182
+ mediaType: chosen.mediaType,
183
+ contentType: chosen.contentType
184
+ })
185
+ : undefined;
174
186
  }
175
187
  /**
176
188
  * Navigate into this operation's response or request body schema along an
@@ -181,11 +193,11 @@ export class OasOperation extends OasBase {
181
193
  * {@link traverseSchema}.
182
194
  *
183
195
  * @param path - Schema path; `path[0]` must be `'SuccessResponse'` or `'RequestBody'`.
184
- * @param mediaType - Media type the body schema is read from (default `application/json`).
196
+ * @param mediaType - Media type the body schema is read from, an exact lookup. Omitted: the JSON media type (`application/json`, else the first JSON-like type, else `*\/*`).
185
197
  * @returns The schema at the path (may be an unresolved `$ref`).
186
198
  * @throws Error on an unknown root, a missing response/request body, or a non-navigable segment.
187
199
  */
188
- traverse(path, mediaType = 'application/json') {
200
+ traverse(path, mediaType) {
189
201
  const [root, ...rest] = path;
190
202
  let rootSchema;
191
203
  switch (root) {
@@ -200,7 +212,8 @@ export class OasOperation extends OasBase {
200
212
  }
201
213
  if (!rootSchema) {
202
214
  const label = root === 'SuccessResponse' ? 'success response' : 'request body';
203
- throw new Error(`Operation "${this.method} ${this.path}" has no ${label} schema for media type "${mediaType}"`);
215
+ const mediaTypeLabel = mediaType === undefined ? 'a JSON media type' : `media type "${mediaType}"`;
216
+ throw new Error(`Operation "${this.method} ${this.path}" has no ${label} schema for ${mediaTypeLabel}`);
204
217
  }
205
218
  return traverseSchema(rootSchema, rest);
206
219
  }
@@ -243,7 +243,7 @@ export declare class OasParameter extends OasBase {
243
243
  * for the specified media type. When `content` has exactly one entry, its
244
244
  * schema is used whatever the media type.
245
245
  *
246
- * @param mediaType - Media type to extract schema from when using content definitions
246
+ * @param mediaType - Media type to extract schema from when using content definitions, an exact lookup. Omitted: the JSON media type (`application/json`, else the first JSON-like type, else `*\/*`).
247
247
  * @returns The parameter's schema object
248
248
  * @throws Error if no schema is found for the specified media type
249
249
  */
@@ -1,5 +1,6 @@
1
1
  import "../../_dnt.polyfills.js";
2
2
  import { OasBase } from '../../types/OasBase.js';
3
+ import { toContentSchema } from '../mediaType/toContentSchema.js';
3
4
  /**
4
5
  * Represents a Parameter Object in the OpenAPI Specification.
5
6
  *
@@ -295,19 +296,21 @@ export class OasParameter extends OasBase {
295
296
  * for the specified media type. When `content` has exactly one entry, its
296
297
  * schema is used whatever the media type.
297
298
  *
298
- * @param mediaType - Media type to extract schema from when using content definitions
299
+ * @param mediaType - Media type to extract schema from when using content definitions, an exact lookup. Omitted: the JSON media type (`application/json`, else the first JSON-like type, else `*\/*`).
299
300
  * @returns The parameter's schema object
300
301
  * @throws Error if no schema is found for the specified media type
301
302
  */
302
- toSchema(mediaType = 'application/json') {
303
+ toSchema(mediaType) {
303
304
  if (this.schema) {
304
305
  return this.schema;
305
306
  }
306
307
  const contentEntries = Object.values(this.content ?? {});
307
308
  const onlyEntry = contentEntries.length === 1 ? contentEntries[0] : undefined;
308
- const schema = this.content?.[mediaType]?.schema ?? onlyEntry?.schema;
309
+ const schema = toContentSchema(this.content, mediaType) ?? onlyEntry?.schema;
309
310
  if (!schema) {
310
- throw new Error(`Schema not found for media type ${mediaType}`);
311
+ throw new Error(mediaType === undefined
312
+ ? 'Schema not found for a JSON media type'
313
+ : `Schema not found for media type "${mediaType}"`);
311
314
  }
312
315
  return schema;
313
316
  }
@@ -6,6 +6,7 @@ import type { ToJsonSchemaOptions } from '../schema/Schema.js';
6
6
  import type { OpenAPIV3 } from 'openapi-types';
7
7
  import { OasBase } from '../../types/OasBase.js';
8
8
  import type { ParseContextType } from '../../context/parseTypes.js';
9
+ import type { JsonContent } from '../mediaType/jsonMediaType.js';
9
10
  /**
10
11
  * Constructor fields for {@link OasRequestBody}.
11
12
  */
@@ -286,6 +287,16 @@ export declare class OasRequestBody extends OasBase {
286
287
  isRef(): this is OasRef<'requestBody'>;
287
288
  resolve(): OasRequestBody;
288
289
  resolveOnce(): OasRequestBody;
290
+ /**
291
+ * The body schema for `mediaType`, an exact key lookup. Without
292
+ * `mediaType`, the schema of the JSON media type that {@link toJsonContent}
293
+ * picks: `application/json`, else the first JSON-like type such as
294
+ * `application/json;v=1.0` or `application/vnd.api+json`, else `*\/*`
295
+ * when its schema is an object, array, union or `$ref`.
296
+ * `undefined` when there is no such media type or it has no schema.
297
+ */
289
298
  toSchema(mediaType?: string): OasSchema | OasRef<'schema'> | undefined;
299
+ /** The media type this body is read from (JSON-like, or `*\/*`), and its schema. See {@link toJsonContent}. */
300
+ toJsonContent(): JsonContent | undefined;
290
301
  toJsonSchema(options: ToJsonSchemaOptions): OpenAPIV3.RequestBodyObject;
291
302
  }
@@ -1,5 +1,7 @@
1
1
  import "../../_dnt.polyfills.js";
2
2
  import { OasBase } from '../../types/OasBase.js';
3
+ import { toJsonContent } from '../mediaType/jsonMediaType.js';
4
+ import { toContentSchema } from '../mediaType/toContentSchema.js';
3
5
  /**
4
6
  * Represents a Request Body Object in the OpenAPI Specification.
5
7
  *
@@ -304,8 +306,20 @@ export class OasRequestBody extends OasBase {
304
306
  resolveOnce() {
305
307
  return this;
306
308
  }
307
- toSchema(mediaType = 'application/json') {
308
- return this.content?.[mediaType]?.schema;
309
+ /**
310
+ * The body schema for `mediaType`, an exact key lookup. Without
311
+ * `mediaType`, the schema of the JSON media type that {@link toJsonContent}
312
+ * picks: `application/json`, else the first JSON-like type such as
313
+ * `application/json;v=1.0` or `application/vnd.api+json`, else `*\/*`
314
+ * when its schema is an object, array, union or `$ref`.
315
+ * `undefined` when there is no such media type or it has no schema.
316
+ */
317
+ toSchema(mediaType) {
318
+ return toContentSchema(this.content, mediaType);
319
+ }
320
+ /** The media type this body is read from (JSON-like, or `*\/*`), and its schema. See {@link toJsonContent}. */
321
+ toJsonContent() {
322
+ return toJsonContent(this.content);
309
323
  }
310
324
  toJsonSchema(options) {
311
325
  return {
@@ -8,6 +8,7 @@ import type { ToJsonSchemaOptions } from '../schema/Schema.js';
8
8
  import type { OpenAPIV3 } from 'openapi-types';
9
9
  import { OasBase } from '../../types/OasBase.js';
10
10
  import type { ParseContextType } from '../../context/parseTypes.js';
11
+ import type { JsonContent } from '../mediaType/jsonMediaType.js';
11
12
  /**
12
13
  * Constructor fields for {@link OasResponse}.
13
14
  */
@@ -220,7 +221,17 @@ export declare class OasResponse extends OasBase {
220
221
  isRef(): this is OasRef<'response'>;
221
222
  resolve(): OasResponse;
222
223
  resolveOnce(): OasResponse;
224
+ /**
225
+ * The body schema for `mediaType`, an exact key lookup. Without
226
+ * `mediaType`, the schema of the JSON media type that {@link toJsonContent}
227
+ * picks: `application/json`, else the first JSON-like type such as
228
+ * `application/json;v=1.0` or `application/vnd.api+json`, else `*\/*`
229
+ * when its schema is an object, array, union or `$ref`.
230
+ * `undefined` when there is no such media type or it has no schema.
231
+ */
223
232
  toSchema(mediaType?: string): OasSchema | OasRef<'schema'> | undefined;
233
+ /** The media type this response is read from (JSON-like, or `*\/*`), and its schema. See {@link toJsonContent}. */
234
+ toJsonContent(): JsonContent | undefined;
224
235
  toJsonSchema(options: ToJsonSchemaOptions): OpenAPIV3.ResponseObject & {
225
236
  summary?: string;
226
237
  };
@@ -1,5 +1,7 @@
1
1
  import "../../_dnt.polyfills.js";
2
2
  import { OasBase } from '../../types/OasBase.js';
3
+ import { toJsonContent } from '../mediaType/jsonMediaType.js';
4
+ import { toContentSchema } from '../mediaType/toContentSchema.js';
3
5
  /**
4
6
  * Represents a Response Object in the OpenAPI Specification.
5
7
  *
@@ -244,8 +246,20 @@ export class OasResponse extends OasBase {
244
246
  resolveOnce() {
245
247
  return this;
246
248
  }
247
- toSchema(mediaType = 'application/json') {
248
- return this.content?.[mediaType]?.schema;
249
+ /**
250
+ * The body schema for `mediaType`, an exact key lookup. Without
251
+ * `mediaType`, the schema of the JSON media type that {@link toJsonContent}
252
+ * picks: `application/json`, else the first JSON-like type such as
253
+ * `application/json;v=1.0` or `application/vnd.api+json`, else `*\/*`
254
+ * when its schema is an object, array, union or `$ref`.
255
+ * `undefined` when there is no such media type or it has no schema.
256
+ */
257
+ toSchema(mediaType) {
258
+ return toContentSchema(this.content, mediaType);
259
+ }
260
+ /** The media type this response is read from (JSON-like, or `*\/*`), and its schema. See {@link toJsonContent}. */
261
+ toJsonContent() {
262
+ return toJsonContent(this.content);
249
263
  }
250
264
  toJsonSchema(options) {
251
265
  return {
@@ -113,10 +113,10 @@ export declare class OasWebhook extends OasBase {
113
113
  */
114
114
  toPayload(): OasRequestBody | undefined;
115
115
  /**
116
- * The schema of the received payload for `mediaType` (default
117
- * `application/json`) — the inbound body shape a handler generator emits a
118
- * type for. `undefined` when there is no body or no schema for that media
119
- * type.
116
+ * The schema of the received payload for `mediaType`, an exact lookup —
117
+ * the inbound body shape a handler generator writes a type for. Without
118
+ * `mediaType`, the media type `OasRequestBody.toJsonContent` picks.
119
+ * `undefined` when there is no body or no schema for that media type.
120
120
  */
121
121
  toPayloadSchema(mediaType?: string): OasSchema | OasRef<'schema'> | undefined;
122
122
  /**
@@ -157,13 +157,13 @@ export class OasWebhook extends OasBase {
157
157
  return this.requestBody?.resolve();
158
158
  }
159
159
  /**
160
- * The schema of the received payload for `mediaType` (default
161
- * `application/json`) — the inbound body shape a handler generator emits a
162
- * type for. `undefined` when there is no body or no schema for that media
163
- * type.
160
+ * The schema of the received payload for `mediaType`, an exact lookup —
161
+ * the inbound body shape a handler generator writes a type for. Without
162
+ * `mediaType`, the media type `OasRequestBody.toJsonContent` picks.
163
+ * `undefined` when there is no body or no schema for that media type.
164
164
  */
165
- toPayloadSchema(mediaType = 'application/json') {
166
- return this.toPayload()?.content[mediaType]?.schema;
165
+ toPayloadSchema(mediaType) {
166
+ return this.toPayload()?.toSchema(mediaType);
167
167
  }
168
168
  /**
169
169
  * Resolve the inbound parameters (e.g. the signature/headers a handler
@@ -0,0 +1,27 @@
1
+ import type { ParseContextType } from '../context/parseTypes.js';
2
+ import type { StackTrail } from '../context/StackTrail.js';
3
+ import type { OasMediaType } from '../oas/mediaType/MediaType.js';
4
+ export type LogBodyMediaTypesArgs = {
5
+ /** The parsed `content` map of a response or request body. */
6
+ content: Record<string, OasMediaType> | undefined;
7
+ /** Trail of the response or request body; the issue is logged at its `content` key. */
8
+ stackTrail: StackTrail;
9
+ context: ParseContextType;
10
+ };
11
+ /**
12
+ * Log at most one issue for a parsed response or request body, from the same
13
+ * choice the lookups make (`toJsonContent`), so the issue and the lookup
14
+ * cannot disagree. Called after the body is built, so a body that fails to
15
+ * parse logs nothing.
16
+ *
17
+ * - `WILDCARD_MEDIA_TYPE` (`warning`): the body is read from `*\/*` as JSON,
18
+ * though the document does not say its format.
19
+ * - `NO_JSON_MEDIA_TYPE` (`debug`): no lookup reads the body, and its keys
20
+ * include a range (`*\/*` with a scalar schema, `application/*`), so its
21
+ * format is unknown.
22
+ *
23
+ * A body with only specific non-JSON types (`multipart/form-data`,
24
+ * `application/octet-stream`, `text/plain`) logs nothing: that is a normal
25
+ * text, form or binary body.
26
+ */
27
+ export declare const logBodyMediaTypes: ({ content, stackTrail, context }: LogBodyMediaTypesArgs) => void;
@@ -0,0 +1,50 @@
1
+ import { isAnyMediaType, toJsonContent, toMediaTypeEssence } from '../oas/mediaType/jsonMediaType.js';
2
+ /**
3
+ * Log at most one issue for a parsed response or request body, from the same
4
+ * choice the lookups make (`toJsonContent`), so the issue and the lookup
5
+ * cannot disagree. Called after the body is built, so a body that fails to
6
+ * parse logs nothing.
7
+ *
8
+ * - `WILDCARD_MEDIA_TYPE` (`warning`): the body is read from `*\/*` as JSON,
9
+ * though the document does not say its format.
10
+ * - `NO_JSON_MEDIA_TYPE` (`debug`): no lookup reads the body, and its keys
11
+ * include a range (`*\/*` with a scalar schema, `application/*`), so its
12
+ * format is unknown.
13
+ *
14
+ * A body with only specific non-JSON types (`multipart/form-data`,
15
+ * `application/octet-stream`, `text/plain`) logs nothing: that is a normal
16
+ * text, form or binary body.
17
+ */
18
+ export const logBodyMediaTypes = ({ content, stackTrail, context }) => {
19
+ const mediaTypes = Object.keys(content ?? {});
20
+ if (mediaTypes.length === 0) {
21
+ return;
22
+ }
23
+ const listed = mediaTypes.map(mediaType => `"${mediaType}"`).join(', ');
24
+ // `parent` is only the keys: the issue log serializes it on every issue.
25
+ const parent = { mediaTypes };
26
+ const chosen = toJsonContent(content);
27
+ if (chosen !== undefined) {
28
+ if (isAnyMediaType(chosen.mediaType)) {
29
+ context.logIssue({
30
+ key: 'content',
31
+ level: 'warning',
32
+ type: 'WILDCARD_MEDIA_TYPE',
33
+ message: `Content has no JSON media type (${listed}), so the body is read from "*/*" as JSON although the document does not say its format`,
34
+ parent,
35
+ stackTrail
36
+ });
37
+ }
38
+ return;
39
+ }
40
+ if (mediaTypes.some(mediaType => toMediaTypeEssence(mediaType).includes('*'))) {
41
+ context.logIssue({
42
+ key: 'content',
43
+ level: 'debug',
44
+ type: 'NO_JSON_MEDIA_TYPE',
45
+ message: `Content names only a media type range or non-JSON types (${listed}), so no lookup without a media type reads its schema`,
46
+ parent,
47
+ stackTrail
48
+ });
49
+ }
50
+ };
@@ -3,6 +3,7 @@ import { toRefV31 } from '../ref/toRefV31.js';
3
3
  import { toMediaTypeItemsV3 } from '../mediaType/toMediaTypeItemV3.js';
4
4
  import { OasRequestBody } from '../../../oas/requestBody/RequestBody.js';
5
5
  import { toSpecificationExtensionsV3 } from '../specificationExtensions/toSpecificationExtensionsV3.js';
6
+ import { logBodyMediaTypes } from '../../logBodyMediaTypes.js';
6
7
  export const toRequestBodyV3 = ({ requestBody, stackTrail, context }) => {
7
8
  if (!requestBody) {
8
9
  return undefined;
@@ -26,7 +27,9 @@ export const toRequestBodyV3 = ({ requestBody, stackTrail, context }) => {
26
27
  required,
27
28
  extensionFields
28
29
  };
29
- return context.withStackTrail(stackTrail, () => new OasRequestBody(fields, context));
30
+ const parsed = context.withStackTrail(stackTrail, () => new OasRequestBody(fields, context));
31
+ logBodyMediaTypes({ content: fields.content, stackTrail, context });
32
+ return parsed;
30
33
  };
31
34
  export const toRequestBodiesV3 = ({ requestBodies, stackTrail, context }) => {
32
35
  if (!requestBodies) {