Skip to content

Commit

Permalink
Converter issue fix (#4851)
Browse files Browse the repository at this point in the history
1. fix discriminator finding logic: previous exit logic for recursion is
wrong
2. refine TSP build-in model filtering logic: add all special models and
also add build-in namespace to models.tsp to resolve some compiling
error
3. fix number enum naming issue
4. change to always use custom patch
5. replace response model for normal operation to fix missing model ref
compiling error
6. fix enum default value compiling error
7. ~remove `@path` for resource key~
8. resolve operation id collision problem
9. fix multi-layer singleton resource with duplicate key issue
10. fix singleton resource base parameter calculation issue
11. refine operaion id logic and add example conversion
12. add basic global check name availability operation
13. fix enum doc missing issue
14. add lro header support
  • Loading branch information
tadelesh authored Jan 11, 2024
1 parent faf5c11 commit a4ebf16
Show file tree
Hide file tree
Showing 1,736 changed files with 132,222 additions and 847 deletions.
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -338,5 +338,6 @@ packages/extensions/core/test/plugins/*/expected/*.json
# MICROSOFT SECURITY.md
/SECURITY.md

# Generated typespec
# Generated typespec and examples
packages/extensions/openapi-to-typespec/test/**/*.tsp
packages/extensions/openapi-to-typespec/test/**/*.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@autorest/openapi-to-typespec",
"comment": "Fix validation issues for converter",
"type": "patch"
}
],
"packageName": "@autorest/openapi-to-typespec"
}
38 changes: 37 additions & 1 deletion common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions packages/extensions/openapi-to-typespec/convert.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ param(
$csharpCodegen = "https://aka.ms/azsdk/openapi-to-typespec-csharp",
[string]
# Specified the converter codegen, default to https://aka.ms/azsdk/openapi-to-typespec.
$converterCodegen = ""
$converterCodegen = "."
)

function GenerateMetadata ()
{
Write-Host "##Generating metadata with csharp codegen in $outputFolder with $csharpCodegen"
$cmd = "autorest --csharp --max-memory-size=8192 --use=`"$csharpCodegen`" --output-folder=$outputFolder --mgmt-debug.only-generate-metadata --azure-arm --skip-csproj $swaggerConfigFile"
$cmd = "autorest --csharp --isAzureSpec --isArm --max-memory-size=8192 --use=`"$csharpCodegen`" --output-folder=$outputFolder --mgmt-debug.only-generate-metadata --azure-arm --skip-csproj $swaggerConfigFile"
Write-Host "$cmd"
Invoke-Expression $cmd
if ($LASTEXITCODE) { exit $LASTEXITCODE }
Expand All @@ -42,7 +42,7 @@ function GenerateMetadata ()
function DoConvert ()
{
Write-Host "##Converting from swagger to tsp with in $outputFolder with $converterCodegen"
$cmd = "autorest --openapi-to-typespec --isArm --use=`"$converterCodegen`" --output-folder=$outputFolder --src-path=tsp-output $swaggerConfigFile"
$cmd = "autorest --openapi-to-typespec --isAzureSpec --isArm --use=`"$converterCodegen`" --output-folder=$outputFolder --src-path=tsp-output $swaggerConfigFile"
Write-Host "$cmd"
Invoke-Expression $cmd
if ($LASTEXITCODE) { exit $LASTEXITCODE }
Expand Down
3 changes: 2 additions & 1 deletion packages/extensions/openapi-to-typespec/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
"@typespec/openapi3": "^0.50.0",
"prettier": "~3.1.0",
"lodash": "~4.17.20",
"pluralize": "^8.0.0"
"pluralize": "^8.0.0",
"change-case-all": "~2.1.0"
},
"devDependencies": {
"autorest": "~3.7.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { join } from "path";
import { getSession } from "../autorest-session";
import { generateArmResource } from "../generate/generate-arm-resource";
import { generateArmResource, generateArmResourceExamples } from "../generate/generate-arm-resource";
import { TypespecProgram, TspArmResource } from "../interfaces";
import { formatTypespecFile } from "../utils/format";
import { getNamespace } from "../utils/namespace";

export async function emitArmResources(program: TypespecProgram, basePath: string) {
// Create a file per resource
const session = getSession();
const { serviceInformation } = program;
for (const armResource of program.models.armResources) {
const { modules, namespaces } = getResourcesImports(program, armResource);
const filePath = join(basePath, `${armResource.name}.tsp`);
Expand All @@ -21,6 +22,18 @@ export async function emitArmResources(program: TypespecProgram, basePath: strin
generatedResource,
].join("\n");
session.writeFile({ filename: filePath, content: await formatTypespecFile(content, filePath) });
// generate examples for each operation
const examples = generateArmResourceExamples(armResource);
for (const [filename, content] of Object.entries(examples)) {
if (serviceInformation.versions) {
session.writeFile({
filename: join(basePath, "examples", serviceInformation.versions[0], `${filename}.json`),
content,
});
} else {
session.writeFile({ filename: join(basePath, "examples", "unknown", `${filename}.json`), content });
}
}
}
}

Expand All @@ -29,6 +42,7 @@ export function getResourcesImports(_program: TypespecProgram, armResource: TspA
modules: [
`import "@azure-tools/typespec-azure-core";`,
`import "@azure-tools/typespec-azure-resource-manager";`,
`import "@typespec/openapi";`,
`import "@typespec/rest";`,
`import "./models.tsp";`,
],
Expand All @@ -37,6 +51,7 @@ export function getResourcesImports(_program: TypespecProgram, armResource: TspA
`using Azure.ResourceManager;`,
`using Azure.ResourceManager.Foundations;`,
`using TypeSpec.Http;`,
`using TypeSpec.OpenAPI;`,
],
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Dictionary } from "@azure-tools/openapi/v3";
import { TypespecOperation, TspArmResource, TspArmResourceOperation } from "interfaces";
import { Case } from "change-case-all";
import { TypespecOperation, TspArmResource } from "interfaces";
import _ from "lodash";
import pluralize from "pluralize";
import { replaceGeneratedResourceObject } from "../transforms/transform-arm-resources";
import { generateDecorators } from "../utils/decorators";
import { generateDocs } from "../utils/docs";
Expand Down Expand Up @@ -41,48 +43,100 @@ export function generateArmResource(resource: TspArmResource): string {
}

function generateArmResourceOperation(resource: TspArmResource): string {
const groupedOperations: Dictionary<(TspArmResourceOperation | TypespecOperation)[]> = {};
const formalOperationGroupName = pluralize(resource.name);
const definitions: string[] = [];

definitions.push("@armResourceOperations");
if (resource.name === formalOperationGroupName) {
definitions.push(`@projectedName("client", "${formalOperationGroupName}")`);
definitions.push(`interface ${formalOperationGroupName}OperationGroup {`);
} else {
definitions.push(`interface ${formalOperationGroupName} {`);
}

for (const operation of resource.resourceOperations) {
if (!groupedOperations[operation.operationGroupName]) {
groupedOperations[operation.operationGroupName] = [];
for (const fixme of operation.fixMe ?? []) {
definitions.push(fixme);
}
definitions.push(generateDocs(operation));
const decorators = generateDecorators(operation.decorators);
decorators && definitions.push(decorators);
if (
operation.operationId &&
operation.operationId !== getGeneratedOperationId(formalOperationGroupName, operation.name)
) {
definitions.push(`@operationId("${operation.operationId}")`);
definitions.push(`#suppress "@azure-tools/typespec-azure-core/no-operation-id" "For backward compatibility"`);
}
if (operation.kind === "ArmResourceExists") {
definitions.push(`op ${operation.name}(${operation.parameters.join(",")}): ${operation.responses.join("|")}`);
} else if (operation.templateParameters?.length) {
definitions.push(
`${operation.name} is ${operation.kind}<${(operation.templateParameters ?? [])
.map(replaceGeneratedResourceObject)
.join(",")}>`,
);
} else {
definitions.push(`${operation.name} is ${operation.kind}`);
}
groupedOperations[operation.operationGroupName].push(operation);
definitions.push("");
}
for (const operation of resource.normalOperations) {
if (!groupedOperations[operation.operationGroupName!]) {
groupedOperations[operation.operationGroupName!] = [];
if (
operation.operationId &&
operation.operationId !== getGeneratedOperationId(formalOperationGroupName, operation.name)
) {
definitions.push(`@operationId("${operation.operationId}")`);
definitions.push(`#suppress "@azure-tools/typespec-azure-core/no-operation-id" "For backward compatibility"`);
}
groupedOperations[operation.operationGroupName!].push(operation);
definitions.push(generateOperation(operation as TypespecOperation));
definitions.push("");
}
const definitions: string[] = [];

for (const [operationGroupName, operations] of Object.entries(groupedOperations)) {
definitions.push("@armResourceOperations");
definitions.push(`interface ${operationGroupName} {`);
for (let operation of operations) {
if ((operation as TspArmResourceOperation).kind) {
operation = operation as TspArmResourceOperation;
for (const fixme of operation.fixMe ?? []) {
definitions.push(fixme);
}
definitions.push(generateDocs(operation));
const decorators = generateDecorators(operation.decorators);
decorators && definitions.push(decorators);
if (operation.kind === "ArmResourceExists") {
definitions.push(`op ${operation.name}(${operation.parameters.join(",")}): ${operation.responses.join("|")}`);
} else {
definitions.push(
`${operation.name} is ${operation.kind}<${(operation.templateParameters ?? [])
.map(replaceGeneratedResourceObject)
.join(",")}>`,
);
}
} else {
definitions.push(generateOperation(operation as TypespecOperation));
}
definitions.push("}\n");

return definitions.join("\n");
}

export function generateArmResourceExamples(resource: TspArmResource): Record<string, string> {
const formalOperationGroupName = pluralize(resource.name);
const examples: Record<string, string> = {};
for (const operation of resource.resourceOperations) {
generateExamples(
operation.examples ?? {},
operation.operationId ?? getGeneratedOperationId(formalOperationGroupName, operation.name),
examples,
);
}
for (const operation of resource.normalOperations) {
generateExamples(
operation.examples ?? {},
operation.operationId ?? getGeneratedOperationId(formalOperationGroupName, operation.name),
examples,
);
}
return examples;
}

function generateExamples(
examples: Record<string, Record<string, unknown>>,
operationId: string,
generatedExamples: Record<string, string>,
) {
const count = _.keys(examples).length;
for (const [title, example] of _.entries(examples)) {
if (!example.operationId) {
example.operationId = operationId;
example.title = title;
}
let filename = operationId;
if (count > 1) {
filename = `${filename}_${Case.pascal(title)}`;
}
definitions.push("}\n");
generatedExamples[filename] = JSON.stringify(example, null, 2);
}
}

return definitions.join("\n");
function getGeneratedOperationId(operationGroupName: string, operationName: string): string {
return `${Case.pascal(operationGroupName)}_${Case.pascal(operationName)}`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ export function generateEnums(typespecEnum: TypespecEnum) {
enum ${typespecEnum.name} {
${typespecEnum.members
.map((m) => {
const kv = `"${m.name}"` !== m.value ? `${m.name}: ${m.value}` : m.value;
const kv = `"${m.name}"` !== m.value ? `"${m.name}": ${m.value}` : m.value;
return `${generateDocs(m)}${kv}`;
})
.join(", ")}
}\n`;
}\n\n`;

definitions.push(enumDefinition);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { TypespecOperation, TypespecOperationGroup, TypespecParameter } from "../interfaces";
import { replaceGeneratedResourceObject } from "../transforms/transform-arm-resources";
import { generateDocs, generateSummary } from "../utils/docs";
import { generateParameter } from "./generate-parameter";

export function generateOperation(operation: TypespecOperation, operationGroup?: TypespecOperationGroup) {
const doc = generateDocs(operation);
const summary = generateSummary(operation);
const { verb, name, route, responses, parameters } = operation;
const { verb, name, route, parameters } = operation;
const responses = operation.responses.map(replaceGeneratedResourceObject);
const params = generateParameters(parameters);
const statements: string[] = [];
summary && statements.push(summary);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TypespecParameter } from "../interfaces";
import { generateDecorators } from "../utils/decorators";
import { generateDocs } from "../utils/docs";
import { transformValue } from "../utils/values";
import { transformDefaultValue } from "../utils/values";

export function generateParameter(parameter: TypespecParameter): string {
const definitions: string[] = [];
Expand All @@ -12,7 +12,7 @@ export function generateParameter(parameter: TypespecParameter): string {
decorators && definitions.push(decorators);
let defaultValue = "";
if (parameter.defaultValue) {
defaultValue = ` = ${transformValue(parameter.defaultValue)}`;
defaultValue = ` = ${transformDefaultValue(parameter.type, parameter.defaultValue)}`;
}
definitions.push(`"${parameter.name}"${parameter.isOptional ? "?" : ""}: ${parameter.type}${defaultValue}`);

Expand Down
Loading

0 comments on commit a4ebf16

Please sign in to comment.