Repository navigation
[Swagger Linter Migration] AllResourcesMustHaveGetOperation -> resource-missing-read #5654
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Yuchao Yan (msyyc)
merged 9 commits into
main
from
promote-lintdiff-all-resources-must-have-get-operation
Oct 19, 2026
Merged
Changes from 6 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
b620347
Promote ARM resource-missing-read lint rule
msyyc 1a5b299
Fix resource-missing-read targets for split ARM interfaces
msyyc 92aa59b
Merge branch 'main' into promote-lintdiff-all-resources-must-have-get…
msyyc 4e68e82
test(arm): remove missing-read suppression matrix
msyyc 6a6ec4b
test: select published resource examples by named documentation section
msyyc cbe60e8
Merge branch 'main' into promote-lintdiff-all-resources-must-have-get…
msyyc ee03812
test: remove bespoke Markdown documentation parsing
msyyc 1b397a8
Merge branch 'main' into promote-lintdiff-all-resources-must-have-get…
msyyc af32bc7
Delete .chronus/changes/promote-lintdiff-all-resources-must-have-get-…
msyyc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| changeKind: feature | ||
| packages: | ||
| - "@azure-tools/typespec-azure-resource-manager" | ||
| --- | ||
|
|
||
| Add the `resource-missing-read` lint rule requiring a read operation for ARM resources with create or update operations. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| changeKind: internal | ||
| packages: | ||
| - "@azure-tools/typespec-azure-rulesets" | ||
| --- | ||
|
|
||
| Register the ARM `resource-missing-read` lint rule as disabled in the resource manager ruleset. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
packages/typespec-azure-resource-manager/src/rules/resource-missing-read.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| An ARM resource with a create or update lifecycle operation must also provide a read operation. | ||
| Clients need to retrieve the resource state after creating or updating it. | ||
|
|
||
| Define `ArmResourceRead<Resource>` alongside the resource's create/update operations. A collection | ||
| list does not replace a read of an individual resource. Delete-only resources are outside this | ||
| rule's scope. | ||
|
|
||
| ## Impact | ||
|
|
||
| - **Area:** API | ||
|
|
||
| Without a read operation, clients cannot retrieve the state of a resource they can create or update. | ||
| The rule reports one warning per resource, on the concrete interface declaring its registered | ||
| create/update operation. Having both create and update operations does not produce two warnings. | ||
| When writes are split across interfaces, the create operation's interface takes priority over | ||
| the update operation's interface. A separate delete interface is not the diagnostic target. | ||
|
|
||
| ## ❌ Incorrect | ||
|
|
||
| For an existing `Widget` resource with `WidgetProperties`, this interface omits the read operation: | ||
|
|
||
| ```typespec | ||
| @armResourceOperations | ||
| interface Widgets { | ||
| createOrUpdate is ArmResourceCreateOrReplaceSync<Widget>; | ||
| update is ArmResourcePatchSync<Widget, WidgetProperties>; | ||
| } | ||
| ``` | ||
|
|
||
| ## ✅ Correct | ||
|
|
||
| Add a read using the standard ARM resource operation template: | ||
|
|
||
| ```typespec | ||
| @armResourceOperations | ||
| interface Widgets { | ||
| createOrUpdate is ArmResourceCreateOrReplaceSync<Widget>; | ||
| update is ArmResourcePatchSync<Widget, WidgetProperties>; | ||
| get is ArmResourceRead<Widget>; | ||
| } | ||
| ``` | ||
|
|
||
| These examples assume a model such as | ||
| `model Widget is TrackedResource<WidgetProperties> { ...ResourceNameParameter<Widget>; }`, | ||
| with a named properties model such as `model WidgetProperties { label?: string; }`. | ||
|
|
||
| ## Suppression | ||
|
|
||
| Prefer adding the standard read operation. Suppress this rule only when an ARM reviewer approves | ||
| a resource design that intentionally cannot be read; include the approval reason in the | ||
| `#suppress` directive on the resource operation interface. | ||
| Place suppression on the concrete create interface, or on the update interface when there is no | ||
| create operation. Suppression on a separate delete interface does not suppress this warning. | ||
|
|
||
| ## LintDiff Equivalent | ||
|
|
||
| This rule corresponds to | ||
| [AllResourcesMustHaveGetOperation](https://github.com/Azure/azure-openapi-validator/blob/main/docs/all-resources-must-have-get-operation.md). | ||
| It checks registered native ARM lifecycle roles, not emitted response references. Legacy HTTP GET | ||
| operations without ARM read metadata do not satisfy this check; registered reads with customized | ||
| responses do. Full executable Swagger equivalence is not claimed. The discriminator-ancestor | ||
| exemption is retained, but a clean supported polymorphic ARM fixture has not been established. |
77 changes: 77 additions & 0 deletions
77
packages/typespec-azure-resource-manager/src/rules/resource-missing-read.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import { | ||
| createRule, | ||
| fileRef, | ||
| getDiscriminator, | ||
| paramMessage, | ||
| type Interface, | ||
| type Model, | ||
| type Program, | ||
| } from "@typespec/compiler"; | ||
| import type { ArmResourceOperation } from "../operations.js"; | ||
| import { getArmResources, type ArmResourceDetails } from "../resource.js"; | ||
| import { getInterface } from "./utils.js"; | ||
|
|
||
| export const resourceMissingReadRule = createRule({ | ||
| name: "resource-missing-read", | ||
| description: "ARM resources with create or update operations must define a read operation.", | ||
| docs: fileRef.fromPackageRoot("src/rules/resource-missing-read.md"), | ||
| url: "https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/resource-missing-read", | ||
| severity: "warning", | ||
| messages: { | ||
| default: paramMessage`Resource '${"name"}' must have a get/read operation.`, | ||
| }, | ||
| create(context) { | ||
| return { | ||
| root: (program) => { | ||
| for (const resource of getArmResources(program)) { | ||
| if ( | ||
| !requiresRead(resource) || | ||
| resource.operations.lifecycle.read !== undefined || | ||
| hasDiscriminatorAncestor(program, resource.typespecType) | ||
| ) { | ||
| continue; | ||
| } | ||
|
|
||
| context.reportDiagnostic({ | ||
| target: getDiagnosticTarget(resource), | ||
| format: { name: resource.name }, | ||
| }); | ||
| } | ||
| }, | ||
| }; | ||
| }, | ||
| }); | ||
|
|
||
| function requiresRead(resource: ArmResourceDetails): boolean { | ||
| return ( | ||
| resource.operations.lifecycle.createOrUpdate !== undefined || | ||
| resource.operations.lifecycle.update !== undefined | ||
| ); | ||
| } | ||
|
|
||
| function hasDiscriminatorAncestor(program: Program, model: Model): boolean { | ||
| for (let current = model.baseModel; current !== undefined; current = current.baseModel) { | ||
| if (getDiscriminator(program, current) !== undefined) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| function getDiagnosticTarget(resource: ArmResourceDetails): Interface | Model { | ||
| return ( | ||
| resource.operations.lifecycle.createOrUpdate?.operation.interface ?? | ||
| resource.operations.lifecycle.update?.operation.interface ?? | ||
| getInterface(resource) ?? | ||
| getOperationInterface(Object.values(resource.operations.lists)) ?? | ||
| getOperationInterface(Object.values(resource.operations.actions)) ?? | ||
| resource.typespecType | ||
| ); | ||
| } | ||
|
|
||
| function getOperationInterface( | ||
| operations: Array<ArmResourceOperation | undefined>, | ||
| ): Interface | undefined { | ||
| return operations.find((operation) => operation?.operation.interface !== undefined)?.operation | ||
| .interface; | ||
| } | ||
41 changes: 41 additions & 0 deletions
41
...espec-azure-resource-manager/test/integration/resource-missing-read-documentation.test.ts
|
msyyc marked this conversation as resolved.
Outdated
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import { createLinterRuleTester } from "@typespec/compiler/testing"; | ||
| import { readFile } from "node:fs/promises"; | ||
| import { it } from "vitest"; | ||
| import { resourceMissingReadRule } from "../../src/rules/resource-missing-read.js"; | ||
| import { Tester } from "../tester.js"; | ||
|
|
||
| it.each([ | ||
| ["❌ Incorrect", true], | ||
| ["✅ Correct", false], | ||
| ] as const)("compiles the published %s resource example", async (heading, violates) => { | ||
| const documentation = await readFile( | ||
|
msyyc marked this conversation as resolved.
Outdated
|
||
| new URL("../../src/rules/resource-missing-read.md", import.meta.url), | ||
| "utf8", | ||
| ); | ||
| const section = documentation | ||
| .split(/^## /m) | ||
| .find((text) => text.split(/\r?\n/, 1)[0] === heading); | ||
| const snippet = section?.match(/```typespec\r?\n([\s\S]*?)```/)?.[1]; | ||
| if (!snippet) throw new Error(`Missing resource-missing-read example in ${heading}.`); | ||
| const tester = createLinterRuleTester( | ||
| await Tester.createInstance(), | ||
| resourceMissingReadRule, | ||
| "@azure-tools/typespec-azure-resource-manager", | ||
| ); | ||
| const fixture = tester.expect(` | ||
| @armProviderNamespace @service namespace Microsoft.Contoso; | ||
| model WidgetProperties { label?: string; } | ||
| model Widget is TrackedResource<WidgetProperties> { | ||
| ...ResourceNameParameter<Widget>; | ||
| } | ||
| ${snippet} | ||
| `); | ||
| if (violates) { | ||
| await fixture.toEmitDiagnostics({ | ||
| code: "@azure-tools/typespec-azure-resource-manager/resource-missing-read", | ||
| message: "Resource 'Widget' must have a get/read operation.", | ||
| }); | ||
| } else { | ||
| await fixture.toBeValid(); | ||
| } | ||
| }); | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.