diff --git a/packages/typespec-lintdiff/src/rules/lro-error-content.ts b/packages/typespec-lintdiff/src/rules/lro-error-content.ts index d13f8113a5..62035a27e7 100644 --- a/packages/typespec-lintdiff/src/rules/lro-error-content.ts +++ b/packages/typespec-lintdiff/src/rules/lro-error-content.ts @@ -6,51 +6,37 @@ import { isArmCommonType, } from "@azure-tools/typespec-azure-resource-manager"; import { - createTCGCContext, - getMarkAsLro, - isInScope, -} from "@azure-tools/typespec-client-generator-core"; -import { - compilerAssert, createRule, - getService, isNullType, listServices, type Operation, type Service, type Type, } from "@typespec/compiler"; -import { unsafe_mutateSubgraphWithNamespace } from "@typespec/compiler/experimental"; import { createMetadataInfo, getHttpService, Visibility, type HttpStatusCodeRange, } from "@typespec/http"; -import { getExtensions, shouldInline } from "@typespec/openapi"; -import { getVersioningMutators } from "@typespec/versioning"; const standardErrorReference = /.*\/common-types\/resource-management\/v(([1-9]\d+)|[2-9])\/types.json#\/definitions\/ErrorResponse/; export const lroErrorContentRule = createRule({ name: "lro-error-content", - description: - "LRO error response references must use the ARM common-types v2 or later ErrorResponse.", + description: "Native ARM LRO error payloads must use the common-types v2 or later ErrorResponse.", severity: "warning", messages: { default: - "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference.", + "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload.", }, create(context) { const program = context.program; const metadata = createMetadataInfo(program, { canonicalVisibility: Visibility.Read }); - const tcgc = createTCGCContext(program, "@azure-tools/typespec-autorest", { - mutateNamespace: false, - }); - const reported = new Set(); + const reported = new Set(); - function hasInvalidReference(type: Type, service: Service, version?: string): boolean { + function isStandardError(type: Type, service: Service): boolean { let reference = getExternalTypeRef(program, type); if ( !reference && @@ -60,110 +46,58 @@ export const lroErrorContentRule = createRule({ type.kind === "Enum" || type.kind === "Union") ) { - reference = getArmCommonTypeOpenAPIRef(program, type, { service, version }); + reference = getArmCommonTypeOpenAPIRef(program, type, { service }); } if (reference) { - // ARM's default directory is interpolated by AutoRest before emitting the reference. - return !standardErrorReference.test( + return standardErrorReference.test( reference.replaceAll("{arm-types-dir}", "/common-types/resource-management"), ); } - if ( - (type.kind === "Scalar" && program.checker.isStdType(type)) || - type.kind === "String" || - type.kind === "StringTemplate" || - type.kind === "Number" || - type.kind === "Boolean" || - (type.kind === "Intrinsic" && type.name === "unknown") - ) { - return false; - } - type = metadata.getEffectivePayloadType(type, Visibility.Read); - if (!shouldInline(program, type)) { - return true; - } - // Inline nullable/single-member unions can still emit their member's top-level $ref. + // A nullable standard error still describes the standard error payload. if (type.kind === "Union") { const members = [...type.variants.values()] .map((variant) => variant.type) .filter((member) => !isNullType(member)); - return members.length === 1 && hasInvalidReference(members[0], service, version); + return members.length === 1 && isStandardError(members[0], service); } return false; } - function checkService(service: Service, version?: string) { - const [httpService] = getHttpService(program, service.type); - for (const httpOperation of httpService.operations) { - const operation = httpOperation.operation; - if (!isInScope(tcgc, operation)) { - continue; - } - const extensions = getExtensions(program, operation); - const isLro = extensions.has("x-ms-long-running-operation") - ? extensions.get("x-ms-long-running-operation") === true - : (httpOperation.verb !== "get" && getLroMetadata(program, operation) !== undefined) || - getMarkAsLro(tcgc, operation); - if (!isLro || reported.has(operation.node)) { - continue; - } - for (const response of httpOperation.responses) { - if (!isErrorResponse(response.statusCodes)) { - continue; - } - // AutoRest selects the last body; different bodies for one status are an emitter error. - const bodies = response.responses.flatMap((r) => (r.body ? [r.body] : [])); - const body = bodies.at(-1); - if ( - !body || - body.bodyKind !== "single" || - (body.type.kind === "Scalar" && - body.type.name === "bytes" && - bodies - .flatMap((b) => b.contentTypes) - .every( - (contentType) => - contentType !== "application/json" && contentType !== "text/plain", - )) - ) { - continue; - } - if (hasInvalidReference(body.type, service, version)) { - context.reportDiagnostic({ target: operation }); - reported.add(operation.node); - break; - } - } - } - } - return { root() { for (const service of listServices(program)) { - // Lintdiff runs mixed ARM/data-plane rules; descendants are included by getHttpService. + // Lintdiff runs mixed ARM/data-plane rules; remove this isolation on ARM promotion. if (!getArmProviderNamespace(program, service.type)) { continue; } - const versioning = getVersioningMutators(program, service.type); - if (versioning === undefined) { - checkService(service); - continue; - } - const snapshots = - versioning.kind === "versioned" - ? versioning.snapshots - : [{ mutator: versioning.mutator, version: undefined }]; - for (const snapshot of snapshots) { - const projected = unsafe_mutateSubgraphWithNamespace( - program, - [snapshot.mutator], - service.type, - ); - compilerAssert(projected.type.kind === "Namespace", "Expected a service namespace."); - checkService( - getService(program, projected.type) ?? { type: projected.type }, - snapshot.version?.value, + const [httpService] = getHttpService(program, service.type); + for (const httpOperation of httpService.operations) { + const operation = httpOperation.operation; + const source = operation.node ?? operation; + if ( + reported.has(source) || + httpOperation.verb === "get" || + getLroMetadata(program, operation) === undefined + ) { + continue; + } + const invalidBody = httpOperation.responses.some( + (response) => + isErrorResponse(response.statusCodes) && + response.responses.some( + ({ body }) => + body !== undefined && + (body.bodyKind !== "single" || + !isStandardError( + metadata.getEffectivePayloadType(body.type, Visibility.Read), + service, + )), + ), ); + if (invalidBody) { + context.reportDiagnostic({ target: operation }); + reported.add(source); + } } } }, diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/external-references/main.tsp b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/external-references/main.tsp index 65021ba085..9a6bdeb385 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/external-references/main.tsp +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/external-references/main.tsp @@ -39,9 +39,15 @@ model Failure { @statusCode statusCode: 400; @body body: T; } -@extension("x-ms-long-running-operation", true) +@route("/status") +op poll is Azure.Core.Foundations.GetOperationStatus; +model Accepted { + @statusCode statusCode: 202; + @header("Operation-Location") operationLocation: string; +} +@Azure.Core.pollingOperation(poll) @post -op Lro(@path name: string): AcceptedResponse | Failure; +op Lro(@path name: string): Accepted | Failure; @route("/old/{name}") op old is Lro; @route("/standard/{name}") diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/external-references/output.json b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/external-references/output.json index dd2218ee2c..7be2424cd0 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/external-references/output.json +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/external-references/output.json @@ -52,7 +52,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -61,6 +66,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -77,7 +85,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -86,6 +99,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -102,7 +118,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -111,6 +132,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -127,7 +151,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -136,6 +165,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -152,7 +184,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -161,9 +198,69 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, + "/status/{operationId}": { + "get": { + "operationId": "Poll", + "description": "Operation that returns the status of another operation.", + "parameters": [ + { + "$ref": "#/parameters/Azure.Core.Foundations.ApiVersionParameter" + }, + { + "name": "operationId", + "in": "path", + "description": "The unique ID of the operation.", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "The request has succeeded.", + "schema": { + "type": "object", + "description": "Provides status details for long running operations.", + "properties": { + "id": { + "type": "string", + "description": "The unique ID of the operation." + }, + "status": { + "$ref": "#/definitions/Azure.Core.Foundations.OperationState", + "description": "The status of the operation" + }, + "error": { + "$ref": "#/definitions/Azure.Core.Foundations.Error", + "description": "Error object that describes the error when status is \"Failed\"." + } + }, + "required": [ + "id", + "status" + ] + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Azure.Core.Foundations.ErrorResponse" + }, + "headers": { + "x-ms-error-code": { + "type": "string", + "description": "String error code indicating what went wrong." + } + } + } + } + } + }, "/wrong/{name}": { "post": { "operationId": "Wrong", @@ -177,7 +274,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -186,11 +288,119 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } } }, "definitions": { + "Accepted": { + "type": "object" + }, + "Azure.Core.Foundations.Error": { + "type": "object", + "description": "The error object.", + "properties": { + "code": { + "type": "string", + "description": "One of a server-defined set of error codes." + }, + "message": { + "type": "string", + "description": "A human-readable representation of the error." + }, + "target": { + "type": "string", + "description": "The target of the error." + }, + "details": { + "type": "array", + "description": "An array of details about specific errors that led to this reported error.", + "items": { + "$ref": "#/definitions/Azure.Core.Foundations.Error" + } + }, + "innererror": { + "$ref": "#/definitions/Azure.Core.Foundations.InnerError", + "description": "An object containing more specific information than the current object about the error." + } + }, + "required": [ + "code", + "message" + ] + }, + "Azure.Core.Foundations.ErrorResponse": { + "type": "object", + "description": "A response containing error details.", + "properties": { + "error": { + "$ref": "#/definitions/Azure.Core.Foundations.Error", + "description": "The error object." + } + }, + "required": [ + "error" + ] + }, + "Azure.Core.Foundations.InnerError": { + "type": "object", + "description": "An object containing more specific information about the error. As per Azure REST API guidelines - https://aka.ms/AzureRestApiGuidelines#handling-errors.", + "properties": { + "code": { + "type": "string", + "description": "One of a server-defined set of error codes." + }, + "innererror": { + "$ref": "#/definitions/Azure.Core.Foundations.InnerError", + "description": "Inner error." + } + } + }, + "Azure.Core.Foundations.OperationState": { + "type": "string", + "description": "Enum describing allowed operation states.", + "enum": [ + "NotStarted", + "Running", + "Succeeded", + "Failed", + "Canceled" + ], + "x-ms-enum": { + "name": "OperationState", + "modelAsString": true, + "values": [ + { + "name": "NotStarted", + "value": "NotStarted", + "description": "The operation has not started." + }, + { + "name": "Running", + "value": "Running", + "description": "The operation is in progress." + }, + { + "name": "Succeeded", + "value": "Succeeded", + "description": "The operation has completed successfully." + }, + { + "name": "Failed", + "value": "Failed", + "description": "The operation has failed." + }, + { + "name": "Canceled", + "value": "Canceled", + "description": "The operation has been canceled by the user." + } + ] + } + }, "LocalError": { "type": "object", "properties": { @@ -200,5 +410,16 @@ } } }, - "parameters": {} + "parameters": { + "Azure.Core.Foundations.ApiVersionParameter": { + "name": "api-version", + "in": "query", + "description": "The API version to use for this operation.", + "required": true, + "type": "string", + "minLength": 1, + "x-ms-parameter-location": "method", + "x-ms-client-name": "apiVersion" + } + } } diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/external-references/tsp-diagnostics.json b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/external-references/tsp-diagnostics.json index c522ad7ad2..cec6bde44e 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/external-references/tsp-diagnostics.json +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/external-references/tsp-diagnostics.json @@ -12,22 +12,22 @@ { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint", @@ -125,9 +125,39 @@ "message": "Using @useRef should never be used in Azure specs." }, { - "code": "@azure-tools/typespec-azure-core/no-openapi", + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The Model named 'Accepted' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The ModelProperty named 'statusCode' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The ModelProperty named 'operationLocation' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "@azure-tools/typespec-azure-resource-manager/arm-resource-operation", + "severity": "warning", + "message": "All operations must be inside an interface declaration." + }, + { + "code": "@azure-tools/typespec-azure-resource-manager/arm-resource-operation", + "severity": "warning", + "message": "All Resource operations must use an api-version parameter. Please include Azure.ResourceManager.ApiVersionParameter in the operation parameter list using the spread (...ApiVersionParameter) operator, or using one of the common resource parameter models." + }, + { + "code": "tsp-lintdiff-local-linter/get-in-operation-name", + "severity": "warning", + "message": "'GET' operation 'Poll' should use method name 'Get' or method name starting with 'List'. Note: If you have already shipped an SDK on top of this spec, fixing this warning may introduce a breaking change." + }, + { + "code": "tsp-lintdiff-local-linter/xms-examples-required", "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." + "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, { "code": "@azure-tools/typespec-azure-core/documentation-required", @@ -169,11 +199,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -204,11 +229,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -249,11 +269,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -284,11 +299,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -329,11 +339,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -364,11 +369,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -409,11 +409,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -444,11 +439,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -489,11 +479,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -524,11 +509,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -574,11 +554,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/expect.json b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/expect.json index bd51430dbe..ba21e936b5 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/expect.json +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/expect.json @@ -1,30 +1,3 @@ { - "violation": false, - "ambientDiagnostics": [ - { "code": "@azure-tools/typespec-azure-core/documentation-required", "count": 83 }, - { "code": "@azure-tools/typespec-azure-core/no-closed-literal-union", "count": 1 }, - { "code": "@azure-tools/typespec-azure-core/no-enum", "count": 1 }, - { "code": "@azure-tools/typespec-azure-core/no-nullable", "count": 1 }, - { "code": "@azure-tools/typespec-azure-core/no-openapi", "count": 42 }, - { "code": "@azure-tools/typespec-azure-core/no-unknown", "count": 1 }, - { "code": "@azure-tools/typespec-azure-core/no-unnamed-types", "count": 1 }, - { "code": "@azure-tools/typespec-azure-core/require-versioned", "count": 1 }, - { "code": "@azure-tools/typespec-azure-resource-manager/arm-no-record", "count": 1 }, - { "code": "@azure-tools/typespec-azure-resource-manager/arm-resource-operation", "count": 53 }, - { "code": "@azure-tools/typespec-azure-resource-manager/lro-location-header", "count": 41 }, - { - "code": "@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint", - "count": 1 - }, - { "code": "@azure-tools/typespec-azure-resource-manager/no-response-body", "count": 1 }, - { "code": "tsp-lintdiff-local-linter/avoid-anonymous-types", "count": 2 }, - { "code": "tsp-lintdiff-local-linter/latest-version-of-common-types-must-be-used", "count": 1 }, - { "code": "tsp-lintdiff-local-linter/long-running-operations-options-validator", "count": 1 }, - { "code": "tsp-lintdiff-local-linter/lro-extension", "count": 1 }, - { "code": "tsp-lintdiff-local-linter/missing-xms-error-response", "count": 41 }, - { "code": "tsp-lintdiff-local-linter/no-error-code-responses", "count": 41 }, - { "code": "tsp-lintdiff-local-linter/non-application-json-type", "count": 17 }, - { "code": "tsp-lintdiff-local-linter/post-operation-id-contains-url-verb", "count": 3 }, - { "code": "tsp-lintdiff-local-linter/xms-examples-required", "count": 42 } - ] + "violation": true } diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/main.tsp b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/main.tsp index 7f8ee86df2..b72258f804 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/main.tsp +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/main.tsp @@ -16,9 +16,15 @@ model Failure { @statusCode statusCode: 500; @body body: T; } -@extension("x-ms-long-running-operation", true) +@route("/status") +op poll is Azure.Core.Foundations.GetOperationStatus; +model Accepted { + @statusCode statusCode: 202; + @header("Operation-Location") operationLocation: string; +} +@Azure.Core.pollingOperation(poll) @post -op Lro(@path name: string): AcceptedResponse | Failure; +op Lro(@path name: string): Accepted | Failure; @route("/standard/{name}") op standard is Lro; @@ -60,18 +66,18 @@ op unknownBody is Lro; @route("/bytes/{name}") op bytesBody is Lro; @route("/no-body") -@extension("x-ms-long-running-operation", true) +@Azure.Core.pollingOperation(poll) @post op noBody(): - | AcceptedResponse + | Accepted | { @statusCode statusCode: 400; }; @route("/binary") -@extension("x-ms-long-running-operation", true) +@Azure.Core.pollingOperation(poll) @post op binary(): - | AcceptedResponse + | Accepted | { @statusCode statusCode: 500; @header contentType: "application/octet-stream"; @@ -91,19 +97,19 @@ model GenericError { } @route("/file") -@extension("x-ms-long-running-operation", true) +@Azure.Core.pollingOperation(poll) @post op fileBody(): - | AcceptedResponse + | Accepted | { @statusCode statusCode: 500; @body body: File; }; @route("/multipart") -@extension("x-ms-long-running-operation", true) +@Azure.Core.pollingOperation(poll) @post op multipartBody(): - | AcceptedResponse + | Accepted | { @statusCode statusCode: 500; @multipartBody body: { @@ -112,11 +118,11 @@ op multipartBody(): }; @route("/success/{name}") -@extension("x-ms-long-running-operation", true) +@Azure.Core.pollingOperation(poll) @post op success(@path name: string): | { @statusCode statusCode: 200; @body body: GenericError; } - | AcceptedResponse; + | Accepted; diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/output.json b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/output.json index d140055d78..bc22f757f3 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/output.json +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/output.json @@ -52,7 +52,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -69,6 +74,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -85,7 +93,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -97,6 +110,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -109,7 +125,12 @@ "parameters": [], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -118,6 +139,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -137,7 +161,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -149,6 +178,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -168,7 +200,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -177,6 +214,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -193,7 +233,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -202,6 +247,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -218,7 +266,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -227,6 +280,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": false } }, @@ -243,7 +299,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -255,6 +316,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -267,7 +331,12 @@ "parameters": [], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -276,6 +345,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -292,7 +364,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -309,6 +386,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -328,7 +408,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -343,6 +428,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -355,7 +443,12 @@ "parameters": [], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -364,6 +457,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -373,12 +469,20 @@ "parameters": [], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax." } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -395,7 +499,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -405,6 +514,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -424,7 +536,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -436,6 +553,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -452,7 +572,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -464,6 +589,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -483,7 +611,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -492,6 +625,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -508,7 +644,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -517,9 +658,69 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, + "/status/{operationId}": { + "get": { + "operationId": "Poll", + "description": "Operation that returns the status of another operation.", + "parameters": [ + { + "$ref": "#/parameters/Azure.Core.Foundations.ApiVersionParameter" + }, + { + "name": "operationId", + "in": "path", + "description": "The unique ID of the operation.", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "The request has succeeded.", + "schema": { + "type": "object", + "description": "Provides status details for long running operations.", + "properties": { + "id": { + "type": "string", + "description": "The unique ID of the operation." + }, + "status": { + "$ref": "#/definitions/Azure.Core.Foundations.OperationState", + "description": "The status of the operation" + }, + "error": { + "$ref": "#/definitions/Azure.Core.Foundations.Error", + "description": "Error object that describes the error when status is \"Failed\"." + } + }, + "required": [ + "id", + "status" + ] + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Azure.Core.Foundations.ErrorResponse" + }, + "headers": { + "x-ms-error-code": { + "type": "string", + "description": "String error code indicating what went wrong." + } + } + } + } + } + }, "/success/{name}": { "post": { "operationId": "Success", @@ -539,9 +740,17 @@ } }, "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -575,7 +784,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -587,6 +801,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -603,7 +820,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -613,6 +835,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -632,7 +857,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", @@ -648,6 +878,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -664,18 +897,131 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "500": { "description": "Server error", "schema": {} } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } } }, "definitions": { + "Accepted": { + "type": "object" + }, + "Azure.Core.Foundations.Error": { + "type": "object", + "description": "The error object.", + "properties": { + "code": { + "type": "string", + "description": "One of a server-defined set of error codes." + }, + "message": { + "type": "string", + "description": "A human-readable representation of the error." + }, + "target": { + "type": "string", + "description": "The target of the error." + }, + "details": { + "type": "array", + "description": "An array of details about specific errors that led to this reported error.", + "items": { + "$ref": "#/definitions/Azure.Core.Foundations.Error" + } + }, + "innererror": { + "$ref": "#/definitions/Azure.Core.Foundations.InnerError", + "description": "An object containing more specific information than the current object about the error." + } + }, + "required": [ + "code", + "message" + ] + }, + "Azure.Core.Foundations.ErrorResponse": { + "type": "object", + "description": "A response containing error details.", + "properties": { + "error": { + "$ref": "#/definitions/Azure.Core.Foundations.Error", + "description": "The error object." + } + }, + "required": [ + "error" + ] + }, + "Azure.Core.Foundations.InnerError": { + "type": "object", + "description": "An object containing more specific information about the error. As per Azure REST API guidelines - https://aka.ms/AzureRestApiGuidelines#handling-errors.", + "properties": { + "code": { + "type": "string", + "description": "One of a server-defined set of error codes." + }, + "innererror": { + "$ref": "#/definitions/Azure.Core.Foundations.InnerError", + "description": "Inner error." + } + } + }, + "Azure.Core.Foundations.OperationState": { + "type": "string", + "description": "Enum describing allowed operation states.", + "enum": [ + "NotStarted", + "Running", + "Succeeded", + "Failed", + "Canceled" + ], + "x-ms-enum": { + "name": "OperationState", + "modelAsString": true, + "values": [ + { + "name": "NotStarted", + "value": "NotStarted", + "description": "The operation has not started." + }, + { + "name": "Running", + "value": "Running", + "description": "The operation is in progress." + }, + { + "name": "Succeeded", + "value": "Succeeded", + "description": "The operation has completed successfully." + }, + { + "name": "Failed", + "value": "Failed", + "description": "The operation has failed." + }, + { + "name": "Canceled", + "value": "Canceled", + "description": "The operation has been canceled by the user." + } + ] + } + }, "Codes": { "type": "string", "enum": [ @@ -698,5 +1044,16 @@ ] } }, - "parameters": {} + "parameters": { + "Azure.Core.Foundations.ApiVersionParameter": { + "name": "api-version", + "in": "query", + "description": "The API version to use for this operation.", + "required": true, + "type": "string", + "minLength": 1, + "x-ms-parameter-location": "method", + "x-ms-client-name": "apiVersion" + } + } } diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/tsp-diagnostics.json b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/tsp-diagnostics.json index 7d814b5397..e4b70f66d7 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/tsp-diagnostics.json +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/inline-and-standard/tsp-diagnostics.json @@ -9,11 +9,116 @@ "severity": "warning", "message": "Use the latest ARM common-types version 'v6' instead of 'v5'." }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, { "code": "@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint", "severity": "warning", "message": "Arm namespace InlineShapes is missing the Operations interface. Add \"interface Operations extends Azure.ResourceManager.Operations {}\"." }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The Model named 'Accepted' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The ModelProperty named 'statusCode' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The ModelProperty named 'operationLocation' should have a documentation or description, use doc comment /** */ to provide it." + }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -25,9 +130,24 @@ "message": "The ModelProperty named 'code' should have a documentation or description, use doc comment /** */ to provide it." }, { - "code": "@azure-tools/typespec-azure-core/no-openapi", + "code": "@azure-tools/typespec-azure-resource-manager/arm-resource-operation", "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." + "message": "All operations must be inside an interface declaration." + }, + { + "code": "@azure-tools/typespec-azure-resource-manager/arm-resource-operation", + "severity": "warning", + "message": "All Resource operations must use an api-version parameter. Please include Azure.ResourceManager.ApiVersionParameter in the operation parameter list using the spread (...ApiVersionParameter) operator, or using one of the common resource parameter models." + }, + { + "code": "tsp-lintdiff-local-linter/get-in-operation-name", + "severity": "warning", + "message": "'GET' operation 'Poll' should use method name 'Get' or method name starting with 'List'. Note: If you have already shipped an SDK on top of this spec, fixing this warning may introduce a breaking change." + }, + { + "code": "tsp-lintdiff-local-linter/xms-examples-required", + "severity": "warning", + "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, { "code": "@azure-tools/typespec-azure-core/documentation-required", @@ -69,11 +189,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -104,11 +219,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -149,11 +259,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -184,11 +289,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -239,11 +339,6 @@ "severity": "warning", "message": "Don't use `| null`. If you meant to have an optional property, use `?`. (e.g. `myProp?: string`)" }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -274,11 +369,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -324,11 +414,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -359,11 +444,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -404,11 +484,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -439,11 +514,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -489,11 +559,6 @@ "severity": "warning", "message": "Model properties or operation parameters should not be of type Record. ARM requires Resource provider teams to define types explicitly." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -524,11 +589,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -569,11 +629,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -604,11 +659,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -654,11 +704,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -694,11 +739,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -744,11 +784,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -784,11 +819,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -834,11 +864,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -874,11 +899,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -924,11 +944,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -964,11 +979,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1014,11 +1024,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1054,11 +1059,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1104,11 +1104,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1139,11 +1134,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1184,11 +1174,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1219,11 +1204,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1274,11 +1254,6 @@ "severity": "warning", "message": "Union of literals should include the base scalar as a variant to make it an open enum. (ex: `union Choice { Yes: \"yes\", No: \"no\", string };`)." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1314,11 +1289,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1364,11 +1334,6 @@ "severity": "warning", "message": "Azure services must not have properties of type `unknown`." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1399,11 +1364,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1449,11 +1409,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1489,11 +1444,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1544,11 +1494,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1634,11 +1579,6 @@ "severity": "warning", "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1664,11 +1604,6 @@ "severity": "warning", "message": "A 202 response should include a Location response header." }, - { - "code": "tsp-lintdiff-local-linter/lro-extension", - "severity": "warning", - "message": "ARM POST operations with a 202 response must set `x-ms-long-running-operation` to `true`." - }, { "code": "tsp-lintdiff-local-linter/missing-xms-error-response", "severity": "warning", @@ -1684,11 +1619,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1719,11 +1649,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1769,11 +1694,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1824,11 +1744,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/migration.md b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/migration.md index beb2cdab43..a5ee3a8cda 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/migration.md +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/migration.md @@ -1,521 +1,478 @@ # LroErrorContent migration evidence -## Conclusion - -**TypeSpec rule update required.** The previous implementation checked model -ancestry rather than the emitted top-level error reference. It missed named -scalar/enum/union references and nested namespaces, accepted locally defined -derived error schemas, reported inline models that Swagger never selects, and -treated GET polling metadata as an emitted LRO flag. - -Official coverage is a **gap**, not template enforcement. The standard ARM -operation templates permit custom `Error` arguments; registered official rules -do not validate the error reference. See [rule.md](rule.md) for the code-backed -coverage check, full emission matrix, upstream links, and promotion boundary. - -The repaired rule follows semantic HTTP endpoints and every version snapshot, -reads native ARM reference/common-type metadata, and uses shared payload and -inline-type APIs. It reports once per authored operation. The native-only -revision removes the `Autorest.getRef` dependency; it does not replace it with -an adapter, private state access, decorator inspection, or Swagger emission. -No emitter, validator, unrelated lint rule, or harness dependency was changed. - -**Partial equivalence to the full Swagger rule.** Emitter-only `@Autorest.useRef` -overrides are deliberately outside the native contract. The native type remains -subject to lint regardless of how that override changes emitted Swagger. The -comparison fixture below proves divergences in both directions. - -The completed native-only full run identifies the same 54 affected projects on -both sides, with five older-version source findings and three additional emitted -occurrences explaining the raw count difference. All 641 native source targets -are unchanged from the previous implementation's corpus result. Neither project -overlap nor matching totals close the emitter-only limitation demonstrated by -the fixtures. Compile-failed projects, arbitrary emitter directory overrides, -and invalid schemas that fail emission also remain outside the equivalence claim. - -## Sources and comparable populations - -| Evidence | Revision / population | -| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [External coverage snapshot](../../../docs/coverage_old.md) | 450 compiled projects, 210 validator rules; imported by repository commit `6a418911dbe5d35992fb5845cf4460d45643fec8` on 2026-08-11. The snapshot does not identify its original specs commit, generation timestamp, generator commit, or per-project results. | -| [Retained observed report](../../../specs/coverage-breakdown.md) | Before this repair: generated 2026-08-10T09:38:18.108Z, 462 successful / 468 selected projects, 215 known validator rules. | -| Specs checkout and corpus dataset | `f6b53f105b95da05276530a0754a1c71b4f16397` | -| Validator source inspection | `Azure/azure-openapi-validator` commit `a970d991d2785184d2786b85e0a345dc3f37bc25`; installed fixture ruleset supplies the same selector and pattern. | -| Retained Swagger rule shard | Generated 2026-08-06T08:03:27.940Z; 3,906 occurrences before successful-project population filtering. | -| TypeSpec comparison | Local `all` ruleset; ARM service isolation inside this rule; normal production validator mode, not staging. | - -Swagger retains each project's dataset-selected API version. TypeSpec source -analysis examines all declared version snapshots, with operation-node -deduplication. Compiler/emitter errors and failed projects are not evidence of -compliance. Both sides of the behavioral comparison exclude TypeSpec failures. -Existing service suppressions remain in effect; fixture ambient warnings are -reviewed separately from the target diagnostic. - -## Report reconciliation - -| Report | Validator projects | Local TypeSpec projects | Official credit | Same-project overlap | Validator only | TypeSpec only | Raw Swagger / TypeSpec | -| ---------------------------------------------- | ------------------ | ----------------------- | ------------------ | -------------------------------------- | -------------- | ------------- | ---------------------- | -| External snapshot (`lint`, 100%) | 55 | 55 | 0 | Not reconstructable from aggregate row | Not available | Not available | Not available | -| Retained observed production row before repair | 54 | 55 | No coverage credit | 54 | 0 | 1 | 639 / 644 | -| Full run before native-only revision | 54 | 54 | No coverage credit | 54 | 0 | 0 | 639 / 641 | -| Final native-only full run (partial semantics) | 54 | 54 | No coverage credit | 54 | 0 | 0 | 639 / 641 | - -The external report credits migration disposition; the observed report requires -same-project diagnostics. For this row both name the local lint, so the 55 vs 54 -validator-project difference cannot be explained by official/template credit. -The report denominators and snapshot metadata differ. Without the external -report's individual projects/revisions, identifying its extra project would be -speculation. The observed report's one-sided project is independently -identifiable: `specification/botservice/resource-manager/Microsoft.BotService/BotService`. - -## Focused behavior - -Four violation fixtures and one compliance fixture compile; the violation -fixtures are classified as partial coverage because of the documented emitter -override limitation. The template/version fixture intentionally includes an -old-only operation: latest Swagger has two violations, while the all-version -native lint has three source diagnostics. The native tests additionally cover -data-plane isolation, unused templates, nested namespaces and multi-status / -multi-version deduplication. Seven native tests pass without importing the -AutoRest TypeSpec library, and a throwing module mock prevents accidental -runtime imports of the emitter from the rule or its helpers. Native standard -errors, `model is` copies, and legacy ARM references are covered directly. -AutoRest scope exclusion is -covered by an additional SDK-only operation in `reference-shapes` and a native -negative test; it adds no target diagnostic. - -The emission matrix is shape-specific, not just response-surface coverage. -Inline array, tuple, record, model, literal, intrinsic, file, multipart and binary -fallthroughs are represented alongside named model/scalar/enum/union references -and authorable external overrides. Nonserializable types and emitter-error -unions are not counted as successful Swagger emission. +## Result and gap summary + +**Native-rule repair completed; partial coverage, not functional equivalence.** +The full production comparison compiled **462/468 ARM projects**: Swagger +reported **639** diagnostics and native TypeSpec **637**, both in the same +**54 projects**, with no one-sided projects. Native output includes five +older-version declarations; excluding those leaves **632** selected-version +source findings. Four Swagger findings are legacy-marked GETs outside the +native selector. Three additional Swagger occurrences come from multiple +error statuses and scope expansions of single authored operations. +Focused fixtures record **20/39** diagnostics because native checking also +rejects inline errors and does not honor emitter overrides or SDK scope. +The required repair removes TCGC, `@typespec/openapi`, and unsafe mutation +while enforcing the explicitly selected standard-payload contract. +Historical return types and emitter-only LROs remain outside its scope. +Six compile failures are excluded from both populations, not treated as clean. + +## Decision and scope + +This is an explicitly authorized follow-up to merged [PR #5425](https://github.com/Azure/typespec-azure/pull/5425), +based on `origin/feature/lintdiff-migration-new` at +`29c4a87b0`. The source implementation required repair because its intended ARM +destination must not depend on TCGC, OpenAPI helpers, or unsafe graph mutation. +The requested development-skill restrictions are isolated in +[PR #5438](https://github.com/Azure/typespec-azure/pull/5438). +No official-library promotion is included. + +The native contract was deliberately selected rather than claiming that +removing three imports preserves behavior. It checks the unprojected authored +HTTP program, recognizes non-GET LROs through Azure Core metadata, and requires +every existing default/4xx/5xx payload to use native ARM v2-or-later +`ErrorResponse` metadata. It accepts native common types, model-is copies, +standard native legacy references, and nullable standard errors. It does not +require bodies, inspect success payloads, or lint synchronous operations. +All response variants are checked, with one diagnostic per authored operation +node across statuses, nested services, and shared template instantiations. + +No TCGC, OpenAPI helper, emitter, private-state adapter, or unsafe mutation is +used to make rule decisions. ARM's native reference APIs remain permitted. +Native tests throw on TCGC/AutoRest imports and exercise the rule without their +TypeSpec libraries or OpenAPI decorators. The tester registers OpenAPI solely +to satisfy the existing ARM library's transitive import. + +## Source of truth and prior coverage + +See [rule.md](rule.md) for direct upstream code, documentation, and test links, +the complete emission matrix, applicability boundaries, and official-rule +coverage analysis. The upstream revision is +`a970d991d2785184d2786b85e0a345dc3f37bc25`. +Its non-resolving selector checks only existing top-level error `schema.$ref` +values for operations explicitly emitted as LROs. It ignores inline schemas +and absent bodies. Its reference pattern accepts common-types v2 and later; +the unanchored regex and unescaped dot are preserved for native reference +metadata. + +Official coverage remains a gap: status-selection rules do not check payload +identity, and native ARM async templates accept custom `Error` arguments. +The unsuppressed native-template fixture demonstrates authorability. +The raw-extension `restart` fixture's two suppressions are retained only to +demonstrate an emitter-only discrepancy, not to prove an actionable native gap. +No unrelated compiler or lint diagnostic is counted as target coverage. + +## Comparable populations and historical reports + +Pinned specs checkout: `f6b53f105b95da05276530a0754a1c71b4f16397`. +The runner receives an isolated specs checkout through `--specs-repo`. +The corpus selects 468 ARM projects, production validator mode, and each +project's dataset-selected API version. The local `all` ruleset runs over +unprojected source, with this rule's ARM service isolation. Existing source +suppressions remain effective. + +The retained Swagger rule shard was generated at +`2026-08-06T08:03:27.940Z`; its 3,906 occurrences span more projects than the +selected TypeSpec population. Comparisons first restrict both sides to selected, +successfully compiled projects. Failed projects are not treated as compliant. +The [external report](../../../docs/coverage_old.md) has only aggregates: +its extra affected project cannot be reconstructed from that snapshot. + +| Historical evidence | Population | Swagger projects | Native projects | Overlap | Raw Swagger/native | +| --------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------- | --------------- | ------------------- | ------------------ | +| External coverage snapshot | 450 compiled projects; 210 rules; imported at `6a418911dbe5d35992fb5845cf4460d45643fec8` | 55 | 55 | Not reconstructable | Not available | +| Checked-in observed report before this repair | Generated `2026-08-10T09:38:18.108Z`; 462/468 successful; 215 known rules | 54 | 55 | 54 | 639/644 | +| Prior implementation's migration evidence | Generated `2026-09-09T06:42:49.568Z`; 462/468 successful | 54 | 54 | 54 | 639/641 | + +These are different report revisions, not current repair results. The external +report credits migration disposition; the +[observed report](../../../specs/coverage-breakdown.md) counts same-project +diagnostics. Neither matching project sets nor raw totals proves equivalence. +The previous migration evidence is preserved in +[the pre-repair source](https://github.com/Azure/typespec-azure/blob/feature/lintdiff-lro-error-content/packages/typespec-lintdiff/test/fixtures/LroErrorContent/migration.md). + +## Focused results + +All six fixtures compile and their refreshed snapshots are stable. The harness +classifies four as partial shared coverage, one as a native violation without +a Swagger violation, and one as a clean control with reviewed ambient +diagnostics. Those classifications are not per-operation parity claims. + +| Fixture | Swagger diagnostics | Native diagnostics | Explanation | +| ----------------------- | ------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------- | +| `non-standard-error` | 2 | 1 | Both flag `restartNative`; only Swagger flags raw-extension `restart`. | +| `reference-shapes` | 12 | 13 | Both flag twelve custom payload endpoints; native also flags SDK-scoped `sdkOnly`. | +| `external-references` | 4 | 4 | Three shared violations; `standard` and `overriddenStandard` diverge in opposite directions. | +| `inline-and-standard` | 0 | 18 | Native rejects inline/primitive/collection/binary payloads and the false-overridden native LRO. | +| `template-and-versions` | 2 | 3 | Shared `createOrUpdate`; native adds `disabled` and removed `oldAction`; Swagger adds legacy-only `marked`. | +| `standard-error` | 0 | 0 | Native ARM templates and an error model-is copy, with unrelated diagnostics reviewed explicitly. | + +The 27 native tests cover type families, standard references including v1/wrong +definition/v10 cases, multiple error statuses, success/no-body/sync exclusions, +binary/multipart payloads, GET exclusion, unrelated services, unused templates, +nested services, shared operation instantiations, and the authored/historical +return-type boundary. The independent reviewer found duplicate diagnostics +through nested services; the repair restores authored-node deduplication and +adds both nested-service and template-instantiation regression tests. ## Code-backed gap examples -### Emitter overrides do not change the native lint contract +### Inline errors: deliberately stronger native payload policy -- **Classification:** emitter-only limitation; both TypeSpec-only and validator-only targets -- **Status:** intentional partial coverage -- **Fixture:** `external-references`, operations `standard` and `overriddenStandard`. - -**TypeSpec source** +- **Classification:** TypeSpec-only. +- **Status:** intentional, explicitly selected native behavior. +- **Source:** `inline-and-standard/main.tsp`, `anonymous`. ```typespec -@Autorest.useRef("../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse") -model StandardReference { - code?: string; -} - -@Autorest.useRef("#/definitions/LocalError") -model OverriddenStandard is Azure.ResourceManager.CommonTypes.ErrorResponse; +@route("/anonymous/{name}") +op anonymous is Lro<{ + anonymousCode: string; +}>; ``` -Each model is the explicit 400 body of an LRO in the comparison fixture. +The fixture's `Lro` template has native polling-operation metadata. Its emitted +500 schema is an inline object with `anonymousCode`, not a `$ref`. +Swagger therefore produces no diagnostic; the native lint reports the custom +payload. This difference extends to the primitive/collection/file/multipart +fallthroughs recorded in the emission matrix. Inlining is not reproduced or +guessed by production rule code. + +### LRO selection and overrides -| Target | Swagger response reference / validator | Native TypeSpec result | -| -------------------- | --------------------------------------- | ---------------------------------------- | -| `standard` | Common-types v5 `ErrorResponse` / clean | Custom named model / violation | -| `overriddenStandard` | `#/definitions/LocalError` / violation | Native standard common-type copy / clean | +- **Classification:** both one-sided directions. +- **Status:** intentional native boundary. +- **Source:** `template-and-versions/main.tsp`, `disabled` and `marked`. -**Explanation:** `@useRef` is emitter-owned state, with no supported native -accessor. Calling `Autorest.getRef`, hiding it in a helper, or scraping its -decorator/state would retain the prohibited dependency. Native -`getExternalTypeRef` and `getArmCommonTypeOpenAPIRef` remain supported: both read -authored ARM library metadata without loading AutoRest or emitted documents. +```typespec +@TypeSpec.OpenAPI.extension("x-ms-long-running-operation", false) +disabled is ArmResourceActionAsync; + +@route("/marked") @post +@Azure.ClientGenerator.Core.Legacy.markAsLro +op marked(): AcceptedResponse | CustomError; +``` -**Disposition:** Keep the native result and mark coverage partial. The -comparison fixture has four findings on each side but only three shared -operation targets; equal counts must not be presented as equivalent behavior. -The standard fix for native authors is `CommonTypes.ErrorResponse`, not an -emitter override on a custom error. Swagger generation remains comparison-only. +| Target | Swagger result | Native result | +| ---------- | ------------------------------------------------------------- | ----------------------------------------------------- | +| `disabled` | Clean: emitted LRO flag is false. | Violation: ARM template supplies native LRO metadata. | +| `marked` | Violation: legacy marker emits true with custom error `$ref`. | Clean: no native LRO metadata. | -### GET polling metadata is not an emitted LRO +The raw-extension-only `non-standard-error/restart` similarly remains +Swagger-only; its new unsuppressed `restartNative` is independently detected. +These differences cannot be repaired by importing TCGC or inspecting OpenAPI +state without violating the requested production boundary. -- **Classification:** TypeSpec-only -- **Status:** fixed -- **Project/API version:** `specification/botservice/resource-manager/Microsoft.BotService/BotService` / `2023-09-15-preview` -- **Source:** `routes.tsp`, `OperationResultsOperationGroup.get`, original diagnostic at line 91. +### SDK scope is not an ARM lint applicability filter -**TypeSpec source** +- **Classification:** TypeSpec-only. +- **Status:** intentional. +- **Source:** `reference-shapes/main.tsp`, `sdkOnly`. ```typespec -@get -get( - ...ApiVersionParameter, - ...SubscriptionIdParameter, - @path operationResultId: string, -): - | ArmResponse - | ArmAcceptedLroResponse & - Azure.Core.Foundations.RetryAfterHeader> - | Error; +@Azure.ClientGenerator.Core.scope("csharp") +@route("/sdk-only/{name}") +op sdkOnly is Lro; ``` -**Emitted OpenAPI or validator behavior** +The native HTTP endpoint has polling metadata and a custom error body, so it +violates the rule. AutoRest omits the operation from Swagger; there is no +emitted node to compare. SDK-scoping decisions are no longer rule inputs. -The retained `botservice.json` operation `OperationResults_Get` has no -`x-ms-long-running-operation` field, even though its error has a local reference: +### Emitter reference overrides do not change the native payload -```json -{ - "operationId": "OperationResults_Get", - "responses": { - "default": { - "description": "An unexpected error response.", - "schema": { "$ref": "#/definitions/Error" } - } - } +- **Classification:** both one-sided directions. +- **Status:** intentional partial coverage. +- **Source:** `external-references/main.tsp`. + +```typespec +@Autorest.useRef("../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse") +model StandardReference { + code?: string; } + +@Autorest.useRef("#/definitions/LocalError") +model OverriddenStandard is CommonTypes.ErrorResponse; ``` -| Engine | Observed result | -| ---------------------- | ------------------------------------------------- | -| Swagger validator | No diagnostic: the operation is not selected. | -| Previous TypeSpec lint | One diagnostic: `getLroMetadata` was sufficient. | -| Repaired TypeSpec lint | GET metadata alone does not select the operation. | +| Target | Emitted 400 reference | Swagger / native | +| -------------------- | ---------------------- | ----------------------------------------------- | +| `standard` | ARM v5 `ErrorResponse` | Clean / violation of the native custom payload. | +| `overriddenStandard` | Local `LocalError` | Violation / native standard error is clean. | -**Explanation:** AutoRest deliberately omits its inferred LRO flag for GET -polling endpoints. This service declares only the selected API version, so this -is not an older-version population mismatch. +The equal fixture totals conceal these different targets. No emitter adapter +is used to force them to match. -**Disposition:** Match emitter LRO selection, preserving explicit extension and -legacy LRO-marker behavior. +### Authored program versus historical versions -### Native templates permit a custom error reference +- **Classification:** version/population difference and explicit coverage limit. +- **Status:** intentional boundary, not all-version equivalence. +- **Source:** `template-and-versions/oldAction` and the native historical-return test. -- **Classification:** count-only / semantic miss in derived-shape cases -- **Status:** fixed -- **Project/API version:** focused `template-and-versions` / `2025-01-01` -- **Source:** `Widgets.createOrUpdate`. +```typespec +@removed(Versions.current) +oldAction is ArmResourceActionAsync; +``` -**TypeSpec source** +The authored operation is still visible to native HTTP traversal and is +diagnosed; latest Swagger contains no `oldAction`. Conversely, the native test +uses `@returnTypeChangedFrom` to record a historical custom response on an +operation whose authored response is standard. It remains native-clean: +the rule no longer reconstructs that historical response. Removed declarations +being checked does not imply that all historical shapes are checked. + +## Corpus execution + +### Final full-run results + +The replacement run completed with exit code zero at +`2026-09-09T18:09:23.3464107+08:00`. The result index records +`generatedAt: 2026-09-09T10:06:21.607Z`, `durationMs: 2199107`, and +`partial: false`. It selected all 468 projects and compiled 462 successfully. +The runner and library baseline is `29c4a87b0`, with this repair's rule changes; +specs and validator revisions are pinned above. + +| Measure | Swagger | Native TypeSpec | +| --------------------------------------- | -------------: | --------------: | +| Affected successfully compiled projects | 54 | 54 | +| Raw diagnostics | 639 | 637 | +| Project + Swagger file + JSON path | 639 | Not applicable | +| Project + JSON path | 639 | Not applicable | +| Project + source file + line + column | Not applicable | 637 | +| Selected-version source attribution | 639 | 632 | + +The complete validator-only and TypeSpec-only project lists are both **empty**; +all 54 affected projects overlap. Among those projects, 47 have equal raw +counts, three have higher native counts, and four have higher Swagger counts. +The positive native-minus-Swagger contributions total **+5**, and negative +contributions total **-7**, producing the net **-2**. These conservative +identities remove no occurrences and do not assert a cross-engine bijection. +The counts were independently grouped from the two rule shards and matched +against the comparison report. + +| Project (suffix of `specification/`) | Selected API version | Swagger | Native raw | Native selected-version | +| ---------------------------------------------------------------------------------- | -------------------- | ------: | ---------: | ----------------------: | +| `batch/resource-manager/Microsoft.Batch/Batch` | `2025-06-01` | 5 | 6 | 5 | +| `containerinstance/resource-manager/Microsoft.ContainerInstance/ContainerInstance` | `2026-08-01-preview` | 8 | 11 | 8 | +| `cost-management/resource-manager/Microsoft.CostManagement/CostManagement` | `2025-03-01` | 8 | 6 | 6 | +| `dataprotection/resource-manager/Microsoft.DataProtection/DataProtection` | `2026-04-01-preview` | 17 | 18 | 17 | +| `iothub/resource-manager/Microsoft.Devices/IoTHub` | `2026-05-01-preview` | 6 | 5 | 5 | +| `resources/resource-manager/Microsoft.Resources/deploymentStacks` | `2025-07-01` | 3 | 1 | 1 | +| `web/resource-manager/Microsoft.Web/AppService` | `2026-07-15` | 68 | 66 | 66 | + +Selected-version attribution removes exactly the five source targets proved +below; it is not a new projection run or a claim that historical return types +were reconstructed. Raw output remains 637. The prior implementation's 641 +raw findings included the four legacy GETs now excluded; its 639/641 result is +superseded, not silently reused. + +### Compile failures + +These six projects failed HTTP compilation and were excluded from both sides. +Their diagnostic codes are compiler errors, not this lint's warnings. + +| Project (suffix of `specification/`) | Error | +| ------------------------------------------------------------------------------------------ | ---------------------------------- | +| `deviceprovisioningservices/resource-manager/Microsoft.Devices/DeviceProvisioningServices` | `@typespec/http/duplicate-body` | +| `monitor/resource-manager/Microsoft.Insights/Insights/TenantActionGroups` | `@typespec/http/missing-uri-param` | +| `network/resource-manager/Microsoft.Network/Network/Network` | `@typespec/http/missing-uri-param` | +| `quota/resource-manager/Microsoft.Quota/Quota` | `@typespec/http/missing-uri-param` | +| `resources/resource-manager/Microsoft.Resources/deployments` | `@typespec/http/duplicate-body` | +| `servicelinker/resource-manager/Microsoft.ServiceLinker/ServiceLinker` | `@typespec/http/duplicate-body` | + +For example, DeviceProvisioningServices reports `duplicate-body` at +`client.tsp:469:57`; Network reports `missing-uri-param` for +`applicationGatewayAvailableSslOption`. The full failure records and raw +stdout/stderr are retained with the session's machine-readable evidence. +The successful runner exit indicates completion of its analysis, not that +these six services compiled. + +### Pinned real-service selector differences + +CostManagement (`2025-03-01`) has eight retained Swagger findings, including two +GET operations marked only through legacy SDK/emitter authoring: +`GenerateCostDetailsReport_GetOperationResults` at `routes.tsp:1167-1191` and +`GenerateDetailedCostReportOperationResults_Get` at +`GenerateDetailedCostReportOperationResult.tsp:31-40`. +The former returns +`ArmResponse | ArmAcceptedResponse | ErrorResponse` +under `@Azure.ClientGenerator.Core.Legacy.markAsLro`. +Its selected Swagger path ends in +`costDetailsOperationResults/{operationId}.get.responses.default.schema.$ref`. +The latter is the same selector difference at +`operationResults/{operationId}.get.responses.default.schema.$ref`. +Both emitted operations have `x-ms-long-running-operation: true`. + +The relevant authored return at `routes.tsp:1188-1191` is: ```typespec -@error -model CustomError { - code: string; -} -createOrUpdate is ArmResourceCreateOrReplaceAsync; + | ArmResponse + | ArmAcceptedResponse + | ErrorResponse; ``` -**Emitted OpenAPI or validator behavior** +The selected emitted GET has these fields: ```json { - "schema": { "$ref": "#/definitions/CustomError" } + "x-ms-long-running-operation": true, + "responses": { + "default": { "schema": { "$ref": "#/definitions/ErrorResponse" } } + } } ``` -| Engine | Observed result | -| ----------------- | -------------------------------------------- | -| Swagger validator | Violation on the default response reference. | -| TypeSpec lint | Violation on the authored operation. | +Swagger reports the local error reference; native checking excludes the GET. +This is an intentional selector difference, not emitted duplication. -**Explanation:** The `Error` template parameter is unconstrained beyond an -object type. A native template is not proof of standard-reference enforcement. -The related `reference-shapes/derived` fixture uses a model extending the -standard error; its top-level reference is still local, despite the common-type -reference inside its definition's `allOf`. +AppService (`2026-07-15`) has 68 retained Swagger findings, including +`WebApps_GetProductionSiteDeploymentStatus` (`CsmDeploymentStatus.tsp:65-79`) +and `WebApps_GetSlotSiteDeploymentStatusSlot` (`CsmDeploymentStatus.tsp:128-142`). +Both are legacy-marked GETs with `DefaultErrorResponse`. The selected paths end +in `sites/{name}/deploymentStatus/{deploymentStatusId}` and +`sites/{name}/slots/{slot}/deploymentStatus/{deploymentStatusId}`, respectively; +both diagnostics target `get.responses.default.schema.$ref`. -**Disposition:** Replace ancestry/name heuristics with reference classification. +These four occurrences remain Swagger violations but are outside the repaired +non-GET native selector. This is an explicit partial-coverage boundary, not a +validator false positive. AppService's unmarked +`StaticSitesAsyncOperations.getOperationResult` also stays native-excluded; it +has no emitted LRO flag and no retained target Swagger diagnostic. -### Removed operation belongs only to the old version +### Older-version declarations in the authored program -- **Classification:** count-only -- **Status:** population mismatch -- **Project/API version:** focused `template-and-versions` / selected `2025-01-01` -- **Source:** `Widgets.oldAction`. +The five older-version findings match these exact current diagnostic targets: -**TypeSpec source** +| Project | Source target (line:column) | Exclusion evidence | +| ----------------- | ------------------------------------------ | ----------------------------------------------------------------- | +| Batch | `Certificate.tsp:130:3` | `delete` removed in selected `2025-06-01` | +| ContainerInstance | `SandboxGroup.tsp:175:3`, `187:3`, `197:3` | `SandboxGroups` removed in selected `2026-08-01-preview` | +| DataProtection | `BackupInstanceResource.tsp:192:3` | `resumeProtectionLegacy` removed in selected `2026-04-01-preview` | -```typespec -@removed(Versions.current) -oldAction is ArmResourceActionAsync; -``` - -The service version enum declares `old: "2024-01-01"` and -`current: "2025-01-01"`. The latest Swagger snapshot omits `oldAction`; therefore -there is no selected-version Swagger operation to compare. - -| Engine | Observed result | -| ----------------- | --------------------------------------------------------------- | -| Swagger validator | Two latest-version operation violations. | -| TypeSpec lint | Three all-version operation diagnostics, including `oldAction`. | - -**Explanation:** This is valid older-version coverage, not a false positive. - -**Disposition:** Preserve the raw TypeSpec count and compare only diagnostics -attributable to the selected API version. - -The same cause is observable in these pinned real-service declarations: - -| Project suffix | Selected API version | Old-only source targets | Evidence | -| ----------------------------------------------- | -------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| `Microsoft.Batch/Batch` | `2025-06-01` | `Certificate.tsp:130`, certificate delete | `@removed(Versions.v2025_06_01)` on the operation at line 129 | -| `Microsoft.ContainerInstance/ContainerInstance` | `2026-08-01-preview` | `SandboxGroup.tsp:175`, `:187`, `:197`, sandbox create/update/delete | `SandboxGroups` interface has `@removed(Versions.v2026_08_01_preview)` at line 148; the latest endpoints instead belong to `AiAgentsGroups` | -| `Microsoft.DataProtection/DataProtection` | `2026-04-01-preview` | `BackupInstanceResource.tsp:192`, `resumeProtectionLegacy` | `@removed(Versions.v2026_04_01_preview)` at line 190; the replacement `resumeProtection` at line 206 is added in that version | - -For example, the actual Batch declaration is: +For example, ContainerInstance declares: ```typespec -@removed(Versions.v2025_06_01) -delete is ArmResourceDeleteWithoutOkAsync< - Certificate, - Response = - | ArmDeletedResponse - | ArmDeleteAcceptedLroResponse - | ArmDeletedNoContentResponse, - Error = CloudError ->; +@armResourceOperations +@added(Versions.v2026_06_01_preview) +@removed(Versions.v2026_08_01_preview) +interface SandboxGroups {} ``` -The retained `2025-06-01` Swagger has no certificate-delete endpoint. Five raw -native source diagnostics across these three projects therefore belong only to -older versions and must be excluded from selected-version cardinality comparisons. -Their source-level version decorators establish the exclusion without guessing -from filenames or suppressing valid older-version diagnostics. +Its create/update/delete operations use `Error = CloudError`. The same file's +new `AiAgentsGroups` interface is added in `2026-08-01-preview`; its diagnostics +at lines 375, 387, and 397 remain in the selected-version population. This is +declaration-level version evidence, not a filename-based filter. Similarly, +DataProtection's replacement `resumeProtection` at line 206 remains included. +The removed targets have no selected-version Swagger operation to compare. +Their five native findings are a population mismatch, not false positives or +a reason to mutate the compiler program. -### Legacy LRO markers select GET operations explicitly +### Multiple error statuses on one authored operation -- **Classification:** count-only -- **Status:** fixed -- **Project/API version:** `specification/cost-management/resource-manager/Microsoft.CostManagement/CostManagement` / `2025-03-01` -- **Source:** `routes.tsp:1170`, `GenerateCostDetailsReport.getOperationResults`; the same cause applies to `GenerateDetailedCostReportOperationResult.tsp:33`. +- **Classification:** count-only, Swagger higher by one. +- **Status:** explained source-to-emission multiplicity; no rule update required. +- **Project/API version:** IoTHub / `2026-05-01-preview`. +- **Source:** `IotHubDescription.tsp:115-125`, `IotHubResource_Delete`. -**TypeSpec source** +The delete operation's `Response` includes this branch in addition to its +default `Error = ErrorDetails`: ```typespec -@Azure.ClientGenerator.Core.Legacy.markAsLro -getOperationResults( - ...ApiVersionParameter, - @path(#{ allowReserved: true }) scope: string, - ...Azure.ResourceManager.Legacy.Provider, - @path @segment("costDetailsOperationResults") operationId: string, -): - | ArmResponse - | ArmAcceptedResponse - | ErrorResponse; + | (NotFoundResponse & Body), + Error = ErrorDetails ``` -**Emitted OpenAPI or validator behavior** +The emitted LRO contains: ```json { - "operationId": "GenerateCostDetailsReport_GetOperationResults", - "x-ms-long-running-operation": true, - "responses": { - "default": { - "description": "An unexpected error response.", - "schema": { "$ref": "#/definitions/ErrorResponse" } - } - } + "404": { "schema": { "$ref": "#/definitions/ErrorDetails" } }, + "default": { "schema": { "$ref": "#/definitions/ErrorDetails" } } } ``` -| Engine | Observed result | -| ---------------------- | ---------------------------------------------------------------------- | -| Swagger validator | Eight violations in the retained project, including both marked GETs. | -| Previous TypeSpec lint | Six diagnostics; it did not inspect the legacy marker. | -| Repaired TypeSpec lint | Uses `getMarkAsLro` in AutoRest scope, including explicit GET markers. | - -**Explanation:** Ignoring all GETs would fix BotService but incorrectly lose -explicitly marked operations. The emitter treats a legacy marker separately -from inferred LRO metadata. - -**Disposition:** Preserve the marker and extension precedence rather than using -an unconditional verb exclusion. - -### A shared template produces three scoped Swagger operations +Swagger diagnoses both `delete.responses.404.schema.$ref` and +`delete.responses.default.schema.$ref`. Native diagnoses the authored delete +once at `IotHubDescription.tsp:115:3`. The other four operation findings +correspond to create/update, manual failover, and private-endpoint +update/delete. This accounts for all six Swagger versus five native findings, +without discarding either error status from validation. -- **Classification:** count-only -- **Status:** intentional -- **Project/API version:** `specification/resources/resource-manager/Microsoft.Resources/deploymentStacks` / `2025-07-01` -- **Source:** `routes.tsp:41`, `DeploymentStackCommonOps.validateStack`. - -**TypeSpec source** - -```typespec -@added(Versions.v2024_03_01) -@action("validate") -validateStack is Extension.ActionAsync< - Scope, - DeploymentStack, - DeploymentStack, - DeploymentStackValidateResult, - OverrideResourceName = ResourceName, - Error = - | ErrorResponse - | ValidationBadRequestResponse ->; -``` +### Scoped template expansion from one authored operation -`DeploymentStacksAtResourceGroup`, `DeploymentStacksAtSubscription` and -`DeploymentStacksAtManagementGroup` inherit this operation from a shared -template. The retained Swagger contains a violating `responses.400.schema.$ref` -under each of those three scope routes. - -| Engine | Observed result | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------- | -| Swagger validator | Three distinct JSON paths, one per scope. | -| Previous TypeSpec lint | Four diagnostics at the identical `routes.tsp:41:3` source location, including a client customization instantiation. | -| Repaired TypeSpec lint | Deduplicates by the authored operation node. | - -**Explanation:** File-independent Swagger paths still differ by scope; source -identity is shared. Equal raw totals would be an inappropriate requirement. - -**Disposition:** Keep source-level reporting and retain separate occurrence -counts. A single actionable source fix addresses the emitted scope variants. - -## New corpus run - -The first representative ContainerApps run completed at -2026-09-08T08:19:13Z: one successful project, ten Swagger and ten native target -diagnostics. The first full run was intentionally interrupted after 172/468 -projects to adopt the independent review's AutoRest endpoint-scope fix. That -partial run is not final evidence. Known generated corpus files and four -runner-owned temporary specs configs were removed before retrying. - -The post-review DataBoxEdge check completed at 2026-09-08T08:37:51Z: one -successful project, 33 Swagger and 33 native target diagnostics. The pre-native-only full -run completed successfully at **2026-09-08T09:07:31Z**, with aggregate generation -timestamp `2026-09-08T09:04:42.926Z` and duration `1,754,113 ms` (about 29 minutes). -It used the existing `specs:typespec --concurrency 6` runner against the isolated -pinned specs checkout, not a new per-rule runner. - -For the native-only revision, the representative ContainerApps run completed at -`2026-09-09T03:39:23Z`, again with 10 Swagger and 10 native findings. A subsequent -terminal-owned full run was interrupted after 301/468 projects; it is not final -evidence. Its four temporary specs configs and generated data were cleaned before -the detached retry, which used a session-local npm prefix for the runner's linking -step. - -The **final native-only full run** completed successfully at -**2026-09-09T06:46:03Z**, with aggregate generation timestamp -`2026-09-09T06:42:49.568Z` and duration `6,735,361 ms` (about 112 minutes). -It used the same pinned specs commit and existing full runner with concurrency -six. The following table describes this latest run. Its failed-project set, -raw totals, and complete native source-target set match the pre-native-only -run; this is observational regression evidence, not proof that emitter overrides -are natively observable. - -| Population / identity | Result | -| ----------------------------------------------------------- | ------------------------------ | -| Selected projects | 468 | -| Successful / failed | 462 / 6 | -| Validator projects / native projects / overlap | 54 / 54 / 54 | -| Complete validator-only project list | `[]` | -| Complete TypeSpec-only project list | `[]` | -| Raw Swagger / native diagnostics on successful projects | 639 / 641 | -| Swagger unique `(project, file, JSON path)` | 639 | -| Swagger unique `(project, JSON path)` | 639 | -| Native unique `(project, source file, line, column)` | 641 | -| Native older-version-only exclusions | 5 | -| Native selected-latest-version source population | 636 diagnostics in 54 projects | -| Raw equal-count / native-higher / validator-higher projects | 49 / 3 / 2 | -| Sum of native-higher / validator-higher raw differences | 5 / 3 | - -The complete set of unequal-count projects is: - -| Project | Selected API version | Swagger raw | Native raw | Native selected-version | Cause | -| ------------------------------------------------------------------------------------------------ | -------------------- | ----------- | ---------- | ----------------------- | -------------------------------------------------------- | -| `specification/batch/resource-manager/Microsoft.Batch/Batch` | `2025-06-01` | 5 | 6 | 5 | Removed certificate delete | -| `specification/containerinstance/resource-manager/Microsoft.ContainerInstance/ContainerInstance` | `2026-08-01-preview` | 8 | 11 | 8 | Removed SandboxGroups interface's three LROs | -| `specification/dataprotection/resource-manager/Microsoft.DataProtection/DataProtection` | `2026-04-01-preview` | 17 | 18 | 17 | Removed `resumeProtectionLegacy` | -| `specification/iothub/resource-manager/Microsoft.Devices/IoTHub` | `2026-05-01-preview` | 6 | 5 | 5 | One delete emits both 404 and default error references | -| `specification/resources/resource-manager/Microsoft.Resources/deploymentStacks` | `2025-07-01` | 3 | 1 | 1 | One authored template operation emits three scope routes | - -All other 49 overlapping projects have equal raw counts. The largest equal-count -projects include Compute (86), AppService (68), and DocumentDB (56). CostManagement -now covers all eight emitted violations, including the two legacy-marked GETs. -BotService is clean on both sides. AppService's two legacy-marked -`CsmDeploymentStatus.tsp` operations are now included, while its unmarked -`StaticSitesAsyncOperations.getOperationResult` GET is excluded. - -No latest-version diagnostic is discarded by a filename heuristic. The five -older-version exclusions are proven by the source decorators recorded above. -After this attribution, the remaining `639 - 636 = 3` difference is exactly the -two extra deploymentStacks scope occurrences plus IoTHub's second error status. -No stronger cross-domain canonical identity or count-equalizing rule change is -needed. - -### Two error statuses share one authored operation - -- **Classification:** count-only -- **Status:** intentional -- **Project/API version:** `specification/iothub/resource-manager/Microsoft.Devices/IoTHub` / `2026-05-01-preview` -- **Source:** `IotHubDescription.tsp:115`, `delete`. - -**TypeSpec source** +- **Classification:** count-only, Swagger higher by two. +- **Status:** explained source-to-emission multiplicity; no rule update required. +- **Project/API version:** deploymentStacks / `2025-07-01`. +- **Source:** `routes.tsp:41-50`, `DeploymentStackCommonOps.validateStack`. ```typespec -delete is ArmResourceDeleteWithoutOkAsync< - IotHubDescription, - Response = - | ArmResponse - | (ArmAcceptedLroResponse & - Azure.Core.Foundations.RetryAfterHeader> & - Body) - | ArmDeletedNoContentResponse - | (NotFoundResponse & Body), - Error = ErrorDetails ->; + validateStack is Extension.ActionAsync< + Scope, + DeploymentStack, + DeploymentStack, + DeploymentStackValidateResult, + OverrideResourceName = ResourceName, + Error = + | ErrorResponse + | ValidationBadRequestResponse + >; ``` -**Emitted OpenAPI or validator behavior** - -The retained delete response set contains: +The template is instantiated for resource group, subscription, and management +group scopes at lines 84-96. Each emitted LRO has: ```json { - "404": { "schema": { "$ref": "#/definitions/ErrorDetails" } }, - "default": { "schema": { "$ref": "#/definitions/ErrorDetails" } } + "400": { + "schema": { "$ref": "#/definitions/DeploymentStackValidateResult" } + } } ``` -| Engine | Observed result | -| ----------------- | ---------------------------------------------------------------------------------------------- | -| Swagger validator | Two findings for this delete: `responses.404.schema.$ref` and `responses.default.schema.$ref`. | -| TypeSpec lint | One finding at `IotHubDescription.tsp:115:3`. | - -**Explanation:** The author fixes one operation's response contract; separate -Swagger status paths are not separate authored operations. - -**Disposition:** Keep once-per-operation reporting, covered by the multi-status -native regression test. Do not force equality by duplicating diagnostics. - -### Compile failures and uncertainty - -The full run has exactly the same six failed projects as the retained baseline: - -| Excluded project | Compiler error | -| -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | -| `specification/deviceprovisioningservices/resource-manager/Microsoft.Devices/DeviceProvisioningServices` | `@typespec/http/duplicate-body`, including `client.tsp:469` and `:534` | -| `specification/monitor/resource-manager/Microsoft.Insights/Insights/TenantActionGroups` | `@typespec/http/missing-uri-param` | -| `specification/network/resource-manager/Microsoft.Network/Network/Network` | `@typespec/http/missing-uri-param` | -| `specification/quota/resource-manager/Microsoft.Quota/Quota` | `@typespec/http/missing-uri-param` | -| `specification/resources/resource-manager/Microsoft.Resources/deployments` | `@typespec/http/duplicate-body` | -| `specification/servicelinker/resource-manager/Microsoft.ServiceLinker/ServiceLinker` | `@typespec/http/duplicate-body` | - -Their errors and source targets are retained in the runner's per-project -`raw/typespec.stdout.txt` / `raw/typespec.stderr.txt` artifacts during analysis. -They are excluded from both sides of the behavioral population; no equivalence -claim is made for them. The external coverage snapshot's unrecorded original -revision remains a report-provenance limit, not evidence of a new semantic gap. - -Generated corpus and coverage data are validation artifacts and are not part of -this PR. Reproduce with the command above and the pinned specs commit; the -retained comparison declaration and code-backed excerpts here remain reviewable -after generated files are restored. - -## Independent review - -The reviewer identified one valid actionable finding: exclude operations outside -the AutoRest emitter's TCGC scope before checking LRO error references. Adopted -with `isInScope` and both native and emitted-fixture regression coverage. -No findings were rejected. The reviewer found no further implementation issues -on follow-up. The final complete-diff and migration-evidence review also reported -no significant issues before committing. - -The native-only revision received a separate code review and a follow-up review -of the completed corpus evidence and full diff. Both reported no significant -issues; no additional findings were adopted or rejected. +Swagger flags `DeploymentStacks_ValidateStackAtResourceGroup`, +`DeploymentStacks_ValidateStackAtSubscription`, and +`DeploymentStacks_ValidateStackAtManagementGroup` at their +`post.responses.400.schema.$ref` paths. Native examines every instance but +reports the shared authored operation once at `routes.tsp:41:3`. +Project/path deduplication correctly retains the three distinct Swagger paths; +it cannot turn them into a single source identity. + +### Conclusion and remaining limits + +All seven unequal-project totals are explained: five native older-version +targets, four Swagger legacy GETs, and three additional Swagger occurrences +from status/scope expansion. No unexplained residual remains in these count +outliers, and no additional production rule update is required for them. +This does not establish per-target parity in every equal-count project. +The focused counterexamples prove that the repaired native rule is **not +functionally equal** to Swagger: it intentionally checks inline payloads and +does not read emitter-only state or reconstruct historical return types. +Six failed projects remain unassessed. The requested native repair is complete; +official ARM promotion is separate work. + +### Run history + +The representative BotService run completed successfully at +`2026-09-09T17:19:15.0734624+08:00` on the pinned checkout. +The first full attempt was intentionally interrupted after 30/468 completions +to preserve authored-node deduplication across template instances. Its output +is not final evidence. Its 24 newly generated graph files and four runner-owned +temporary configs were identified and removed; tracked generated corpus paths +were restored before restarting. + +The replacement full run used the existing `specs:typespec` runner with +concurrency six: + +```powershell +mise exec -- pnpm --dir packages\typespec-lintdiff specs:typespec ` + --specs-repo "" ` + --concurrency 6 +``` + +Replace `` with the local isolated checkout path. + +The final index, comparison report, both raw rule shards, selected outlier +Swagger files, six failure logs, deterministic aggregation output, and run log +are retained in this session's `files/lro-final-evidence` and adjacent artifacts. +Generated corpus files are validation-only and are not included in the repair +PR. Re-running the command at the pinned revisions regenerates the evidence; +the checked-in corpus report remains the historical snapshot described above. diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/main.tsp b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/main.tsp index d8ff4717c2..cf768d84fc 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/main.tsp +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/main.tsp @@ -55,6 +55,7 @@ interface Widgets { get is ArmResourceRead; createOrUpdate is ArmResourceCreateOrReplaceAsync; delete is ArmResourceDeleteWithoutOkAsync; + restartNative is ArmResourceActionAsync; #suppress "@azure-tools/typespec-azure-resource-manager/arm-post-operation-response-codes" "LRO POST needs 202 response" #suppress "@azure-tools/typespec-azure-core/no-openapi" "Need x-ms-long-running-operation to test non-standard error in LRO" diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/output.json b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/output.json index aebf96da47..35d37af4cf 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/output.json +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/output.json @@ -300,6 +300,63 @@ }, "x-ms-long-running-operation": true } + }, + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.TestService/widgets/{widgetName}/restartNative": { + "post": { + "operationId": "Widgets_RestartNative", + "tags": [ + "Widgets" + ], + "description": "", + "parameters": [ + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/ResourceGroupNameParameter" + }, + { + "name": "widgetName", + "in": "path", + "description": "The name of the widget", + "required": true, + "type": "string", + "pattern": "^[a-zA-Z0-9_-]+$" + } + ], + "responses": { + "202": { + "description": "Resource operation accepted.", + "headers": { + "Location": { + "type": "string", + "description": "The Location header contains the URL where the status of the long running operation can be checked." + }, + "Retry-After": { + "type": "integer", + "format": "int32", + "description": "The Retry-After header can indicate how long the client should wait before polling the operation status." + } + } + }, + "204": { + "description": "Azure operation completed successfully." + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/CustomApiError" + } + } + }, + "x-ms-long-running-operation-options": { + "final-state-via": "location" + }, + "x-ms-long-running-operation": true + } } }, "definitions": { diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/tsp-diagnostics.json b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/tsp-diagnostics.json index 98588abb13..aee1eae382 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/tsp-diagnostics.json +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/tsp-diagnostics.json @@ -1,4 +1,9 @@ [ + { + "code": "@azure-tools/typespec-azure-resource-manager/arm-post-operation-response-codes", + "severity": "warning", + "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." + }, { "code": "tsp-lintdiff-local-linter/latest-version-of-common-types-must-be-used", "severity": "warning", @@ -7,7 +12,7 @@ { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/top-level-resources-list-by-resource-group", @@ -79,6 +84,31 @@ "severity": "warning", "message": "Path parameter should specify a maximum length (maxLength)." }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The Operation named 'restartNative' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "tsp-lintdiff-local-linter/descriptive-description-required", + "severity": "warning", + "message": "Descriptions cannot be empty or whitespace-only. Provide a meaningful doc string." + }, + { + "code": "tsp-lintdiff-local-linter/xms-examples-required", + "severity": "warning", + "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." + }, + { + "code": "tsp-lintdiff-local-linter/path-parameter-schema", + "severity": "warning", + "message": "Path parameter should specify a maximum length (maxLength)." + }, + { + "code": "tsp-lintdiff-local-linter/path-parameter-schema", + "severity": "warning", + "message": "Path parameter should specify a maximum length (maxLength)." + }, { "code": "tsp-lintdiff-local-linter/long-running-operations-options-validator", "severity": "warning", diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/validator-diagnostics.json b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/validator-diagnostics.json index 50aa672e0c..216ac6c8da 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/validator-diagnostics.json +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/non-standard-error/validator-diagnostics.json @@ -12,5 +12,19 @@ "$ref" ], "severity": 0 + }, + { + "code": "LroErrorContent", + "message": "Error response content of long running operations must follow the error schema provided in the common types v2 and above.", + "path": [ + "paths", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.TestService/widgets/{widgetName}/restartNative", + "post", + "responses", + "default", + "schema", + "$ref" + ], + "severity": 0 } ] diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/reference-shapes/main.tsp b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/reference-shapes/main.tsp index 8345014d59..85eb60b75c 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/reference-shapes/main.tsp +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/reference-shapes/main.tsp @@ -42,9 +42,15 @@ model Failure { @statusCode statusCode: 400; @body body: T; } -@extension("x-ms-long-running-operation", true) +@route("/status") +op poll is Azure.Core.Foundations.GetOperationStatus; +model Accepted { + @statusCode statusCode: 202; + @header("Operation-Location") operationLocation: string; +} +@Azure.Core.pollingOperation(poll) @post -op Lro(@path name: string): AcceptedResponse | Failure; +op Lro(@path name: string): Accepted | Failure; @route("/models/{name}") op models is Lro; diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/reference-shapes/output.json b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/reference-shapes/output.json index e19d5d1961..824f0e7f0b 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/reference-shapes/output.json +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/reference-shapes/output.json @@ -52,7 +52,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -61,6 +66,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -77,7 +85,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -86,6 +99,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -102,7 +118,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -111,6 +132,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -127,7 +151,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -136,6 +165,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -152,7 +184,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -161,6 +198,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -177,7 +217,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -186,6 +231,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -202,7 +250,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -212,6 +265,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -228,7 +284,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -237,6 +298,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -256,7 +320,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -265,6 +334,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -281,7 +353,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -290,6 +367,9 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, @@ -306,7 +386,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -315,9 +400,69 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } }, + "/status/{operationId}": { + "get": { + "operationId": "Poll", + "description": "Operation that returns the status of another operation.", + "parameters": [ + { + "$ref": "#/parameters/Azure.Core.Foundations.ApiVersionParameter" + }, + { + "name": "operationId", + "in": "path", + "description": "The unique ID of the operation.", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "The request has succeeded.", + "schema": { + "type": "object", + "description": "Provides status details for long running operations.", + "properties": { + "id": { + "type": "string", + "description": "The unique ID of the operation." + }, + "status": { + "$ref": "#/definitions/Azure.Core.Foundations.OperationState", + "description": "The status of the operation" + }, + "error": { + "$ref": "#/definitions/Azure.Core.Foundations.Error", + "description": "Error object that describes the error when status is \"Failed\"." + } + }, + "required": [ + "id", + "status" + ] + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/Azure.Core.Foundations.ErrorResponse" + }, + "headers": { + "x-ms-error-code": { + "type": "string", + "description": "String error code indicating what went wrong." + } + } + } + } + } + }, "/union/{name}": { "post": { "operationId": "UnionError", @@ -334,7 +479,12 @@ ], "responses": { "202": { - "description": "The request has been accepted for processing, but processing has not yet completed." + "description": "The request has been accepted for processing, but processing has not yet completed.", + "headers": { + "Operation-Location": { + "type": "string" + } + } }, "400": { "description": "The server could not understand the request due to invalid syntax.", @@ -343,11 +493,119 @@ } } }, + "x-ms-long-running-operation-options": { + "final-state-via": "operation-location" + }, "x-ms-long-running-operation": true } } }, "definitions": { + "Accepted": { + "type": "object" + }, + "Azure.Core.Foundations.Error": { + "type": "object", + "description": "The error object.", + "properties": { + "code": { + "type": "string", + "description": "One of a server-defined set of error codes." + }, + "message": { + "type": "string", + "description": "A human-readable representation of the error." + }, + "target": { + "type": "string", + "description": "The target of the error." + }, + "details": { + "type": "array", + "description": "An array of details about specific errors that led to this reported error.", + "items": { + "$ref": "#/definitions/Azure.Core.Foundations.Error" + } + }, + "innererror": { + "$ref": "#/definitions/Azure.Core.Foundations.InnerError", + "description": "An object containing more specific information than the current object about the error." + } + }, + "required": [ + "code", + "message" + ] + }, + "Azure.Core.Foundations.ErrorResponse": { + "type": "object", + "description": "A response containing error details.", + "properties": { + "error": { + "$ref": "#/definitions/Azure.Core.Foundations.Error", + "description": "The error object." + } + }, + "required": [ + "error" + ] + }, + "Azure.Core.Foundations.InnerError": { + "type": "object", + "description": "An object containing more specific information about the error. As per Azure REST API guidelines - https://aka.ms/AzureRestApiGuidelines#handling-errors.", + "properties": { + "code": { + "type": "string", + "description": "One of a server-defined set of error codes." + }, + "innererror": { + "$ref": "#/definitions/Azure.Core.Foundations.InnerError", + "description": "Inner error." + } + } + }, + "Azure.Core.Foundations.OperationState": { + "type": "string", + "description": "Enum describing allowed operation states.", + "enum": [ + "NotStarted", + "Running", + "Succeeded", + "Failed", + "Canceled" + ], + "x-ms-enum": { + "name": "OperationState", + "modelAsString": true, + "values": [ + { + "name": "NotStarted", + "value": "NotStarted", + "description": "The operation has not started." + }, + { + "name": "Running", + "value": "Running", + "description": "The operation is in progress." + }, + { + "name": "Succeeded", + "value": "Succeeded", + "description": "The operation has completed successfully." + }, + { + "name": "Failed", + "value": "Failed", + "description": "The operation has failed." + }, + { + "name": "Canceled", + "value": "Canceled", + "description": "The operation has been canceled by the user." + } + ] + } + }, "CustomScalar": { "type": "string" }, @@ -442,5 +700,16 @@ } } }, - "parameters": {} + "parameters": { + "Azure.Core.Foundations.ApiVersionParameter": { + "name": "api-version", + "in": "query", + "description": "The API version to use for this operation.", + "required": true, + "type": "string", + "minLength": 1, + "x-ms-parameter-location": "method", + "x-ms-client-name": "apiVersion" + } + } } diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/reference-shapes/tsp-diagnostics.json b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/reference-shapes/tsp-diagnostics.json index 815d23da44..7f140c5937 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/reference-shapes/tsp-diagnostics.json +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/reference-shapes/tsp-diagnostics.json @@ -12,62 +12,67 @@ { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." + }, + { + "code": "tsp-lintdiff-local-linter/lro-error-content", + "severity": "warning", + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint", @@ -130,9 +135,39 @@ "message": "Models should not equate to type Record. ARM requires Resource provider teams to define types explicitly." }, { - "code": "@azure-tools/typespec-azure-core/no-openapi", + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The Model named 'Accepted' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The ModelProperty named 'statusCode' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The ModelProperty named 'operationLocation' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "@azure-tools/typespec-azure-resource-manager/arm-resource-operation", + "severity": "warning", + "message": "All operations must be inside an interface declaration." + }, + { + "code": "@azure-tools/typespec-azure-resource-manager/arm-resource-operation", + "severity": "warning", + "message": "All Resource operations must use an api-version parameter. Please include Azure.ResourceManager.ApiVersionParameter in the operation parameter list using the spread (...ApiVersionParameter) operator, or using one of the common resource parameter models." + }, + { + "code": "tsp-lintdiff-local-linter/get-in-operation-name", "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." + "message": "'GET' operation 'Poll' should use method name 'Get' or method name starting with 'List'. Note: If you have already shipped an SDK on top of this spec, fixing this warning may introduce a breaking change." + }, + { + "code": "tsp-lintdiff-local-linter/xms-examples-required", + "severity": "warning", + "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, { "code": "@azure-tools/typespec-azure-core/documentation-required", @@ -174,11 +209,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -209,11 +239,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -254,11 +279,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -289,11 +309,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -334,11 +349,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -369,11 +379,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -419,11 +424,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -459,11 +459,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -519,11 +514,6 @@ "severity": "warning", "message": "The EnumMember named 'bad' should have a documentation or description, use doc comment /** */ to provide it." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -554,11 +544,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -624,11 +609,6 @@ "severity": "warning", "message": "The UnionVariant named 'worse' should have a documentation or description, use doc comment /** */ to provide it." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -664,11 +644,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -709,11 +684,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -744,11 +714,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -789,11 +754,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -824,11 +784,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -874,11 +829,6 @@ "severity": "warning", "message": "@friendlyName should decorate template and use template parameter's properties in friendly name." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -909,11 +859,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -959,11 +904,6 @@ "severity": "warning", "message": "Don't use `| null`. If you meant to have an optional property, use `?`. (e.g. `myProp?: string`)" }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -994,11 +934,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1049,11 +984,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1084,11 +1014,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", @@ -1134,11 +1059,6 @@ "severity": "warning", "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." }, - { - "code": "@azure-tools/typespec-azure-core/no-openapi", - "severity": "warning", - "message": "Azure specs should not be using decorator \"$extension\" from @typespec/openapi or @azure-tools/typespec-autorest. They will not apply to other emitter." - }, { "code": "@azure-tools/typespec-azure-core/documentation-required", "severity": "warning", diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/rule.md b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/rule.md index 39439f1fd7..e45918da46 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/rule.md +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/rule.md @@ -9,145 +9,151 @@ tspRuleset: resource-manager # LroErrorContent -**Severity:** error - -**Applies to:** Resource Manager (ARM) +**Severity:** Swagger error; local TypeSpec warning. +**Applies to:** Resource Manager (ARM), guideline `RPC-Common-V1-05`. - linter code: [LroErrorContent](https://github.com/Azure/azure-openapi-validator/blob/a970d991d2785184d2786b85e0a345dc3f37bc25/packages/rulesets/src/spectral/az-arm.ts#L233-L249) - linter doc: [lro-error-content.md](https://github.com/Azure/azure-openapi-validator/blob/a970d991d2785184d2786b85e0a345dc3f37bc25/docs/lro-error-content.md) -- Guideline: `RPC-Common-V1-05`. +- upstream tests: [lro-error-response.test.ts](https://github.com/Azure/azure-openapi-validator/blob/a970d991d2785184d2786b85e0a345dc3f37bc25/packages/rulesets/src/spectral/test/lro-error-response.test.ts) -## Contract and official coverage +## Swagger contract and official coverage -The non-resolving Swagger rule visits `paths` and `x-ms-paths`, selects operations +The non-resolving validator visits `paths` and `x-ms-paths`, selects operations whose `x-ms-long-running-operation` is exactly `true`, and checks only existing -`schema.$ref` values of `default`, 4xx and 5xx responses. Its pattern requires -`/common-types/resource-management/v2-or-later/types.json#/definitions/ErrorResponse`. -It does not structurally validate inline schemas, require a response body, follow -references, or inspect success responses. The original regex is unanchored and -its dot in `types.json` is unescaped; this migration preserves that behavior for -references available through native Azure metadata APIs. - -Official coverage classification: **gap**. No registered rule in azure-core or -azure-resource-manager enforces this reference check. `no-error-status-codes` -checks status selection; `arm-post-operation-response-codes` checks status sets -and success bodies, not error references. The RPC inventory does not list -`RPC-Common-V1-05`. Standard templates supply `CommonTypes.ErrorResponse` by -default, but `ArmResourceCreateOrReplaceAsync` and `ArmResourceActionAsync` expose -an `Error extends {}` parameter, so custom errors require neither raw extensions -nor suppression. `template-and-versions` proves that native authoring path. - -## Native implementation - -The rule obtains AutoRest-scoped HTTP endpoints from each ARM service, including nested -namespaces, and inspects all declared version snapshots using versioning -mutators. Diagnostics are deduplicated by authored operation node across error -statuses and versions. Removed operations remain diagnosable in the versions -where they existed; they are not compared with latest-only Swagger occurrences. - -LRO selection follows AutoRest: non-GET LRO metadata or AutoRest-scoped -`Legacy.markAsLro`, followed by the explicit OpenAPI extension override. -Response-body handling uses HTTP payload metadata, external/common-type reference -APIs, effective payload models, and `shouldInline`. It does not import or call -AutoRest, run an emitter, or read generated Swagger. `getExternalTypeRef` and -`getArmCommonTypeOpenAPIRef` are native Azure Resource Manager APIs that read -authored program metadata; the latter constructs a reference from common-type -records without emission or file access. The TypeSpec OpenAPI library supplies -shared semantic helpers, not an emitter. - -The ARM service predicate is lintdiff-only isolation because this package enables -both ARM and data-plane rules. On promotion, use the official ARM ruleset as the -applicability boundary rather than adding descendant provider-namespace guards. -There is no AutoRest override adapter to carry into the official library. - -Reference interpretation assumes the standard ARM common-types directory, as in -the fixture and corpus emitter configuration. An arbitrary emitter -`arm-types-dir` override is not a native service semantic and is outside this -comparison. Common-type references interpolate `{arm-types-dir}` before matching. - -`isInScope` uses the same AutoRest TCGC context as the emitter. SDK-only operations -are excluded before LRO detection; `reference-shapes/sdkOnly` and a native -negative test prove that a scoped-out custom-error LRO adds no target diagnostic. -The AutoRest scope name is a string passed to a native TCGC API, not an import or -invocation of the emitter. All native unit tests compile without loading the -AutoRest TypeSpec library. - -### Emitter-only limitation - -Coverage is **partial** for the full Swagger contract. `@Autorest.useRef` -belongs to the emitter and has no supported native equivalent. The lint does not -read its state, inspect its decorator applications, or infer its output. It -validates the underlying native type and native ARM reference metadata instead. -A custom model overridden to a standard Swagger error remains a native -violation; a native standard error overridden to a nonstandard Swagger reference -remains native-clean. These are explicit scope differences, not full equivalence. - -`external-references/standard` and `overriddenStandard` prove both directions. -AutoRest is imported only in the comparison fixture to demonstrate the divergent -Swagger output. Native authors should use `CommonTypes.ErrorResponse` rather -than relying on an emitter override to satisfy the rule. +`schema.$ref` values on default, 4xx, and 5xx responses. References must match +the ARM common-types v2-or-later `ErrorResponse` pattern. It does not require +bodies, validate inline schemas, resolve references, or check success responses. +The regex is unanchored and the dot in `types.json` is unescaped. Upstream tests +cover a local reference, v1 rejection, and v3/v10 acceptance. + +Official coverage is a **gap** on the repair target +`origin/feature/lintdiff-migration-new`. No registered core or ARM rule enforces +this error-payload contract. `no-error-status-codes` checks status selection; +`arm-post-operation-response-codes` checks status sets and success bodies. +Neither checks the error type. The RPC inventory does not list this guideline. +ARM async templates allow an `Error` argument: custom error payloads do not +require bypassing the templates. The `template-and-versions` fixture demonstrates +this authoring path. + +## Repaired native contract + +For non-GET HTTP endpoints with `Azure.Core.getLroMetadata`, every existing +default, 4xx, or 5xx error payload must use ARM common-types v2-or-later +`ErrorResponse`. This includes inline, primitive, collection, binary, and +multipart payloads: schema inlining is not a semantic exemption. The user +explicitly selected this native payload policy during the repair. + +The rule uses HTTP response and read-visibility payload metadata, native ARM +common-type references, and native ARM legacy external-reference metadata. +Standard errors, `model is` copies, and a nullable standard error are accepted. +Derived/custom error models and other payload types are rejected. No body means +no payload to check. Success responses and synchronous operations are excluded. +All response bodies are examined, not an emitter's last-body selection; the +diagnostic is reported once on the authored operation. + +`getArmCommonTypeOpenAPIRef` is an ARM library API reading native common-type +records, not an import of `@typespec/openapi` or an emitter invocation. Native +references retain the validator regex, including its quirks; `{arm-types-dir}` +is interpreted as the standard ARM common-types directory. + +The production rule does not use: + +- TCGC context, SDK scope, or legacy TCGC LRO markers; +- `@typespec/openapi`, extension overrides, or schema-inlining helpers; +- unsafe compiler mutation or version snapshots; +- AutoRest, generated Swagger, private state maps, or copied emitter logic. + +The native tests mock AutoRest and TCGC to throw if imported. Their TypeSpec +snippets need neither library nor OpenAPI decorators. OpenAPI is registered in +the virtual test filesystem solely because the existing ARM library imports it. + +### Applicability and version boundary + +The ARM provider predicate is **lintdiff-only isolation** because the mixed +local ruleset also runs on data-plane services. HTTP service traversal includes +nested namespaces and excludes unused operation templates. Promotion to ARM +should remove this isolation guard, not require provider decorators on each +operation. + +The rule inspects the **unprojected authored program**, not every historical +API-version shape. A removed operation remains visible as authored, while an old +return type recorded by `@returnTypeChangedFrom` is not reconstructed. Native +tests cover both facts. Common-type resolution uses the service's native +metadata/default, without claiming per-version dependency resolution. + +## Explicit partial-coverage boundaries + +These are intentional, observable differences, not full Swagger equivalence: + +- Raw `x-ms-long-running-operation: true` and TCGC-only `markAsLro` do not turn a + synchronous native operation into an LRO for this lint. +- `x-ms-long-running-operation: false` does not disable native LRO checking. +- SDK scope does not exclude an authored HTTP endpoint. +- Inline error schemas that Swagger skips still violate the native standard + payload policy. +- `@Autorest.useRef` does not change native type checking. A custom error + overridden to a standard reference remains a native violation; a standard + native error overridden to a custom reference remains native-clean. +- Historical return types, arbitrary emitter directory overrides, and + serialization decisions not represented in the authored HTTP payload are + outside the native comparison. ## Emission matrix -Source: `packages/typespec-autorest/src/openapi.ts`, especially -`emitResponseObject`, `getSchemaForResponseBody`, `resolveExternalRef`, -`getSchemaOrRef`, `getSchemaForUnion`; and -`core/packages/openapi/src/helpers.ts::shouldInline`. -All outcomes below refer to the selected **top-level** error-response `$ref`. -The surface alone is not evidence for the type shape. - -| Authored shape | Emitter branch | Selected field/value | Swagger / TypeSpec | Fixture | -| ------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------------- | -| Named custom model | Effective payload then non-inline schema | Local `$ref` | Violation / violation | `reference-shapes/models` | -| Model extending standard error | Non-inline derived definition (`allOf` is inside definition) | Local `$ref` | Violation / violation | `reference-shapes/derived` | -| Standard error, `model is` copy | `resolveExternalRef`, copied decorators | v5 ErrorResponse `$ref` | Clean / clean | `inline-and-standard/standard`, `copied` | -| Spread plus added property | Non-inline new definition | Local `$ref` | Violation / violation | `reference-shapes/spreads` | -| Anonymous exact spread | `getEffectivePayloadType` recovers named model | Local `$ref` | Violation / violation | `reference-shapes/effective` | -| Unique anonymous model | Inline model | Absent, object | Clean / clean | `inline-and-standard/anonymous` | -| Named scalar, enum, union | Non-inline pending schema | Local `$ref` | Violation / violation | `reference-shapes/scalarError`, `enumError`, `unionError` | -| Named array / record | Non-inline model | Local `$ref` | Violation / violation | `reference-shapes/arrayError`, `recordError` | -| Anonymous array / record / generic model | `shouldInline` | Absent, array/object | Clean / clean | `inline-and-standard/arrayBody`, `recordBody`, `generic` | -| Friendly-name generic model | `shouldInline` returns false | Local `$ref` | Violation / violation | `reference-shapes/friendly` | -| Nullable custom / standard model | Inline single non-null union member calls `getSchemaOrRef` | Local / common-type `$ref` plus nullable | Violation / violation; clean / clean | `reference-shapes/nullable`, `inline-and-standard/nullableStandard` | -| String, number, boolean literals and string template | Early literal/template branch | Absent, primitive | Clean / clean | `inline-and-standard/literalBody`, `numberBody`, `booleanBody`, `templateBody` | -| Built-in scalar / bytes with JSON | Early standard scalar branch | Absent, primitive | Clean / clean | `inline-and-standard/scalarBody`, `bytesBody` | -| Enum member / tuple / literal union | Inline schema branches, including tuple fallback | Absent, primitive/array | Clean / clean | `inline-and-standard/enumMember`, `tupleBody`, `unionBody` | -| Unknown | Early intrinsic branch | Absent, empty schema | Clean / clean | `inline-and-standard/unknownBody` | -| File / binary bytes / multipart | `getSchemaForResponseBody` bypasses references | Absent, file/string | Clean / clean | `inline-and-standard/fileBody`, `binary`, `multipartBody` | -| No body | No schema emitted | Absent | Clean / clean | `inline-and-standard/noBody` | -| `@useRef` local / v1 / wrong definition | External override before type dispatch | Nonmatching `$ref` | Violation / violation | `external-references/localError`, `old`, `wrong` | -| `@useRef` to v5 on a custom model | Emitter-only external override | Matching `$ref` | Clean / violation (native custom type) | `external-references/standard` | -| `@useRef` to a local definition on a native standard error | Emitter-only external override | Nonmatching `$ref` | Violation / clean (native common type) | `external-references/overriddenStandard` | -| Native ARM legacy external reference to v5 | Native reference metadata | Matching `$ref` | Clean / clean | `external-references/legacy` | -| Success response / sync operation / explicit LRO false | Outside selector | Not selected | Clean / clean | `inline-and-standard/success`, `sync`, `disabled`; `template-and-versions/disabled` | -| Native async template with custom `Error` / legacy LRO marker | LRO flag from metadata / TCGC | Local default-response `$ref` | Violation / violation | `template-and-versions/createOrUpdate`, `marked` | - -HTTP payload resolution passes an explicit body property's **type**, not the -ModelProperty itself, to the response emitter. Namespace, operation, interface, -and template-parameter types cannot form concrete serializable response bodies. -`void`/`never` do not produce an error-response body. Unsupported non-enum -multi-model unions and null-only schemas produce emitter errors; failed emission -is not evidence of clean Swagger. These are excluded from the successful corpus -population rather than assigned a compliance claim. - -## Focused fixtures - -| Fixture | Intent | Target evidence | -| ----------------------- | ---------- | ---------------------------------------------------------------------------------- | -| `non-standard-error` | Violation | Retained raw-extension regression, one custom error | -| `reference-shapes` | Violation | Twelve distinct local reference operations, including a nested namespace | -| `external-references` | Violation | Three shared violations, a native legacy control, and two emitter-only divergences | -| `inline-and-standard` | Compliance | Inline/binary/file/multipart fallthroughs and standard references | -| `template-and-versions` | Violation | Native custom errors, legacy marker, explicit false override, old-only operation | - -The compliant fixture intentionally exercises OpenAPI shapes that other Azure -guidelines discourage. Its `expect.json` records reviewed ambient diagnostics: -documentation, versioning, operation/template conventions, example requirements, -explicit status codes, non-JSON content, and discouraged inline/enum/nullable -types. These do not enforce the target `$ref` contract and are not suppression or -coverage credit. The target rule and validator must both remain silent. - -Focused native tests additionally assert data-plane isolation, unused-template -exclusion, nested ARM traversal, and once-per-source-operation deduplication -across statuses and versions. See [migration evidence](migration.md) for corpus -populations, residual differences, and the final conclusion. +Emitter source is research only: `packages/typespec-autorest/src/openapi.ts`, +especially `emitResponseObject`, `getSchemaForResponseBody`, +`resolveExternalRef`, `getSchemaOrRef`, and `getSchemaForUnion`. +The selected Swagger field is the **top-level error-response `schema.$ref`**. +Unless specified otherwise, rows use native LRO metadata. + +| Authored shape | Emitter branch / selected field | Swagger / native result | Evidence | +| -------------------------------------------------------------------- | ------------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------- | +| Named custom model, derived standard model, spread with extra fields | Named definition / local `$ref` | Violation / violation | `reference-shapes/models`, `derived`, `spreads` | +| Anonymous exact spread of a custom model | Effective payload / local `$ref` | Violation / violation | `reference-shapes/effective` | +| Standard error and `model is` copy | ARM reference metadata / standard `$ref` | Clean / clean | `standard-error`, `inline-and-standard/standard`, `copied` | +| Named scalar, enum, union, array, record | Named definition / local `$ref` | Violation / violation | `reference-shapes/scalarError`, `enumError`, `unionError`, `arrayError`, `recordError` | +| Friendly-name generic | Named definition / local `$ref` | Violation / violation | `reference-shapes/friendly` | +| Nullable custom / standard error | Single non-null member / local or standard `$ref` | Violation / violation; clean / clean | `reference-shapes/nullable`, `inline-and-standard/nullableStandard` | +| Unique anonymous model | Inline model / absent | Clean / violation | `inline-and-standard/anonymous` | +| Anonymous array, record, generic | Inline array or object / absent | Clean / violation | `inline-and-standard/arrayBody`, `recordBody`, `generic` | +| String/number/boolean literal, string template | Literal schema / absent | Clean / violation | `inline-and-standard/literalBody`, `numberBody`, `booleanBody`, `templateBody` | +| Built-in scalar and JSON bytes | Primitive schema / absent | Clean / violation | `inline-and-standard/scalarBody`, `bytesBody` | +| Enum member, tuple, literal union | Inline schema/fallthrough / absent | Clean / violation | `inline-and-standard/enumMember`, `tupleBody`, `unionBody` | +| Unknown | Empty schema / absent | Clean / violation | `inline-and-standard/unknownBody` | +| File, binary bytes, multipart | Body-specialization branch / absent | Clean / violation | `inline-and-standard/fileBody`, `binary`, `multipartBody` | +| Absent body | No schema / absent | Clean / clean | `inline-and-standard/noBody` | +| Emitter override: local, v1, wrong definition | Override / nonstandard `$ref` | Violation / violation of underlying custom type | `external-references/localError`, `old`, `wrong` | +| Emitter override: custom type to standard reference | Override / standard `$ref` | Clean / violation | `external-references/standard` | +| Emitter override: native standard type to local reference | Override / nonstandard `$ref` | Violation / clean | `external-references/overriddenStandard` | +| Native ARM legacy external reference | Native metadata / standard `$ref` | Clean / clean | `external-references/legacy`; native tests also cover v1/wrong definition/v10 | +| Custom success payload or synchronous operation | Outside error/LRO selector | Clean / clean | `inline-and-standard/success`, `sync` | +| Native LRO with explicit false extension | LRO extension override / not selected | Clean / violation | `template-and-versions/disabled`, `inline-and-standard/disabled` | +| Custom error through native async template | LRO metadata / local default `$ref` | Violation / violation | `template-and-versions/createOrUpdate`, `non-standard-error/restartNative` | +| Raw true extension only or legacy TCGC LRO marker only | Emitter-specific LRO / local `$ref` | Violation / clean | `non-standard-error/restart`, `template-and-versions/marked` | +| SDK-only native endpoint | AutoRest scope / operation absent | No emitted comparison / violation | `reference-shapes/sdkOnly` | +| Removed native operation | Latest version excludes operation | No latest comparison / violation | `template-and-versions/oldAction` | +| Historical custom return type, authored standard return type | Old projection differs from authored type | Old violation / authored clean | Native `checks authored return types without reconstructing historical versions` test | + +`void` and `never` do not produce payloads. Namespace, operation, interface, and +uninstantiated template-parameter types are not concrete serializable bodies. +Unsupported multi-model unions and null-only schemas can fail emission; failure +is not evidence of Swagger compliance. No parity claim is made for failed +emissions. + +## Fixture intent and evidence + +Four fixtures have shared violations and explicit partial coverage. +`inline-and-standard` is now a native-violation fixture with **zero Swagger +violations**, not a compliant fixture. `standard-error` is the new compliant +native-template control; its ambient documentation, status-set, naming, path, +version, and example diagnostics are explicitly reviewed in `expect.json`. +They do not validate error payloads or count as target coverage. + +The original raw-extension `restart` retains its two suppressions solely as +divergence evidence. The added native `restartNative` and +`template-and-versions/createOrUpdate` prove the actionable error-payload gap +without those suppressions. A partial fixture result does not imply every +operation in it matches: see the operation-level matrix above. + +See [migration.md](migration.md) for measured fixture counts, corpus populations, +one-sided projects, failures, and remaining uncertainty. diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/standard-error/expect.json b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/standard-error/expect.json new file mode 100644 index 0000000000..3d42a66949 --- /dev/null +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/standard-error/expect.json @@ -0,0 +1,22 @@ +{ + "violation": false, + "ambientDiagnostics": [ + { "code": "@azure-tools/typespec-azure-core/documentation-required", "count": 8 }, + { "code": "@azure-tools/typespec-azure-core/require-versioned", "count": 1 }, + { + "code": "@azure-tools/typespec-azure-resource-manager/arm-post-operation-response-codes", + "count": 1 + }, + { + "code": "@azure-tools/typespec-azure-resource-manager/arm-resource-name-pattern", + "count": 1 + }, + { "code": "tsp-lintdiff-local-linter/descriptive-description-required", "count": 1 }, + { "code": "tsp-lintdiff-local-linter/get-in-operation-name", "count": 1 }, + { "code": "tsp-lintdiff-local-linter/latest-version-of-common-types-must-be-used", "count": 1 }, + { "code": "tsp-lintdiff-local-linter/path-parameter-schema", "count": 9 }, + { "code": "tsp-lintdiff-local-linter/top-level-resources-list-by-resource-group", "count": 1 }, + { "code": "tsp-lintdiff-local-linter/tracked-resource-patch-operation", "count": 1 }, + { "code": "tsp-lintdiff-local-linter/xms-examples-required", "count": 4 } + ] +} diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/standard-error/main.tsp b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/standard-error/main.tsp new file mode 100644 index 0000000000..8abd041a20 --- /dev/null +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/standard-error/main.tsp @@ -0,0 +1,29 @@ +import "../../lib/imports.tsp"; +using TypeSpec.Http; +using TypeSpec.Rest; +using Azure.ResourceManager; + +@armProviderNamespace +@service +@armCommonTypesVersion(CommonTypes.Versions.v5) +namespace Microsoft.StandardErrors; + +model Widget is TrackedResource { + @key("widgetName") + @segment("widgets") + @path + name: string; +} +model WidgetProperties { + @visibility(Lifecycle.Read) + provisioningState?: ResourceProvisioningState; +} +model ErrorCopy is CommonTypes.ErrorResponse; +interface Operations extends Azure.ResourceManager.Operations {} +@armResourceOperations +interface Widgets { + read is ArmResourceRead; + createOrUpdate is ArmResourceCreateOrReplaceAsync; + delete is ArmResourceDeleteWithoutOkAsync; + action is ArmResourceActionAsync; +} diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/standard-error/output.json b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/standard-error/output.json new file mode 100644 index 0000000000..1c1505a9e4 --- /dev/null +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/standard-error/output.json @@ -0,0 +1,354 @@ +{ + "swagger": "2.0", + "info": { + "title": "(title)", + "version": "0000-00-00", + "x-typespec-generated": [ + { + "emitter": "@azure-tools/typespec-autorest" + } + ] + }, + "schemes": [ + "https" + ], + "host": "management.azure.com", + "produces": [ + "application/json" + ], + "consumes": [ + "application/json" + ], + "security": [ + { + "azure_auth": [ + "user_impersonation" + ] + } + ], + "securityDefinitions": { + "azure_auth": { + "type": "oauth2", + "description": "Azure Active Directory OAuth2 Flow.", + "flow": "implicit", + "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/authorize", + "scopes": { + "user_impersonation": "impersonate your user account" + } + } + }, + "tags": [ + { + "name": "Operations" + }, + { + "name": "Widgets" + } + ], + "paths": { + "/providers/Microsoft.StandardErrors/operations": { + "get": { + "operationId": "Operations_List", + "tags": [ + "Operations" + ], + "description": "List the operations for the provider", + "parameters": [ + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + } + ], + "responses": { + "200": { + "description": "Azure operation completed successfully.", + "schema": { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/definitions/OperationListResult" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-pageable": { + "nextLinkName": "nextLink" + } + } + }, + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.StandardErrors/widgets/{widgetName}": { + "get": { + "operationId": "Widgets_Read", + "tags": [ + "Widgets" + ], + "description": "Get a Widget", + "parameters": [ + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/ResourceGroupNameParameter" + }, + { + "name": "widgetName", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Azure operation completed successfully.", + "schema": { + "$ref": "#/definitions/Widget" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + } + }, + "put": { + "operationId": "Widgets_CreateOrUpdate", + "tags": [ + "Widgets" + ], + "description": "Create a Widget", + "parameters": [ + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/ResourceGroupNameParameter" + }, + { + "name": "widgetName", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "resource", + "in": "body", + "description": "Resource create parameters.", + "required": true, + "schema": { + "$ref": "#/definitions/Widget" + } + } + ], + "responses": { + "200": { + "description": "Resource 'Widget' update operation succeeded", + "schema": { + "$ref": "#/definitions/Widget" + } + }, + "201": { + "description": "Resource 'Widget' create operation succeeded", + "schema": { + "$ref": "#/definitions/Widget" + }, + "headers": { + "Azure-AsyncOperation": { + "type": "string", + "description": "A link to the status monitor" + }, + "Retry-After": { + "type": "integer", + "format": "int32", + "description": "The Retry-After header can indicate how long the client should wait before polling the operation status." + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-long-running-operation-options": { + "final-state-via": "azure-async-operation" + }, + "x-ms-long-running-operation": true + }, + "delete": { + "operationId": "Widgets_Delete", + "tags": [ + "Widgets" + ], + "description": "Delete a Widget", + "parameters": [ + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/ResourceGroupNameParameter" + }, + { + "name": "widgetName", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "202": { + "description": "Resource deletion accepted.", + "headers": { + "Location": { + "type": "string", + "description": "The Location header contains the URL where the status of the long running operation can be checked." + }, + "Retry-After": { + "type": "integer", + "format": "int32", + "description": "The Retry-After header can indicate how long the client should wait before polling the operation status." + } + } + }, + "204": { + "description": "Resource does not exist." + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-long-running-operation-options": { + "final-state-via": "location" + }, + "x-ms-long-running-operation": true + } + }, + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.StandardErrors/widgets/{widgetName}/action": { + "post": { + "operationId": "Widgets_Action", + "tags": [ + "Widgets" + ], + "description": "", + "parameters": [ + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/parameters/ResourceGroupNameParameter" + }, + { + "name": "widgetName", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "202": { + "description": "Resource operation accepted.", + "headers": { + "Location": { + "type": "string", + "description": "The Location header contains the URL where the status of the long running operation can be checked." + }, + "Retry-After": { + "type": "integer", + "format": "int32", + "description": "The Retry-After header can indicate how long the client should wait before polling the operation status." + } + } + }, + "204": { + "description": "Azure operation completed successfully." + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-long-running-operation-options": { + "final-state-via": "location" + }, + "x-ms-long-running-operation": true + } + } + }, + "definitions": { + "Azure.ResourceManager.ResourceProvisioningState": { + "type": "string", + "description": "The provisioning state of a resource type.", + "enum": [ + "Succeeded", + "Failed", + "Canceled" + ], + "x-ms-enum": { + "name": "ResourceProvisioningState", + "modelAsString": true, + "values": [ + { + "name": "Succeeded", + "value": "Succeeded", + "description": "Resource has been created." + }, + { + "name": "Failed", + "value": "Failed", + "description": "Resource creation failed." + }, + { + "name": "Canceled", + "value": "Canceled", + "description": "Resource creation was canceled." + } + ] + } + }, + "Widget": { + "type": "object", + "description": "Concrete tracked resource types can be created by aliasing this type using a specific property type.", + "properties": { + "properties": { + "$ref": "#/definitions/WidgetProperties", + "description": "The resource-specific properties for this resource." + } + }, + "allOf": [ + { + "$ref": "../../../../../common-types/resource-management/v5/types.json#/definitions/TrackedResource" + } + ] + }, + "WidgetProperties": { + "type": "object", + "properties": { + "provisioningState": { + "$ref": "#/definitions/Azure.ResourceManager.ResourceProvisioningState", + "readOnly": true + } + } + } + }, + "parameters": {} +} diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/standard-error/tsp-diagnostics.json b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/standard-error/tsp-diagnostics.json new file mode 100644 index 0000000000..80e76b3143 --- /dev/null +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/standard-error/tsp-diagnostics.json @@ -0,0 +1,147 @@ +[ + { + "code": "@azure-tools/typespec-azure-core/require-versioned", + "severity": "warning", + "message": "Azure services should use the versioning library to define versions for their services. Add the '@versioned' decorator to the service namespace." + }, + { + "code": "@azure-tools/typespec-azure-resource-manager/arm-post-operation-response-codes", + "severity": "warning", + "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." + }, + { + "code": "@azure-tools/typespec-azure-resource-manager/arm-resource-name-pattern", + "severity": "warning", + "message": "The resource name parameter should be defined with a 'pattern' restriction. Please use 'ResourceNameParameter' to specify the name parameter with options to override default pattern RegEx expression." + }, + { + "code": "tsp-lintdiff-local-linter/latest-version-of-common-types-must-be-used", + "severity": "warning", + "message": "Use the latest ARM common-types version 'v6' instead of 'v5'." + }, + { + "code": "tsp-lintdiff-local-linter/top-level-resources-list-by-resource-group", + "severity": "warning", + "message": "Top-level resource 'Widget' should define a list by resource group operation." + }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The Model named 'WidgetProperties' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The ModelProperty named 'provisioningState' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The ModelProperty named 'name' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "tsp-lintdiff-local-linter/tracked-resource-patch-operation", + "severity": "warning", + "message": "Tracked resource 'Widget' must have patch operation that at least supports the update of tags." + }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The ModelProperty named 'widgetName' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "tsp-lintdiff-local-linter/get-in-operation-name", + "severity": "warning", + "message": "'GET' operation 'Widgets_Read' should use method name 'Get' or method name starting with 'List'. Note: If you have already shipped an SDK on top of this spec, fixing this warning may introduce a breaking change." + }, + { + "code": "tsp-lintdiff-local-linter/xms-examples-required", + "severity": "warning", + "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." + }, + { + "code": "tsp-lintdiff-local-linter/path-parameter-schema", + "severity": "warning", + "message": "Path parameter should specify a maximum length (maxLength) and characters allowed (pattern)." + }, + { + "code": "tsp-lintdiff-local-linter/path-parameter-schema", + "severity": "warning", + "message": "Path parameter should specify a maximum length (maxLength) and characters allowed (pattern)." + }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The ModelProperty named 'widgetName' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "tsp-lintdiff-local-linter/xms-examples-required", + "severity": "warning", + "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." + }, + { + "code": "tsp-lintdiff-local-linter/path-parameter-schema", + "severity": "warning", + "message": "Path parameter should specify a maximum length (maxLength) and characters allowed (pattern)." + }, + { + "code": "tsp-lintdiff-local-linter/path-parameter-schema", + "severity": "warning", + "message": "Path parameter should specify a maximum length (maxLength) and characters allowed (pattern)." + }, + { + "code": "tsp-lintdiff-local-linter/path-parameter-schema", + "severity": "warning", + "message": "Path parameter should specify a maximum length (maxLength) and characters allowed (pattern)." + }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The ModelProperty named 'widgetName' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "tsp-lintdiff-local-linter/xms-examples-required", + "severity": "warning", + "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." + }, + { + "code": "tsp-lintdiff-local-linter/path-parameter-schema", + "severity": "warning", + "message": "Path parameter should specify a maximum length (maxLength) and characters allowed (pattern)." + }, + { + "code": "tsp-lintdiff-local-linter/path-parameter-schema", + "severity": "warning", + "message": "Path parameter should specify a maximum length (maxLength) and characters allowed (pattern)." + }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The Operation named 'action' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "@azure-tools/typespec-azure-core/documentation-required", + "severity": "warning", + "message": "The ModelProperty named 'widgetName' should have a documentation or description, use doc comment /** */ to provide it." + }, + { + "code": "tsp-lintdiff-local-linter/descriptive-description-required", + "severity": "warning", + "message": "Descriptions cannot be empty or whitespace-only. Provide a meaningful doc string." + }, + { + "code": "tsp-lintdiff-local-linter/xms-examples-required", + "severity": "warning", + "message": "Please provide x-ms-examples describing minimum/maximum property set for response/request payloads for operations." + }, + { + "code": "tsp-lintdiff-local-linter/path-parameter-schema", + "severity": "warning", + "message": "Path parameter should specify a maximum length (maxLength) and characters allowed (pattern)." + }, + { + "code": "tsp-lintdiff-local-linter/path-parameter-schema", + "severity": "warning", + "message": "Path parameter should specify a maximum length (maxLength) and characters allowed (pattern)." + } +] diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/standard-error/validator-diagnostics.json b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/standard-error/validator-diagnostics.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/standard-error/validator-diagnostics.json @@ -0,0 +1 @@ +[] diff --git a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/template-and-versions/tsp-diagnostics.json b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/template-and-versions/tsp-diagnostics.json index 56bd367b6d..132ea19fde 100644 --- a/packages/typespec-lintdiff/test/fixtures/LroErrorContent/template-and-versions/tsp-diagnostics.json +++ b/packages/typespec-lintdiff/test/fixtures/LroErrorContent/template-and-versions/tsp-diagnostics.json @@ -32,17 +32,17 @@ { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/lro-error-content", "severity": "warning", - "message": "Error response references of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error reference." + "message": "Error payloads of long running operations must use the common-types v2 or later ErrorResponse. Use `Azure.ResourceManager.CommonTypes.ErrorResponse` instead of a custom error payload." }, { "code": "tsp-lintdiff-local-linter/top-level-resources-list-by-resource-group", diff --git a/packages/typespec-lintdiff/test/rules/lro-error-content.test.ts b/packages/typespec-lintdiff/test/rules/lro-error-content.test.ts index 27013d8094..e51336f265 100644 --- a/packages/typespec-lintdiff/test/rules/lro-error-content.test.ts +++ b/packages/typespec-lintdiff/test/rules/lro-error-content.test.ts @@ -1,28 +1,45 @@ import { resolvePath } from "@typespec/compiler"; import { createLinterRuleTester, createTester } from "@typespec/compiler/testing"; -import { describe, it, vi } from "vitest"; +import { readFile } from "node:fs/promises"; +import { createSourceFile, isImportDeclaration, ScriptTarget } from "typescript"; +import { describe, expect, it, vi } from "vitest"; import { lroErrorContentRule } from "../../src/rules/lro-error-content.js"; vi.mock("@azure-tools/typespec-autorest", () => { throw new Error("Native lint must not load the AutoRest emitter."); }); +vi.mock("@azure-tools/typespec-client-generator-core", () => { + throw new Error("ARM lint must not load TCGC."); +}); const Tester = createTester(resolvePath(import.meta.dirname, "../.."), { libraries: [ "@typespec/http", + // ARM's own TypeSpec library imports OpenAPI; the rule and test snippets do not use it. "@typespec/openapi", "@typespec/rest", "@typespec/versioning", "@azure-tools/typespec-azure-core", "@azure-tools/typespec-azure-resource-manager", - "@azure-tools/typespec-client-generator-core", ], }).importLibraries(); const header = ` using TypeSpec.Http; - using TypeSpec.OpenAPI; using Azure.ResourceManager; + @armProviderNamespace @service + @armCommonTypesVersion(CommonTypes.Versions.v5) + namespace Arm; + @route("/status") + op poll is Azure.Core.Foundations.GetOperationStatus; + model Accepted { + @statusCode statusCode: 202; + @header("Operation-Location") operationLocation: string; + } + model Failure { @statusCode statusCode: 400; @body body: T; } + @Azure.Core.pollingOperation(poll) + @post + op Lro(): Accepted | Failure; `; const diagnostic = { code: "tsp-lintdiff-local-linter/lro-error-content", @@ -38,125 +55,193 @@ async function tester() { } describe("lro-error-content", () => { - it("accepts native common-type errors and model-is copies without an emitter", async () => { + it("does not import TCGC, OpenAPI, or experimental compiler APIs", async () => { + const source = await readFile( + new URL("../../src/rules/lro-error-content.ts", import.meta.url), + "utf8", + ); + const imports = createSourceFile("lro-error-content.ts", source, ScriptTarget.Latest) + .statements.filter(isImportDeclaration) + .map((statement) => statement.moduleSpecifier.text); + expect(imports).not.toContain("@azure-tools/typespec-client-generator-core"); + expect(imports).not.toContain("@typespec/openapi"); + expect(imports).not.toContain("@typespec/compiler/experimental"); + }); + + it("accepts native common-type errors, model-is copies, and nullable standard errors", async () => { const rule = await tester(); await rule .expect( `${header} - @armProviderNamespace @service - @armCommonTypesVersion(CommonTypes.Versions.v5) - namespace Arm; - model ErrorCopy is CommonTypes.ErrorResponse; - @extension("x-ms-long-running-operation", true) - @route("/standard") @post op standard(): AcceptedResponse | CommonTypes.ErrorResponse; - @extension("x-ms-long-running-operation", true) - @route("/copy") @post op copy(): AcceptedResponse | ErrorCopy; + model Copy is CommonTypes.ErrorResponse; + @route("/standard") op standard is Lro; + @route("/copy") op copy is Lro; + @route("/nullable") op nullable is Lro; `, ) .toBeValid(); }); - it("uses native ARM external-reference metadata without an emitter", async () => { + it("uses native ARM external references and rejects v1 and the wrong definition", async () => { const rule = await tester(); await rule .expect( `${header} - @armProviderNamespace @service namespace Arm; - @Legacy.externalTypeRef("../../common-types/resource-management/v5/types.json#/definitions/ErrorResponse") - @error model StandardError { code?: string; } + @Legacy.externalTypeRef("../../common-types/resource-management/v10/types.json#/definitions/ErrorResponse") + model Standard { code?: string; } @Legacy.externalTypeRef("../../common-types/resource-management/v1/types.json#/definitions/ErrorResponse") - @error model OldError { code?: string; } - @extension("x-ms-long-running-operation", true) - @route("/standard") @post op standard(): AcceptedResponse | StandardError; - @extension("x-ms-long-running-operation", true) - @route("/old") @post op old(): AcceptedResponse | OldError; + model Old { code?: string; } + @Legacy.externalTypeRef("../../common-types/resource-management/v5/types.json#/definitions/ErrorDetail") + model Wrong { code?: string; } + @route("/standard") op standard is Lro; + @route("/old") op old is Lro; + @route("/wrong") op wrong is Lro; + `, + ) + .toEmitDiagnostics([diagnostic, diagnostic]); + }); + + it.each([ + ["named model", "model Custom { code: string; }", "Custom"], + ["derived model", "model Custom extends CommonTypes.ErrorResponse {}", "Custom"], + ["spread model", "model Custom { ...CommonTypes.ErrorResponse; extra: string; }", "Custom"], + ["inline model", "", "{ code: string; }"], + ["scalar", "scalar Custom extends string;", "Custom"], + ["enum", "enum Custom { bad }", "Custom"], + ["union", 'union Custom { "bad", "worse" }', "Custom"], + ["array", "", "string[]"], + ["record", "", "Record"], + ["generic", "model Custom { value: T; }", "Custom"], + ["primitive", "", "string"], + ["literal", "", '"bad"'], + ["tuple", "", "[string, int32]"], + ["unknown", "", "unknown"], + ["bytes", "", "bytes"], + ["nullable custom error", "model Custom { code: string; }", "Custom | null"], + ])("rejects a custom %s error payload", async (_name, declaration, payload) => { + const rule = await tester(); + await rule + .expect( + `${header} + ${declaration} + @route("/run") op run is Lro<${payload}>; `, ) .toEmitDiagnostics([diagnostic]); }); - it("ignores operations scoped out of AutoRest", async () => { + it("reports once across default, 4xx, and 5xx error responses", async () => { const rule = await tester(); await rule .expect( `${header} - @armProviderNamespace @service namespace Arm; - @error model Error { code: string; } - @Azure.ClientGenerator.Core.scope("csharp") - @extension("x-ms-long-running-operation", true) - @route("/sdk-only") @post op sdkOnly(): AcceptedResponse | Error; + @error model Custom { code: string; } + @Azure.Core.pollingOperation(poll) @route("/run") @post + op run(): Accepted | Custom | + { @statusCode statusCode: 400; @body body: string; } | + { @statusCode statusCode: 500; @body body: string; }; `, ) - .toBeValid(); + .toEmitDiagnostics([diagnostic]); }); - it("does not lint an unrelated data-plane service or unused templates", async () => { + it("ignores success responses, absent error bodies, and synchronous operations", async () => { const rule = await tester(); await rule .expect( `${header} - @service namespace DataPlane { - @error model Error { code: string; } - @extension("x-ms-long-running-operation", true) - @route("/run") @post op run(): AcceptedResponse | Error; - } - @armProviderNamespace @service namespace Arm { - @error model Error { code: string; } - @extension("x-ms-long-running-operation", true) - @route("/template") @post op unused(): AcceptedResponse | Error; - } + @Azure.Core.pollingOperation(poll) @route("/no-body") @post + op noBody(): Accepted | { @statusCode statusCode: 400; }; + @Azure.Core.pollingOperation(poll) @route("/success") @post + op success(): Accepted | { @statusCode statusCode: 200; @body body: string; }; + @route("/sync") @post op sync(): Accepted | Failure; `, ) .toBeValid(); }); - it("reports once per operation across error statuses and service versions", async () => { + it("rejects binary and multipart error payloads", async () => { const rule = await tester(); await rule .expect( `${header} - using TypeSpec.Versioning; - @armProviderNamespace @service @versioned(Versions) - namespace Arm; - enum Versions { v1: "2024-01-01", v2: "2025-01-01" } - model Error { code: string; } - @extension("x-ms-long-running-operation", true) - @route("/run") @post op run(): AcceptedResponse | - { @statusCode statusCode: 400; @body body: Error; } | - { @statusCode statusCode: 500; @body body: Error; }; + @Azure.Core.pollingOperation(poll) @route("/binary") @post + op binary(): Accepted | + { @statusCode statusCode: 500; @header contentType: "application/octet-stream"; @body body: bytes; }; + @Azure.Core.pollingOperation(poll) @route("/multipart") @post + op multipart(): Accepted | + { @statusCode statusCode: 500; @multipartBody body: { part: HttpPart; }; }; `, ) - .toEmitDiagnostics([diagnostic]); + .toEmitDiagnostics([diagnostic, diagnostic]); }); - it("includes operations in nested ARM namespaces", async () => { + it("includes nested namespaces but not GET polling operations", async () => { const rule = await tester(); await rule .expect( `${header} - @armProviderNamespace @service namespace Arm { - namespace Nested { - @error model Error { code: string; } - @extension("x-ms-long-running-operation", true) - @route("/run") @post op run(): AcceptedResponse | Error; - } + namespace Nested { @route("/run") op run is Lro; } + @Azure.Core.pollingOperation(poll) @route("/get") @get + op read(): Accepted | Failure; + `, + ) + .toEmitDiagnostics([diagnostic]); + }); + + it("ignores unrelated data-plane services and unused templates", async () => { + const rule = await tester(); + await rule + .expect( + `${header.replace("namespace Arm;", "namespace Arm {")} + } + @service namespace Other { + @Azure.Core.pollingOperation(Arm.poll) @route("/run") @post + op run(): Arm.Accepted | Arm.Failure; } `, + ) + .toBeValid(); + }); + + it("reports a nested service operation only once", async () => { + const rule = await tester(); + await rule + .expect( + `${header} + @service namespace Child { + @route("/child") op run is Lro; + } + `, ) .toEmitDiagnostics([diagnostic]); }); - it("ignores an explicit false extension", async () => { + it("reports a shared operation declaration once across template instantiations", async () => { const rule = await tester(); await rule .expect( `${header} - @armProviderNamespace @service namespace Arm; - @error model Error { code: string; } - @extension("x-ms-long-running-operation", false) - @route("/run") @post op run(): AcceptedResponse | Error; + interface Actions { @route("/run") op run is Lro; } + @route("/one") interface One extends Actions {} + @route("/two") interface Two extends Actions {} + `, + ) + .toEmitDiagnostics([diagnostic]); + }); + + it("checks authored return types without reconstructing historical versions", async () => { + const rule = await tester(); + await rule + .expect( + `${header.replace("@armProviderNamespace @service", "@armProviderNamespace @service @TypeSpec.Versioning.versioned(Versions)")} + enum Versions { v1: "2024-01-01", v2: "2025-01-01" } + @TypeSpec.Versioning.returnTypeChangedFrom(Versions.v2, Accepted | Failure) + @route("/changed") op changed is Lro; + @TypeSpec.Versioning.removed(Versions.v2) + @route("/removed") op removed is Lro; `, ) - .toBeValid(); + .toEmitDiagnostics([diagnostic]); }); });