Skip to content

no-openapi-client-extensions

Id
@azure-tools/typespec-azure-core/no-openapi-client-extensions

Azure specs should not use @typespec/openapi @extension to emit client-altering x-ms-* extensions


Azure specs should not use the @typespec/openapi @extension decorator to emit client-altering x-ms-* (and x-nullable) OpenAPI extensions.

These extensions change how clients, SDKs, and the ARM platform interpret an API — for example whether an operation is long-running, pageable, a secret, or an ARM resource. When they are hand-written with the raw @extension decorator they only appear in the OpenAPI (Swagger) output. Every other emitter (client SDK, service, ARM, etc.) works from the semantic TypeSpec model and never sees the extension, so it produces an incorrect representation of the API. Worse, the value is not validated or kept in sync with the rest of the spec.

Each of these extensions has a first-class TypeSpec construct that carries the same intent through the semantic model, so all emitters — including the OpenAPI emitter — produce a consistent and validated result. Use the construct instead of the raw extension.

This rule flags the following extensions:

ExtensionUse instead
x-ms-skip-url-encoding@path(#{ allowReserved: true }) from @typespec/http
x-ms-enumAn extensible union (a union with a string variant)
x-ms-parameter-groupingGroup the parameters into a model and spread it into the operation
x-ms-parameter-locationDetermined automatically by the emitter; use @clientLocation from @azure-tools/typespec-client-generator-core when overriding client placement
x-ms-client-name@clientName from @azure-tools/typespec-client-generator-core
x-ms-discriminator-value@discriminator with a named model hierarchy
x-ms-client-flatten@flattenProperty from @azure-tools/typespec-client-generator-core
x-ms-parameterized-host@server from @typespec/http
x-ms-pageable@list / the Azure.Core paging operation templates
x-ms-long-running-operationThe Azure.Core / Azure.ResourceManager long-running operation templates (e.g. LongRunningResourceCreateOrReplace)
x-ms-long-running-operation-optionsThe long-running operation templates together with @pollingOperation / @finalOperation
x-nullableMake the property optional (?), or model the value explicitly; Azure specs should not use nullable types
x-ms-internal@access(Access.internal) from @azure-tools/typespec-client-generator-core
x-ms-azure-resourceThe Azure.ResourceManager resource templates (TrackedResource, ProxyResource, ExtensionResource, …)
x-ms-arm-id-detailsarmResourceIdentifier scalar from @azure-tools/typespec-azure-resource-manager
x-ms-secret@secret from the TypeSpec standard library
  • Area: API, SDK, Emitters

Hand-written client-altering extensions change the OpenAPI output without informing the semantic model, so client SDKs, service code, and ARM tooling misrepresent the API (missing long-running/pageable behavior, wrong client names, unflattened models, secrets that are not treated as secrets, resources that are not recognized as ARM resources, and so on). Using the equivalent TypeSpec construct keeps every emitter consistent and lets the OpenAPI emitter generate the extension for you.

Several LintDiff rules require or validate these same extensions in Swagger. Because those extensions are generated for you when you use the corresponding TypeSpec construct, expressing the behavior semantically (rather than hand-writing the extension) is what keeps the generated OpenAPI compliant with these rules:

@OpenAPI.extension("x-ms-long-running-operation", true)
op createWidget(...Widget): Widget;
op createWidget is Azure.Core.ResourceOperations.LongRunningResourceCreateOrReplace<Widget>;
@OpenAPI.extension("x-ms-enum", #{ name: "PetKind", modelAsString: true })
enum PetKind {
Cat,
Dog,
}
union PetKind {
Cat: "Cat",
Dog: "Dog",
string,
}
model Widget {
@OpenAPI.extension("x-ms-client-name", "widgetName")
name: string;
}
model Widget {
@clientName("widgetName")
name: string;
}
model Credentials {
@OpenAPI.extension("x-ms-secret", true)
key: string;
}
model Credentials {
@secret
key: string;
}

Do not suppress. Replace the raw extension with the equivalent TypeSpec construct listed above so every emitter — not just the OpenAPI emitter — reflects the intended behavior. If the extension is a genuinely emitter-only, non-client-altering annotation that has no TypeSpec construct, it does not belong in this list; open an issue rather than suppressing.