Skip to content

[Swagger Linter Migration] ParametersSchemaAsTypeObject - #5361

Open
Yuchao Yan (msyyc) wants to merge 10 commits into
Azure:mainfrom
msyyc:promote-parameters-schema-as-type-object-to-arm
Open

[Swagger Linter Migration] ParametersSchemaAsTypeObject#5361
Yuchao Yan (msyyc) wants to merge 10 commits into
Azure:mainfrom
msyyc:promote-parameters-schema-as-type-object-to-arm

Conversation

@msyyc

@msyyc Yuchao Yan (msyyc) commented Sep 1, 2026

Copy link
Copy Markdown
Member

Original Swagger linter

The original rule performs these checks:

  • traverses operation parameters under both paths and x-ms-paths
  • resolves $ref values before evaluating the selected schema
  • selects request-body schema type properties whose value is not object
  • reports the emitted schema.type location
  • ignores parameters without a schema type, object schemas, and non-body parameters

How 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 type property when it is present and
not object. resolved: true means referenced schemas are followed before the predicate is applied.
The falsy function then reports the selected schema.type node.

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, while
rpcGuidelineCode is RPC-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

  • Validator rule id: ParametersSchemaAsTypeObject
  • Local rule: parameters-schema-as-type-object
  • Canonical validator slug: parameters-schema-as-type-object
  • Source branch: feature/lintdiff-parameters-schema-as-type-object-schema-less-repair at
    e2b79feabf68f982488690cff8a52d3320c9e6d6 (merged by [Swagger Linter Migration] ParametersSchemaAsTypeObject (origin) #5350 into
    feature/lintdiff-migration-new)
  • Source worktree: C:\dev\worktrees\lintdiff-parameters-schema-as-type-object
  • Source file: parameters-schema-as-type-object.ts
  • Uncommitted source-rule changes: none

The 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: ARM and
sources: ["arm"], the fixture documentation says Resource Manager, and the implementation needs
ARM's getInlineAzureType behavior to match AutoRest schema replacement for Azure Core scalar
properties. Azure Core was considered but rejected because it cannot depend on the ARM library.
Existing Core rule request-body-problem covers only raw array request bodies, so it is partial rather
than equivalent coverage.

How the promoted TypeSpec linter works

The rule visits semantic operations and uses getHttpOperation to 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:

  • object models pass, including nullable and singleton-union wrappers
  • arrays are recognized through model, base-model, and source-model ancestry
  • enum-convertible unions are diagnosed; unsupported unions without an emitted type pass
  • unbased scalars and empty enums pass unless encoding produces an explicit schema type
  • scalar and model-property @encode, supported AutoRest formats, @secret, inline templates,
    defaulted enums/unions, @friendlyName, and @inlineAzureType are handled according to whether the
    emitted schema keeps, gains, replaces, or loses type
  • literals, tuples, enum members, union variants, string templates, binary bodies, and file bodies are
    diagnosed 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

Original lintdiff fixture Native vitest case Coverage note
non-object-body it("reports a primitive POST request body") Primitive POST body
put-non-object-body it("reports a primitive PUT request body") Primitive PUT body
named-array-model-body it("reports a named array model used by an ARM action") Array ancestry and template-owned parameter target
object-body it("allows a named object request body") Referenced object
inline-object-body it("allows an inline object request body") Inline object
nullable-object-body it("allows a nullable object request body") Nullable object
single-variant-unions it("allows a singleton object union request body") Singleton object union
single-variant-unions it("allows a nullable unknown request body") Nullable schema-less intrinsic
unsupported-union-body it("allows an unsupported model union request body") Unsupported union emits no type
enum-union-body it("reports a string enum union request body") Enum-convertible union
unbased-scalar-body it("allows schema-less scalar, enum, and encoded scalar request bodies") Unbased and untyped-replacement scalars
empty-enum-body it("allows schema-less scalar, enum, and encoded scalar request bodies") Empty enum
empty-encoded-scalar-body it("allows schema-less scalar, enum, and encoded scalar request bodies") Empty and secret untyped encodings
encoded-model-property-body it("follows referenced schemas and inline untyped property-encoding replacements") Referenced, inline, defaulted, friendly-name, and Azure scalar paths
multipart-body it("allows multipart request bodies") Multipart exemption
explicit-schema-type-bodies it("reports request bodies with every directly emitted non-object schema type") Scalars, enums, members, variants, tuples, literals, templates, and binary bytes
explicit-schema-type-bodies it("reports a file body used by an ARM action") File body
explicit-schema-type-bodies it("reports scalar and inline-property encodings that emit a schema type") Typed scalar and inline-property replacements
explicit-schema-type-bodies it("reports nested encodings and preserves the date-time type for an empty encoding") Nested, unsupported, and date-time encoding merge branches
no-body-action it("allows an ARM action with a synthetic void request body") ARM template's synthetic void body
unknown-body-action it("allows an unknown request body") Schema-less unknown body

Migration 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

  • focused native rule suite: 19 tests passed
  • ARM package build and lint passed
  • ARM docs regenerated; package README and website linter reference formatted and checked
  • Azure rulesets build passed; 4 tests passed
  • full ARM package suite: 57 files and 390 tests passed
  • focused promotion review: no significant issues
  • full-repo lint, formatting, spelling, changeset, and diff-hygiene checks passed

Validation blocker

pnpm validate:pr completed in about 12 minutes with 6/9 checks passing. Its broad build and docs
regeneration failed in the unrelated @typespec/playground-website package because
@typespec/samples was unavailable to core/packages/playground-website/samples/build.ts. The broad
test step consequently had one unrelated failed suite because
packages/typespec-azure-playground-website/samples/dist/samples.js was not generated; the remaining
325 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

Yuchao Yan (msyyc) and others added 3 commits September 1, 2026 14:54
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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 Sep 1, 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 Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 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 use-model-request-body ARM lint rule, migrated from the Swagger ParametersSchemaAsTypeObject validator rule.

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

Register the ARM use-model-request-body lint rule as disabled in the resource manager ruleset.

@github-actions

github-actions Bot commented Sep 1, 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-java 13.51 MB → 13.51 MB -139 B (-0.0%) 15.04 MB → 15.04 MB
@azure-tools/typespec-azure-resource-manager 186.32 KB → 186.39 KB +67 B (+0.0%) 1.12 MB → 1.12 MB +633 B (+0.1%)
@azure-tools/typespec-azure-rulesets 5.35 KB → 5.36 KB +15 B (+0.3%) 33.04 KB → 33.23 KB +196 B (+0.6%)
@azure-tools/azure-http-specs 146.77 KB → 146.77 KB 1.16 MB → 1.16 MB
@azure-tools/typespec-autorest 81.23 KB → 81.23 KB 396.61 KB → 396.61 KB
@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.83 KB → 709.83 KB
@azure-tools/typespec-azure-portal-core 42.47 KB → 42.47 KB 193.15 KB → 193.15 KB
@azure-tools/typespec-client-generator-core 237.05 KB → 237.05 KB 1.27 MB → 1.27 MB
@azure-tools/typespec-go 261.39 KB → 261.39 KB 1.34 MB → 1.34 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 540.59 KB → 540.59 KB 2.60 MB → 2.60 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 Sep 1, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 6b8c017

@azure-sdk-automation

Copy link
Copy Markdown
Contributor

You can try these changes here

🛝 Playground 🌐 Website

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

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.

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

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.

🔵 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 an index -> linter -> rule -> index cycle. 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

Comment thread packages/typespec-azure-resource-manager/src/rules/request-body-must-be-object.ts Outdated
Comment thread .chronus/changes/promote-parameters-schema-as-type-object-2026-08-31.md Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@msyyc

Copy link
Copy Markdown
Member Author

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(

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.

why do we need this whole format thing

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.

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.

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.

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.

@msyyc Yuchao Yan (msyyc) Sep 7, 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.

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

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.

yeah by model I mean a plain model (one without an indexer) which is easily checkable, what does this have to do with encode.

@msyyc Yuchao Yan (msyyc) Sep 9, 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.

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.

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