Commit 2a20433
feat(openapi): add array form for @tagMetadata to control tag declaration order (#10770)
Adds a new array form for the `@tagMetadata` decorator that lets authors
explicitly specify tags and their order in a single decorator call,
rather than relying on the bottom-up decorator execution order.
## Changes
- **`packages/openapi/lib/decorators.tsp`**: Added `TagMetadataWithName`
model; updated `@tagMetadata` signature to accept `string |
TagMetadataWithName[]` with the second parameter optional (only used in
inline form).
- **`packages/openapi/generated-defs/TypeSpec.OpenAPI.ts`**: Regenerated
with new `TagMetadataWithName` interface (including `summary` and `kind`
fields) and updated `TagMetadataDecorator` type.
- **`packages/openapi/src/lib.ts`**: Added `mixed-tag-metadata-form`
diagnostic — emitted when mixing the array form and inline form on the
same namespace. Added `tag-metadata-array-with-metadata-arg` diagnostic
— emitted when the second `tagMetadata` argument is provided alongside
the array form.
- **`packages/openapi/src/types.ts`**: Added publicly documented
`TagMetadataWithName` interface (with `summary` and `kind` fields).
- **`packages/openapi/src/index.ts`**: Exported new public type.
- **`packages/openapi/src/decorators.ts`**: State storage changed from
`{ [name: string]: TagMetadata }` to `TagMetadataWithName[]`; handles
both inline and array forms; reports error on mixing or on using the
second argument with the array form.
- **`packages/openapi3/src/openapi.ts`**: Updated `resolveDocumentTags`
to iterate the new array-based state.
-
**`packages/openapi3/src/cli/actions/convert/generators/generate-tags.ts`**:
Updated the OpenAPI→TypeSpec converter to emit `@tagMetadata(#[...])`
(array form) instead of multiple inline calls, ensuring import/export
order symmetry. Emits `parent`, `summary`, and `kind` fields natively
for OpenAPI 3.2.0 tags.
-
**`packages/openapi3/src/cli/actions/convert/transforms/transform-tags.ts`**:
Passes through `parent`, `summary`, and `kind` fields from
`OpenAPITag3_2`; reads `summary` and `kind` from
`x-oai-summary`/`x-oai-kind` extensions as fallbacks for OpenAPI 3.0/3.1
documents.
- **`packages/openapi3/src/cli/actions/convert/interfaces.ts`**: Added
`parent`, `summary`, and `kind` fields to `TypeSpecTagMetadata`.
- **`packages/openapi/test/decorators.test.ts`**: Updated existing tests
for the new array return type; added tests for array form, mixing error,
and second-argument-with-array-form error.
- **`packages/openapi3/test/tagmetadata.test.ts`**: Added tests for
array form ordering, operation-tag insertion behavior, and parent/child
tag scenarios.
-
**`packages/openapi3/test/tsp-openapi3/convert-openapi3-doc.test.ts`**:
Added unit tests verifying converter handling of OpenAPI 3.2.0 `parent`,
`summary`, and `kind` tag fields, and `x-oai-` extension fallbacks for
3.0/3.1 documents.
- **Converter snapshot outputs** (`tag-metadata`,
`playground-http-service`, `petstore-swagger`, `tag-metadata-3-2`):
Updated to reflect the new array form emitted by the converter.
### Array form example
```typespec
@service
@tagMetadata(#[
#{ name: "First Tag", description: "First tag description" },
#{ name: "Second Tag", description: "Second tag description" },
#{ name: "Third Tag", description: "Third tag description" },
])
namespace PetStore {}
```
Tags are emitted in the exact order specified in the array.
### Mixing forms is an error
Using both forms on the same namespace reports a
`mixed-tag-metadata-form` diagnostic:
```typespec
@service
@tagMetadata(#[#{ name: "tag1" }])
@tagMetadata("tag2", #{}) // error: cannot mix array and inline form
namespace PetStore {}
```
### Passing the second argument with the array form is an error
```typespec
@service
@tagMetadata(#[#{ name: "tag1" }], #{description: "not allowed"}) // error: tag-metadata-array-with-metadata-arg
namespace PetStore {}
```
### Converter output
The OpenAPI→TypeSpec converter now emits the array form so that tag
order in the source OpenAPI document is preserved in the output
TypeSpec. OpenAPI 3.2.0 `parent`, `summary`, and `kind` tag fields are
emitted natively. For OpenAPI 3.0/3.1 documents, `summary` and `kind`
are also read from `x-oai-summary` and `x-oai-kind` extensions:
```typespec
@tagMetadata(#[
#{ name: "pet", description: "Everything about your Pets", externalDocs: #{ url: "...", description: "Find out more" } },
#{ name: "store", description: "Access to Petstore orders", externalDocs: #{ url: "...", description: "Find out more about our store" } },
#{ name: "user", description: "Operations about user" },
#{ name: "extensive", description: "A tag with all 3.2 fields", summary: "Short summary", kind: "OperationGroup", parent: "parent-tag" },
])
namespace SwaggerPetstoreOpenAPI30;
```
### Tag insertion behavior (unchanged)
- Tags used only at the operation level (via `@tag`) and **not**
declared with `@tagMetadata` appear first in the output.
- Tags declared with `@tagMetadata` follow in their stored order.
- A tag used at both the operation level and in `@tagMetadata` is
emitted exactly once, at its `@tagMetadata`-declared position with its
metadata.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
Co-authored-by: Vincent Biret <vibiret@microsoft.com>
Co-authored-by: Timothee Guerin <tiguerin@microsoft.com>1 parent c030232 commit 2a20433
22 files changed
Lines changed: 629 additions & 115 deletions
File tree
- .chronus/changes
- packages
- openapi3
- src
- cli/actions/convert
- generators
- transforms
- test
- tsp-openapi3
- output
- petstore-swagger
- playground-http-service
- tag-metadata-3-2
- tag-metadata
- openapi
- generated-defs
- lib
- src
- test
- website/src/content/docs/docs/libraries/openapi/reference
Lines changed: 19 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
147 | 147 | | |
148 | 148 | | |
149 | 149 | | |
150 | | - | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
151 | 154 | | |
152 | 155 | | |
153 | | - | |
| 156 | + | |
154 | 157 | | |
155 | 158 | | |
156 | 159 | | |
| |||
159 | 162 | | |
160 | 163 | | |
161 | 164 | | |
162 | | - | |
163 | | - | |
164 | | - | |
165 | | - | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
166 | 169 | | |
167 | 170 | | |
168 | 171 | | |
| 172 | + | |
| 173 | + | |
169 | 174 | | |
170 | 175 | | |
171 | 176 | | |
| |||
181 | 186 | | |
182 | 187 | | |
183 | 188 | | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
20 | 30 | | |
21 | 31 | | |
22 | 32 | | |
| |||
128 | 138 | | |
129 | 139 | | |
130 | 140 | | |
131 | | - | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
132 | 144 | | |
133 | | - | |
134 | | - | |
135 | | - | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
136 | 148 | | |
137 | 149 | | |
138 | 150 | | |
139 | 151 | | |
140 | 152 | | |
141 | 153 | | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
142 | 163 | | |
143 | 164 | | |
144 | 165 | | |
145 | 166 | | |
146 | | - | |
147 | | - | |
| 167 | + | |
| 168 | + | |
148 | 169 | | |
149 | 170 | | |
150 | 171 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
118 | 118 | | |
119 | 119 | | |
120 | 120 | | |
121 | | - | |
| 121 | + | |
122 | 122 | | |
123 | 123 | | |
124 | | - | |
| 124 | + | |
125 | 125 | | |
126 | 126 | | |
127 | 127 | | |
| |||
137 | 137 | | |
138 | 138 | | |
139 | 139 | | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
140 | 148 | | |
141 | 149 | | |
142 | 150 | | |
| |||
150 | 158 | | |
151 | 159 | | |
152 | 160 | | |
153 | | - | |
154 | | - | |
155 | | - | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
156 | 164 | | |
157 | | - | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
158 | 169 | | |
159 | 170 | | |
160 | 171 | | |
161 | 172 | | |
162 | 173 | | |
163 | 174 | | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
164 | 185 | | |
165 | | - | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
0 commit comments