Skip to content

[Swagger Linter Migration] PatchBodyParametersSchema - #5294

Open
Yuchao Yan (msyyc) wants to merge 8 commits into
Azure:mainfrom
msyyc:promote-lintdiff-patch-body-parameters-schema-to-arm
Open

[Swagger Linter Migration] PatchBodyParametersSchema#5294
Yuchao Yan (msyyc) wants to merge 8 commits into
Azure:mainfrom
msyyc:promote-lintdiff-patch-body-parameters-schema-to-arm

Conversation

@msyyc

@msyyc Yuchao Yan (msyyc) commented Aug 25, 2026

Copy link
Copy Markdown
Member

Original Swagger linter

The original rule implements ARM RPC guideline RPC-Patch-V1-10.

The Swagger linter checks ARM PATCH request-body schemas and reports:

  • properties emitted as required in the PATCH body schema
  • properties emitted with a default
  • properties emitted as x-ms-mutability: ["create"]
  • nested properties reached through emitted schema references
  • required discriminator properties that Autorest emits or synthesizes
  • top-level emitted identity properties are skipped to preserve the Swagger rule's ARM identity-envelope exception

How the Swagger linter works

The Swagger validator runs over emitted OpenAPI PATCH operations. It finds the PATCH operation request body schema, traverses emitted schema properties and referenced definitions, and reports diagnostics for properties that are required, carry defaults, or carry create-only x-ms-mutability. Diagnostics are tied to emitted Swagger schema locations rather than TypeSpec source declarations.

Known Swagger behavior intentionally not copied verbatim: the validator checks default values by truthiness, so it misses emitted defaults such as false, 0, and "". The promoted TypeSpec rule treats any authored default as invalid because the stated rule bans defaults, not only truthy defaults. The Swagger rule also skips a top-level emitted property named identity; the promoted rule keeps that exception.

Source TypeSpec lintdiff rule

Source lintdiff rule id: PatchBodyParametersSchema

Local lintdiff rule name: patch-body-parameters-schema

Canonical validator rule slug: patch-body-parameters-schema

Source branch: feature/lintdiff-patch-body-parameters-schema

Source worktree: C:\dev\worktrees\lintdiff-patch-body-parameters-schema

Source commit: 9106cc7d74909f10b246cb1138ac58065b6960a2

Source worktree uncommitted rule changes: none.

Original lintdiff source rule: https://github.com/msyyc/typespec-azure/blob/feature/lintdiff-patch-body-parameters-schema/packages/typespec-lintdiff/src/rules/patch-body-parameters-schema.ts

The user-marked done source rule was not modified during promotion. The source branch was pushed unchanged to make branch-based evidence links resolvable.

Destination analysis

Selected package: @azure-tools/typespec-azure-resource-manager.

Core was considered but rejected because the rule is ARM-specific. Evidence:

  • fixture front matter says Applies to: Resource Manager (ARM)
  • catalog metadata has applicability: "ARM" and sources: ["arm"]
  • the lintdiff source imports ARM provider namespace resolution and gates diagnostics to ARM provider namespaces
  • the rule checks ARM PATCH request bodies, ARM lifecycle visibility, and the ARM top-level identity PATCH exception
  • validation report entries infer the resource-manager ruleset for the direct lintdiff mapping
  • no existing official core or ARM rule had equivalent coverage

Official rule name mapping: PatchBodyParametersSchema / patch-body-parameters-schema promotes to patch-body-invalid-property to follow TypeSpec linter naming conventions with a concise subject-oriented rule name.

How the promoted TypeSpec linter works

The promoted rule registers @azure-tools/typespec-azure-resource-manager/patch-body-invalid-property. It visits ARM provider-namespace operations, filters to HTTP PATCH operations, and reads the effective request body from getHttpOperation.

The rule uses resolveRequestVisibility, createMetadataInfo, MetadataInfo.isTransformed, MetadataInfo.isPayloadProperty, and MetadataInfo.isOptional so checks follow the same effective PATCH payload decisions used by Autorest. It recurses through model properties and single non-null union variants, skips unsupported multi-model unions, skips never-typed properties omitted from emitted payloads, and retargets diagnostics from imported-library declarations to the nearest project-owned model or operation.

It reports:

  • required when a PATCH body property is effectively required or a discriminator is emitted as required
  • default when a PATCH body property has any authored default value, including falsy defaults
  • createOnly when a PATCH body property is visible only for Lifecycle.Create

The diagnostic property name uses the emitted JSON name from resolveEncodedName, and top-level emitted JSON identity remains exempt to match ARM PATCH identity-envelope behavior.

Fixture-to-native test mapping

Original lintdiff fixture Native vitest case Coverage note
required-patch-property it("emits diagnostics for required PATCH body properties", async () => { Required PATCH body property branch.
nullable-body-required-property it("emits diagnostics for required properties in nullable top-level PATCH bodies", async () => { Nullable top-level body unwraps to a single model variant.
nullable-model-required-property it("emits diagnostics for required properties in nullable nested PATCH models", async () => { Nullable nested model traversal.
discriminator-required-patch-property it("emits diagnostics for discriminator properties that Autorest requires", async () => { Authored optional, synthesized, and inherited discriminator requiredness.
default-patch-property it("emits diagnostics for PATCH body properties with defaults", async () => { Truthy and falsy default-valued properties. Falsy defaults are intentional TypeSpec coverage documented in migration.md.
create-only-patch-property it("emits diagnostics for PATCH body properties that are only visible on create", async () => { Exact create-only lifecycle visibility branch.
encoded-non-identity-violating it("emits diagnostics for authored identity properties encoded away from top-level identity", async () => { Authored identity is checked when emitted JSON name is not top-level identity.
multi-model-union-compliant it("allows unsupported unions with multiple model variants", async () => { Unsupported multi-model unions emit no traversable PATCH schema properties.
never-property-compliant it("allows required PATCH body properties omitted because their type is never", async () => { Required never property is omitted from the emitted PATCH payload.
top-level-identity-compliant it("allows top-level identity PATCH body properties", async () => { Top-level emitted identity property exception.
synthesized-identity-discriminator-compliant it("allows top-level identity discriminator properties synthesized by Autorest", async () => { Top-level synthesized identity discriminator exception.
encoded-identity-compliant it("allows PATCH body properties encoded as top-level identity", async () => { Authored non-identity property emitted as top-level JSON identity is skipped.
implicit-optional-patch-compliant it("allows required and create-only source properties removed from the emitted PATCH schema", async () => { PATCH transform optionalizes required properties and omits create-only payload properties.
N/A (baseline compliant native case) it("allows optional PATCH body properties", async () => { Minimal compliant PATCH body model.

Migration evidence

Detailed migration evidence, focused fixture results, real-service project comparison, latest full-corpus counts, one-sided project explanations, compile failures, and remaining uncertainty are in the lintdiff source branch: https://github.com/msyyc/typespec-azure/blob/feature/lintdiff-patch-body-parameters-schema/packages/typespec-lintdiff/test/fixtures/PatchBodyParametersSchema/migration.md

Validation

  • mise exec -- pnpm -r --filter "@azure-tools/typespec-azure-resource-manager..." build
  • mise exec -- pnpm --filter @azure-tools/typespec-azure-resource-manager exec vitest run test/rules/patch-body-invalid-property.test.ts
  • mise exec -- pnpm --filter @azure-tools/typespec-azure-resource-manager build
  • mise exec -- pnpm --filter @azure-tools/typespec-azure-resource-manager lint
  • mise exec -- pnpm --filter @azure-tools/typespec-azure-resource-manager regen-docs
  • mise exec -- pnpm run format:check
  • mise exec -- pnpm --filter "@azure-tools/typespec-azure-website..." run build
  • mise exec -- pnpm --filter @azure-tools/typespec-azure-rulesets build
  • mise exec -- pnpm --filter @azure-tools/typespec-azure-rulesets test
  • mise exec -- pnpm --filter @azure-tools/typespec-azure-resource-manager test
  • git diff --check

Validation blocker

mise exec -- pnpm validate:pr was run with a bounded wait and stopped after 5 minutes with no visible progress beyond step 1:

Pre-PR Validation
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 1. Branch is up to date ................................. ✅ (2.7s)

The narrower required validation set above completed successfully after the new files were formatted.

Promotion sync policy

Semantic gaps found after promotion should block this promotion PR until the user explicitly reopens lintdiff repair. The lintdiff source rule was not modified during promotion.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@msyyc Yuchao Yan (msyyc) added the int:azure-specs Run integration tests against azure-rest-api-specs label Aug 25, 2026
@microsoft-github-policy-service microsoft-github-policy-service Bot added lib:azure-resource-manager Issues for @azure-tools/typespec-azure-core library meta:website TypeSpec.io updates linter Issues related to linter rules labels Aug 25, 2026
@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • @azure-tools/typespec-azure-resource-manager
  • @azure-tools/typespec-azure-rulesets
Show changes

@azure-tools/typespec-azure-resource-manager - feature ✏️

Add the no-unsafe-patch-body-properties ARM linter rule to report required, default-valued, and create-only properties emitted in PATCH request bodies.

@azure-tools/typespec-azure-rulesets - feature ✏️

Add the no-unsafe-patch-body-properties ARM linter rule to report required, default-valued, and create-only properties emitted in PATCH request bodies.

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

📦 Package size report

✅ No notable package size changes compared to the base branch.

13 package(s) with no notable change
Package Packed (base → head) Δ Packed Unpacked (base → head) Δ Unpacked
@azure-tools/typespec-go 260.21 KB → 261.24 KB +1.03 KB (+0.4%) 1.33 MB → 1.34 MB +4.82 KB (+0.4%)
@azure-tools/typespec-azure-resource-manager 175.59 KB → 175.74 KB +153 B (+0.1%) 1.06 MB → 1.06 MB +1.26 KB (+0.1%)
@azure-tools/typespec-autorest 80.93 KB → 81.01 KB +85 B (+0.1%) 395.06 KB → 395.40 KB +348 B (+0.1%)
@azure-tools/typespec-java 13.51 MB → 13.51 MB -70 B (-0.0%) 15.03 MB → 15.03 MB
@azure-tools/typespec-azure-rulesets 5.19 KB → 5.25 KB +53 B (+1.0%) 32.38 KB → 32.78 KB +410 B (+1.2%)
@azure-tools/azure-http-specs 146.71 KB → 146.71 KB 1.16 MB → 1.16 MB
@azure-tools/typespec-autorest-canonical 7.42 KB → 7.42 KB 26.00 KB → 26.00 KB
@azure-tools/typespec-azure-core 130.84 KB → 130.84 KB 709.84 KB → 709.84 KB
@azure-tools/typespec-azure-portal-core 42.40 KB → 42.40 KB 192.91 KB → 192.91 KB
@azure-tools/typespec-client-generator-core 229.48 KB → 229.48 KB 1.23 MB → 1.23 MB
@azure-tools/typespec-metadata 15.91 KB → 15.91 KB 62.26 KB → 62.26 KB
@azure-tools/typespec-python 42.22 KB → 42.22 KB 164.91 KB → 164.91 KB
@azure-tools/typespec-ts 536.70 KB → 536.70 KB 2.58 MB → 2.58 MB

Packed = gzipped .tgz published to npm. Unpacked = total extracted size. 🆕 added, 🗑️ removed. Packages from the core/ submodule are not included.
🔴 grew · 🟢 shrank — only changes of at least 512 B and 0.5% are marked.

@pkg-pr-new

pkg-pr-new Bot commented Aug 25, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@azure-tools/typespec-azure-resource-manager@5294
npm i https://pkg.pr.new/@azure-tools/typespec-azure-rulesets@5294

commit: 7f6971c

@azure-sdk-automation

Copy link
Copy Markdown
Contributor

You can try these changes here

🛝 Playground 🌐 Website

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: cf125def-85b2-462d-8902-7ac7beb588a3
messageId: "required" | "default" | "createOnly";
};

function findViolations(program: Program, patchBody: Type, operation: Operation): Violation[] {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks to be quite a complex rule, can you incluide some benchmark of how fast/slow it runs on large spec(network) compared to others

@msyyc Yuchao Yan (msyyc) Sep 1, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timothee Guerin (@timotheeguerin) I benchmarked this against the large Network spec from azure-rest-api-specs using the existing external benchmark runner (2 warmups + 10 measured iterations).

The rule averaged 10.24 ms per compile (median 10.21 ms, range 9.72–10.95 ms, CV 4.02%). That is 0.066% of the total compile time and 0.465% of total lint time. It ranked 17th of 83 enabled rules; for comparison, the most expensive rules in this run took 186–407 ms each.

The disabled/enabled end-to-end averages were 15.63s and 15.54s respectively, so there is no measurable total compile-time regression—the difference is within run noise.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

ARM namespace detection is incorrect, and the standard ruleset currently disables the new rule.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Promotes PatchBodyParametersSchema into the ARM TypeSpec linter.

Changes:

  • Implements effective PATCH payload validation.
  • Adds comprehensive rule tests and documentation.
  • Registers the rule and updates generated catalogs.
File summaries
File Description
.chronus/changes/promote-patch-body-parameters-schema-2026-08-25-05-25-55.md Records package features.
packages/typespec-azure-resource-manager/README.md Lists the rule.
packages/typespec-azure-resource-manager/src/linter.ts Registers the implementation.
packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.md Documents usage and remediation.
packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.ts Implements PATCH validation.
packages/typespec-azure-resource-manager/test/rules/patch-body-invalid-property.test.ts Tests rule behavior.
packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts Adds the rule to the ARM ruleset.
website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md Updates the website catalog.
Review details

Suppressed comments (2)

packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.ts:43

  • resolveProviderNamespace searches the supplied namespace and its descendants; it does not determine whether the operation is inside an ARM provider namespace. This skips PATCH operations declared in a provider subnamespace and can lint unrelated global/parent operations whenever they contain a provider descendant. Use getArmProviderNamespace, which walks ancestors, and reject an undefined namespace.
        const namespace = operation.interface?.namespace ?? operation.namespace;
        if (resolveProviderNamespace(context.program, namespace) === undefined) {

packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.md:48

  • The LintDiff equivalent must link to the rule's canonical automated-guideline entry. PatchBodyParametersSchema is R2016, while this link targets the legacy validator documentation instead.
This rule corresponds to the LintDiff rule [PatchBodyParametersSchema](https://github.com/Azure/azure-openapi-validator/blob/main/docs/patch-body-parameters-schema.md).
  • Files reviewed: 8/8 changed files
  • Comments generated: 4
  • Review effort level: Balanced

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts Outdated
Comment thread packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.ts Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 727b6828-4146-484f-a492-7b567658ca41
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 727b6828-4146-484f-a492-7b567658ca41
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 727b6828-4146-484f-a492-7b567658ca41
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 727b6828-4146-484f-a492-7b567658ca41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

int:azure-specs Run integration tests against azure-rest-api-specs lib:azure-resource-manager Issues for @azure-tools/typespec-azure-core library linter Issues related to linter rules meta:website TypeSpec.io updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants