@typespec/openapi3 0.59.0-dev.5 → 0.59.0-dev.7
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/src/cli/actions/convert/transforms/transform-operation-responses.d.ts.map +1 -1
- package/dist/src/cli/actions/convert/transforms/transform-operation-responses.js +6 -4
- package/dist/src/cli/actions/convert/transforms/transform-operation-responses.js.map +1 -1
- package/dist/src/encoding.d.ts +5 -0
- package/dist/src/encoding.d.ts.map +1 -0
- package/dist/src/encoding.js +41 -0
- package/dist/src/encoding.js.map +1 -0
- package/dist/src/openapi.d.ts.map +1 -1
- package/dist/src/openapi.js +229 -340
- package/dist/src/openapi.js.map +1 -1
- package/dist/src/schema-emitter.d.ts.map +1 -1
- package/dist/src/schema-emitter.js +5 -72
- package/dist/src/schema-emitter.js.map +1 -1
- package/dist/src/std-scalar-schemas.d.ts +7 -0
- package/dist/src/std-scalar-schemas.d.ts.map +1 -0
- package/dist/src/std-scalar-schemas.js +63 -0
- package/dist/src/std-scalar-schemas.js.map +1 -0
- package/dist/src/types.d.ts +4 -4
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/src/openapi.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { compilerAssert, createDiagnosticCollector, emitFile, getAllTags, getAnyExtensionFromPath, getDoc,
|
|
1
|
+
import { compilerAssert, createDiagnosticCollector, emitFile, getAllTags, getAnyExtensionFromPath, getDoc, getFormat, getKnownValues, getMaxItems, getMaxLength, getMaxValue, getMaxValueExclusive, getMinItems, getMinLength, getMinValue, getMinValueExclusive, getNamespaceFullName, getOpExamples, getPattern, getService, getSummary, ignoreDiagnostics, interpolatePath, isDeprecated, isGlobalNamespace, isNeverType, isSecret, isVoidType, listServices, navigateTypesInNamespace, projectProgram, resolvePath, serializeValueAsJson, } from "@typespec/compiler";
|
|
2
2
|
import { createAssetEmitter } from "@typespec/compiler/emitter-framework";
|
|
3
3
|
import { createMetadataInfo, getHttpService, getServers, getStatusCodeDescription, isContentTypeHeader, isOrExtendsHttpFile, isOverloadSameEndpoint, reportIfNoRoutes, resolveAuthentication, resolveRequestVisibility, Visibility, } from "@typespec/http";
|
|
4
4
|
import { getExtensions, getExternalDocs, getOpenAPITypeName, getParameterKey, isDefaultResponse, isReadonlyProperty, resolveInfo, resolveOperationId, shouldInline, } from "@typespec/openapi";
|
|
5
5
|
import { buildVersionProjections } from "@typespec/versioning";
|
|
6
6
|
import { stringify } from "yaml";
|
|
7
7
|
import { getRef } from "./decorators.js";
|
|
8
|
+
import { applyEncoding } from "./encoding.js";
|
|
8
9
|
import { createDiagnostic } from "./lib.js";
|
|
9
10
|
import { getDefaultValue, isBytesKeptRaw, OpenAPI3SchemaEmitter } from "./schema-emitter.js";
|
|
10
11
|
import { deepEquals, isDefined } from "./util.js";
|
|
@@ -76,10 +77,10 @@ function createOAPIEmitter(context, options) {
|
|
|
76
77
|
let root;
|
|
77
78
|
let diagnostics;
|
|
78
79
|
let currentService;
|
|
80
|
+
let serviceAuth;
|
|
79
81
|
// Get the service namespace string for use in name shortening
|
|
80
82
|
let serviceNamespaceName;
|
|
81
83
|
let currentPath;
|
|
82
|
-
let currentEndpoint;
|
|
83
84
|
let metadataInfo;
|
|
84
85
|
let visibilityUsage;
|
|
85
86
|
// Map model properties that represent shared parameters to their parameter
|
|
@@ -316,15 +317,7 @@ function createOAPIEmitter(context, options) {
|
|
|
316
317
|
/**
|
|
317
318
|
* Validates that common responses are consistent and returns the minimal set that describes the differences.
|
|
318
319
|
*/
|
|
319
|
-
function
|
|
320
|
-
const statusCodeResponses = [];
|
|
321
|
-
for (const op of ops) {
|
|
322
|
-
for (const response of op.responses) {
|
|
323
|
-
if (getOpenAPI3StatusCodes(response.statusCodes, response.type).includes(statusCode)) {
|
|
324
|
-
statusCodeResponses.push(response);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
320
|
+
function deduplicateCommonResponses(statusCodeResponses) {
|
|
328
321
|
const ref = statusCodeResponses[0];
|
|
329
322
|
const sameTypeKind = statusCodeResponses.every((r) => r.type.kind === ref.type.kind);
|
|
330
323
|
const sameTypeValue = statusCodeResponses.every((r) => r.type === ref.type);
|
|
@@ -336,53 +329,53 @@ function createOAPIEmitter(context, options) {
|
|
|
336
329
|
return statusCodeResponses;
|
|
337
330
|
}
|
|
338
331
|
}
|
|
339
|
-
/**
|
|
340
|
-
* Validates that common bodies are consistent and returns the minimal set that describes the differences.
|
|
341
|
-
*/
|
|
342
|
-
function validateCommonBodies(ops) {
|
|
343
|
-
const allBodies = ops.map((op) => op.parameters.body);
|
|
344
|
-
return [...new Set(allBodies)];
|
|
345
|
-
}
|
|
346
332
|
/**
|
|
347
333
|
* Validates that common parameters are consistent and returns the minimal set that describes the differences.
|
|
348
334
|
*/
|
|
349
|
-
function
|
|
335
|
+
function resolveSharedRouteParameters(ops) {
|
|
350
336
|
const finalParams = [];
|
|
351
|
-
const
|
|
337
|
+
const parameters = new Map();
|
|
352
338
|
for (const op of ops) {
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
339
|
+
for (const parameter of op.parameters.parameters) {
|
|
340
|
+
const existing = parameters.get(parameter.name);
|
|
341
|
+
if (existing) {
|
|
342
|
+
existing.push(parameter);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
parameters.set(parameter.name, [parameter]);
|
|
346
|
+
}
|
|
356
347
|
}
|
|
357
348
|
}
|
|
358
|
-
|
|
359
|
-
if (!reference) {
|
|
349
|
+
if (parameters.size === 0) {
|
|
360
350
|
return [];
|
|
361
351
|
}
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
352
|
+
for (const sharedParams of parameters.values()) {
|
|
353
|
+
const reference = sharedParams[0];
|
|
354
|
+
const inAllOps = ops.length === sharedParams.length;
|
|
355
|
+
const sameLocations = sharedParams.every((p) => p.type === reference.type);
|
|
356
|
+
const sameOptionality = sharedParams.every((p) => p.param.optional === reference.param.optional);
|
|
357
|
+
const sameTypeKind = sharedParams.every((p) => p.param.type.kind === reference.param.type.kind);
|
|
358
|
+
const sameTypeValue = sharedParams.every((p) => p.param.type === reference.param.type);
|
|
359
|
+
if (inAllOps && sameLocations && sameOptionality && sameTypeKind && sameTypeValue) {
|
|
360
|
+
// param is consistent and in all shared operations. Only need one copy.
|
|
361
|
+
finalParams.push(reference);
|
|
362
|
+
}
|
|
363
|
+
else if (!inAllOps && sameLocations && sameOptionality && sameTypeKind && sameTypeValue) {
|
|
364
|
+
// param is consistent when used, but does not appear in all shared operations. Only need one copy, but it must be optional.
|
|
365
|
+
reference.param.optional = true;
|
|
366
|
+
finalParams.push(reference);
|
|
367
|
+
}
|
|
368
|
+
else if (inAllOps && !(sameLocations && sameOptionality && sameTypeKind)) {
|
|
369
|
+
// param is in all shared operations, but is not consistent. Need multiple copies, which must be optional.
|
|
370
|
+
// exception allowed when the params only differ by their value (e.g. string enum values)
|
|
371
|
+
sharedParams.forEach((p) => {
|
|
372
|
+
p.param.optional = true;
|
|
373
|
+
});
|
|
374
|
+
finalParams.push(...sharedParams);
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
finalParams.push(...sharedParams);
|
|
378
|
+
}
|
|
386
379
|
}
|
|
387
380
|
return finalParams;
|
|
388
381
|
}
|
|
@@ -433,59 +426,11 @@ function createOAPIEmitter(context, options) {
|
|
|
433
426
|
}
|
|
434
427
|
return codes;
|
|
435
428
|
}
|
|
436
|
-
function
|
|
437
|
-
|
|
438
|
-
const paramMap = new Map();
|
|
439
|
-
const responseMap = new Map();
|
|
440
|
-
for (const op of operations) {
|
|
441
|
-
// determine which parameters are shared by shared route operations
|
|
442
|
-
for (const param of op.parameters.parameters) {
|
|
443
|
-
if (paramMap.has(param.name)) {
|
|
444
|
-
paramMap.get(param.name).push(op);
|
|
445
|
-
}
|
|
446
|
-
else {
|
|
447
|
-
paramMap.set(param.name, [op]);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
// determine which responses are shared by shared route operations
|
|
451
|
-
for (const response of op.responses) {
|
|
452
|
-
const statusCodes = getOpenAPI3StatusCodes(response.statusCodes, op.operation);
|
|
453
|
-
for (const statusCode of statusCodes) {
|
|
454
|
-
if (responseMap.has(statusCode)) {
|
|
455
|
-
responseMap.get(statusCode).push(op);
|
|
456
|
-
}
|
|
457
|
-
else {
|
|
458
|
-
responseMap.set(statusCode, [op]);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
const totalOps = operations.length;
|
|
464
|
-
const shared = {
|
|
429
|
+
function buildSharedOperation(operations) {
|
|
430
|
+
return {
|
|
465
431
|
kind: "shared",
|
|
466
|
-
|
|
467
|
-
description: joinOps(operations, getDoc, " "),
|
|
468
|
-
summary: joinOps(operations, getSummary, " "),
|
|
469
|
-
path: operations[0].path,
|
|
470
|
-
verb: operations[0].verb,
|
|
471
|
-
operations: operations.map((op) => op.operation),
|
|
472
|
-
parameters: {
|
|
473
|
-
parameters: [],
|
|
474
|
-
},
|
|
475
|
-
bodies: undefined,
|
|
476
|
-
authentication: operations[0].authentication,
|
|
477
|
-
responses: new Map(),
|
|
432
|
+
operations: operations,
|
|
478
433
|
};
|
|
479
|
-
for (const [paramName, ops] of paramMap) {
|
|
480
|
-
const commonParams = validateCommonParameters(ops, paramName, totalOps);
|
|
481
|
-
shared.parameters.parameters.push(...commonParams);
|
|
482
|
-
}
|
|
483
|
-
shared.bodies = validateCommonBodies(operations);
|
|
484
|
-
for (const [statusCode, ops] of responseMap) {
|
|
485
|
-
shared.responses.set(statusCode, validateCommonResponses(statusCode, ops));
|
|
486
|
-
}
|
|
487
|
-
results.push(shared);
|
|
488
|
-
return results;
|
|
489
434
|
}
|
|
490
435
|
/**
|
|
491
436
|
* Groups HttpOperations together if they share the same route.
|
|
@@ -507,10 +452,7 @@ function createOAPIEmitter(context, options) {
|
|
|
507
452
|
result.push(ops[0]);
|
|
508
453
|
}
|
|
509
454
|
else {
|
|
510
|
-
|
|
511
|
-
for (const op of sharedOps) {
|
|
512
|
-
result.push(op);
|
|
513
|
-
}
|
|
455
|
+
result.push(buildSharedOperation(ops));
|
|
514
456
|
}
|
|
515
457
|
}
|
|
516
458
|
return result;
|
|
@@ -518,17 +460,15 @@ function createOAPIEmitter(context, options) {
|
|
|
518
460
|
async function getOpenApiFromVersion(service, version) {
|
|
519
461
|
try {
|
|
520
462
|
const httpService = ignoreDiagnostics(getHttpService(program, service.type));
|
|
521
|
-
const auth = resolveAuthentication(httpService);
|
|
463
|
+
const auth = (serviceAuth = resolveAuthentication(httpService));
|
|
522
464
|
initializeEmitter(service, auth.schemes, auth.defaultAuth, version);
|
|
523
465
|
reportIfNoRoutes(program, httpService.operations);
|
|
524
466
|
for (const op of resolveOperations(httpService.operations)) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const opAuth = auth.operationsAuth.get(op.operation);
|
|
531
|
-
emitOperation(op, opAuth);
|
|
467
|
+
const result = getOperationOrSharedOperation(op);
|
|
468
|
+
if (result) {
|
|
469
|
+
const { operation, path, verb } = result;
|
|
470
|
+
currentPath[path] ??= {};
|
|
471
|
+
currentPath[path][verb] = operation;
|
|
532
472
|
}
|
|
533
473
|
}
|
|
534
474
|
emitParameters();
|
|
@@ -566,26 +506,45 @@ function createOAPIEmitter(context, options) {
|
|
|
566
506
|
return undefined;
|
|
567
507
|
}
|
|
568
508
|
}
|
|
569
|
-
function
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
509
|
+
function computeSharedOperationId(shared) {
|
|
510
|
+
return shared.operations.map((op) => resolveOperationId(program, op.operation)).join("_");
|
|
511
|
+
}
|
|
512
|
+
function getOperationOrSharedOperation(operation) {
|
|
513
|
+
if (isSharedHttpOperation(operation)) {
|
|
514
|
+
return getSharedOperation(operation);
|
|
573
515
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
currentPath[verb] = {};
|
|
516
|
+
else {
|
|
517
|
+
return getOperation(operation);
|
|
577
518
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
519
|
+
}
|
|
520
|
+
function getSharedOperation(shared) {
|
|
521
|
+
const operations = shared.operations;
|
|
522
|
+
const verb = operations[0].verb;
|
|
523
|
+
const path = operations[0].path;
|
|
524
|
+
const oai3Operation = {
|
|
525
|
+
operationId: computeSharedOperationId(shared),
|
|
526
|
+
parameters: [],
|
|
527
|
+
description: joinOps(operations, getDoc, " "),
|
|
528
|
+
summary: joinOps(operations, getSummary, " "),
|
|
529
|
+
responses: getSharedResponses(shared),
|
|
530
|
+
};
|
|
531
|
+
for (const op of operations) {
|
|
532
|
+
applyExternalDocs(op.operation, oai3Operation);
|
|
533
|
+
attachExtensions(program, op.operation, oai3Operation);
|
|
534
|
+
if (isDeprecated(program, op.operation)) {
|
|
535
|
+
oai3Operation.deprecated = true;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
for (const op of operations) {
|
|
539
|
+
const opTags = getAllTags(program, op.operation);
|
|
581
540
|
if (opTags) {
|
|
582
|
-
const currentTags =
|
|
541
|
+
const currentTags = oai3Operation.tags;
|
|
583
542
|
if (currentTags) {
|
|
584
543
|
// combine tags but eliminate duplicates
|
|
585
|
-
|
|
544
|
+
oai3Operation.tags = [...new Set([...currentTags, ...opTags])];
|
|
586
545
|
}
|
|
587
546
|
else {
|
|
588
|
-
|
|
547
|
+
oai3Operation.tags = opTags;
|
|
589
548
|
}
|
|
590
549
|
for (const tag of opTags) {
|
|
591
550
|
// Add to root tags if not already there
|
|
@@ -593,105 +552,97 @@ function createOAPIEmitter(context, options) {
|
|
|
593
552
|
}
|
|
594
553
|
}
|
|
595
554
|
}
|
|
596
|
-
// Set up basic endpoint fields
|
|
597
|
-
currentEndpoint.operationId = shared.operationId;
|
|
598
|
-
for (const op of ops) {
|
|
599
|
-
applyExternalDocs(op, currentEndpoint);
|
|
600
|
-
}
|
|
601
|
-
currentEndpoint.summary = shared.summary;
|
|
602
|
-
currentEndpoint.description = shared.description;
|
|
603
|
-
currentEndpoint.parameters = [];
|
|
604
|
-
currentEndpoint.responses = {};
|
|
605
555
|
// Error out if shared routes do not have consistent `@parameterVisibility`. We can
|
|
606
556
|
// lift this restriction in the future if a use case develops.
|
|
607
|
-
const visibilities =
|
|
608
|
-
return resolveRequestVisibility(program, op, verb);
|
|
609
|
-
});
|
|
557
|
+
const visibilities = operations.map((op) => resolveRequestVisibility(program, op.operation, verb));
|
|
610
558
|
if (visibilities.some((v) => v !== visibilities[0])) {
|
|
611
559
|
diagnostics.add(createDiagnostic({
|
|
612
560
|
code: "inconsistent-shared-route-request-visibility",
|
|
613
|
-
target:
|
|
561
|
+
target: operations[0].operation,
|
|
614
562
|
}));
|
|
615
563
|
}
|
|
616
|
-
const visibility =
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
emitMergedRequestBody(shared, shared.bodies, visibility);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
emitSharedResponses(shared, shared.responses);
|
|
627
|
-
for (const op of ops) {
|
|
628
|
-
if (isDeprecated(program, op)) {
|
|
629
|
-
currentEndpoint.deprecated = true;
|
|
630
|
-
}
|
|
631
|
-
attachExtensions(program, op, currentEndpoint);
|
|
564
|
+
const visibility = visibilities[0];
|
|
565
|
+
oai3Operation.parameters = getEndpointParameters(resolveSharedRouteParameters(operations), visibility);
|
|
566
|
+
const bodies = [
|
|
567
|
+
...new Set(operations.map((op) => op.parameters.body).filter((x) => x !== undefined)),
|
|
568
|
+
];
|
|
569
|
+
if (bodies) {
|
|
570
|
+
oai3Operation.requestBody = getRequestBody(shared, bodies, visibility);
|
|
632
571
|
}
|
|
572
|
+
const authReference = serviceAuth.operationsAuth.get(shared.operations[0].operation);
|
|
633
573
|
if (authReference) {
|
|
634
|
-
|
|
574
|
+
oai3Operation.security = getEndpointSecurity(authReference);
|
|
635
575
|
}
|
|
576
|
+
return { operation: oai3Operation, verb, path };
|
|
636
577
|
}
|
|
637
|
-
function
|
|
578
|
+
function getOperation(operation) {
|
|
638
579
|
const { path: fullPath, operation: op, verb, parameters } = operation;
|
|
639
580
|
// If path contains a query string, issue msg and don't emit this endpoint
|
|
640
581
|
if (fullPath.indexOf("?") > 0) {
|
|
641
582
|
diagnostics.add(createDiagnostic({ code: "path-query", target: op }));
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
if (!root.paths[fullPath]) {
|
|
645
|
-
root.paths[fullPath] = {};
|
|
646
|
-
}
|
|
647
|
-
currentPath = root.paths[fullPath];
|
|
648
|
-
if (!currentPath[verb]) {
|
|
649
|
-
currentPath[verb] = {};
|
|
583
|
+
return undefined;
|
|
650
584
|
}
|
|
651
|
-
|
|
585
|
+
const visibility = resolveRequestVisibility(program, operation.operation, verb);
|
|
586
|
+
const oai3Operation = {
|
|
587
|
+
operationId: resolveOperationId(program, operation.operation),
|
|
588
|
+
summary: getSummary(program, operation.operation),
|
|
589
|
+
description: getDoc(program, operation.operation),
|
|
590
|
+
parameters: getEndpointParameters(parameters.parameters, visibility),
|
|
591
|
+
responses: getResponses(operation, operation.responses),
|
|
592
|
+
};
|
|
652
593
|
const currentTags = getAllTags(program, op);
|
|
653
594
|
if (currentTags) {
|
|
654
|
-
|
|
595
|
+
oai3Operation.tags = currentTags;
|
|
655
596
|
for (const tag of currentTags) {
|
|
656
597
|
// Add to root tags if not already there
|
|
657
598
|
tags.add(tag);
|
|
658
599
|
}
|
|
659
600
|
}
|
|
660
|
-
|
|
661
|
-
applyExternalDocs(op, currentEndpoint);
|
|
601
|
+
applyExternalDocs(op, oai3Operation);
|
|
662
602
|
// Set up basic endpoint fields
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const visibility = resolveRequestVisibility(program, operation.operation, verb);
|
|
668
|
-
emitEndpointParameters(parameters.parameters, visibility);
|
|
669
|
-
emitRequestBody(operation, parameters.body, visibility);
|
|
670
|
-
emitResponses(operation, operation.responses);
|
|
603
|
+
if (parameters.body) {
|
|
604
|
+
oai3Operation.requestBody = getRequestBody(operation, parameters.body && [parameters.body], visibility);
|
|
605
|
+
}
|
|
606
|
+
const authReference = serviceAuth.operationsAuth.get(operation.operation);
|
|
671
607
|
if (authReference) {
|
|
672
|
-
|
|
608
|
+
oai3Operation.security = getEndpointSecurity(authReference);
|
|
673
609
|
}
|
|
674
610
|
if (isDeprecated(program, op)) {
|
|
675
|
-
|
|
611
|
+
oai3Operation.deprecated = true;
|
|
676
612
|
}
|
|
677
|
-
attachExtensions(program, op,
|
|
613
|
+
attachExtensions(program, op, oai3Operation);
|
|
614
|
+
return { operation: oai3Operation, path: fullPath, verb };
|
|
678
615
|
}
|
|
679
|
-
function
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
616
|
+
function getSharedResponses(operation) {
|
|
617
|
+
const responseMap = new Map();
|
|
618
|
+
for (const op of operation.operations) {
|
|
619
|
+
for (const response of op.responses) {
|
|
620
|
+
const statusCodes = getOpenAPI3StatusCodes(response.statusCodes, op.operation);
|
|
621
|
+
for (const statusCode of statusCodes) {
|
|
622
|
+
if (responseMap.has(statusCode)) {
|
|
623
|
+
responseMap.get(statusCode).push(response);
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
responseMap.set(statusCode, [response]);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
686
629
|
}
|
|
687
630
|
}
|
|
631
|
+
const result = {};
|
|
632
|
+
for (const [statusCode, statusCodeResponses] of responseMap) {
|
|
633
|
+
const dedupeResponses = deduplicateCommonResponses(statusCodeResponses);
|
|
634
|
+
result[statusCode] = getResponseForStatusCode(operation, statusCode, dedupeResponses);
|
|
635
|
+
}
|
|
636
|
+
return result;
|
|
688
637
|
}
|
|
689
|
-
function
|
|
638
|
+
function getResponses(operation, responses) {
|
|
639
|
+
const result = {};
|
|
690
640
|
for (const response of responses) {
|
|
691
641
|
for (const statusCode of getOpenAPI3StatusCodes(response.statusCodes, response.type)) {
|
|
692
|
-
|
|
642
|
+
result[statusCode] = getResponseForStatusCode(operation, statusCode, [response]);
|
|
693
643
|
}
|
|
694
644
|
}
|
|
645
|
+
return result;
|
|
695
646
|
}
|
|
696
647
|
function isBinaryPayload(body, contentType) {
|
|
697
648
|
return (body.kind === "Scalar" &&
|
|
@@ -699,13 +650,16 @@ function createOAPIEmitter(context, options) {
|
|
|
699
650
|
contentType !== "application/json" &&
|
|
700
651
|
contentType !== "text/plain");
|
|
701
652
|
}
|
|
702
|
-
function
|
|
653
|
+
function getResponseForStatusCode(operation, statusCode, responses) {
|
|
703
654
|
const openApiResponse = {
|
|
704
|
-
description:
|
|
705
|
-
content: {},
|
|
655
|
+
description: "",
|
|
706
656
|
};
|
|
707
657
|
const schemaMap = new Map();
|
|
708
658
|
for (const response of responses) {
|
|
659
|
+
const refUrl = getRef(program, response.type);
|
|
660
|
+
if (refUrl) {
|
|
661
|
+
return { $ref: refUrl };
|
|
662
|
+
}
|
|
709
663
|
if (response.description && response.description !== openApiResponse.description) {
|
|
710
664
|
openApiResponse.description = openApiResponse.description
|
|
711
665
|
? `${openApiResponse.description} ${response.description}`
|
|
@@ -716,20 +670,8 @@ function createOAPIEmitter(context, options) {
|
|
|
716
670
|
if (!openApiResponse.description) {
|
|
717
671
|
openApiResponse.description = getResponseDescriptionForStatusCode(statusCode);
|
|
718
672
|
}
|
|
719
|
-
currentEndpoint.responses[statusCode] = openApiResponse;
|
|
720
673
|
}
|
|
721
|
-
|
|
722
|
-
function emitResponseObject(operation, statusCode, response) {
|
|
723
|
-
const openApiResponse = currentEndpoint.responses[statusCode] ?? {
|
|
724
|
-
description: response.description ?? getResponseDescriptionForStatusCode(statusCode),
|
|
725
|
-
};
|
|
726
|
-
const refUrl = getRef(program, response.type);
|
|
727
|
-
if (refUrl) {
|
|
728
|
-
openApiResponse.$ref = refUrl;
|
|
729
|
-
}
|
|
730
|
-
emitResponseHeaders(openApiResponse, response.responses, response.type);
|
|
731
|
-
emitResponseContent(operation, openApiResponse, response.responses);
|
|
732
|
-
currentEndpoint.responses[statusCode] = openApiResponse;
|
|
674
|
+
return openApiResponse;
|
|
733
675
|
}
|
|
734
676
|
function emitResponseHeaders(obj, responses, target) {
|
|
735
677
|
for (const data of responses) {
|
|
@@ -846,7 +788,7 @@ function createOAPIEmitter(context, options) {
|
|
|
846
788
|
}
|
|
847
789
|
function findOperationExamples(operation) {
|
|
848
790
|
if (isSharedHttpOperation(operation)) {
|
|
849
|
-
return operation.operations.flatMap((op) => getOpExamples(program, op).map((x) => [op, x]));
|
|
791
|
+
return operation.operations.flatMap((op) => getOpExamples(program, op.operation).map((x) => [op.operation, x]));
|
|
850
792
|
}
|
|
851
793
|
else {
|
|
852
794
|
return getOpExamples(program, operation.operation).map((x) => [operation.operation, x]);
|
|
@@ -996,56 +938,57 @@ function createOAPIEmitter(context, options) {
|
|
|
996
938
|
}
|
|
997
939
|
return false;
|
|
998
940
|
}
|
|
999
|
-
function
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
}
|
|
1010
|
-
const refUrl = getRef(program, property);
|
|
1011
|
-
if (refUrl) {
|
|
1012
|
-
return {
|
|
1013
|
-
$ref: refUrl,
|
|
941
|
+
function getParameter(parameter, visibility) {
|
|
942
|
+
const param = {
|
|
943
|
+
name: parameter.name,
|
|
944
|
+
in: parameter.type,
|
|
945
|
+
...getOpenAPIParameterBase(parameter.param, visibility),
|
|
946
|
+
};
|
|
947
|
+
const format = mapParameterFormat(parameter);
|
|
948
|
+
if (format === undefined) {
|
|
949
|
+
param.schema = {
|
|
950
|
+
type: "string",
|
|
1014
951
|
};
|
|
1015
952
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
}
|
|
1019
|
-
const placeholder = {};
|
|
1020
|
-
// only parameters inherited by spreading from non-inlined type are shared in #/components/parameters
|
|
1021
|
-
if (spreadParam && property.model && !shouldInline(program, property.model)) {
|
|
1022
|
-
params.set(property, placeholder);
|
|
1023
|
-
paramModels.add(property.model);
|
|
953
|
+
else {
|
|
954
|
+
Object.assign(param, format);
|
|
1024
955
|
}
|
|
1025
|
-
return
|
|
956
|
+
return param;
|
|
1026
957
|
}
|
|
1027
|
-
function
|
|
958
|
+
function getEndpointParameters(parameters, visibility) {
|
|
959
|
+
const result = [];
|
|
1028
960
|
for (const httpOpParam of parameters) {
|
|
1029
961
|
if (params.has(httpOpParam.param)) {
|
|
1030
|
-
|
|
962
|
+
result.push(params.get(httpOpParam.param));
|
|
1031
963
|
continue;
|
|
1032
964
|
}
|
|
965
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
1033
966
|
if (httpOpParam.type === "header" && isContentTypeHeader(program, httpOpParam.param)) {
|
|
1034
967
|
continue;
|
|
1035
968
|
}
|
|
1036
|
-
|
|
969
|
+
const param = getParameterOrRef(httpOpParam, visibility);
|
|
970
|
+
if (param) {
|
|
971
|
+
const existing = result.find((x) => !("$ref" in param) && !("$ref" in x) && x.name === param.name && x.in === param.in);
|
|
972
|
+
if (existing && !("$ref" in param) && !("$ref" in existing)) {
|
|
973
|
+
mergeOpenApiParameters(existing, param);
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
result.push(param);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
1037
979
|
}
|
|
980
|
+
return result;
|
|
1038
981
|
}
|
|
1039
|
-
function
|
|
1040
|
-
if (bodies === undefined) {
|
|
1041
|
-
return;
|
|
982
|
+
function getRequestBody(operation, bodies, visibility) {
|
|
983
|
+
if (bodies === undefined || bodies.every((x) => isVoidType(x.type))) {
|
|
984
|
+
return undefined;
|
|
1042
985
|
}
|
|
1043
986
|
const requestBody = {
|
|
1044
|
-
|
|
987
|
+
required: bodies.every((body) => (body.property ? !body.property.optional : true)),
|
|
1045
988
|
content: {},
|
|
1046
989
|
};
|
|
1047
990
|
const schemaMap = new Map();
|
|
1048
|
-
for (const body of bodies) {
|
|
991
|
+
for (const body of bodies.filter((x) => !isVoidType(x.type))) {
|
|
1049
992
|
const desc = body.property ? getDoc(program, body.property) : undefined;
|
|
1050
993
|
if (desc) {
|
|
1051
994
|
requestBody.description = requestBody.description
|
|
@@ -1054,67 +997,67 @@ function createOAPIEmitter(context, options) {
|
|
|
1054
997
|
}
|
|
1055
998
|
const contentTypes = body.contentTypes.length > 0 ? body.contentTypes : ["application/json"];
|
|
1056
999
|
for (const contentType of contentTypes) {
|
|
1057
|
-
const
|
|
1058
|
-
|
|
1059
|
-
|
|
1000
|
+
const entry = getBodyContentEntry(operation, "request", body, visibility, contentType);
|
|
1001
|
+
const existing = schemaMap.get(contentType);
|
|
1002
|
+
if (existing) {
|
|
1003
|
+
existing.push(entry);
|
|
1060
1004
|
}
|
|
1061
1005
|
else {
|
|
1062
|
-
schemaMap.set(contentType, [
|
|
1006
|
+
schemaMap.set(contentType, [entry]);
|
|
1063
1007
|
}
|
|
1064
1008
|
}
|
|
1065
1009
|
}
|
|
1066
|
-
const content = {};
|
|
1067
1010
|
for (const [contentType, schemaArray] of schemaMap) {
|
|
1068
1011
|
if (schemaArray.length === 1) {
|
|
1069
|
-
content[contentType] =
|
|
1012
|
+
requestBody.content[contentType] = schemaArray[0];
|
|
1070
1013
|
}
|
|
1071
1014
|
else {
|
|
1072
|
-
content[contentType] = {
|
|
1073
|
-
schema: { anyOf: schemaArray },
|
|
1015
|
+
requestBody.content[contentType] = {
|
|
1016
|
+
schema: { anyOf: schemaArray.map((x) => x.schema).filter((x) => x !== undefined) },
|
|
1017
|
+
encoding: schemaArray.find((x) => x.encoding)?.encoding,
|
|
1074
1018
|
};
|
|
1075
1019
|
}
|
|
1076
1020
|
}
|
|
1077
|
-
requestBody
|
|
1078
|
-
currentEndpoint.requestBody = requestBody;
|
|
1021
|
+
return requestBody;
|
|
1079
1022
|
}
|
|
1080
|
-
function
|
|
1081
|
-
if (
|
|
1082
|
-
return;
|
|
1023
|
+
function getParameterOrRef(parameter, visibility) {
|
|
1024
|
+
if (isNeverType(parameter.param.type)) {
|
|
1025
|
+
return undefined;
|
|
1083
1026
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1027
|
+
let spreadParam = false;
|
|
1028
|
+
let property = parameter.param;
|
|
1029
|
+
if (property.sourceProperty) {
|
|
1030
|
+
// chase our sources all the way back to the first place this property
|
|
1031
|
+
// was defined.
|
|
1032
|
+
spreadParam = true;
|
|
1033
|
+
property = property.sourceProperty;
|
|
1034
|
+
while (property.sourceProperty) {
|
|
1035
|
+
property = property.sourceProperty;
|
|
1036
|
+
}
|
|
1092
1037
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1038
|
+
const refUrl = getRef(program, property);
|
|
1039
|
+
if (refUrl) {
|
|
1040
|
+
return {
|
|
1041
|
+
$ref: refUrl,
|
|
1042
|
+
};
|
|
1098
1043
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
populateParameter(existing, parameter, visibility);
|
|
1044
|
+
if (params.has(property)) {
|
|
1045
|
+
return params.get(property);
|
|
1102
1046
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
populateParameter(ph, parameter, visibility);
|
|
1109
|
-
}
|
|
1047
|
+
const param = getParameter(parameter, visibility);
|
|
1048
|
+
// only parameters inherited by spreading from non-inlined type are shared in #/components/parameters
|
|
1049
|
+
if (spreadParam && property.model && !shouldInline(program, property.model)) {
|
|
1050
|
+
params.set(property, param);
|
|
1051
|
+
paramModels.add(property.model);
|
|
1110
1052
|
}
|
|
1053
|
+
return param;
|
|
1111
1054
|
}
|
|
1112
1055
|
function getOpenAPIParameterBase(param, visibility) {
|
|
1113
1056
|
const typeSchema = getSchemaForType(param.type, visibility);
|
|
1114
1057
|
if (!typeSchema) {
|
|
1115
1058
|
return undefined;
|
|
1116
1059
|
}
|
|
1117
|
-
const schema = applyEncoding(param, applyIntrinsicDecorators(param, typeSchema));
|
|
1060
|
+
const schema = applyEncoding(program, param, applyIntrinsicDecorators(param, typeSchema), options);
|
|
1118
1061
|
if (param.defaultValue) {
|
|
1119
1062
|
schema.default = getDefaultValue(program, param.defaultValue);
|
|
1120
1063
|
}
|
|
@@ -1128,35 +1071,18 @@ function createOAPIEmitter(context, options) {
|
|
|
1128
1071
|
attachExtensions(program, param, oaiParam);
|
|
1129
1072
|
return oaiParam;
|
|
1130
1073
|
}
|
|
1131
|
-
function mergeOpenApiParameters(
|
|
1132
|
-
if (
|
|
1133
|
-
const schema =
|
|
1134
|
-
if (schema.enum &&
|
|
1135
|
-
schema.enum = [...new Set([...schema.enum, ...
|
|
1074
|
+
function mergeOpenApiParameters(target, apply) {
|
|
1075
|
+
if (target.schema) {
|
|
1076
|
+
const schema = target.schema;
|
|
1077
|
+
if (schema.enum && apply.schema.enum) {
|
|
1078
|
+
schema.enum = [...new Set([...schema.enum, ...apply.schema.enum])];
|
|
1136
1079
|
}
|
|
1137
|
-
|
|
1138
|
-
}
|
|
1139
|
-
else {
|
|
1140
|
-
Object.assign(param, base);
|
|
1141
|
-
}
|
|
1142
|
-
return param;
|
|
1143
|
-
}
|
|
1144
|
-
function populateParameter(ph, parameter, visibility) {
|
|
1145
|
-
ph.name = parameter.name;
|
|
1146
|
-
ph.in = parameter.type;
|
|
1147
|
-
const paramBase = getOpenAPIParameterBase(parameter.param, visibility);
|
|
1148
|
-
if (paramBase) {
|
|
1149
|
-
ph = mergeOpenApiParameters(ph, paramBase);
|
|
1150
|
-
}
|
|
1151
|
-
const format = mapParameterFormat(parameter);
|
|
1152
|
-
if (format === undefined) {
|
|
1153
|
-
ph.schema = {
|
|
1154
|
-
type: "string",
|
|
1155
|
-
};
|
|
1080
|
+
target.schema = schema;
|
|
1156
1081
|
}
|
|
1157
1082
|
else {
|
|
1158
|
-
Object.assign(
|
|
1083
|
+
Object.assign(target, apply);
|
|
1159
1084
|
}
|
|
1085
|
+
return target;
|
|
1160
1086
|
}
|
|
1161
1087
|
function mapParameterFormat(parameter) {
|
|
1162
1088
|
switch (parameter.type) {
|
|
@@ -1337,44 +1263,6 @@ function createOAPIEmitter(context, options) {
|
|
|
1337
1263
|
attachExtensions(program, typespecType, newTarget);
|
|
1338
1264
|
return newTarget;
|
|
1339
1265
|
}
|
|
1340
|
-
function applyEncoding(typespecType, target) {
|
|
1341
|
-
const encodeData = getEncode(program, typespecType);
|
|
1342
|
-
if (encodeData) {
|
|
1343
|
-
const newTarget = { ...target };
|
|
1344
|
-
const newType = callSchemaEmitter(encodeData.type, Visibility.Read, false, "application/json");
|
|
1345
|
-
newTarget.type = newType.type;
|
|
1346
|
-
// If the target already has a format it takes priority. (e.g. int32)
|
|
1347
|
-
newTarget.format = mergeFormatAndEncoding(newTarget.format, encodeData.encoding, newType.format);
|
|
1348
|
-
return newTarget;
|
|
1349
|
-
}
|
|
1350
|
-
return target;
|
|
1351
|
-
}
|
|
1352
|
-
function mergeFormatAndEncoding(format, encoding, encodeAsFormat) {
|
|
1353
|
-
switch (format) {
|
|
1354
|
-
case undefined:
|
|
1355
|
-
return encodeAsFormat ?? encoding;
|
|
1356
|
-
case "date-time":
|
|
1357
|
-
switch (encoding) {
|
|
1358
|
-
case "rfc3339":
|
|
1359
|
-
return "date-time";
|
|
1360
|
-
case "unixTimestamp":
|
|
1361
|
-
return "unixtime";
|
|
1362
|
-
case "rfc7231":
|
|
1363
|
-
return "http-date";
|
|
1364
|
-
default:
|
|
1365
|
-
return encoding;
|
|
1366
|
-
}
|
|
1367
|
-
case "duration":
|
|
1368
|
-
switch (encoding) {
|
|
1369
|
-
case "ISO8601":
|
|
1370
|
-
return "duration";
|
|
1371
|
-
default:
|
|
1372
|
-
return encodeAsFormat ?? encoding;
|
|
1373
|
-
}
|
|
1374
|
-
default:
|
|
1375
|
-
return encodeAsFormat ?? encoding;
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
1266
|
function applyExternalDocs(typespecType, target) {
|
|
1379
1267
|
const externalDocs = getExternalDocs(program, typespecType);
|
|
1380
1268
|
if (externalDocs) {
|
|
@@ -1410,14 +1298,15 @@ function createOAPIEmitter(context, options) {
|
|
|
1410
1298
|
});
|
|
1411
1299
|
return security;
|
|
1412
1300
|
}
|
|
1413
|
-
function
|
|
1301
|
+
function getEndpointSecurity(authReference) {
|
|
1414
1302
|
const security = getOpenAPISecurity(authReference);
|
|
1415
1303
|
if (deepEquals(security, root.security)) {
|
|
1416
|
-
return;
|
|
1304
|
+
return undefined;
|
|
1417
1305
|
}
|
|
1418
1306
|
if (security.length > 0) {
|
|
1419
|
-
|
|
1307
|
+
return security;
|
|
1420
1308
|
}
|
|
1309
|
+
return undefined;
|
|
1421
1310
|
}
|
|
1422
1311
|
function getOpenAPI3Scheme(auth) {
|
|
1423
1312
|
const scheme = getOpenAPI3SchemeInternal(auth);
|