[Swagger Linter Migration] ParametersSchemaAsTypeObject - #5361
[Swagger Linter Migration] ParametersSchemaAsTypeObject#5361Yuchao Yan (msyyc) wants to merge 10 commits into
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…hema-as-type-object-to-arm
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
All changed packages have been documented.
Show changes
|
📦 Package size report✅ No notable package size changes compared to the base branch. 13 package(s) with no notable change
Packed = gzipped |
commit: |
|
You can try these changes here
|
There was a problem hiding this comment.
🟡 Changes recommended
Required suppression guidance and the manually maintained ARM rules table entry are missing.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Promotes the Swagger request-body schema check into the ARM TypeSpec linter.
Changes:
- Adds and registers
request-body-must-be-object. - Covers AutoRest schema-emission behavior with extensive tests.
- Updates rulesets, references, and release metadata.
File summaries
| File | Description |
|---|---|
.chronus/changes/promote-parameters-schema-as-type-object-2026-08-31.md |
Records the feature release. |
packages/typespec-azure-resource-manager/README.md |
Lists the new rule. |
packages/typespec-azure-resource-manager/src/linter.ts |
Registers the rule. |
packages/typespec-azure-resource-manager/src/rules/request-body-must-be-object.md |
Documents rule behavior. |
packages/typespec-azure-resource-manager/src/rules/request-body-must-be-object.ts |
Implements schema classification. |
packages/typespec-azure-resource-manager/test/rules/request-body-must-be-object.test.ts |
Tests supported body forms. |
packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts |
Adds the disabled ruleset entry. |
website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md |
Updates website rule references. |
Review details
- Files reviewed: 8/8 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The implementation self-imports its package root, creating a circular dependency and coupling source builds to generated output.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
packages/typespec-azure-resource-manager/src/rules/request-body-must-be-object.ts:2
- This self-import resolves through the package's public
dist/src/index.*export and creates anindex -> linter -> rule -> indexcycle. It can also bind source builds/tests to stale generated output instead of the current implementation. Use the direct intra-package module import, consistent with the rest of this package.
- Files reviewed: 9/9 changed files
- Comments generated: 0 new
- Review effort level: Balanced
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Timothee Guerin (@timotheeguerin) updated with your comments and pls take a review again. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| } | ||
| const targetFormat = isSecret(program, target) ? "password" : undefined; | ||
| const encodedSchema = getEmittedScalarSchema(program, encoding.type); | ||
| const mergedFormat = mergeFormatAndEncoding( |
There was a problem hiding this comment.
why do we need this whole format thing
There was a problem hiding this comment.
This is not handling authored @format; that support was removed. It mirrors AutoRest's internal @encode behavior because encoding only replaces the schema type when mergeFormatAndEncoding() produces a format.
For example:
model Payload {
value: string;
}
model Request {
@encode("custom", int32)
payload: Payload;
}
@post op create(@body body: Request.payload): void;Although the underlying TypeSpec property is model-shaped, AutoRest applies the encoding and emits an integer schema such as { type: "integer", format: "int32" }; the migrated Swagger rule must therefore report it. Other encodings can produce no usable format and leave an untyped schema, which the original rule does not report.
So the format value is only an intermediate signal used to determine whether AutoRest applies the encoding and changes schema.type. I agree the reason is subtle and will add documentation around this logic.
There was a problem hiding this comment.
but this doesn't make sense, the goal of this rule is to check the body is a model, either it is or it;s not. If it has any encode to anything else it is not(If that is even worth checking), that's it don't need to check further. The rules should NOT try to mimic exactly what autorest output did they should work wiht what you can do in typespec.
There was a problem hiding this comment.
Context
A TypeSpec Model is a compiler-level construct, not necessarily a Swagger type: object.
Examples:
model Request {
value: string;
}emits an object schema.
model Request is Array<string>;is also a TypeSpec Model, but emits:
{ "type": "array", "items": { "type": "string" } }Visibility transformations, model properties, templates, files, and encodings can further change the effective emitted schema.
The reverse also matters: a non-Model TypeSpec type can emit no Swagger type, such as unknown; the Swagger rule ignores it even though it is not a TypeSpec model.
Need discussion
Now the gap is clear and we should answer the question: whether real behavior equivalence is strict standard for the rule migration. And I need discussion with catalinaperalta
There was a problem hiding this comment.
yeah by model I mean a plain model (one without an indexer) which is easily checkable, what does this have to do with encode.
There was a problem hiding this comment.
This resolves my response to the array example: although arrays have kind === "Model", they have an indexer, so the simple plain-model check still rejects them. The remaining decision is product-level: whether this promotion prioritizes Swagger diagnostic parity or translates the Swagger guideline into idiomatic TypeSpec policy. CC catalinaperalta
yeah by model I mean a plain model (one without an indexer) which is easily checkable, what does this have to do with encode.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Original Swagger linter
The original rule performs these checks:
pathsandx-ms-paths$refvalues before evaluating the selected schematypeproperties whose value is notobjectschema.typelocationtype, object schemas, and non-body parametersHow the Swagger linter works
The Spectral rule runs only on OpenAPI 2 documents. Its JSONPath,
$[paths,'x-ms-paths'].*.*.parameters.*.schema[?(@property === 'type' && @ !=='object')],walks every emitted operation parameter and selects a schema's
typeproperty when it is present andnot
object.resolved: truemeans referenced schemas are followed before the predicate is applied.The
falsyfunction then reports the selectedschema.typenode.The documentation says an object schema must also include a definition, but the implementation does
not enforce that condition; this migration follows the executable rule. The validator source also has
stale RPC labeling: the nearby comment and documentation cite
RPC-Arg-V1-01, whilerpcGuidelineCodeisRPC-POST-V1-05. Neither discrepancy is copied into the TypeSpec behavior.Swagger findings are emitted-path occurrences, while TypeSpec findings target semantic source
declarations, so duplicate emitted occurrences are intentionally represented by one source target.
Source TypeSpec lintdiff rule
ParametersSchemaAsTypeObjectparameters-schema-as-type-objectparameters-schema-as-type-objectfeature/lintdiff-parameters-schema-as-type-object-schema-less-repairate2b79feabf68f982488690cff8a52d3320c9e6d6(merged by [Swagger Linter Migration] ParametersSchemaAsTypeObject (origin) #5350 intofeature/lintdiff-migration-new)C:\dev\worktrees\lintdiff-parameters-schema-as-type-objectThe user-marked done source rule was not modified during promotion. The merged feature branch is used
for durable source links because GitHub deleted the merged topic branch.
Destination analysis
The rule is promoted as
@azure-tools/typespec-azure-resource-manager/request-body-must-be-object.ARM is the correct destination because the validator metadata says
applicability: ARMandsources: ["arm"], the fixture documentation says Resource Manager, and the implementation needsARM's
getInlineAzureTypebehavior to match AutoRest schema replacement for Azure Core scalarproperties. Azure Core was considered but rejected because it cannot depend on the ARM library.
Existing Core rule
request-body-problemcovers only raw array request bodies, so it is partial ratherthan equivalent coverage.
How the promoted TypeSpec linter works
The rule visits semantic operations and uses
getHttpOperationto obtain the effective request body.It ignores absent and multipart bodies, while file bodies are diagnosed directly. Single bodies are
classified according to AutoRest's emitted schema behavior:
@encode, supported AutoRest formats,@secret, inline templates,defaulted enums/unions,
@friendlyName, and@inlineAzureTypeare handled according to whether theemitted schema keeps, gains, replaces, or loses
typediagnosed when they emit a non-object type
Diagnostics target a project-owned body property where one exists and otherwise the authored
operation, preventing library-owned ARM template parameters from suppressing findings. No projection
or API-version filtering is added: the linter observes the semantic program, and duplicate projected
or emitted occurrences converge on the same source target.
The official rule name is intentionally shortened from the validator id to the actionable TypeSpec
name
request-body-must-be-object.Fixture-to-native test mapping
it("reports a primitive POST request body")it("reports a primitive PUT request body")it("reports a named array model used by an ARM action")it("allows a named object request body")it("allows an inline object request body")it("allows a nullable object request body")it("allows a singleton object union request body")it("allows a nullable unknown request body")it("allows an unsupported model union request body")it("reports a string enum union request body")it("allows schema-less scalar, enum, and encoded scalar request bodies")it("allows schema-less scalar, enum, and encoded scalar request bodies")it("allows schema-less scalar, enum, and encoded scalar request bodies")it("follows referenced schemas and inline untyped property-encoding replacements")it("allows multipart request bodies")it("reports request bodies with every directly emitted non-object schema type")it("reports a file body used by an ARM action")it("reports scalar and inline-property encodings that emit a schema type")it("reports nested encodings and preserves the date-time type for an empty encoding")it("allows an ARM action with a synthetic void request body")voidbodyit("allows an unknown request body")unknownbodyMigration evidence
The focused fixture results, real-service project comparison, full-corpus counts, compile failures,
deduplication evidence, and remaining external-reference observability boundary are documented in
migration.md.
Validation
Validation blocker
pnpm validate:prcompleted in about 12 minutes with 6/9 checks passing. Its broad build and docsregeneration failed in the unrelated
@typespec/playground-websitepackage because@typespec/sampleswas unavailable tocore/packages/playground-website/samples/build.ts. The broadtest step consequently had one unrelated failed suite because
packages/typespec-azure-playground-website/samples/dist/samples.jswas not generated; the remaining325 files and 3,183 tests passed. The targeted ARM and ruleset validation above is complete.
Promotion sync policy
If review discovers a semantic gap, this promotion should be blocked until the user explicitly
reopens lintdiff repair. The user-marked done source rule remains the behavioral source of truth and
was not changed during this promotion.
NOTE: heavy