diff --git a/.chronus/changes/arm-no-repeated-path-info.md b/.chronus/changes/arm-no-repeated-path-info.md new file mode 100644 index 0000000000..0128799985 --- /dev/null +++ b/.chronus/changes/arm-no-repeated-path-info.md @@ -0,0 +1,9 @@ +--- +changeKind: feature +packages: + - "@azure-tools/typespec-azure-resource-manager" +--- + +Add the `no-repeated-path-info` lint rule to warn when ARM PUT resource properties repeat path or query parameter names. + +This complements `arm-resource-duplicate-property`, which checks duplication of resource envelope property names rather than URI parameter names. diff --git a/.chronus/changes/register-no-repeated-path-info-disabled.md b/.chronus/changes/register-no-repeated-path-info-disabled.md new file mode 100644 index 0000000000..79517cf85e --- /dev/null +++ b/.chronus/changes/register-no-repeated-path-info-disabled.md @@ -0,0 +1,7 @@ +--- +changeKind: internal +packages: + - "@azure-tools/typespec-azure-rulesets" +--- + +Register the ARM `no-repeated-path-info` lint rule as disabled in the resource manager ruleset. diff --git a/packages/typespec-azure-resource-manager/README.md b/packages/typespec-azure-resource-manager/README.md index 4d28d8b253..4e481ad091 100644 --- a/packages/typespec-azure-resource-manager/README.md +++ b/packages/typespec-azure-resource-manager/README.md @@ -70,6 +70,7 @@ Available ruleSets: | [`@azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/missing-x-ms-identifiers) | Array properties should describe their identifying properties with x-ms-identifiers. Decorate the property with @OpenAPI.extension("x-ms-identifiers", #[id-prop]) where "id-prop" is a list of the names of identifying properties in the item type. | | [`@azure-tools/typespec-azure-resource-manager/no-response-body`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-response-body) | Check that the body is empty for 202 and 204 responses, and not empty for other success (2xx) responses. | | [`@azure-tools/typespec-azure-resource-manager/no-query-in-post`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-query-in-post) | ARM POST operations must not declare query parameters other than api-version. | +| [`@azure-tools/typespec-azure-resource-manager/no-repeated-path-info`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-repeated-path-info) | ARM PUT request bodies must not repeat path or query parameters in the resource properties bag. | | [`@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/missing-operations-endpoint) | Check for missing Operations interface. | | [`@azure-tools/typespec-azure-resource-manager/patch-envelope`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/patch-envelope) | Patch envelope properties should match the resource properties. | | [`@azure-tools/typespec-azure-resource-manager/arm-resource-patch`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/arm-resource-patch) | Validate ARM PATCH operations. | diff --git a/packages/typespec-azure-resource-manager/src/linter.ts b/packages/typespec-azure-resource-manager/src/linter.ts index eb049e6dd1..35503410e2 100644 --- a/packages/typespec-azure-resource-manager/src/linter.ts +++ b/packages/typespec-azure-resource-manager/src/linter.ts @@ -35,6 +35,7 @@ import { noOverridePropsRule } from "./rules/no-override-props.js"; import { noQueryInCollectionRule } from "./rules/no-query-in-collection.js"; import { noQueryInPointOpRule } from "./rules/no-query-in-point-op.js"; import { noQueryInPostRule } from "./rules/no-query-in-post.js"; +import { noRepeatedPathInfoRule } from "./rules/no-repeated-path-info.js"; import { noReservedResourcePropertyRule } from "./rules/no-reserved-resource-property.js"; import { deleteOperationMissingRule } from "./rules/no-resource-delete-operation.js"; import { noResponseBodyRule } from "./rules/no-response-body.js"; @@ -97,6 +98,7 @@ const rules = [ missingXmsIdentifiersRule, noResponseBodyRule, noQueryInPostRule, + noRepeatedPathInfoRule, operationsInterfaceMissingRule, patchEnvelopePropertiesRules, patchOperationsRule, diff --git a/packages/typespec-azure-resource-manager/src/rules/arm-resource-duplicate-property.md b/packages/typespec-azure-resource-manager/src/rules/arm-resource-duplicate-property.md index c20c144b14..7d18ce1d42 100644 --- a/packages/typespec-azure-resource-manager/src/rules/arm-resource-duplicate-property.md +++ b/packages/typespec-azure-resource-manager/src/rules/arm-resource-duplicate-property.md @@ -1,5 +1,13 @@ Envelope properties should not be duplicated in the `properties` model. +This rule compares properties-bag members with envelope property names on ARM +resource models, even without a PUT operation. It complements +[`no-repeated-path-info`](./no-repeated-path-info.md), which compares them with +HTTP path and query parameter names on PUT operations. For example, an envelope +`name` with `@key("widgetName")` makes `properties.widgetName` a URI duplication +only, while `properties.identity` duplicates an envelope `identity` even when +there is no URI parameter named `identity`. Neither rule replaces the other. + ## Impact - **Area:** API, SDK diff --git a/packages/typespec-azure-resource-manager/src/rules/no-repeated-path-info.md b/packages/typespec-azure-resource-manager/src/rules/no-repeated-path-info.md new file mode 100644 index 0000000000..fb9fc01a91 --- /dev/null +++ b/packages/typespec-azure-resource-manager/src/rules/no-repeated-path-info.md @@ -0,0 +1,112 @@ +ARM PUT request bodies should not repeat information already supplied in path or +query parameters. Keep resource identity in the request URI and resource-specific +data in the resource's `properties` bag, so callers do not have to supply +potentially conflicting values twice. + +This rule compares authored member names in the model-valued `properties` bag +with the PUT operation's HTTP path and query parameter names. It includes +inherited members and reports on the repeated property declaration. Each matching +name is reported once per concrete PUT operation; a declaration shared by two PUT +operations can receive two diagnostics. + +This rule complements +[`arm-resource-duplicate-property`](./arm-resource-duplicate-property.md), which +compares properties-bag members with envelope property names on ARM resource +models, independently of operations. For example, an envelope `name` with +`@key("widgetName")` makes `properties.widgetName` a URI duplication only. +Conversely, `properties.identity` duplicates an envelope `identity` only under +the envelope rule when no URI parameter is named `identity`. Both rules can +report a member when its name matches both an envelope property and a URI parameter. + +The check does not recurse into nested models, inspect top-level envelope +duplicates, or check PATCH bodies. Template sources are skipped, but concrete +operation aliases are checked. Provider namespace metadata is not required when +this rule is selected. The rule is available but disabled by default in the +Azure resource-manager ruleset. + +`@encodedName("application/json", ...)` does not change an authored property +name for this check. A JSON alias alone cannot introduce or remove a diagnostic; +HTTP `@path` and `@query` name overrides do determine the parameter names. + +## Impact + +- **Area:** API, SDK + +Repeating URI information in resource properties creates redundant SDK inputs +and ambiguity when the request URI and payload disagree. + +## ❌ Incorrect + +The standard ARM create-or-replace operation carries `widgetName` in the URI. +Repeating it in `WidgetProperties` triggers this rule. + +```tsp +@armProviderNamespace +namespace Microsoft.Contoso; + +model Widget is TrackedResource { + @key("widgetName") + @segment("widgets") + @path + name: string; +} + +model WidgetProperties { + widgetName?: string; + description?: string; +} + +@armResourceOperations +interface Widgets { + createOrUpdate is ArmResourceCreateOrReplaceSync; +} +``` + +## ✅ Correct + +Keep the same operation template and remove only the redundant properties-bag +member. + +```tsp +@armProviderNamespace +namespace Microsoft.Contoso; + +model Widget is TrackedResource { + @key("widgetName") + @segment("widgets") + @path + name: string; +} + +model WidgetProperties { + description?: string; +} + +@armResourceOperations +interface Widgets { + createOrUpdate is ArmResourceCreateOrReplaceSync; +} +``` + +## Suppression + +Prefer removing the redundant properties-bag member. If a published API must +retain the duplication for compatibility and an ARM reviewer has approved the +exception, suppress the warning on that member with a specific justification: + +```tsp +model WidgetProperties { + #suppress "@azure-tools/typespec-azure-resource-manager/no-repeated-path-info" "Retained for compatibility with the approved published API." + widgetName?: string; +} +``` + +## LintDiff Equivalent + +This rule corresponds to +[RepeatedPathInfo](https://github.com/Azure/azure-openapi-validator/blob/main/docs/repeated-path-info.md) +and ARM guideline `RPC-Put-V1-05`. Unlike the Swagger validator, which compares +serialized JSON keys, this rule deliberately compares authored TypeSpec member +names. JSON name overrides therefore have only partial Swagger parity. +The rule checks the compiler's native operation graph, not an emitted +single-version schema. diff --git a/packages/typespec-azure-resource-manager/src/rules/no-repeated-path-info.ts b/packages/typespec-azure-resource-manager/src/rules/no-repeated-path-info.ts new file mode 100644 index 0000000000..89c45fc611 --- /dev/null +++ b/packages/typespec-azure-resource-manager/src/rules/no-repeated-path-info.ts @@ -0,0 +1,79 @@ +import type { Model } from "@typespec/compiler"; +import { + createRule, + fileRef, + getProperty, + isTemplateInstance, + paramMessage, + walkPropertiesInherited, +} from "@typespec/compiler"; +import { getHttpOperation } from "@typespec/http"; +import { isTemplatedInterfaceOperation } from "./utils.js"; + +export const noRepeatedPathInfoRule = createRule({ + name: "no-repeated-path-info", + docs: fileRef.fromPackageRoot("src/rules/no-repeated-path-info.md"), + description: + "ARM PUT request bodies must not repeat path or query parameters in the resource properties bag.", + severity: "warning", + url: "https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-repeated-path-info", + messages: { + default: paramMessage`Request body property '${"name"}' repeats information already carried in the path or query.`, + }, + create(context) { + return { + operation: (operation) => { + if (isTemplateInstance(operation) || isTemplatedInterfaceOperation(operation)) { + return; + } + + const [httpOperation] = getHttpOperation(context.program, operation); + if (httpOperation.verb !== "put") { + return; + } + + const repeatedNames = new Set( + httpOperation.parameters.parameters + .filter((parameter) => parameter.type === "path" || parameter.type === "query") + .map((parameter) => parameter.name), + ); + if (repeatedNames.size === 0) { + return; + } + + const body = httpOperation.parameters.body; + if (body === undefined || body.bodyKind !== "single" || body.type.kind !== "Model") { + return; + } + + const propertiesModel = getPropertiesModel(body.type); + if (propertiesModel === undefined) { + return; + } + + const reportedNames = new Set(); + for (const property of walkPropertiesInherited(propertiesModel)) { + if (!repeatedNames.has(property.name)) { + continue; + } + if (reportedNames.has(property.name)) { + continue; + } + reportedNames.add(property.name); + + context.reportDiagnostic({ + target: property, + format: { + name: property.name, + }, + }); + } + }, + }; + }, +}); + +function getPropertiesModel(model: Model): Model | undefined { + const property = getProperty(model, "properties"); + return property?.type.kind === "Model" ? property.type : undefined; +} diff --git a/packages/typespec-azure-resource-manager/test/package-isolation.test.ts b/packages/typespec-azure-resource-manager/test/package-isolation.test.ts new file mode 100644 index 0000000000..140bc811ff --- /dev/null +++ b/packages/typespec-azure-resource-manager/test/package-isolation.test.ts @@ -0,0 +1,13 @@ +import { expect, it, vi } from "vitest"; +import * as library from "../src/index.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("Native lint must not load TCGC."); +}); + +it("loads the ARM public entry point without emitter or TCGC dependencies", () => { + expect(library.$lib.name).toBe("@azure-tools/typespec-azure-resource-manager"); +}); diff --git a/packages/typespec-azure-resource-manager/test/rules/no-repeated-path-info.test.ts b/packages/typespec-azure-resource-manager/test/rules/no-repeated-path-info.test.ts new file mode 100644 index 0000000000..c5ec825791 --- /dev/null +++ b/packages/typespec-azure-resource-manager/test/rules/no-repeated-path-info.test.ts @@ -0,0 +1,318 @@ +import { Tester } from "#test/tester.js"; +import { getSourceLocation } from "@typespec/compiler"; +import { + createLinterRuleTester, + expectDiagnostics, + type LinterRuleTester, + type TesterInstance, +} from "@typespec/compiler/testing"; +import { beforeEach, describe, it } from "vitest"; +import { noRepeatedPathInfoRule } from "../../src/rules/no-repeated-path-info.js"; + +let runner: TesterInstance; + +let tester: LinterRuleTester; + +beforeEach(async () => { + runner = await Tester.createInstance(); + tester = createLinterRuleTester( + runner, + noRepeatedPathInfoRule, + "@azure-tools/typespec-azure-resource-manager", + ); +}); + +function diagnostic(name = "widgetName") { + return { + code: "@azure-tools/typespec-azure-resource-manager/no-repeated-path-info", + severity: "warning" as const, + message: `Request body property '${name}' repeats information already carried in the path or query.`, + }; +} + +function armResource(properties: string, parameters?: string) { + return ` + @armProviderNamespace + namespace Microsoft.Contoso; + + model Widget is TrackedResource { + @key("widgetName") @segment("widgets") @path name: string; + } + model WidgetProperties { ${properties} } + + @armResourceOperations + interface Widgets { + createOrUpdate is ArmResourceCreateOrReplaceSync; + } + `; +} + +it("compiles the standard incorrect and correct examples with the rule enabled", async () => { + const incorrect = `@armProviderNamespace +namespace Microsoft.Contoso; + +model Widget is TrackedResource { + @key("widgetName") + @segment("widgets") + @path + name: string; +} + +model WidgetProperties { + /*repeated*/widgetName?: string; + description?: string; +} + +@armResourceOperations +interface Widgets { + createOrUpdate is ArmResourceCreateOrReplaceSync; +} +`; + const correct = `@armProviderNamespace +namespace Microsoft.Contoso; + +model Widget is TrackedResource { + @key("widgetName") + @segment("widgets") + @path + name: string; +} + +model WidgetProperties { + description?: string; +} + +@armResourceOperations +interface Widgets { + createOrUpdate is ArmResourceCreateOrReplaceSync; +} +`; + + await tester.expect(incorrect).toEmitDiagnostics(({ repeated }) => ({ + ...diagnostic(), + pos: getSourceLocation(repeated).pos, + end: getSourceLocation(repeated).end, + })); + + await tester.expect(correct).toBeValid(); +}); + +const operation = ` + @put @route("/widgets/{widgetName}") + op create(@path widgetName: string, @body body: { properties: Properties }): void; +`; + +it("reports a path name repeated in ARM PUT resource properties", async () => { + await tester.expect(armResource("widgetName?: string;")).toEmitDiagnostics([diagnostic()]); +}); + +it("targets the repeated authored property, including inherited properties", async () => { + await tester + .expect( + ` + ${operation} + model Base { /*repeated*/widgetName?: string; } + model Properties extends Base {} + `, + ) + .toEmitDiagnostics(({ repeated }) => ({ + ...diagnostic(), + pos: getSourceLocation(repeated).pos, + end: getSourceLocation(repeated).end, + })); +}); + +it("reports a query name repeated in ARM PUT resource properties", async () => { + await tester + .expect(armResource("mode?: string;", "{ @query mode?: string; }")) + .toEmitDiagnostics([diagnostic("mode")]); +}); + +it("reports each repeated ARM path name", async () => { + await tester + .expect(armResource("widgetName?: string; resourceGroupName?: string;")) + .toEmitDiagnostics([diagnostic(), diagnostic("resourceGroupName")]); +}); + +it("reports a repeated tenant resource path name", async () => { + await tester + .expect( + ` + @armProviderNamespace + namespace Microsoft.Contoso; + @tenantResource + model TenantConfig is ProxyResource<{ configName?: string; setting?: string; }> { + @key("configName") @segment("configurations") @path name: string; + } + @armResourceOperations + interface TenantConfigs { + createOrUpdate is ArmResourceCreateOrReplaceSync< + TenantConfig, BaseParameters = Foundations.TenantBaseParameters + >; + } + `, + ) + .toEmitDiagnostics([diagnostic("configName")]); +}); + +it("allows ARM PUT properties with no repeated names", async () => { + await tester.expect(armResource("description?: string; sku?: string;")).toBeValid(); +}); + +it("ignores repeated top-level body properties", async () => { + await tester + .expect( + ` + @put @route("/widgets/{widgetName}") + op create(@path widgetName: string, + @body body: { widgetName?: string; properties: { description?: string } }): void; + `, + ) + .toBeValid(); +}); + +it("ignores repeated properties in PATCH bodies", async () => { + await tester + .expect( + ` + @patch @route("/widgets/{widgetName}") + op update(@path widgetName: string, + @body body: { properties: { widgetName?: string } }): void; + `, + ) + .toBeValid(); +}); + +it("allows a JSON alias that alone repeats a path name", async () => { + await tester + .expect(armResource('@encodedName("application/json", "widgetName") otherName?: string;')) + .toBeValid(); +}); + +it("reports an authored name even when its JSON alias differs", async () => { + await tester + .expect(armResource('@encodedName("application/json", "otherName") widgetName?: string;')) + .toEmitDiagnostics([diagnostic()]); +}); + +it("uses supported HTTP parameter names rather than parameter source identifiers", async () => { + await tester + .expect( + ` + model Properties { wirePath?: string; wireQuery?: string; pathSource?: string; } + @put @route("/widgets/{wirePath}") + op create(@path("wirePath") pathSource: string, @query("wireQuery") querySource: string, + @body body: { properties: Properties }): void; + `, + ) + .toEmitDiagnostics([diagnostic("wirePath"), diagnostic("wireQuery")]); +}); + +it("does not recurse into cycles or shared sibling models", async () => { + await tester + .expect( + ` + ${operation} + model Nested { widgetName?: string; parent?: Properties; } + model Properties { self?: Properties; first?: Nested; second?: Nested; } + `, + ) + .toBeValid(); +}); + +it("reports a shared declaration once per PUT operation at the same source target", async () => { + await tester + .expect( + ` + ${operation} + model Properties { /*repeated*/widgetName?: string; } + @put @route("/other/{widgetName}") + op other(@path widgetName: string, @body body: { properties: Properties }): void; + `, + ) + .toEmitDiagnostics(({ repeated }) => + [1, 2].map(() => ({ + ...diagnostic(), + pos: getSourceLocation(repeated).pos, + end: getSourceLocation(repeated).end, + })), + ); +}); + +it("ignores missing bodies, non-model bodies, and missing or non-model properties bags", async () => { + await tester + .expect( + ` + @put @route("/none/{widgetName}") + op noBody(@path widgetName: string): void; + @put @route("/string/{widgetName}") + op stringBody(@path widgetName: string, @body body: string): void; + @put @route("/missing/{widgetName}") + op missingBag(@path widgetName: string, @body body: { description?: string }): void; + @put @route("/scalar/{widgetName}") + op scalarBag(@path widgetName: string, @body body: { properties: string }): void; + `, + ) + .toBeValid(); +}); + +it("checks ordinary project namespaces without provider metadata", async () => { + await tester + .expect(`namespace Contoso; ${operation} model Properties { widgetName?: string; }`) + .toEmitDiagnostics([diagnostic()]); +}); + +describe("URI and envelope name boundaries", () => { + it.each(["widgetName", "identity"])( + "distinguishes envelope duplication from URI duplication with @key(%s)", + async (parameterName) => { + const [{ widgetName, identity }, diagnostics] = await Tester.compileAndDiagnose( + ` + @armProviderNamespace + namespace Microsoft.Contoso; + model Widget is TrackedResource { + @key("${parameterName}") @segment("widgets") @path name: string; + ...ManagedServiceIdentityProperty; + } + model WidgetProperties { + /*widgetName*/widgetName?: string; + /*identity*/identity?: string; + } + @armResourceOperations + interface Widgets { + createOrUpdate is ArmResourceCreateOrReplaceSync; + } + `, + { + compilerOptions: { + linterRuleSet: { + enable: { + "@azure-tools/typespec-azure-resource-manager/arm-resource-duplicate-property": true, + "@azure-tools/typespec-azure-resource-manager/no-repeated-path-info": true, + }, + }, + }, + }, + ); + const envelopeLocation = getSourceLocation(identity); + const uriLocation = getSourceLocation(parameterName === "widgetName" ? widgetName : identity); + expectDiagnostics(diagnostics, [ + { + code: "@azure-tools/typespec-azure-resource-manager/arm-resource-duplicate-property", + severity: "warning", + message: + 'Duplicate property "identity" found in the resource envelope and resource properties. Please do not duplicate envelope properties in resource properties.', + pos: envelopeLocation.pos, + end: envelopeLocation.end, + }, + { + code: "@azure-tools/typespec-azure-resource-manager/no-repeated-path-info", + severity: "warning", + message: `Request body property '${parameterName}' repeats information already carried in the path or query.`, + pos: uriLocation.pos, + end: uriLocation.end, + }, + ]); + }, + ); +}); diff --git a/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts b/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts index f5fef2dee8..33933fa75c 100644 --- a/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts +++ b/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts @@ -102,6 +102,7 @@ export default { "@azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers": true, "@azure-tools/typespec-azure-resource-manager/no-response-body": true, "@azure-tools/typespec-azure-resource-manager/no-query-in-post": false, + "@azure-tools/typespec-azure-resource-manager/no-repeated-path-info": false, "@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint": true, "@azure-tools/typespec-azure-resource-manager/patch-envelope": true, "@azure-tools/typespec-azure-resource-manager/arm-resource-patch": true, diff --git a/website/src/content/docs/docs/howtos/ARM/arm-rules.md b/website/src/content/docs/docs/howtos/ARM/arm-rules.md index 129723444f..e395bb3623 100644 --- a/website/src/content/docs/docs/howtos/ARM/arm-rules.md +++ b/website/src/content/docs/docs/howtos/ARM/arm-rules.md @@ -130,6 +130,7 @@ The tables below provide guidance to rule authors and ARM reviewers on how to ev | [`missing-x-ms-identifiers`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/missing-x-ms-identifiers/) | [R4041](https://github.com/Azure/azure-rest-api-specs/blob/main/documentation/openapi-authoring-automated-guidelines.md#r4041) (warning) | **API.** No real impact (an old pattern). | | [`no-empty-model`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-empty-model/) | [R4037](https://github.com/Azure/azure-rest-api-specs/blob/main/documentation/openapi-authoring-automated-guidelines.md#r4037) | **API, SDK.** Accepting any schema makes the API and SDK difficult to use. | | [`no-override-props`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-override-props/) | — | **SDK, Tooling.** Violations can crash the breaking-change tool and are unsupported by most languages. | +| [`no-repeated-path-info`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-repeated-path-info/) | [`RepeatedPathInfo`](https://github.com/Azure/azure-openapi-validator/blob/main/docs/repeated-path-info.md) (partial) | **API, SDK.** ARM PUT resource properties repeat URI inputs, creating redundant SDK inputs and potentially conflicting values. | | [`no-query-in-collection`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-query-in-collection/) | [`QueryParametersInCollectionGet`](https://github.com/Azure/azure-openapi-validator/blob/main/packages/rulesets/src/spectral/functions/query-parameters-in-collection-get.ts) | **API, SDK.** Nonstandard collection query parameters make list operations and generated SDK signatures inconsistent. | | [`no-query-in-point-op`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-query-in-point-op/) | [`ValidQueryParametersForPointOperations`](https://github.com/Azure/azure-openapi-validator/blob/main/docs/valid-query-parameters-for-point-operations.md) | **API, SDK.** Point-operation query parameters beyond `api-version` violate the ARM request contract and complicate SDK signatures. | | [`no-query-in-post`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-query-in-post/) | [`ParametersInPost`](https://github.com/Azure/azure-openapi-validator/blob/main/docs/parameters-in-post.md) | **API, SDK.** POST inputs outside the request body create nonstandard APIs and unstable SDK signatures. | diff --git a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md index 7e2b168993..94cfacf19f 100644 --- a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md +++ b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md @@ -64,6 +64,7 @@ Available ruleSets: | [`@azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers`](../rules/missing-x-ms-identifiers.md) | Array properties should describe their identifying properties with x-ms-identifiers. Decorate the property with @OpenAPI.extension("x-ms-identifiers", #[id-prop]) where "id-prop" is a list of the names of identifying properties in the item type. | | [`@azure-tools/typespec-azure-resource-manager/no-response-body`](../rules/no-response-body.md) | Check that the body is empty for 202 and 204 responses, and not empty for other success (2xx) responses. | | [`@azure-tools/typespec-azure-resource-manager/no-query-in-post`](../rules/no-query-in-post.md) | ARM POST operations must not declare query parameters other than api-version. | +| [`@azure-tools/typespec-azure-resource-manager/no-repeated-path-info`](../rules/no-repeated-path-info.md) | ARM PUT request bodies must not repeat path or query parameters in the resource properties bag. | | [`@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint`](../rules/missing-operations-endpoint.md) | Check for missing Operations interface. | | [`@azure-tools/typespec-azure-resource-manager/patch-envelope`](../rules/patch-envelope.md) | Patch envelope properties should match the resource properties. | | [`@azure-tools/typespec-azure-resource-manager/arm-resource-patch`](../rules/arm-resource-patch.md) | Validate ARM PATCH operations. |