Skip to content

Commit

Permalink
Get POST tests to pass.
Browse files Browse the repository at this point in the history
  • Loading branch information
tjprescott committed Mar 11, 2024
1 parent b7397ba commit 7804a05
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Program, createRule } from "@typespec/compiler";

import { getLroMetadata } from "@azure-tools/typespec-azure-core";
import { HttpOperationBody, HttpOperationResponse } from "@typespec/http";
import { ArmResourceOperation } from "../operations.js";
import { getArmResources } from "../resource.js";

Expand All @@ -17,6 +18,19 @@ export const armPostResponseCodesRule = createRule({
async: `Long-running post operations must have 202 and default responses. They must also have a 200 response if the final response has a schema. They must not have any other responses.`,
},
create(context) {
function getResponseBody(
response: HttpOperationResponse | undefined
): HttpOperationBody | undefined {
if (response === undefined) return undefined;
if (response.responses.length > 1) {
throw new Error("Multiple responses are not supported.");
}
if (response.responses[0].body !== undefined) {
return response.responses[0].body;
}
return undefined;
}

function validateAsyncPost(op: ArmResourceOperation) {
const statusCodes = new Set([
...op.httpOperation.responses.map((r) => r.statusCodes.toString()),
Expand All @@ -32,7 +46,17 @@ export const armPostResponseCodesRule = createRule({
}
// validate that 202 does not have a schema
const response202 = op.httpOperation.responses.find((r) => r.statusCodes === 202);
if (response202 && response202.type) {
const body202 = getResponseBody(response202);
if (body202 !== undefined) {
context.reportDiagnostic({
target: op.operation.returnType,
messageId: "async",
});
}
// validate that a 200 response does have a schema
const response200 = op.httpOperation.responses.find((r) => r.statusCodes === 200);
const body200 = getResponseBody(response200);
if (response200 && body200 === undefined) {
context.reportDiagnostic({
target: op.operation.returnType,
messageId: "async",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,15 @@ it("Does not emit a warning for a synchronous post operation that contains 204 a
.toBeValid();
});

it("Emits a warning for a long-running post operation that does not contain the appropriate response codes", async () => {
it("Does not emit a warning for a long-running post operation that satisfies the requirements.", async () => {
await tester
.expect(
`
using Azure.Core;
@armProviderNamespace
@useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1)
@useDependency(Azure.Core.Versions.v1_0_Preview_1)
namespace Microsoft.Contoso;
model PollingStatus {
Expand All @@ -133,23 +136,162 @@ it("Emits a warning for a long-running post operation that does not contain the
@path
name: string;
}
@armResourceOperations
interface Widgets {
@get getWidget is Azure.Core.StandardResourceOperations.ResourceRead<Widget>;
@get getStatus(...KeysOf<Widget>, @path @segment("statuses") statusId: string): PollingStatus | ErrorResponse;
@pollingOperation(Widgets.getStatus, {widgetName: RequestParameter<"name">, statusId: ResponseProperty<"operationId">})
@finalOperation(Widgets.getWidget, {widgetName: RequestParameter<"name">})
@armResourceAction(Widget)
@post create(...KeysOf<Widget>, @body body: Widget): {
@statusCode code: "202";
@header("x-ms-operationid") operationId: string;
} | {
@statusCode code: "200";
@header("x-ms-operationid") operationId: string;
@body result: Widget;
} | ErrorResponse;
}`
)
.toBeValid();
});

it("Emits a warning for a long-running post operation that has a 202 response with a schema.", async () => {
await tester
.expect(
`
using Azure.Core;
@armProviderNamespace
@useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1)
@useDependency(Azure.Core.Versions.v1_0_Preview_1)
namespace Microsoft.Contoso;
model PollingStatus {
@Azure.Core.lroStatus
statusValue: "Succeeded" | "Canceled" | "Failed" | "Running";
}
model Widget is TrackedResource<{}> {
@doc("Widget name")
@key("widgetName")
@segment("widgets")
@path
name: string;
}
@armResourceOperations
interface Widgets {
@route("/simpleWidgets/{id}/operations/{operationId}")
@get op getStatus(@path id: string, @path operationId: string): PollingStatus;
@get getWidget is Azure.Core.StandardResourceOperations.ResourceRead<Widget>;
@get getStatus(...KeysOf<Widget>, @path @segment("statuses") statusId: string): PollingStatus | ErrorResponse;
@pollingOperation(Widgets.getStatus, {widgetName: RequestParameter<"name">, statusId: ResponseProperty<"operationId">})
@finalOperation(Widgets.getWidget, {widgetName: RequestParameter<"name">})
@armResourceAction(Widget)
@post create(...KeysOf<Widget>, @body body: Widget): {
@statusCode code: "202";
@header("x-ms-operationid") operationId: string;
@body body: Widget;
} | ErrorResponse;
}`
)
.toEmitDiagnostics({
code: "@azure-tools/typespec-azure-resource-manager/arm-post-operation-response-codes",
message:
"Long-running post operations must have 202 and default responses. They must also have a 200 response if the final response has a schema. They must not have any other responses.",
});
});

@post
@pollingOperation(Widgets.getStatus)
it("Emits a warning for a long-running post operation that has a 200 response with no schema.", async () => {
await tester
.expect(
`
using Azure.Core;
@armProviderNamespace
@useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1)
@useDependency(Azure.Core.Versions.v1_0_Preview_1)
namespace Microsoft.Contoso;
model PollingStatus {
@Azure.Core.lroStatus
statusValue: "Succeeded" | "Canceled" | "Failed" | "Running";
}
model Widget is TrackedResource<{}> {
@doc("Widget name")
@key("widgetName")
@segment("widgets")
@path
name: string;
}
@armResourceOperations
interface Widgets {
@get getWidget is Azure.Core.StandardResourceOperations.ResourceRead<Widget>;
@get getStatus(...KeysOf<Widget>, @path @segment("statuses") statusId: string): PollingStatus | ErrorResponse;
@pollingOperation(Widgets.getStatus, {widgetName: RequestParameter<"name">, statusId: ResponseProperty<"operationId">})
@finalOperation(Widgets.getWidget, {widgetName: RequestParameter<"name">})
@armResourceAction(Widget)
@post create(...KeysOf<Widget>, @body body: Widget): {
@statusCode code: "202";
@header("x-ms-operationid") operationId: string;
} | {
@statusCode code: "200";
@header("x-ms-operationid") operationId: string;
} | ErrorResponse;
}`
)
.toEmitDiagnostics({
code: "@azure-tools/typespec-azure-resource-manager/arm-post-operation-response-codes",
message:
"Long-running post operations must have 202 and default responses. They must also have a 200 response if the final response has a schema. They must not have any other responses.",
});
});

it("Emits a warning for a long-running post operation that has invalid response codes.", async () => {
await tester
.expect(
`
using Azure.Core;
@armProviderNamespace
@useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1)
@useDependency(Azure.Core.Versions.v1_0_Preview_1)
namespace Microsoft.Contoso;
model PollingStatus {
@Azure.Core.lroStatus
statusValue: "Succeeded" | "Canceled" | "Failed" | "Running";
}
model Widget is TrackedResource<{}> {
@doc("Widget name")
@key("widgetName")
@segment("widgets")
@path
name: string;
}
@armResourceOperations
interface Widgets {
@get getWidget is Azure.Core.StandardResourceOperations.ResourceRead<Widget>;
@get getStatus(...KeysOf<Widget>, @path @segment("statuses") statusId: string): PollingStatus | ErrorResponse;
@pollingOperation(Widgets.getStatus, {widgetName: RequestParameter<"name">, statusId: ResponseProperty<"operationId">})
@finalOperation(Widgets.getWidget, {widgetName: RequestParameter<"name">})
@armResourceAction(Widget)
longRunning(...ApiVersionParameter): Widget | {
@statusCode statusCode: 201;
@header id: string,
@header("operation-id") operate: string,
@finalLocation @header("Location") location: ResourceLocation<Widget>,
@pollingLocation @header("Operation-Location") opLink: string,
@lroResult @body body?: Widget
}
@post create(...KeysOf<Widget>, @body body: Widget): {
@statusCode code: "203";
@header("x-ms-operationid") operationId: string
} | ErrorResponse;
}`
)
.toEmitDiagnostics({
Expand Down

0 comments on commit 7804a05

Please sign in to comment.