@effect/openapi-generator 4.0.0-beta.9 → 4.0.0-beta.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/HttpApiTransformer.d.ts +41 -0
- package/dist/HttpApiTransformer.d.ts.map +1 -0
- package/dist/HttpApiTransformer.js +393 -0
- package/dist/HttpApiTransformer.js.map +1 -0
- package/dist/JsonSchemaGenerator.d.ts +25 -3
- package/dist/JsonSchemaGenerator.d.ts.map +1 -1
- package/dist/JsonSchemaGenerator.js +178 -47
- package/dist/JsonSchemaGenerator.js.map +1 -1
- package/dist/OpenApiGenerator.d.ts +81 -7
- package/dist/OpenApiGenerator.d.ts.map +1 -1
- package/dist/OpenApiGenerator.js +763 -119
- package/dist/OpenApiGenerator.js.map +1 -1
- package/dist/OpenApiPatch.d.ts +47 -24
- package/dist/OpenApiPatch.d.ts.map +1 -1
- package/dist/OpenApiPatch.js +42 -19
- package/dist/OpenApiPatch.js.map +1 -1
- package/dist/OpenApiTransformer.d.ts +85 -12
- package/dist/OpenApiTransformer.d.ts.map +1 -1
- package/dist/OpenApiTransformer.js +88 -11
- package/dist/OpenApiTransformer.js.map +1 -1
- package/dist/ParsedOperation.d.ts +184 -1
- package/dist/ParsedOperation.d.ts.map +1 -1
- package/dist/ParsedOperation.js +32 -0
- package/dist/ParsedOperation.js.map +1 -1
- package/dist/Utils.d.ts +51 -0
- package/dist/Utils.d.ts.map +1 -1
- package/dist/Utils.js +61 -0
- package/dist/Utils.js.map +1 -1
- package/dist/main.d.ts +12 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +41 -8
- package/dist/main.js.map +1 -1
- package/package.json +10 -8
- package/src/HttpApiTransformer.ts +552 -0
- package/src/JsonSchemaGenerator.ts +272 -47
- package/src/OpenApiGenerator.ts +1057 -165
- package/src/OpenApiPatch.ts +56 -31
- package/src/OpenApiTransformer.ts +92 -15
- package/src/ParsedOperation.ts +235 -1
- package/src/Utils.ts +61 -0
- package/src/main.ts +58 -10
package/dist/OpenApiGenerator.js
CHANGED
|
@@ -1,19 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `OpenApiGenerator` module orchestrates converting OpenAPI and Swagger
|
|
3
|
+
* documents into generated Effect source.
|
|
4
|
+
*
|
|
5
|
+
* It normalizes Swagger 2.0 input, resolves local references, builds the
|
|
6
|
+
* parsed operation model, registers request and response schemas, applies
|
|
7
|
+
* HttpApi-specific adaptations such as multipart helpers and security metadata,
|
|
8
|
+
* emits warnings for unsupported or lossy OpenAPI features, and then delegates
|
|
9
|
+
* final rendering to the HttpClient or HttpApi code generators.
|
|
10
|
+
*
|
|
11
|
+
* @since 4.0.0
|
|
12
|
+
*/
|
|
13
|
+
import * as Context from "effect/Context";
|
|
1
14
|
import * as Effect from "effect/Effect";
|
|
2
15
|
import * as Layer from "effect/Layer";
|
|
3
16
|
import * as Predicate from "effect/Predicate";
|
|
4
|
-
import * as ServiceMap from "effect/ServiceMap";
|
|
5
17
|
import * as String from "effect/String";
|
|
6
18
|
import SwaggerToOpenApi from "swagger2openapi";
|
|
19
|
+
import * as HttpApiTransformer from "./HttpApiTransformer.js";
|
|
7
20
|
import * as JsonSchemaGenerator from "./JsonSchemaGenerator.js";
|
|
8
21
|
import * as OpenApiTransformer from "./OpenApiTransformer.js";
|
|
9
22
|
import * as ParsedOperation from "./ParsedOperation.js";
|
|
10
23
|
import * as Utils from "./Utils.js";
|
|
11
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Service for turning OpenAPI or Swagger specifications into generated Effect
|
|
26
|
+
* HTTP client or HttpApi source code.
|
|
27
|
+
*
|
|
28
|
+
* @category services
|
|
29
|
+
* @since 4.0.0
|
|
30
|
+
*/
|
|
31
|
+
export class OpenApiGenerator extends /*#__PURE__*/Context.Service()("OpenApiGenerator") {}
|
|
12
32
|
const methodNames = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
33
|
+
/**
|
|
34
|
+
* Constructs the OpenAPI generator service implementation.
|
|
35
|
+
*
|
|
36
|
+
* @category constructors
|
|
37
|
+
* @since 4.0.0
|
|
38
|
+
*/
|
|
13
39
|
export const make = /*#__PURE__*/Effect.gen(function* () {
|
|
14
40
|
const generate = Effect.fn(function* (spec, options) {
|
|
15
41
|
const generator = JsonSchemaGenerator.make();
|
|
16
42
|
const openApiTransformer = yield* OpenApiTransformer.OpenApiTransformer;
|
|
43
|
+
const emitWarning = makeWarningEmitter(options);
|
|
17
44
|
// If we receive a Swagger 2.0 spec, convert it to an OpenApi 3.0 spec
|
|
18
45
|
if (isSwaggerSpec(spec)) {
|
|
19
46
|
spec = yield* convertSwaggerSpec(spec);
|
|
@@ -26,154 +53,771 @@ export const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
26
53
|
}
|
|
27
54
|
return current;
|
|
28
55
|
}
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
const multipartSchemaRefs = options.format === "httpapi" ? makeHttpApiMultipartSchemaRefs(spec.components?.schemas ?? {}) : undefined;
|
|
57
|
+
const parsed = parseOpenApi(spec, generator, resolveRef, options.format, emitWarning, multipartSchemaRefs);
|
|
58
|
+
// TODO: make a CLI option ?
|
|
59
|
+
const importName = "Schema";
|
|
60
|
+
const source = getDialect(spec);
|
|
61
|
+
const generation = options.format === "httpapi" ? generator.generateHttpApi(source, withHttpApiMultipartSchemas(spec.components?.schemas ?? {}, multipartSchemaRefs), {
|
|
62
|
+
onEnter: options.onEnter,
|
|
63
|
+
multipartSchemaRefs
|
|
64
|
+
}) : generator.generate(source, spec.components?.schemas ?? {}, options.format === "httpclient-type-only", {
|
|
65
|
+
onEnter: options.onEnter
|
|
66
|
+
});
|
|
67
|
+
if (options.format === "httpapi") {
|
|
68
|
+
const needsMultipartImport = generation.includes("Multipart.");
|
|
69
|
+
return String.stripMargin(`|${HttpApiTransformer.imports(importName, {
|
|
70
|
+
multipart: needsMultipartImport
|
|
71
|
+
})}
|
|
72
|
+
|${generation}
|
|
73
|
+
|${HttpApiTransformer.toImplementation(importName, options.name, parsed)}`);
|
|
74
|
+
}
|
|
75
|
+
return String.stripMargin(`|${openApiTransformer.imports(importName, parsed)}
|
|
76
|
+
|${generation}
|
|
77
|
+
|${openApiTransformer.toImplementation(importName, options.name, parsed)}
|
|
78
|
+
|
|
|
79
|
+
|${openApiTransformer.toTypes(importName, options.name, parsed)}`);
|
|
80
|
+
}, (effect, _, options) => Effect.provideServiceEffect(effect, OpenApiTransformer.OpenApiTransformer, options.format === "httpclient-type-only" ? Effect.sync(OpenApiTransformer.makeTransformerTs) : Effect.sync(OpenApiTransformer.makeTransformerSchema)));
|
|
81
|
+
return {
|
|
82
|
+
generate
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
const makeWarningEmitter = options => warning => {
|
|
86
|
+
options.onWarning?.(warning);
|
|
87
|
+
};
|
|
88
|
+
const parseOpenApi = (spec, generator, resolveRef, format, emitWarning, multipartSchemaRefs) => {
|
|
89
|
+
const operations = [];
|
|
90
|
+
const reservedSchemaNames = new Set(Object.keys(spec.components?.schemas ?? {}));
|
|
91
|
+
const isHttpApi = format === "httpapi";
|
|
92
|
+
const securitySchemes = isHttpApi ? parseSecuritySchemes(spec, resolveRef) : [];
|
|
93
|
+
const addSchema = (baseName, schema, operation) => {
|
|
94
|
+
let candidate = baseName;
|
|
95
|
+
let index = 2;
|
|
96
|
+
while (reservedSchemaNames.has(candidate)) {
|
|
97
|
+
candidate = `${baseName}${index}`;
|
|
98
|
+
index += 1;
|
|
99
|
+
}
|
|
100
|
+
if (candidate !== baseName) {
|
|
101
|
+
warnForOperation(emitWarning, operation, {
|
|
102
|
+
code: "naming-collision",
|
|
103
|
+
message: `Schema name "${baseName}" collided with an existing name and was renamed to "${candidate}".`
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
reservedSchemaNames.add(candidate);
|
|
107
|
+
return generator.addSchema(candidate, schema);
|
|
108
|
+
};
|
|
109
|
+
for (const [path, methods] of Object.entries(spec.paths)) {
|
|
110
|
+
for (const method of methodNames) {
|
|
111
|
+
const operation = methods[method];
|
|
112
|
+
if (Predicate.isUndefined(operation)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const id = operation.operationId ? Utils.camelize(operation.operationId) : `${method.toUpperCase()}${path}`;
|
|
116
|
+
const description = Utils.nonEmptyString(operation.description) ?? Utils.nonEmptyString(operation.summary);
|
|
117
|
+
const {
|
|
118
|
+
pathIds,
|
|
119
|
+
pathTemplate
|
|
120
|
+
} = processPath(path);
|
|
121
|
+
const op = ParsedOperation.makeDeepMutable({
|
|
122
|
+
id,
|
|
123
|
+
method,
|
|
124
|
+
description,
|
|
125
|
+
pathIds,
|
|
126
|
+
pathTemplate
|
|
127
|
+
});
|
|
128
|
+
op.path = path;
|
|
129
|
+
op.operationId = Utils.nonEmptyString(operation.operationId);
|
|
130
|
+
op.tags = [...(operation.tags ?? [])];
|
|
131
|
+
if (isHttpApi && op.tags.length > 1) {
|
|
132
|
+
warnForOperation(emitWarning, op, {
|
|
133
|
+
code: "additional-tags-dropped",
|
|
134
|
+
message: `Additional tags (${op.tags.slice(1).join(", ")}) were dropped. Only the first tag ("${op.tags[0]}") is used for grouping.`
|
|
48
135
|
});
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
136
|
+
}
|
|
137
|
+
op.metadata = {
|
|
138
|
+
summary: Utils.nonEmptyString(operation.summary),
|
|
139
|
+
description: Utils.nonEmptyString(operation.description),
|
|
140
|
+
deprecated: operation.deprecated === true,
|
|
141
|
+
externalDocs: operation.externalDocs
|
|
142
|
+
};
|
|
143
|
+
op.effectiveSecurity = cloneSecurityRequirements(operation.security ?? spec.security ?? []);
|
|
144
|
+
if (isHttpApi) {
|
|
145
|
+
warnForAndSecurityRequirements(emitWarning, op);
|
|
146
|
+
}
|
|
147
|
+
const schemaId = Utils.identifier(operation.operationId ?? path);
|
|
148
|
+
const pathParameters = Predicate.isObject(methods) && Array.isArray(methods.parameters) ? methods.parameters : undefined;
|
|
149
|
+
const parameters = resolveOperationParameters(pathParameters, Array.isArray(operation.parameters) ? operation.parameters : undefined, resolveRef);
|
|
150
|
+
for (const parameter of parameters) {
|
|
151
|
+
const parsedParameter = {
|
|
152
|
+
name: parameter.name,
|
|
153
|
+
in: parameter.in,
|
|
154
|
+
required: parameter.required === true,
|
|
155
|
+
description: Utils.nonEmptyString(parameter.description),
|
|
156
|
+
schema: parameter.schema
|
|
157
|
+
};
|
|
158
|
+
switch (parameter.in) {
|
|
159
|
+
case "path":
|
|
160
|
+
{
|
|
161
|
+
op.parameters.path.push(parsedParameter);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case "query":
|
|
165
|
+
{
|
|
166
|
+
op.parameters.query.push(parsedParameter);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case "header":
|
|
170
|
+
{
|
|
171
|
+
op.parameters.header.push(parsedParameter);
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
case "cookie":
|
|
175
|
+
{
|
|
176
|
+
op.parameters.cookie.push(parsedParameter);
|
|
177
|
+
warnForOperation(emitWarning, op, {
|
|
178
|
+
code: "cookie-parameter-dropped",
|
|
179
|
+
message: `Cookie parameter "${parameter.name}" was dropped because non-security cookie parameters are not supported.`
|
|
180
|
+
});
|
|
181
|
+
break;
|
|
63
182
|
}
|
|
64
|
-
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const requestBody = resolveReference(operation.requestBody, resolveRef);
|
|
186
|
+
if (isHttpApi && !methodSupportsRequestBody(op.method) && Predicate.isObject(requestBody)) {
|
|
187
|
+
warnForOperation(emitWarning, op, {
|
|
188
|
+
code: "no-body-method-request-body-skipped",
|
|
189
|
+
message: `Operation was skipped because ${op.method.toUpperCase()} does not support request bodies.`
|
|
190
|
+
});
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const resolvedResponses = Object.entries(operation.responses ?? {}).map(([status, response]) => [status, resolveReference(response, resolveRef)]);
|
|
194
|
+
const hasExplicitSuccessResponse = resolvedResponses.some(([status]) => {
|
|
195
|
+
if (!/^\d{3}$/.test(status)) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
return Number(status) < 400;
|
|
199
|
+
});
|
|
200
|
+
if (isHttpApi && hasUnsupportedSuccessfulSseResponse(resolvedResponses, hasExplicitSuccessResponse)) {
|
|
201
|
+
warnForOperation(emitWarning, op, {
|
|
202
|
+
code: "sse-operation-skipped",
|
|
203
|
+
message: "Operation was skipped because a successful SSE response is missing x-effect-stream metadata required for HttpApi generation."
|
|
204
|
+
});
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const validParameters = parameters.filter(parameter => parameter.in !== "path" && parameter.in !== "cookie");
|
|
208
|
+
const combinedParameterSchema = buildParameterSchema(validParameters, (parameter, added) => {
|
|
209
|
+
if (parameter.in === "query") {
|
|
210
|
+
Utils.spreadElementsInto(added, op.urlParams);
|
|
211
|
+
} else if (parameter.in === "header") {
|
|
212
|
+
Utils.spreadElementsInto(added, op.headers);
|
|
213
|
+
} else if (parameter.in === "cookie") {
|
|
214
|
+
Utils.spreadElementsInto(added, op.cookies);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
if (combinedParameterSchema !== undefined) {
|
|
218
|
+
op.params = addSchema(`${schemaId}Params`, combinedParameterSchema.schema, op);
|
|
219
|
+
op.paramsOptional = combinedParameterSchema.optional;
|
|
220
|
+
}
|
|
221
|
+
if (isHttpApi) {
|
|
222
|
+
const pathParameterSchema = buildParameterSchema(op.parameters.path);
|
|
223
|
+
if (pathParameterSchema !== undefined) {
|
|
224
|
+
op.pathSchema = addSchema(`${schemaId}PathParams`, pathParameterSchema.schema, op);
|
|
225
|
+
}
|
|
226
|
+
const queryParameterSchema = buildParameterSchema(op.parameters.query);
|
|
227
|
+
if (queryParameterSchema !== undefined) {
|
|
228
|
+
op.querySchema = addSchema(`${schemaId}Query`, queryParameterSchema.schema, op);
|
|
229
|
+
op.querySchemaOptional = queryParameterSchema.optional;
|
|
230
|
+
}
|
|
231
|
+
const headerParameterSchema = buildParameterSchema(op.parameters.header);
|
|
232
|
+
if (headerParameterSchema !== undefined) {
|
|
233
|
+
op.headersSchema = addSchema(`${schemaId}Headers`, headerParameterSchema.schema, op);
|
|
234
|
+
op.headersSchemaOptional = headerParameterSchema.optional;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (Predicate.isNotUndefined(requestBody) && Predicate.isObject(requestBody)) {
|
|
238
|
+
const content = Predicate.isObject(requestBody.content) ? requestBody.content : {};
|
|
239
|
+
const requestSchemaNames = new Map();
|
|
240
|
+
op.requestBody = {
|
|
241
|
+
required: requestBody.required === true,
|
|
242
|
+
contentTypes: Object.keys(content)
|
|
243
|
+
};
|
|
244
|
+
if (isHttpApi && requestBody.required === false) {
|
|
245
|
+
warnForOperation(emitWarning, op, {
|
|
246
|
+
code: "optional-request-body-approximated",
|
|
247
|
+
message: "Optional request body was approximated by adding a no-content payload alternative."
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if (Predicate.isNotUndefined(content["application/json"]?.schema)) {
|
|
251
|
+
op.payload = addSchema(`${schemaId}RequestJson`, content["application/json"].schema, op);
|
|
252
|
+
requestSchemaNames.set("application/json", op.payload);
|
|
253
|
+
}
|
|
254
|
+
if (Predicate.isNotUndefined(content["multipart/form-data"]?.schema)) {
|
|
255
|
+
op.payload = addSchema(`${schemaId}RequestFormData`, transformMultipartSchema(content["multipart/form-data"].schema, multipartSchemaRefs, resolveRef), op);
|
|
256
|
+
op.payloadFormData = true;
|
|
257
|
+
requestSchemaNames.set("multipart/form-data", op.payload);
|
|
258
|
+
}
|
|
259
|
+
if (Predicate.isNotUndefined(content["application/x-www-form-urlencoded"]?.schema)) {
|
|
260
|
+
op.payload = addSchema(`${schemaId}RequestFormUrlEncoded`, content["application/x-www-form-urlencoded"].schema, op);
|
|
261
|
+
op.payloadFormUrlEncoded = true;
|
|
262
|
+
requestSchemaNames.set("application/x-www-form-urlencoded", op.payload);
|
|
263
|
+
}
|
|
264
|
+
if (isHttpApi) {
|
|
265
|
+
const representableRequestBody = [];
|
|
266
|
+
for (const [contentType, mediaType] of Object.entries(content)) {
|
|
267
|
+
if (!Predicate.isObject(mediaType) || Predicate.isUndefined(mediaType.schema)) {
|
|
65
268
|
continue;
|
|
66
269
|
}
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const required = "required" in paramSchema ? paramSchema.required : [];
|
|
71
|
-
for (const [name, propSchema] of Object.entries(paramSchema.properties)) {
|
|
72
|
-
const adjustedName = `${parameter.name}[${name}]`;
|
|
73
|
-
schema.properties[adjustedName] = propSchema;
|
|
74
|
-
if (required.includes(name)) {
|
|
75
|
-
schema.required.push(adjustedName);
|
|
76
|
-
}
|
|
77
|
-
added.push(adjustedName);
|
|
78
|
-
}
|
|
79
|
-
} else {
|
|
80
|
-
schema.properties[parameter.name] = parameter.schema;
|
|
81
|
-
if (parameter.required) {
|
|
82
|
-
schema.required.push(parameter.name);
|
|
83
|
-
}
|
|
84
|
-
added.push(parameter.name);
|
|
270
|
+
const encoding = getRequestMediaTypeEncoding(contentType);
|
|
271
|
+
if (encoding === undefined) {
|
|
272
|
+
continue;
|
|
85
273
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
Utils.spreadElementsInto(added, op.cookies);
|
|
274
|
+
let schemaName = requestSchemaNames.get(contentType);
|
|
275
|
+
if (schemaName === undefined) {
|
|
276
|
+
const schema = encoding === "multipart" ? transformMultipartSchema(mediaType.schema, multipartSchemaRefs, resolveRef) : mediaType.schema;
|
|
277
|
+
schemaName = addSchema(`${schemaId}Request${mediaTypeToSuffix(contentType)}`, schema, op);
|
|
278
|
+
requestSchemaNames.set(contentType, schemaName);
|
|
92
279
|
}
|
|
280
|
+
representableRequestBody.push({
|
|
281
|
+
contentType,
|
|
282
|
+
encoding,
|
|
283
|
+
schema: schemaName
|
|
284
|
+
});
|
|
93
285
|
}
|
|
94
|
-
op.
|
|
95
|
-
|
|
286
|
+
op.requestBodyRepresentable = representableRequestBody;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
let defaultSchema;
|
|
290
|
+
for (const [status, response] of resolvedResponses) {
|
|
291
|
+
if (!Predicate.isObject(response)) {
|
|
292
|
+
continue;
|
|
96
293
|
}
|
|
97
|
-
|
|
98
|
-
|
|
294
|
+
const parsedStatus = isHttpApi ? remapDefaultResponseStatusForHttpApi(status, hasExplicitSuccessResponse) : status;
|
|
295
|
+
if (isHttpApi && status === "default") {
|
|
296
|
+
warnForOperation(emitWarning, op, {
|
|
297
|
+
code: "default-response-remapped",
|
|
298
|
+
message: `Default response was remapped to status ${parsedStatus} for HttpApi generation.`
|
|
299
|
+
});
|
|
99
300
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
op
|
|
301
|
+
const content = Predicate.isObject(response.content) ? response.content : undefined;
|
|
302
|
+
if (isHttpApi && Predicate.isNotUndefined(response.headers)) {
|
|
303
|
+
warnForOperation(emitWarning, op, {
|
|
304
|
+
code: "response-headers-ignored",
|
|
305
|
+
message: `Response headers on status ${status} were ignored in HttpApi generation.`
|
|
306
|
+
});
|
|
103
307
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
308
|
+
const representable = [];
|
|
309
|
+
let jsonSchemaName;
|
|
310
|
+
const jsonResponseSchema = content?.["application/json"]?.schema;
|
|
311
|
+
if (Predicate.isNotUndefined(jsonResponseSchema)) {
|
|
312
|
+
jsonSchemaName = addSchema(`${schemaId}${status}`, jsonResponseSchema, op);
|
|
313
|
+
if (isHttpApi) {
|
|
314
|
+
representable.push({
|
|
315
|
+
contentType: "application/json",
|
|
316
|
+
encoding: "json",
|
|
317
|
+
schema: jsonSchemaName
|
|
318
|
+
});
|
|
110
319
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
320
|
+
}
|
|
321
|
+
if (isHttpApi) {
|
|
322
|
+
for (const [contentType, mediaType] of Object.entries(content ?? {})) {
|
|
323
|
+
if (contentType === "application/json") {
|
|
115
324
|
continue;
|
|
116
325
|
}
|
|
117
|
-
|
|
118
|
-
const statusMajorNumber = Number(status[0]);
|
|
119
|
-
if (Number.isNaN(statusMajorNumber)) {
|
|
326
|
+
if (!Predicate.isObject(mediaType)) {
|
|
120
327
|
continue;
|
|
121
328
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
329
|
+
const statusMajorNumber = Number(parsedStatus[0]);
|
|
330
|
+
const streamEncoding = !Number.isNaN(statusMajorNumber) && statusMajorNumber < 4 ? getEffectStreamEncoding(mediaType) : undefined;
|
|
331
|
+
if (streamEncoding === "uint8array") {
|
|
332
|
+
representable.push({
|
|
333
|
+
contentType,
|
|
334
|
+
encoding: "binary",
|
|
335
|
+
effectStream: "uint8array"
|
|
336
|
+
});
|
|
337
|
+
continue;
|
|
126
338
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
339
|
+
if (streamEncoding === "sse") {
|
|
340
|
+
const errorSchema = getEffectStreamErrorSchema(mediaType);
|
|
341
|
+
if (Predicate.isUndefined(mediaType.schema) || Predicate.isUndefined(errorSchema)) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
representable.push({
|
|
345
|
+
contentType,
|
|
346
|
+
encoding: "text",
|
|
347
|
+
effectStream: "sse",
|
|
348
|
+
schema: addSchema(`${schemaId}${status}Sse`, mediaType.schema, op),
|
|
349
|
+
errorSchema: addSchema(`${schemaId}${status}SseError`, errorSchema, op)
|
|
350
|
+
});
|
|
351
|
+
continue;
|
|
133
352
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (Predicate.isNotUndefined(response.content?.["application/octet-stream"])) {
|
|
137
|
-
const statusMajorNumber = Number(status[0]);
|
|
138
|
-
if (!Number.isNaN(statusMajorNumber) && statusMajorNumber < 4) {
|
|
139
|
-
op.binaryResponse = true;
|
|
353
|
+
if (Predicate.isUndefined(mediaType.schema)) {
|
|
354
|
+
continue;
|
|
140
355
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
op.voidSchemas.add(status.toLowerCase());
|
|
356
|
+
const encoding = getResponseMediaTypeEncoding(contentType);
|
|
357
|
+
if (encoding === undefined) {
|
|
358
|
+
continue;
|
|
145
359
|
}
|
|
360
|
+
const schemaName = addSchema(`${schemaId}${status}${mediaTypeToSuffix(contentType)}`, mediaType.schema, op);
|
|
361
|
+
representable.push({
|
|
362
|
+
contentType,
|
|
363
|
+
encoding,
|
|
364
|
+
schema: schemaName
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const isEmptyResponse = Predicate.isUndefined(content) || Object.keys(content).length === 0;
|
|
369
|
+
const parsedResponse = {
|
|
370
|
+
status: parsedStatus,
|
|
371
|
+
description: Utils.nonEmptyString(response.description),
|
|
372
|
+
contentTypes: Predicate.isNotUndefined(content) ? Object.keys(content) : [],
|
|
373
|
+
hasHeaders: Predicate.isNotUndefined(response.headers),
|
|
374
|
+
isEmpty: isEmptyResponse,
|
|
375
|
+
representable
|
|
376
|
+
};
|
|
377
|
+
op.responses.push(parsedResponse);
|
|
378
|
+
if (status === "default") {
|
|
379
|
+
op.defaultResponse = parsedResponse;
|
|
380
|
+
}
|
|
381
|
+
if (Predicate.isNotUndefined(jsonSchemaName)) {
|
|
382
|
+
const schemaName = jsonSchemaName;
|
|
383
|
+
if (status === "default" && !isHttpApi) {
|
|
384
|
+
defaultSchema = schemaName;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const statusLower = parsedStatus.toLowerCase();
|
|
388
|
+
const statusMajorNumber = Number(parsedStatus[0]);
|
|
389
|
+
if (Number.isNaN(statusMajorNumber)) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (statusMajorNumber < 4) {
|
|
393
|
+
op.successSchemas.set(statusLower, schemaName);
|
|
394
|
+
} else {
|
|
395
|
+
op.errorSchemas.set(statusLower, schemaName);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const sseResponseSchema = content?.["text/event-stream"]?.schema;
|
|
399
|
+
if (!isHttpApi && Predicate.isUndefined(op.sseSchema) && Predicate.isNotUndefined(sseResponseSchema)) {
|
|
400
|
+
const statusMajorNumber = Number(parsedStatus[0]);
|
|
401
|
+
if (!Number.isNaN(statusMajorNumber) && statusMajorNumber < 4) {
|
|
402
|
+
op.sseSchema = addSchema(`${schemaId}${status}Sse`, sseResponseSchema, op);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (Predicate.isNotUndefined(content?.["application/octet-stream"])) {
|
|
406
|
+
const statusMajorNumber = Number(parsedStatus[0]);
|
|
407
|
+
if (!Number.isNaN(statusMajorNumber) && statusMajorNumber < 4) {
|
|
408
|
+
op.binaryResponse = true;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (isEmptyResponse) {
|
|
412
|
+
if (parsedStatus !== "default") {
|
|
413
|
+
op.voidSchemas.add(parsedStatus.toLowerCase());
|
|
146
414
|
}
|
|
147
415
|
}
|
|
148
|
-
|
|
149
|
-
|
|
416
|
+
}
|
|
417
|
+
if (!isHttpApi && op.successSchemas.size === 0 && Predicate.isNotUndefined(defaultSchema)) {
|
|
418
|
+
op.successSchemas.set("2xx", defaultSchema);
|
|
419
|
+
warnForOperation(emitWarning, op, {
|
|
420
|
+
code: "default-response-remapped",
|
|
421
|
+
message: "Default response was remapped to 2xx for the current HttpClient outputs."
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
operations.push(op);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
metadata: {
|
|
429
|
+
title: spec.info.title,
|
|
430
|
+
version: spec.info.version,
|
|
431
|
+
summary: Utils.nonEmptyString(spec.info.summary),
|
|
432
|
+
description: Utils.nonEmptyString(spec.info.description),
|
|
433
|
+
license: spec.info.license,
|
|
434
|
+
servers: spec.servers
|
|
435
|
+
},
|
|
436
|
+
tags: (spec.tags ?? []).map(tag => ({
|
|
437
|
+
name: tag.name,
|
|
438
|
+
description: Utils.nonEmptyString(tag.description),
|
|
439
|
+
externalDocs: tag.externalDocs
|
|
440
|
+
})),
|
|
441
|
+
securitySchemes,
|
|
442
|
+
operations
|
|
443
|
+
};
|
|
444
|
+
};
|
|
445
|
+
const isOpenApiParameter = parameter => {
|
|
446
|
+
if (!Predicate.isObject(parameter)) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
return typeof parameter.name === "string" && (parameter.in === "path" || parameter.in === "query" || parameter.in === "header" || parameter.in === "cookie");
|
|
450
|
+
};
|
|
451
|
+
const resolveOperationParameters = (pathParameters, operationParameters, resolveRef) => {
|
|
452
|
+
const resolved = new Map();
|
|
453
|
+
const add = parameter => {
|
|
454
|
+
const current = resolveReference(parameter, resolveRef);
|
|
455
|
+
if (!isOpenApiParameter(current)) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const key = `${current.in}:${current.name}`;
|
|
459
|
+
if (resolved.has(key)) {
|
|
460
|
+
resolved.delete(key);
|
|
461
|
+
}
|
|
462
|
+
resolved.set(key, current);
|
|
463
|
+
};
|
|
464
|
+
for (const parameter of pathParameters ?? []) {
|
|
465
|
+
add(parameter);
|
|
466
|
+
}
|
|
467
|
+
for (const parameter of operationParameters ?? []) {
|
|
468
|
+
add(parameter);
|
|
469
|
+
}
|
|
470
|
+
return [...resolved.values()];
|
|
471
|
+
};
|
|
472
|
+
const buildParameterSchema = (parameters, onAdded) => {
|
|
473
|
+
if (parameters.length === 0) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const schema = {
|
|
477
|
+
type: "object",
|
|
478
|
+
properties: {},
|
|
479
|
+
required: [],
|
|
480
|
+
additionalProperties: false
|
|
481
|
+
};
|
|
482
|
+
for (const parameter of parameters) {
|
|
483
|
+
const paramSchema = parameter.schema;
|
|
484
|
+
const added = [];
|
|
485
|
+
if (Predicate.isObject(paramSchema) && "properties" in paramSchema && Predicate.isObject(paramSchema.properties)) {
|
|
486
|
+
const required = "required" in paramSchema ? paramSchema.required : [];
|
|
487
|
+
for (const [name, propertySchema] of Object.entries(paramSchema.properties)) {
|
|
488
|
+
const adjustedName = `${parameter.name}[${name}]`;
|
|
489
|
+
schema.properties[adjustedName] = propertySchema;
|
|
490
|
+
if (required.includes(name)) {
|
|
491
|
+
schema.required.push(adjustedName);
|
|
150
492
|
}
|
|
151
|
-
|
|
493
|
+
added.push(adjustedName);
|
|
152
494
|
}
|
|
495
|
+
} else {
|
|
496
|
+
schema.properties[parameter.name] = parameter.schema;
|
|
497
|
+
if (parameter.required) {
|
|
498
|
+
schema.required.push(parameter.name);
|
|
499
|
+
}
|
|
500
|
+
added.push(parameter.name);
|
|
153
501
|
}
|
|
154
|
-
|
|
155
|
-
|
|
502
|
+
onAdded?.(parameter, added);
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
schema,
|
|
506
|
+
optional: schema.required.length === 0
|
|
507
|
+
};
|
|
508
|
+
};
|
|
509
|
+
const mediaTypeToSuffix = contentType => {
|
|
510
|
+
const normalized = contentType.toLowerCase();
|
|
511
|
+
switch (normalized) {
|
|
512
|
+
case "application/json":
|
|
513
|
+
return "Json";
|
|
514
|
+
case "multipart/form-data":
|
|
515
|
+
return "FormData";
|
|
516
|
+
case "application/x-www-form-urlencoded":
|
|
517
|
+
return "FormUrlEncoded";
|
|
518
|
+
case "text/plain":
|
|
519
|
+
return "Text";
|
|
520
|
+
case "application/octet-stream":
|
|
521
|
+
return "Binary";
|
|
522
|
+
}
|
|
523
|
+
const suffix = Utils.identifier(contentType);
|
|
524
|
+
return suffix.length > 0 ? suffix : "Body";
|
|
525
|
+
};
|
|
526
|
+
const makeHttpApiMultipartSchemaRefs = definitions => {
|
|
527
|
+
const names = new Set(Object.keys(definitions));
|
|
528
|
+
const allocate = base => {
|
|
529
|
+
let candidate = base;
|
|
530
|
+
let index = 2;
|
|
531
|
+
while (names.has(candidate)) {
|
|
532
|
+
candidate = `${base}${index}`;
|
|
533
|
+
index += 1;
|
|
156
534
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const generation = generator.generate(source, spec.components?.schemas ?? {}, options.typeOnly, {
|
|
161
|
-
onEnter: options.onEnter
|
|
162
|
-
});
|
|
163
|
-
return String.stripMargin(`|${openApiTransformer.imports(importName, operations)}
|
|
164
|
-
|${generation}
|
|
165
|
-
|${openApiTransformer.toImplementation(importName, options.name, operations)}
|
|
166
|
-
|
|
|
167
|
-
|${openApiTransformer.toTypes(importName, options.name, operations)}`);
|
|
168
|
-
}, (effect, _, options) => Effect.provideServiceEffect(effect, OpenApiTransformer.OpenApiTransformer, options.typeOnly ? Effect.sync(OpenApiTransformer.makeTransformerTs) : Effect.sync(OpenApiTransformer.makeTransformerSchema)));
|
|
535
|
+
names.add(candidate);
|
|
536
|
+
return candidate;
|
|
537
|
+
};
|
|
169
538
|
return {
|
|
170
|
-
|
|
539
|
+
singleFile: allocate("__HttpApiMultipartSingleFile"),
|
|
540
|
+
files: allocate("__HttpApiMultipartFiles")
|
|
171
541
|
};
|
|
172
|
-
}
|
|
542
|
+
};
|
|
543
|
+
const toDefinitionRef = name => `#/$defs/${name.replaceAll("~", "~0").replaceAll("/", "~1")}`;
|
|
544
|
+
const withHttpApiMultipartSchemas = (definitions, multipartSchemaRefs) => {
|
|
545
|
+
if (multipartSchemaRefs === undefined) {
|
|
546
|
+
return definitions;
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
...definitions,
|
|
550
|
+
[multipartSchemaRefs.singleFile]: {
|
|
551
|
+
type: "string",
|
|
552
|
+
format: "binary"
|
|
553
|
+
},
|
|
554
|
+
[multipartSchemaRefs.files]: {
|
|
555
|
+
type: "array",
|
|
556
|
+
items: {
|
|
557
|
+
$ref: toDefinitionRef(multipartSchemaRefs.singleFile)
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
};
|
|
562
|
+
const transformMultipartSchema = (schema, multipartSchemaRefs, resolveRef) => {
|
|
563
|
+
if (multipartSchemaRefs === undefined) {
|
|
564
|
+
return schema;
|
|
565
|
+
}
|
|
566
|
+
const singleFileRef = toDefinitionRef(multipartSchemaRefs.singleFile);
|
|
567
|
+
const filesRef = toDefinitionRef(multipartSchemaRefs.files);
|
|
568
|
+
const cache = new Map();
|
|
569
|
+
const stack = new Set();
|
|
570
|
+
const visit = value => {
|
|
571
|
+
if (Array.isArray(value)) {
|
|
572
|
+
return value.map(visit);
|
|
573
|
+
}
|
|
574
|
+
if (!Predicate.isObject(value)) {
|
|
575
|
+
return value;
|
|
576
|
+
}
|
|
577
|
+
if (typeof value.$ref === "string" && value.$ref.startsWith("#/components/schemas/")) {
|
|
578
|
+
const cached = cache.get(value.$ref);
|
|
579
|
+
if (cached !== undefined) {
|
|
580
|
+
return cached;
|
|
581
|
+
}
|
|
582
|
+
if (stack.has(value.$ref)) {
|
|
583
|
+
return value;
|
|
584
|
+
}
|
|
585
|
+
stack.add(value.$ref);
|
|
586
|
+
const transformed = visit(resolveSchemaReference(value.$ref, resolveRef));
|
|
587
|
+
stack.delete(value.$ref);
|
|
588
|
+
cache.set(value.$ref, transformed);
|
|
589
|
+
return transformed;
|
|
590
|
+
}
|
|
591
|
+
if (isMultipartBinaryFile(value)) {
|
|
592
|
+
return {
|
|
593
|
+
$ref: singleFileRef
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
const out = {};
|
|
597
|
+
for (const [key, current] of Object.entries(value)) {
|
|
598
|
+
out[key] = visit(current);
|
|
599
|
+
}
|
|
600
|
+
if (isMultipartBinaryFiles(out, singleFileRef)) {
|
|
601
|
+
return {
|
|
602
|
+
$ref: filesRef
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
return out;
|
|
606
|
+
};
|
|
607
|
+
return visit(schema);
|
|
608
|
+
};
|
|
609
|
+
const resolveSchemaReference = (ref, resolveRef) => {
|
|
610
|
+
let current = {
|
|
611
|
+
$ref: ref
|
|
612
|
+
};
|
|
613
|
+
const seen = new Set();
|
|
614
|
+
while (Predicate.isObject(current) && typeof current.$ref === "string") {
|
|
615
|
+
if (seen.has(current.$ref)) {
|
|
616
|
+
return current;
|
|
617
|
+
}
|
|
618
|
+
seen.add(current.$ref);
|
|
619
|
+
current = resolveRef(current.$ref);
|
|
620
|
+
}
|
|
621
|
+
return current;
|
|
622
|
+
};
|
|
623
|
+
const isMultipartBinaryFile = value => Predicate.isObject(value) && value.type === "string" && (typeof value.format === "string" && value.format.toLowerCase() === "binary" || typeof value.contentEncoding === "string" && value.contentEncoding.toLowerCase() === "binary");
|
|
624
|
+
const isMultipartBinaryFiles = (value, singleFileRef) => {
|
|
625
|
+
if (value.type !== "array") {
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
const items = value.items;
|
|
629
|
+
return isMultipartBinaryFile(items) || Predicate.isObject(items) && items.$ref === singleFileRef;
|
|
630
|
+
};
|
|
631
|
+
const isJsonMediaType = contentType => contentType === "application/json" || contentType.startsWith("application/") && contentType.endsWith("+json");
|
|
632
|
+
const isTextMediaType = contentType => contentType.startsWith("text/");
|
|
633
|
+
const isBinaryMediaType = contentType => contentType === "application/octet-stream" || contentType.startsWith("application/") && (contentType.includes("binary") || contentType.endsWith("+octet-stream"));
|
|
634
|
+
const getRequestMediaTypeEncoding = contentType => {
|
|
635
|
+
const normalized = contentType.toLowerCase();
|
|
636
|
+
if (isJsonMediaType(normalized)) {
|
|
637
|
+
return "json";
|
|
638
|
+
}
|
|
639
|
+
if (normalized === "multipart/form-data") {
|
|
640
|
+
return "multipart";
|
|
641
|
+
}
|
|
642
|
+
if (normalized === "application/x-www-form-urlencoded") {
|
|
643
|
+
return "form-url-encoded";
|
|
644
|
+
}
|
|
645
|
+
if (isTextMediaType(normalized)) {
|
|
646
|
+
return "text";
|
|
647
|
+
}
|
|
648
|
+
if (isBinaryMediaType(normalized)) {
|
|
649
|
+
return "binary";
|
|
650
|
+
}
|
|
651
|
+
return;
|
|
652
|
+
};
|
|
653
|
+
const getResponseMediaTypeEncoding = contentType => {
|
|
654
|
+
const normalized = contentType.toLowerCase();
|
|
655
|
+
if (isJsonMediaType(normalized)) {
|
|
656
|
+
return "json";
|
|
657
|
+
}
|
|
658
|
+
if (normalized === "application/x-www-form-urlencoded") {
|
|
659
|
+
return "form-url-encoded";
|
|
660
|
+
}
|
|
661
|
+
if (isTextMediaType(normalized)) {
|
|
662
|
+
return "text";
|
|
663
|
+
}
|
|
664
|
+
if (isBinaryMediaType(normalized)) {
|
|
665
|
+
return "binary";
|
|
666
|
+
}
|
|
667
|
+
return;
|
|
668
|
+
};
|
|
669
|
+
const getEffectStreamEncoding = mediaType => {
|
|
670
|
+
const stream = mediaType["x-effect-stream"];
|
|
671
|
+
if (!Predicate.isObject(stream)) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
return stream.encoding === "uint8array" || stream.encoding === "sse" ? stream.encoding : undefined;
|
|
675
|
+
};
|
|
676
|
+
const getEffectStreamErrorSchema = mediaType => {
|
|
677
|
+
const stream = mediaType["x-effect-stream"];
|
|
678
|
+
if (!Predicate.isObject(stream) || !Predicate.isObject(stream.errorSchema)) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
return stream.errorSchema;
|
|
682
|
+
};
|
|
683
|
+
const resolveReference = (input, resolveRef) => {
|
|
684
|
+
let current = input;
|
|
685
|
+
while (Predicate.isObject(current) && typeof current.$ref === "string") {
|
|
686
|
+
current = resolveRef(current.$ref);
|
|
687
|
+
}
|
|
688
|
+
return current;
|
|
689
|
+
};
|
|
690
|
+
const cloneSecurityRequirements = security => security.map(requirement => Object.fromEntries(Object.entries(requirement).map(([name, scopes]) => [name, [...scopes]])));
|
|
691
|
+
const parseSecuritySchemes = (spec, resolveRef) => {
|
|
692
|
+
const securitySchemes = spec.components?.securitySchemes ?? {};
|
|
693
|
+
const parsed = [];
|
|
694
|
+
for (const [name, value] of Object.entries(securitySchemes)) {
|
|
695
|
+
const scheme = resolveReference(value, resolveRef);
|
|
696
|
+
if (!Predicate.isObject(scheme)) {
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
if (scheme.type === "http" && typeof scheme.scheme === "string") {
|
|
700
|
+
const normalizedScheme = scheme.scheme.toLowerCase();
|
|
701
|
+
if (normalizedScheme === "basic") {
|
|
702
|
+
parsed.push({
|
|
703
|
+
name,
|
|
704
|
+
type: "basic",
|
|
705
|
+
description: Utils.nonEmptyString(scheme.description),
|
|
706
|
+
bearerFormat: undefined,
|
|
707
|
+
scheme: undefined,
|
|
708
|
+
key: undefined,
|
|
709
|
+
in: undefined
|
|
710
|
+
});
|
|
711
|
+
} else if (normalizedScheme === "bearer") {
|
|
712
|
+
parsed.push({
|
|
713
|
+
name,
|
|
714
|
+
type: "bearer",
|
|
715
|
+
description: Utils.nonEmptyString(scheme.description),
|
|
716
|
+
bearerFormat: Utils.nonEmptyString(scheme.bearerFormat),
|
|
717
|
+
scheme: undefined,
|
|
718
|
+
key: undefined,
|
|
719
|
+
in: undefined
|
|
720
|
+
});
|
|
721
|
+
} else {
|
|
722
|
+
parsed.push({
|
|
723
|
+
name,
|
|
724
|
+
type: "http",
|
|
725
|
+
description: Utils.nonEmptyString(scheme.description),
|
|
726
|
+
bearerFormat: Utils.nonEmptyString(scheme.bearerFormat),
|
|
727
|
+
scheme: scheme.scheme,
|
|
728
|
+
key: undefined,
|
|
729
|
+
in: undefined
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
if (scheme.type === "apiKey" && (scheme.in === "header" || scheme.in === "query" || scheme.in === "cookie") && typeof scheme.name === "string") {
|
|
735
|
+
parsed.push({
|
|
736
|
+
name,
|
|
737
|
+
type: "apiKey",
|
|
738
|
+
description: Utils.nonEmptyString(scheme.description),
|
|
739
|
+
bearerFormat: undefined,
|
|
740
|
+
scheme: undefined,
|
|
741
|
+
key: scheme.name,
|
|
742
|
+
in: scheme.in
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return parsed;
|
|
747
|
+
};
|
|
748
|
+
const warnForAndSecurityRequirements = (emitWarning, operation) => {
|
|
749
|
+
if (operation.effectiveSecurity.some(requirement => Object.keys(requirement).length === 0)) {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
for (const requirement of operation.effectiveSecurity) {
|
|
753
|
+
const schemes = Object.keys(requirement);
|
|
754
|
+
if (schemes.length <= 1) {
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
warnForOperation(emitWarning, operation, {
|
|
758
|
+
code: "security-and-downgraded",
|
|
759
|
+
message: `Security requirement requiring all of [${schemes.join(", ")}] was downgraded to a placeholder middleware.`
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
const hasUnsupportedSuccessfulSseResponse = (responses, hasExplicitSuccessResponse) => {
|
|
764
|
+
for (const [status, response] of responses) {
|
|
765
|
+
if (!Predicate.isObject(response)) {
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
const remappedStatus = remapDefaultResponseStatusForHttpApi(status, hasExplicitSuccessResponse);
|
|
769
|
+
const statusCode = Number(remappedStatus);
|
|
770
|
+
if (Number.isNaN(statusCode) || statusCode >= 400) {
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
const content = Predicate.isObject(response.content) ? response.content : undefined;
|
|
774
|
+
if (Predicate.isUndefined(content)) {
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
for (const [contentType, mediaType] of Object.entries(content)) {
|
|
778
|
+
if (!Predicate.isObject(mediaType)) {
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
const streamEncoding = getEffectStreamEncoding(mediaType);
|
|
782
|
+
if (streamEncoding === "sse") {
|
|
783
|
+
if (Predicate.isUndefined(mediaType.schema) || Predicate.isUndefined(getEffectStreamErrorSchema(mediaType))) {
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
if (contentType === "text/event-stream") {
|
|
789
|
+
return true;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return false;
|
|
794
|
+
};
|
|
795
|
+
const remapDefaultResponseStatusForHttpApi = (status, hasExplicitSuccessResponse) => status === "default" ? hasExplicitSuccessResponse ? "500" : "200" : status;
|
|
796
|
+
const methodSupportsRequestBody = method => method !== "get" && method !== "head" && method !== "options" && method !== "trace";
|
|
797
|
+
const warnForOperation = (emitWarning, operation, warning) => {
|
|
798
|
+
emitWarning({
|
|
799
|
+
...warning,
|
|
800
|
+
path: operation.path,
|
|
801
|
+
method: operation.method,
|
|
802
|
+
operationId: operation.operationId
|
|
803
|
+
});
|
|
804
|
+
};
|
|
173
805
|
function getDialect(spec) {
|
|
174
806
|
return spec.openapi.trim().startsWith("3.0") ? "openapi-3.0" : "openapi-3.1";
|
|
175
807
|
}
|
|
808
|
+
/**
|
|
809
|
+
* Layer providing an OpenAPI generator for Schema-backed HTTP client and HttpApi output.
|
|
810
|
+
*
|
|
811
|
+
* @category layers
|
|
812
|
+
* @since 4.0.0
|
|
813
|
+
*/
|
|
176
814
|
export const layerTransformerSchema = /*#__PURE__*/Layer.effect(OpenApiGenerator, make);
|
|
815
|
+
/**
|
|
816
|
+
* Layer providing an OpenAPI generator for type-only HTTP client output.
|
|
817
|
+
*
|
|
818
|
+
* @category layers
|
|
819
|
+
* @since 4.0.0
|
|
820
|
+
*/
|
|
177
821
|
export const layerTransformerTs = /*#__PURE__*/Layer.effect(OpenApiGenerator, make);
|
|
178
822
|
const isSwaggerSpec = spec => "swagger" in spec;
|
|
179
823
|
const convertSwaggerSpec = /*#__PURE__*/Effect.fn(spec => Effect.callback(resume => {
|