@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.
@@ -1,10 +1,11 @@
1
- import { compilerAssert, createDiagnosticCollector, emitFile, getAllTags, getAnyExtensionFromPath, getDoc, getEncode, 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";
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 validateCommonResponses(statusCode, ops) {
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 validateCommonParameters(ops, name, totalOps) {
335
+ function resolveSharedRouteParameters(ops) {
350
336
  const finalParams = [];
351
- const commonParams = [];
337
+ const parameters = new Map();
352
338
  for (const op of ops) {
353
- const param = op.parameters.parameters.find((p) => p.name === name);
354
- if (param) {
355
- commonParams.push(param);
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
- const reference = commonParams[0];
359
- if (!reference) {
349
+ if (parameters.size === 0) {
360
350
  return [];
361
351
  }
362
- const inAllOps = ops.length === totalOps;
363
- const sameLocations = commonParams.every((p) => p.type === reference.type);
364
- const sameOptionality = commonParams.every((p) => p.param.optional === reference.param.optional);
365
- const sameTypeKind = commonParams.every((p) => p.param.type.kind === reference.param.type.kind);
366
- const sameTypeValue = commonParams.every((p) => p.param.type === reference.param.type);
367
- if (inAllOps && sameLocations && sameOptionality && sameTypeKind && sameTypeValue) {
368
- // param is consistent and in all shared operations. Only need one copy.
369
- finalParams.push(reference);
370
- }
371
- else if (!inAllOps && sameLocations && sameOptionality && sameTypeKind && sameTypeValue) {
372
- // param is consistent when used, but does not appear in all shared operations. Only need one copy, but it must be optional.
373
- reference.param.optional = true;
374
- finalParams.push(reference);
375
- }
376
- else if (inAllOps && !(sameLocations && sameOptionality && sameTypeKind)) {
377
- // param is in all shared operations, but is not consistent. Need multiple copies, which must be optional.
378
- // exception allowed when the params only differ by their value (e.g. string enum values)
379
- commonParams.forEach((p) => {
380
- p.param.optional = true;
381
- });
382
- finalParams.push(...commonParams);
383
- }
384
- else {
385
- finalParams.push(...commonParams);
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 buildSharedOperations(operations) {
437
- const results = [];
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
- operationId: operations.map((op) => resolveOperationId(program, op.operation)).join("_"),
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
- const sharedOps = buildSharedOperations(ops);
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
- if (op.kind === "shared") {
526
- const opAuth = auth.operationsAuth.get(op.operations[0]);
527
- emitSharedOperation(op, opAuth);
528
- }
529
- else {
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 emitSharedOperation(shared, authReference) {
570
- const { path: fullPath, verb: verb, operations: ops } = shared;
571
- if (!root.paths[fullPath]) {
572
- root.paths[fullPath] = {};
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
- currentPath = root.paths[fullPath];
575
- if (!currentPath[verb]) {
576
- currentPath[verb] = {};
516
+ else {
517
+ return getOperation(operation);
577
518
  }
578
- currentEndpoint = currentPath[verb];
579
- for (const op of ops) {
580
- const opTags = getAllTags(program, op);
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 = currentEndpoint.tags;
541
+ const currentTags = oai3Operation.tags;
583
542
  if (currentTags) {
584
543
  // combine tags but eliminate duplicates
585
- currentEndpoint.tags = [...new Set([...currentTags, ...opTags])];
544
+ oai3Operation.tags = [...new Set([...currentTags, ...opTags])];
586
545
  }
587
546
  else {
588
- currentEndpoint.tags = opTags;
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 = shared.operations.map((op) => {
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: ops[0],
561
+ target: operations[0].operation,
614
562
  }));
615
563
  }
616
- const visibility = resolveRequestVisibility(program, shared.operations[0], verb);
617
- emitEndpointParameters(shared.parameters.parameters, visibility);
618
- if (shared.bodies) {
619
- if (shared.bodies.length === 1) {
620
- emitRequestBody(shared, shared.bodies[0], visibility);
621
- }
622
- else if (shared.bodies.length > 1) {
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
- emitEndpointSecurity(authReference);
574
+ oai3Operation.security = getEndpointSecurity(authReference);
635
575
  }
576
+ return { operation: oai3Operation, verb, path };
636
577
  }
637
- function emitOperation(operation, authReference) {
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
- currentEndpoint = currentPath[verb];
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
- currentEndpoint.tags = currentTags;
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
- currentEndpoint.operationId = resolveOperationId(program, operation.operation);
661
- applyExternalDocs(op, currentEndpoint);
601
+ applyExternalDocs(op, oai3Operation);
662
602
  // Set up basic endpoint fields
663
- currentEndpoint.summary = getSummary(program, operation.operation);
664
- currentEndpoint.description = getDoc(program, operation.operation);
665
- currentEndpoint.parameters = [];
666
- currentEndpoint.responses = {};
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
- emitEndpointSecurity(authReference);
608
+ oai3Operation.security = getEndpointSecurity(authReference);
673
609
  }
674
610
  if (isDeprecated(program, op)) {
675
- currentEndpoint.deprecated = true;
611
+ oai3Operation.deprecated = true;
676
612
  }
677
- attachExtensions(program, op, currentEndpoint);
613
+ attachExtensions(program, op, oai3Operation);
614
+ return { operation: oai3Operation, path: fullPath, verb };
678
615
  }
679
- function emitSharedResponses(operation, responses) {
680
- for (const [statusCode, statusCodeResponses] of responses) {
681
- if (statusCodeResponses.length === 1) {
682
- emitResponseObject(operation, statusCode, statusCodeResponses[0]);
683
- }
684
- else {
685
- emitMergedResponseObject(operation, statusCode, statusCodeResponses);
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 emitResponses(operation, responses) {
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
- emitResponseObject(operation, statusCode, response);
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 emitMergedResponseObject(operation, statusCode, responses) {
653
+ function getResponseForStatusCode(operation, statusCode, responses) {
703
654
  const openApiResponse = {
704
- description: undefined,
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 getParamPlaceholder(property) {
1000
- let spreadParam = false;
1001
- if (property.sourceProperty) {
1002
- // chase our sources all the way back to the first place this property
1003
- // was defined.
1004
- spreadParam = true;
1005
- property = property.sourceProperty;
1006
- while (property.sourceProperty) {
1007
- property = property.sourceProperty;
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
- if (params.has(property)) {
1017
- return params.get(property);
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 placeholder;
956
+ return param;
1026
957
  }
1027
- function emitEndpointParameters(parameters, visibility) {
958
+ function getEndpointParameters(parameters, visibility) {
959
+ const result = [];
1028
960
  for (const httpOpParam of parameters) {
1029
961
  if (params.has(httpOpParam.param)) {
1030
- currentEndpoint.parameters.push(params.get(httpOpParam.param));
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
- emitParameter(httpOpParam, visibility);
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 emitMergedRequestBody(operation, bodies, visibility) {
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
- description: undefined,
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 { schema: bodySchema } = getBodyContentEntry(operation, "request", body, visibility, contentType);
1058
- if (schemaMap.has(contentType)) {
1059
- schemaMap.get(contentType).push(bodySchema);
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, [bodySchema]);
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] = { schema: schemaArray[0] };
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.content = content;
1078
- currentEndpoint.requestBody = requestBody;
1021
+ return requestBody;
1079
1022
  }
1080
- function emitRequestBody(operation, body, visibility) {
1081
- if (body === undefined || isVoidType(body.type)) {
1082
- return;
1023
+ function getParameterOrRef(parameter, visibility) {
1024
+ if (isNeverType(parameter.param.type)) {
1025
+ return undefined;
1083
1026
  }
1084
- const requestBody = {
1085
- description: body.property ? getDoc(program, body.property) : undefined,
1086
- required: body.property ? !body.property.optional : true,
1087
- content: {},
1088
- };
1089
- const contentTypes = body.contentTypes.length > 0 ? body.contentTypes : ["application/json"];
1090
- for (const contentType of contentTypes) {
1091
- requestBody.content[contentType] = getBodyContentEntry(operation, "request", body, visibility, contentType);
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
- currentEndpoint.requestBody = requestBody;
1094
- }
1095
- function emitParameter(parameter, visibility) {
1096
- if (isNeverType(parameter.param.type)) {
1097
- return;
1038
+ const refUrl = getRef(program, property);
1039
+ if (refUrl) {
1040
+ return {
1041
+ $ref: refUrl,
1042
+ };
1098
1043
  }
1099
- const existing = currentEndpoint.parameters.find((p) => p.name === parameter.name && p.in === parameter.type);
1100
- if (existing) {
1101
- populateParameter(existing, parameter, visibility);
1044
+ if (params.has(property)) {
1045
+ return params.get(property);
1102
1046
  }
1103
- else {
1104
- const ph = getParamPlaceholder(parameter.param);
1105
- currentEndpoint.parameters.push(ph);
1106
- // If the parameter already has a $ref, don't bother populating it
1107
- if (!("$ref" in ph)) {
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(param, base) {
1132
- if (param.schema) {
1133
- const schema = param.schema;
1134
- if (schema.enum && base.schema.enum) {
1135
- schema.enum = [...new Set([...schema.enum, ...base.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
- param.schema = schema;
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(ph, format);
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 emitEndpointSecurity(authReference) {
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
- currentEndpoint.security = security;
1307
+ return security;
1420
1308
  }
1309
+ return undefined;
1421
1310
  }
1422
1311
  function getOpenAPI3Scheme(auth) {
1423
1312
  const scheme = getOpenAPI3SchemeInternal(auth);