@nxgt/typespec 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -3
- package/docs/guide/errors.md +65 -0
- package/docs/roadmap.md +12 -5
- package/docs/troubleshooting.md +9 -0
- package/lib/errors.tsp +28 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -131,6 +131,24 @@ The generator also declares the validators' own 400, `ValidationErrorBody`,
|
|
|
131
131
|
on every operation that takes a parameter or a body. With `BadRequest`, a
|
|
132
132
|
400 reply is typed `BadRequestBody | ValidationErrorBody`.
|
|
133
133
|
|
|
134
|
+
Each verb has an alias for the errors it usually answers, so a resource's
|
|
135
|
+
operations do not repeat the same unions. Combine them, and add a response
|
|
136
|
+
an alias leaves out:
|
|
137
|
+
|
|
138
|
+
```tsp
|
|
139
|
+
@route("/users")
|
|
140
|
+
interface Users {
|
|
141
|
+
@get list(...PageParameters): Page<User> | ListErrors;
|
|
142
|
+
@get get(@path id: uuid): User | GetErrors;
|
|
143
|
+
@post create(@body user: User): User | CreateErrors | AuthErrors | TooManyRequests;
|
|
144
|
+
@patch update(@path id: uuid, @body user: User): User | UpdateErrors;
|
|
145
|
+
@delete delete(@path id: uuid): NoContentResponse | DeleteErrors;
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
An alias is exactly its union, so the OpenAPI it emits is the same as
|
|
150
|
+
writing the responses out.
|
|
151
|
+
|
|
134
152
|
### Pagination
|
|
135
153
|
|
|
136
154
|
Spread the query of a page into the operation's parameters, and answer the
|
|
@@ -445,6 +463,15 @@ envelope and the rate limit's headers.
|
|
|
445
463
|
`@error` model with its `@statusCode` and body, and
|
|
446
464
|
`ErrorResponse<Status>` for any other status.
|
|
447
465
|
|
|
466
|
+
| Alias | What it is |
|
|
467
|
+
| --- | --- |
|
|
468
|
+
| `ListErrors` | `BadRequest` |
|
|
469
|
+
| `GetErrors` | `NotFound` |
|
|
470
|
+
| `CreateErrors` | `BadRequest \| Conflict` |
|
|
471
|
+
| `UpdateErrors` | `BadRequest \| NotFound \| Conflict` |
|
|
472
|
+
| `DeleteErrors` | `NotFound` |
|
|
473
|
+
| `AuthErrors` | `Unauthorized \| Forbidden`, with the envelope; a route that answers without a body declares `AuthenticationRequired \| AccessDenied` instead |
|
|
474
|
+
|
|
448
475
|
## Traps
|
|
449
476
|
|
|
450
477
|
- **Do not declare a schema named `ValidationErrorBody`.** The generator
|
|
@@ -466,7 +493,8 @@ envelope and the rate limit's headers.
|
|
|
466
493
|
- **One status, one reply.** `@typespec/openapi3` merges two replies of one
|
|
467
494
|
status into one. With `AuthenticationRequired` and `Unauthorized`, the 401
|
|
468
495
|
keeps the envelope and the reply without a body is lost: the library
|
|
469
|
-
refuses it, `duplicate-status-reply`. With `Conflict`
|
|
496
|
+
refuses it, `duplicate-status-reply`. With `Conflict` (which
|
|
497
|
+
`CreateErrors` and `UpdateErrors` hold) beside
|
|
470
498
|
`IdempotencyInProgress`, or `UnprocessableEntity` beside
|
|
471
499
|
`IdempotencyKeyReused`, both bodies stay, under the first reply's
|
|
472
500
|
description: the library warns, `merged-status-reply`. Declare the one the
|
|
@@ -503,8 +531,8 @@ envelope and the rate limit's headers.
|
|
|
503
531
|
|
|
504
532
|
## Documentation
|
|
505
533
|
|
|
506
|
-
- [Error replies](docs/guide/errors.md): the envelope, each response,
|
|
507
|
-
what the generator makes of them;
|
|
534
|
+
- [Error replies](docs/guide/errors.md): the envelope, each response, the
|
|
535
|
+
aliases by verb, and what the generator makes of them;
|
|
508
536
|
- [Pagination](docs/guide/pagination.md): offset and cursor pages, and
|
|
509
537
|
what a handler answers;
|
|
510
538
|
- [Authentication](docs/guide/auth.md): `JanusAuth`, and the guards'
|
package/docs/guide/errors.md
CHANGED
|
@@ -173,6 +173,71 @@ Name them in an operation's return type, beside the success:
|
|
|
173
173
|
@get getPost(@path postId: string): Post | NotFound | Unauthorized | Forbidden;
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
+
## Aliases by verb
|
|
177
|
+
|
|
178
|
+
Most operations of a resource answer the same errors. Each verb has an alias
|
|
179
|
+
for them:
|
|
180
|
+
|
|
181
|
+
| Alias | Responses | Statuses |
|
|
182
|
+
| --- | --- | --- |
|
|
183
|
+
| `ListErrors` | `BadRequest` | 400 |
|
|
184
|
+
| `GetErrors` | `NotFound` | 404 |
|
|
185
|
+
| `CreateErrors` | `BadRequest \| Conflict` | 400, 409 |
|
|
186
|
+
| `UpdateErrors` | `BadRequest \| NotFound \| Conflict` | 400, 404, 409 |
|
|
187
|
+
| `DeleteErrors` | `NotFound` | 404 |
|
|
188
|
+
| `AuthErrors` | `Unauthorized \| Forbidden` | 401, 403 |
|
|
189
|
+
|
|
190
|
+
```tsp
|
|
191
|
+
import "@typespec/http";
|
|
192
|
+
import "@nxgt/typespec";
|
|
193
|
+
|
|
194
|
+
using Http;
|
|
195
|
+
using Nxgt;
|
|
196
|
+
|
|
197
|
+
@service
|
|
198
|
+
namespace Shop;
|
|
199
|
+
|
|
200
|
+
model User {
|
|
201
|
+
id: uuid;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@route("/users")
|
|
205
|
+
interface Users {
|
|
206
|
+
@get list(...PageParameters): Page<User> | ListErrors;
|
|
207
|
+
@get get(@path id: uuid): User | GetErrors;
|
|
208
|
+
@post create(@body user: User): User | CreateErrors;
|
|
209
|
+
@patch update(@path id: uuid, @body user: User): User | UpdateErrors;
|
|
210
|
+
@delete delete(@path id: uuid): NoContentResponse | DeleteErrors;
|
|
211
|
+
@get @route("/me") me(): User | AuthErrors;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
An alias is a union, and nothing more: it emits the same OpenAPI as the
|
|
216
|
+
responses written out. Combine aliases, and add a response an alias leaves
|
|
217
|
+
out:
|
|
218
|
+
|
|
219
|
+
```tsp
|
|
220
|
+
@post create(@body user: User): User | CreateErrors | AuthErrors | TooManyRequests;
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
A union of your own names the errors your routes share:
|
|
224
|
+
|
|
225
|
+
```tsp
|
|
226
|
+
alias WriteErrors = CreateErrors | AuthErrors | TooManyRequests;
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Do not add a response the alias already holds. `UpdateErrors | NotFound`
|
|
230
|
+
declares the 404 twice, and the library warns, `merged-status-reply`. A 409
|
|
231
|
+
beside `CreateErrors` or `UpdateErrors`, such as `IdempotencyInProgress`,
|
|
232
|
+
warns too: declare the responses one by one there
|
|
233
|
+
([Headers](headers.md), [troubleshooting](../troubleshooting.md)).
|
|
234
|
+
|
|
235
|
+
`AuthErrors` carries the envelope, for a handler's own refusal. A route
|
|
236
|
+
whose guard answers the 401 and 403 without a body, as `@nxgt/janus-hono`'s
|
|
237
|
+
do, declares `AuthenticationRequired | AccessDenied` instead
|
|
238
|
+
([Authentication](auth.md)). The two in one operation are an error,
|
|
239
|
+
`duplicate-status-reply`.
|
|
240
|
+
|
|
176
241
|
## Another status, named
|
|
177
242
|
|
|
178
243
|
`ErrorResponse<503>` works, but its body is emitted inline and generated as
|
package/docs/roadmap.md
CHANGED
|
@@ -6,11 +6,10 @@ generated by `@nxgt/openapi-codegen` in CI before it ships.
|
|
|
6
6
|
|
|
7
7
|
## Now
|
|
8
8
|
|
|
9
|
-
- **
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- **`query`, a verb.** `query` in `Users` is `queryUsers`, like `search`.
|
|
9
|
+
- **Error aliases by verb.** `ListErrors`, `GetErrors`, `CreateErrors`,
|
|
10
|
+
`UpdateErrors`, `DeleteErrors` and `AuthErrors` name the errors each verb
|
|
11
|
+
usually answers, so an operation writes `User | GetErrors` instead of the
|
|
12
|
+
union. Each is exactly its union, and they combine.
|
|
14
13
|
|
|
15
14
|
## Later
|
|
16
15
|
|
|
@@ -24,6 +23,14 @@ generated by `@nxgt/openapi-codegen` in CI before it ships.
|
|
|
24
23
|
|
|
25
24
|
## Shipped
|
|
26
25
|
|
|
26
|
+
### 0.6.0
|
|
27
|
+
|
|
28
|
+
- **Verbs name their methods.** An operation named after one of the
|
|
29
|
+
library's verbs and sent with another method is a warning,
|
|
30
|
+
`verb-method-mismatch`: `create` is a `POST`, `list` a `GET` or a `HEAD`,
|
|
31
|
+
`search` a `GET` or a `POST`. `#suppress` silences one operation.
|
|
32
|
+
- **`query`, a verb.** `query` in `Users` is `queryUsers`, like `search`.
|
|
33
|
+
|
|
27
34
|
### 0.5.0
|
|
28
35
|
|
|
29
36
|
- **Verbs in operation ids.** In an interface `@operationIds` marks, a known
|
package/docs/troubleshooting.md
CHANGED
|
@@ -281,6 +281,15 @@ header either reply declares required becomes optional.
|
|
|
281
281
|
@post createPost(@body post: Post): Post | IdempotencyInProgress;
|
|
282
282
|
```
|
|
283
283
|
|
|
284
|
+
An alias can hide the duplicate: `CreateErrors` holds a `Conflict`, so
|
|
285
|
+
`Post | CreateErrors | IdempotencyInProgress` declares two 409s, and
|
|
286
|
+
`UpdateErrors | NotFound` two 404s. Spell the alias's other errors out
|
|
287
|
+
instead:
|
|
288
|
+
|
|
289
|
+
```tsp
|
|
290
|
+
@post createPost(@body post: Post): Post | BadRequest | IdempotencyInProgress;
|
|
291
|
+
```
|
|
292
|
+
|
|
284
293
|
Two plain bodies, `Post | Draft`, are one reply whose body is either; two
|
|
285
294
|
`@error` models without a `@statusCode` share `default`, and one
|
|
286
295
|
description; bodies of different content types are negotiated. All three
|
package/lib/errors.tsp
CHANGED
|
@@ -146,3 +146,31 @@ model ErrorResponse<Status extends integer> {
|
|
|
146
146
|
@statusCode _: Status;
|
|
147
147
|
@body body: ErrorBody<Status>;
|
|
148
148
|
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The errors a list declares: a query its validators refuse.
|
|
152
|
+
*
|
|
153
|
+
* ```tsp
|
|
154
|
+
* @get list(...PageParameters): Page<User> | ListErrors;
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
alias ListErrors = BadRequest;
|
|
158
|
+
|
|
159
|
+
/** The errors a read of one declares: nothing at that id. */
|
|
160
|
+
alias GetErrors = NotFound;
|
|
161
|
+
|
|
162
|
+
/** The errors a create declares: a body refused, or one that conflicts. */
|
|
163
|
+
alias CreateErrors = BadRequest | Conflict;
|
|
164
|
+
|
|
165
|
+
/** The errors an update declares: a body refused, nothing at that id, or a conflict. */
|
|
166
|
+
alias UpdateErrors = BadRequest | NotFound | Conflict;
|
|
167
|
+
|
|
168
|
+
/** The errors a delete declares: nothing at that id. */
|
|
169
|
+
alias DeleteErrors = NotFound;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The errors of an operation behind authentication, with the nxgt envelope.
|
|
173
|
+
* A route that answers them without a body, as Janus does, declares
|
|
174
|
+
* `AuthenticationRequired | AccessDenied` instead.
|
|
175
|
+
*/
|
|
176
|
+
alias AuthErrors = Unauthorized | Forbidden;
|