@azure-tools/typespec-autorest 0.43.0-dev.1 → 0.43.0-dev.11

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,6 +1,7 @@
1
- import { extractLroStates, getArmResourceIdentifierConfig, getAsEmbeddingVector, getLroMetadata, getPagedResult, getUnionAsEnum, isFixed, } from "@azure-tools/typespec-azure-core";
1
+ import { FinalStateValue, extractLroStates, getArmResourceIdentifierConfig, getAsEmbeddingVector, getLroMetadata, getPagedResult, getUnionAsEnum, } from "@azure-tools/typespec-azure-core";
2
+ import { getArmCommonTypeOpenAPIRef, isArmCommonType, } from "@azure-tools/typespec-azure-resource-manager";
2
3
  import { shouldFlattenProperty } from "@azure-tools/typespec-client-generator-core";
3
- import { NoTarget, SyntaxKind, compilerAssert, createDiagnosticCollector, getAllTags, getDirectoryPath, getDiscriminator, getDoc, getEncode, getFormat, getKnownValues, getMaxItems, getMaxLength, getMaxValue, getMinItems, getMinLength, getMinValue, getPattern, getProjectedName, getProperty, getPropertyType, getRelativePathFromDirectory, getRootLength, getSummary, getVisibility, ignoreDiagnostics, interpolatePath, isArrayModelType, isDeprecated, isErrorModel, isErrorType, isGlobalNamespace, isNeverType, isNullType, isNumericType, isRecordModelType, isSecret, isService, isStringType, isTemplateDeclaration, isTemplateDeclarationOrInstance, isVoidType, navigateTypesInNamespace, resolveEncodedName, resolvePath, stringTemplateToString, } from "@typespec/compiler";
4
+ import { NoTarget, SyntaxKind, compilerAssert, createDiagnosticCollector, explainStringTemplateNotSerializable, getAllTags, getDirectoryPath, getDiscriminator, getDoc, getEncode, getFormat, getKnownValues, getMaxItems, getMaxLength, getMaxValue, getMinItems, getMinLength, getMinValue, getPattern, getProjectedName, getProperty, getPropertyType, getRelativePathFromDirectory, getRootLength, getSummary, getVisibility, ignoreDiagnostics, interpolatePath, isArrayModelType, isDeprecated, isErrorModel, isErrorType, isGlobalNamespace, isNeverType, isNullType, isNumericType, isRecordModelType, isSecret, isService, isStringType, isTemplateDeclaration, isTemplateDeclarationOrInstance, isVoidType, navigateTypesInNamespace, resolveEncodedName, resolvePath, } from "@typespec/compiler";
4
5
  import { TwoLevelMap } from "@typespec/compiler/utils";
5
6
  import { Visibility, createMetadataInfo, getAllHttpServices, getAuthentication, getHeaderFieldOptions, getQueryParamOptions, getServers, getStatusCodeDescription, getVisibilitySuffix, isContentTypeHeader, isSharedRoute, reportIfNoRoutes, resolveRequestVisibility, } from "@typespec/http";
6
7
  import { checkDuplicateTypeName, getExtensions, getExternalDocs, getOpenAPITypeName, getParameterKey, isReadonlyProperty, resolveInfo, shouldInline, } from "@typespec/openapi";
@@ -126,6 +127,7 @@ export async function getOpenAPIForService(context, options) {
126
127
  }
127
128
  })
128
129
  .filter((x) => x),
130
+ outputFile: context.outputFile,
129
131
  };
130
132
  function resolveHost(program, namespace) {
131
133
  const servers = getServers(program, namespace);
@@ -238,6 +240,40 @@ export async function getOpenAPIForService(context, options) {
238
240
  // strip everything from the key including and after the ?
239
241
  return path.replace(/\/?\?.*/, "");
240
242
  }
243
+ function getFinalStateVia(metadata) {
244
+ switch (metadata.finalStateVia) {
245
+ case FinalStateValue.azureAsyncOperation:
246
+ return "azure-async-operation";
247
+ case FinalStateValue.location:
248
+ return "location";
249
+ case FinalStateValue.operationLocation:
250
+ return "operation-location";
251
+ case FinalStateValue.originalUri:
252
+ return "original-uri";
253
+ default:
254
+ return undefined;
255
+ }
256
+ }
257
+ function getFinalStateSchema(metadata) {
258
+ if (metadata.finalResult !== undefined &&
259
+ metadata.finalResult !== "void" &&
260
+ metadata.finalResult.name.length > 0) {
261
+ const model = metadata.finalResult;
262
+ const schemaOrRef = resolveExternalRef(metadata.finalResult);
263
+ if (schemaOrRef !== undefined) {
264
+ const ref = new Ref();
265
+ ref.value = schemaOrRef.$ref;
266
+ return { "final-state-schema": ref };
267
+ }
268
+ const pending = pendingSchemas.getOrAdd(metadata.finalResult, Visibility.Read, () => ({
269
+ type: model,
270
+ visibility: Visibility.Read,
271
+ ref: refs.getOrAdd(model, Visibility.Read, () => new Ref()),
272
+ }));
273
+ return { "final-state-schema": pending.ref };
274
+ }
275
+ return undefined;
276
+ }
241
277
  function emitOperation(operation) {
242
278
  let { path: fullPath, operation: op, verb, parameters } = operation;
243
279
  let pathsObject = root.paths;
@@ -294,6 +330,22 @@ export async function getOpenAPIForService(context, options) {
294
330
  // which does have LRO metadata.
295
331
  if (lroMetadata !== undefined && operation.verb !== "get") {
296
332
  currentEndpoint["x-ms-long-running-operation"] = true;
333
+ if (options.emitLroOptions !== "none") {
334
+ const finalState = getFinalStateVia(lroMetadata);
335
+ if (finalState !== undefined) {
336
+ const finalSchema = getFinalStateSchema(lroMetadata);
337
+ let lroOptions = {
338
+ "final-state-via": finalState,
339
+ };
340
+ if (finalSchema !== undefined && options.emitLroOptions === "all") {
341
+ lroOptions = {
342
+ "final-state-via": finalState,
343
+ ...finalSchema,
344
+ };
345
+ }
346
+ currentEndpoint["x-ms-long-running-operation-options"] = lroOptions;
347
+ }
348
+ }
297
349
  }
298
350
  // Extract paged metadata from Azure.Core.Page
299
351
  extractPagedMetadata(program, operation);
@@ -442,19 +494,27 @@ export async function getOpenAPIForService(context, options) {
442
494
  }
443
495
  }
444
496
  if (body) {
445
- const isBinary = contentTypes.every((t) => isBinaryPayload(body.type, t));
446
- openapiResponse.schema = isBinary
447
- ? { type: "file" }
448
- : getSchemaOrRef(body.type, {
449
- visibility: Visibility.Read,
450
- ignoreMetadataAnnotations: body.isExplicit && body.containsMetadataAnnotations,
451
- });
497
+ openapiResponse.schema = getSchemaForResponseBody(body, contentTypes);
452
498
  }
453
499
  for (const contentType of contentTypes) {
454
500
  currentProduces.add(contentType);
455
501
  }
456
502
  currentEndpoint.responses[statusCode] = openapiResponse;
457
503
  }
504
+ function getSchemaForResponseBody(body, contentTypes) {
505
+ const isBinary = contentTypes.every((t) => isBinaryPayload(body.type, t));
506
+ if (isBinary) {
507
+ return { type: "file" };
508
+ }
509
+ if (body.bodyKind === "multipart") {
510
+ // OpenAPI2 doesn't support multipart responses, so we just return a string schema
511
+ return { type: "string" };
512
+ }
513
+ return getSchemaOrRef(body.type, {
514
+ visibility: Visibility.Read,
515
+ ignoreMetadataAnnotations: body.isExplicit && body.containsMetadataAnnotations,
516
+ });
517
+ }
458
518
  function getResponseHeader(prop) {
459
519
  const header = {};
460
520
  populateParameter(header, prop, "header", {
@@ -466,7 +526,7 @@ export async function getOpenAPIForService(context, options) {
466
526
  delete header.required;
467
527
  return header;
468
528
  }
469
- function resolveRef(ref) {
529
+ function expandRef(ref) {
470
530
  const absoluteRef = interpolatePath(ref, {
471
531
  "arm-types-dir": options.armTypesDir,
472
532
  });
@@ -475,13 +535,31 @@ export async function getOpenAPIForService(context, options) {
475
535
  }
476
536
  return getRelativePathFromDirectory(getDirectoryPath(context.outputFile), absoluteRef, false);
477
537
  }
478
- function getSchemaOrRef(type, schemaContext) {
479
- const refUrl = getRef(program, type, { version: context.version, service: context.service });
538
+ function resolveExternalRef(type) {
539
+ const refUrl = getRef(program, type);
480
540
  if (refUrl) {
481
541
  return {
482
- $ref: resolveRef(refUrl),
542
+ $ref: expandRef(refUrl),
483
543
  };
484
544
  }
545
+ if (isArmCommonType(type) && (type.kind === "Model" || type.kind === "ModelProperty")) {
546
+ const ref = getArmCommonTypeOpenAPIRef(program, type, {
547
+ version: context.version,
548
+ service: context.service,
549
+ });
550
+ if (ref) {
551
+ return {
552
+ $ref: expandRef(ref),
553
+ };
554
+ }
555
+ }
556
+ return undefined;
557
+ }
558
+ function getSchemaOrRef(type, schemaContext) {
559
+ const ref = resolveExternalRef(type);
560
+ if (ref) {
561
+ return ref;
562
+ }
485
563
  if (type.kind === "Scalar" && program.checker.isStdType(type)) {
486
564
  return getSchemaForScalar(type);
487
565
  }
@@ -560,14 +638,9 @@ export async function getOpenAPIForService(context, options) {
560
638
  property = property.sourceProperty;
561
639
  }
562
640
  }
563
- const refUrl = getRef(program, property, {
564
- version: context.version,
565
- service: context.service,
566
- });
567
- if (refUrl) {
568
- return {
569
- $ref: resolveRef(refUrl),
570
- };
641
+ const ref = resolveExternalRef(property);
642
+ if (ref) {
643
+ return ref;
571
644
  }
572
645
  const parameter = params.get(property);
573
646
  if (parameter) {
@@ -611,33 +684,66 @@ export async function getOpenAPIForService(context, options) {
611
684
  currentConsumes.add(consume);
612
685
  }
613
686
  if (methodParams.body && !isVoidType(methodParams.body.type)) {
614
- const isBinary = isBinaryPayload(methodParams.body.type, consumes);
615
- const schemaContext = {
616
- visibility,
617
- ignoreMetadataAnnotations: methodParams.body.isExplicit && methodParams.body.containsMetadataAnnotations,
618
- };
619
- const schema = isBinary
620
- ? { type: "string", format: "binary" }
621
- : getSchemaOrRef(methodParams.body.type, schemaContext);
622
- if (currentConsumes.has("multipart/form-data")) {
623
- const bodyModelType = methodParams.body.type;
624
- // Assert, this should never happen. Rest library guard against that.
625
- compilerAssert(bodyModelType.kind === "Model", "Body should always be a Model.");
626
- if (bodyModelType) {
627
- for (const param of bodyModelType.properties.values()) {
628
- emitParameter(param, "formData", schemaContext, getJsonName(param));
629
- }
687
+ emitBodyParameters(methodParams.body, visibility);
688
+ }
689
+ }
690
+ function emitBodyParameters(body, visibility) {
691
+ switch (body.bodyKind) {
692
+ case "single":
693
+ emitSingleBodyParameters(body, visibility);
694
+ break;
695
+ case "multipart":
696
+ emitMultipartBodyParameters(body, visibility);
697
+ break;
698
+ }
699
+ }
700
+ function emitSingleBodyParameters(body, visibility) {
701
+ const isBinary = isBinaryPayload(body.type, body.contentTypes);
702
+ const schemaContext = {
703
+ visibility,
704
+ ignoreMetadataAnnotations: body.isExplicit && body.containsMetadataAnnotations,
705
+ };
706
+ const schema = isBinary
707
+ ? { type: "string", format: "binary" }
708
+ : getSchemaOrRef(body.type, schemaContext);
709
+ if (currentConsumes.has("multipart/form-data")) {
710
+ const bodyModelType = body.type;
711
+ // Assert, this should never happen. Rest library guard against that.
712
+ compilerAssert(bodyModelType.kind === "Model", "Body should always be a Model.");
713
+ if (bodyModelType) {
714
+ for (const param of bodyModelType.properties.values()) {
715
+ emitParameter(param, "formData", schemaContext, getJsonName(param));
630
716
  }
631
717
  }
632
- else if (methodParams.body.parameter) {
633
- emitParameter(methodParams.body.parameter, "body", { visibility, ignoreMetadataAnnotations: false }, getJsonName(methodParams.body.parameter), schema);
634
- }
635
- else {
718
+ }
719
+ else if (body.property) {
720
+ emitParameter(body.property, "body", { visibility, ignoreMetadataAnnotations: false }, getJsonName(body.property), schema);
721
+ }
722
+ else {
723
+ currentEndpoint.parameters.push({
724
+ name: "body",
725
+ in: "body",
726
+ schema,
727
+ required: true,
728
+ });
729
+ }
730
+ }
731
+ function emitMultipartBodyParameters(body, visibility) {
732
+ for (const [index, part] of body.parts.entries()) {
733
+ const partName = part.name ?? `part${index}`;
734
+ let schema = getFormDataSchema(part.body.type, { visibility, ignoreMetadataAnnotations: false }, partName);
735
+ if (schema) {
736
+ if (part.multi) {
737
+ schema = {
738
+ type: "array",
739
+ items: schema.type === "file" ? { type: "string", format: "binary" } : schema,
740
+ };
741
+ }
636
742
  currentEndpoint.parameters.push({
637
- name: "body",
638
- in: "body",
639
- schema,
640
- required: true,
743
+ name: partName,
744
+ in: "formData",
745
+ required: !part.optional,
746
+ ...schema,
641
747
  });
642
748
  }
643
749
  }
@@ -848,13 +954,13 @@ export async function getOpenAPIForService(context, options) {
848
954
  }
849
955
  return false;
850
956
  }
851
- function isVersionEnum(program, enumObj) {
852
- const versions = getVersionsForEnum(program, enumObj);
853
- if (versions !== undefined && versions.length > 0) {
854
- return true;
855
- }
856
- return false;
957
+ }
958
+ function isVersionEnum(program, enumObj) {
959
+ const [_, map] = getVersionsForEnum(program, enumObj);
960
+ if (map !== undefined && map.getVersions()[0].enumMember.enum === enumObj) {
961
+ return true;
857
962
  }
963
+ return false;
858
964
  }
859
965
  function emitTags() {
860
966
  for (const tag of tags) {
@@ -903,6 +1009,29 @@ export async function getOpenAPIForService(context, options) {
903
1009
  });
904
1010
  return {};
905
1011
  }
1012
+ /**
1013
+ * Version enum is special so we we just render the current version with modelAsString: true
1014
+ */
1015
+ function getSchemaForVersionEnum(e, currentVersion) {
1016
+ const member = [...e.members.values()].find((x) => (x.value ?? x.name) === currentVersion);
1017
+ compilerAssert(member, `Version enum ${e.name} does not have a member for ${currentVersion}.`, e);
1018
+ return {
1019
+ type: "string",
1020
+ description: getDoc(program, e),
1021
+ enum: [member.value ?? member.name],
1022
+ "x-ms-enum": {
1023
+ name: e.name,
1024
+ modelAsString: true,
1025
+ values: [
1026
+ {
1027
+ name: member.name,
1028
+ value: member.value ?? member.name,
1029
+ description: getDoc(program, member),
1030
+ },
1031
+ ],
1032
+ },
1033
+ };
1034
+ }
906
1035
  function getSchemaForEnum(e) {
907
1036
  const values = [];
908
1037
  if (e.members.size === 0) {
@@ -919,6 +1048,10 @@ export async function getOpenAPIForService(context, options) {
919
1048
  values.push(option.value ?? option.name);
920
1049
  }
921
1050
  }
1051
+ // If we are rendering a specific version and trying to render the version enum we should treat it specially to only include the current version.
1052
+ if (isVersionEnum(program, e) && context.version) {
1053
+ return getSchemaForVersionEnum(e, context.version);
1054
+ }
922
1055
  const schema = { type, description: getDoc(program, e) };
923
1056
  if (values.length > 0) {
924
1057
  schema.enum = values;
@@ -1115,6 +1248,7 @@ export async function getOpenAPIForService(context, options) {
1115
1248
  modelSchema.required = [propertyName];
1116
1249
  }
1117
1250
  }
1251
+ applySummary(model, modelSchema);
1118
1252
  applyExternalDocs(model, modelSchema);
1119
1253
  for (const prop of model.properties.values()) {
1120
1254
  if (!metadataInfo.isPayloadProperty(prop, schemaContext.visibility, schemaContext.ignoreMetadataAnnotations)) {
@@ -1150,6 +1284,7 @@ export async function getOpenAPIForService(context, options) {
1150
1284
  if (description) {
1151
1285
  property.description = description;
1152
1286
  }
1287
+ applySummary(prop, property);
1153
1288
  if (prop.defaultValue && !("$ref" in property)) {
1154
1289
  property.default = getDefaultValue(prop.defaultValue);
1155
1290
  }
@@ -1215,10 +1350,10 @@ export async function getOpenAPIForService(context, options) {
1215
1350
  }
1216
1351
  function resolveProperty(prop, context) {
1217
1352
  let propSchema;
1218
- if (prop.type.kind === "Enum" && prop.default) {
1353
+ if (prop.type.kind === "Enum" && prop.defaultValue) {
1219
1354
  propSchema = getSchemaForEnum(prop.type);
1220
1355
  }
1221
- else if (prop.type.kind === "Union" && prop.default) {
1356
+ else if (prop.type.kind === "Union" && prop.defaultValue) {
1222
1357
  const [asEnum, _] = getUnionAsEnum(prop.type);
1223
1358
  if (asEnum) {
1224
1359
  propSchema = getSchemaForUnionEnum(prop.type, asEnum);
@@ -1275,6 +1410,10 @@ export async function getOpenAPIForService(context, options) {
1275
1410
  if (docStr) {
1276
1411
  newTarget.description = docStr;
1277
1412
  }
1413
+ const title = getSummary(program, typespecType);
1414
+ if (title) {
1415
+ target.title = title;
1416
+ }
1278
1417
  const formatStr = getFormat(program, typespecType);
1279
1418
  if (isString && formatStr) {
1280
1419
  const allowedStringFormats = [
@@ -1394,6 +1533,12 @@ export async function getOpenAPIForService(context, options) {
1394
1533
  return encodeAsFormat ?? encoding;
1395
1534
  }
1396
1535
  }
1536
+ function applySummary(typespecType, target) {
1537
+ const summary = getSummary(program, typespecType);
1538
+ if (summary) {
1539
+ target.title = summary;
1540
+ }
1541
+ }
1397
1542
  function applyExternalDocs(typespecType, target) {
1398
1543
  const externalDocs = getExternalDocs(program, typespecType);
1399
1544
  if (externalDocs) {
@@ -1404,7 +1549,7 @@ export async function getOpenAPIForService(context, options) {
1404
1549
  if (type.node && type.node.parent && type.node.parent.kind === SyntaxKind.ModelStatement) {
1405
1550
  schema["x-ms-enum"] = {
1406
1551
  name: type.node.parent.id.sv,
1407
- modelAsString: true,
1552
+ modelAsString: false,
1408
1553
  };
1409
1554
  }
1410
1555
  else if (type.kind === "String") {
@@ -1415,7 +1560,7 @@ export async function getOpenAPIForService(context, options) {
1415
1560
  else if (type.kind === "Enum") {
1416
1561
  schema["x-ms-enum"] = {
1417
1562
  name: type.name,
1418
- modelAsString: isFixed(program, type) ? false : true,
1563
+ modelAsString: false,
1419
1564
  };
1420
1565
  const values = [];
1421
1566
  let foundCustom = false;
@@ -1437,12 +1582,14 @@ export async function getOpenAPIForService(context, options) {
1437
1582
  return schema;
1438
1583
  }
1439
1584
  function getSchemaForStringTemplate(stringTemplate) {
1440
- const [value, diagnostics] = stringTemplateToString(stringTemplate);
1441
- if (diagnostics.length > 0) {
1442
- program.reportDiagnostics(diagnostics.map((x) => ({ ...x, severity: "warning" })));
1585
+ if (stringTemplate.stringValue === undefined) {
1586
+ program.reportDiagnostics(explainStringTemplateNotSerializable(stringTemplate).map((x) => ({
1587
+ ...x,
1588
+ severity: "warning",
1589
+ })));
1443
1590
  return { type: "string" };
1444
1591
  }
1445
- return { type: "string", enum: [value] };
1592
+ return { type: "string", enum: [stringTemplate.stringValue] };
1446
1593
  }
1447
1594
  function getSchemaForLiterals(typespecType) {
1448
1595
  switch (typespecType.kind) {
@@ -1654,7 +1801,7 @@ class ErrorTypeFoundError extends Error {
1654
1801
  export function sortOpenAPIDocument(doc) {
1655
1802
  // Doing this to make sure the classes with toJSON are resolved.
1656
1803
  const unsorted = JSON.parse(JSON.stringify(doc));
1657
- const sorted = sortWithJsonSchema(unsorted, AutorestOpenAPISchema, "#/$defs/AutorestOpenAPISchema");
1804
+ const sorted = sortWithJsonSchema(unsorted, AutorestOpenAPISchema);
1658
1805
  return sorted;
1659
1806
  }
1660
1807
  async function loadExamples(host, options, version) {