Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@azure-tools/typespec-client-generator-core"
---

Generate names for anonymous models in LroMetadata.
22 changes: 22 additions & 0 deletions packages/typespec-client-generator-core/src/public-utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getLroMetadata } from "@azure-tools/typespec-azure-core";
import {
Diagnostic,
Enum,
Expand Down Expand Up @@ -503,6 +504,27 @@ function getContextPath(
}
}

const lroMetadata = getLroMetadata(context.program, root);
if (lroMetadata) {
const anonymousCandidates = [
{ lroResultType: lroMetadata.finalResult, label: "FinalResult" },
{ lroResultType: lroMetadata.logicalResult, label: "LogicalResult" },
{ lroResultType: lroMetadata.envelopeResult, label: "EnvelopeResult" },
{ lroResultType: lroMetadata.finalEnvelopeResult, label: "FinalEnvelopeResult" },
];

for (const { lroResultType, label } of anonymousCandidates) {
if (!lroResultType || lroResultType === "void") {
continue;
}
visited.clear();
result = [{ name: root.name, type: root }];
if (dfsModelProperties(typeToFind, lroResultType, label)) {
return result;
}
}
}

const overriddenClientMethod = getOverriddenClientMethod(context, root);
visited.clear();
result = [{ name: root.name, type: root }];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,81 @@ describe("data plane LRO templates", () => {
);
strictEqual(lroMetadata.pollingStep.responseBody, analyzeOperationModel);
});

// https://github.com/Azure/typespec-azure/issues/2325
it("LroMetadata synthetic anonymous ResourceOperationStatus", async () => {
await runner.compileWithVersionedService(`
alias ServiceTraits = NoRepeatableRequests &
NoConditionalRequests &
NoClientRequestId;

alias apiOperations = Azure.Core.ResourceOperations<ServiceTraits>;

@doc("Create an instruction.")
@pollingOperation(
OperationProgress.getOperationResult
)
op update is apiOperations.LongRunningResourceCreateOrUpdate<Instruction>;

@resource("instruction")
model Instruction {
@key
@visibility(Lifecycle.Read)
id: string;

@visibility(Lifecycle.Update)
instructionId: string;
}

@doc("Operation Response Model")
@resource("operation")
model OperationResultQuery {
@doc("The operation status.")
@visibility(Lifecycle.Read)
status: Foundations.OperationState;

@doc("The operation id.")
@key("operationId")
@visibility(Lifecycle.Read)
operationId: string;

@doc("The error message.")
@visibility(Lifecycle.Read)
errorMessage: string[];
}

model UpdateFinalResult {
id2: string;
}

@doc("Get operation progress")
interface OperationProgress {
@doc("Get operation progress")
getOperationResult is apiOperations.ResourceRead<OperationResultQuery>;
}
`);
const method = runner.context.sdkPackage.clients[0].methods.find((m) => m.name === "update");
assert.exists(method);
strictEqual(method.kind, "lro");
const response = method.response.type;
assert.strictEqual(response?.kind, "model");
if (!response || response.kind !== "model") {
assert.fail("Expected final response to be a model.");
}
assert.isTrue(response.isGeneratedName);
const generatedName = response.name;
// duplicate with existing model named "UpdateFinalResult" so the generated name will be "UpdateFinalResult1"
assert.strictEqual("UpdateFinalResult1", generatedName);
const crossLanguageId = response.crossLanguageDefinitionId;
assert.isFalse(crossLanguageId.includes(".."));
const lroMetadata = method.lroMetadata;
assert.exists(lroMetadata);
const finalResponse = lroMetadata.finalResponse;
assert.exists(finalResponse);
const finalResult = finalResponse.result;
assert.exists(finalResult);
assert.strictEqual(finalResult.name, "UpdateFinalResult1");
});
});

describe("Arm LRO templates", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { AzureCoreTestLibrary } from "@azure-tools/typespec-azure-core/testing";
import { AzureResourceManagerTestLibrary } from "@azure-tools/typespec-azure-resource-manager/testing";
import {
BasicTestRunner,
createLinterRuleTester,
LinterRuleTester,
} from "@typespec/compiler/testing";
import { OpenAPITestLibrary } from "@typespec/openapi/testing";
import { beforeEach, describe, it } from "vitest";
import { noUnnamedTypesRule } from "../../src/rules/no-unnamed-types.rule.js";
import { createSdkTestRunner } from "../test-host.js";
Expand All @@ -11,7 +14,10 @@ let runner: BasicTestRunner;
let tester: LinterRuleTester;

beforeEach(async () => {
runner = await createSdkTestRunner();
runner = await createSdkTestRunner({
librariesToAdd: [AzureCoreTestLibrary, OpenAPITestLibrary],
autoImports: ["@azure-tools/typespec-azure-core"],
});
tester = createLinterRuleTester(
runner,
noUnnamedTypesRule,
Expand Down Expand Up @@ -233,6 +239,49 @@ describe("models", () => {
)
.toBeValid();
});

it("anonymous model caused by lro metadata", async () => {
const armRunner = await createSdkTestRunner({
librariesToAdd: [AzureResourceManagerTestLibrary, AzureCoreTestLibrary, OpenAPITestLibrary],
autoImports: ["@azure-tools/typespec-azure-resource-manager"],
autoUsings: ["Azure.ResourceManager", "Azure.Core", "Azure.Core.Traits"],
});
const armTester = createLinterRuleTester(
armRunner,
noUnnamedTypesRule,
"@azure-tools/typespec-client-generator-core",
);
await armTester
.expect(
`
@armProviderNamespace
@service
@versioned(Versions)
namespace TestClient;
enum Versions {
@armCommonTypesVersion(Azure.ResourceManager.CommonTypes.Versions.v5)
v1: "v1",
}
model Employee is TrackedResource<EmployeeProperties> {
...ResourceNameParameter<Employee>;
}
model MoveRequest {
targetResourceGroup?: string;
}
model EmployeeProperties {
age?: int32;
}
op move is ArmResourceActionAsync<Employee, MoveRequest, {@body body: {id?: string}}>;
`,
)
.toEmitDiagnostics([
{
code: "@azure-tools/typespec-client-generator-core/no-unnamed-types",
severity: "warning",
message: `Anonymous model with generated name "MoveFinalResult" detected. Define this model separately with a proper name to improve code readability and reusability.`,
},
]);
});
});

describe("unions", () => {
Expand Down
Loading