typespec-http-zod 0.30.0 → 0.32.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 +22 -0
- package/dist/src/api.d.ts +31 -0
- package/dist/src/api.js +1475 -362
- package/dist/src/constraints.d.ts +31 -2
- package/dist/src/constraints.js +242 -46
- package/dist/src/lib.d.ts +163 -13
- package/dist/src/lib.js +207 -18
- package/dist/src/openapi-versions.d.ts +20 -0
- package/dist/src/openapi-versions.js +31 -0
- package/dist/src/published.d.ts +139 -0
- package/dist/src/published.js +798 -0
- package/dist/src/registry.d.ts +79 -45
- package/dist/src/registry.js +788 -200
- package/dist/src/types.d.ts +12 -2
- package/dist/src/types.js +267 -92
- package/dist/src/uri-template.d.ts +110 -8
- package/dist/src/uri-template.js +251 -46
- package/dist/src/zod.d.ts +764 -39
- package/dist/src/zod.js +4313 -541
- package/package.json +1 -1
package/dist/src/lib.js
CHANGED
|
@@ -67,7 +67,21 @@ const diagnostics = {
|
|
|
67
67
|
"unsupported-type": {
|
|
68
68
|
severity: "error",
|
|
69
69
|
messages: {
|
|
70
|
-
default: paramMessage `Cannot emit ${"artefact"} for ${"kind"}: ${"why"}. Replace
|
|
70
|
+
default: paramMessage `Cannot emit ${"artefact"} for ${"kind"}: ${"why"}. Replace or remove the declaration: ${"remedy"}`,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
/**
|
|
74
|
+
* **A default that is no value the position accepts.** openapi3 publishes a default as the JSON it
|
|
75
|
+
* serializes, beside a schema the default need not satisfy - an encoding can change the type
|
|
76
|
+
* under it, a constraint can rule out the literal it names, a key it sets can be one the position
|
|
77
|
+
* does not declare. Filled in, it handed the handler a value its own type refuses, and the module
|
|
78
|
+
* did not compile. A warning, because the property itself is still validated as the document
|
|
79
|
+
* says; only the fallback is dropped.
|
|
80
|
+
*/
|
|
81
|
+
"invalid-default": {
|
|
82
|
+
severity: "warning",
|
|
83
|
+
messages: {
|
|
84
|
+
default: paramMessage `The default of '${"name"}' is not emitted: ${"why"}, so filling it in would hand the handler a value the property refuses. Replace it with a value the property accepts, or remove it.`,
|
|
71
85
|
},
|
|
72
86
|
},
|
|
73
87
|
"unsupported-default": {
|
|
@@ -110,6 +124,8 @@ const diagnostics = {
|
|
|
110
124
|
severity: "warning",
|
|
111
125
|
messages: {
|
|
112
126
|
default: paramMessage `'${"name"}' is required, so its default can never apply: a required property is never absent. The OpenAPI document publishes it in 'required' and carries the default only as an annotation, and this validator does the same - a request omitting '${"name"}' is refused. Declare it optional, '${"name"}?: ... = ...', if callers may omit it.`,
|
|
127
|
+
discriminator: paramMessage `'${"name"}' is a discriminator, which '@typespec/openapi3' always publishes in its base's 'required', so it is required here however it is declared and its default can never apply. The validator refuses a value omitting it, as the document does. Remove the '?' and the default.`,
|
|
128
|
+
ancestor: paramMessage `'${"name"}' is required by a declaration of its key above it, which the document holds the value to through 'allOf', so it is required here however it is declared and its default can never apply. The validator refuses a value omitting it, as the document does. Remove the '?' and the default, or make the declaration above optional.`,
|
|
113
129
|
},
|
|
114
130
|
},
|
|
115
131
|
/**
|
|
@@ -184,6 +200,7 @@ const diagnostics = {
|
|
|
184
200
|
severity: "error",
|
|
185
201
|
messages: {
|
|
186
202
|
default: paramMessage `'${"name"}' serialises with '${"property"}' inside each variant, but '${"variant"}' does not declare it - so the OpenAPI document omits it, which OpenAPI 3.1 forbids for a discriminator ("the discriminator field MUST be a required field"). Emitting a validator that required it anyway would enforce a rule the published contract does not state. Declare '${"property"}' on '${"variant"}'.`,
|
|
203
|
+
open: paramMessage `'${"name"}' serialises with '${"property"}' inside each variant, but '${"variant"}' declares it as no one fixed value, so nothing can tell its values from another variant's and no dispatch can hold it. Declare '${"property"}' on '${"variant"}' as the literal its variant name says.`,
|
|
187
204
|
},
|
|
188
205
|
},
|
|
189
206
|
"empty-union": {
|
|
@@ -220,6 +237,60 @@ const diagnostics = {
|
|
|
220
237
|
default: paramMessage `'seal-object-schemas' resolves to true for ${"sealed"} and false for ${"open"}, and '@typespec/openapi3' has no per-service options - it applies one value to the whole program. So one of these services would publish a document that disagrees with the validator emitted beside it. Give every service the same value, or split the surfaces into separate compiles.`,
|
|
221
238
|
},
|
|
222
239
|
},
|
|
240
|
+
/**
|
|
241
|
+
* **A model a discriminated base's `mapping` names, which no dispatch can hold.**
|
|
242
|
+
*
|
|
243
|
+
* The mapping names a subtype for the values its discriminator fixes, and a dispatch reads one
|
|
244
|
+
* fixed value per subtype. A subtype whose discriminator is not fixed - `"lab" | string`, or `kind`
|
|
245
|
+
* renamed with `@encodedName` so its base's open `string` is what it publishes - made Zod throw
|
|
246
|
+
* "Invalid discriminated union option" on import or on the first parse. A subtype whose value a
|
|
247
|
+
* declaration above it refuses - `kind: "cobra"` under `@maxLength(3)` - is published as `allOf`
|
|
248
|
+
* that declaration, so no value is one, and was accepted.
|
|
249
|
+
*
|
|
250
|
+
* **An error, as `undeclared-discriminator` is**: the spec says something the document cannot
|
|
251
|
+
* hold as a dispatch, so it is refused rather than read some other way, and the base dispatches to
|
|
252
|
+
* its other subtypes until it is fixed.
|
|
253
|
+
*/
|
|
254
|
+
"invalid-subtype-discriminator": {
|
|
255
|
+
severity: "error",
|
|
256
|
+
messages: {
|
|
257
|
+
default: paramMessage `'${"name"}' is named by the mapping of '${"base"}''s discriminator '${"property"}', and ${"why"}. Declare '${"property"}' on '${"name"}' as one fixed value that every declaration of '${"property"}' above it admits.`,
|
|
258
|
+
invisible: paramMessage `'${"name"}' is named by the mapping of '${"base"}''s discriminator '${"property"}', and ${"why"}. Make '${"property"}' visible at '${"at"}' on '${"name"}' and every model above it that declares it: a value is told as a '${"name"}' by its '${"property"}'.`,
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
/**
|
|
262
|
+
* **A template instance that adds properties to a base openapi3 seals.**
|
|
263
|
+
*
|
|
264
|
+
* openapi3 seals a model with no derived models it publishes, and does not count a template
|
|
265
|
+
* instance as one, so under `seal-object-schemas` `Pet<string> extends Animal` leaves `Animal`
|
|
266
|
+
* sealed. `Pet<string>` is published as `allOf` that sealed `Animal`, whose own
|
|
267
|
+
* `unevaluatedProperties` cannot see `Pet<string>`'s properties - so the document refuses every
|
|
268
|
+
* value carrying one. An error, because the published schema refuses what the spec declares, and
|
|
269
|
+
* no validator can agree with both.
|
|
270
|
+
*/
|
|
271
|
+
"sealed-base-extended": {
|
|
272
|
+
severity: "error",
|
|
273
|
+
messages: {
|
|
274
|
+
default: paramMessage `'${"name"}' extends '${"base"}', which '@typespec/openapi3' seals under 'seal-object-schemas' because it does not count a template instance as a derived model. The document publishes '${"name"}' as 'allOf' the sealed '${"base"}', so it refuses every value carrying ${"keys"}, which '${"name"}' adds. Declare it as a model of its own with 'model ... is ${"name"}', which openapi3 counts as derived and so leaves '${"base"}' open.`,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
/**
|
|
278
|
+
* **A redeclared property no one validator can hold to both of its declarations.**
|
|
279
|
+
*
|
|
280
|
+
* openapi3 publishes a subtype as `allOf` its base, so a property the subtype redeclares must meet
|
|
281
|
+
* every declaration of it. Where the subtype's narrows the base's - a derived scalar, a model that
|
|
282
|
+
* extends it, a literal the base admits - the base's constraints are applied to the subtype's
|
|
283
|
+
* value. Where it does not - a model that only looks like the base's, a scalar that is not one of
|
|
284
|
+
* the base's - the value is held to two schemas, and intersecting them in Zod merged sealed
|
|
285
|
+
* objects' keys and threw on two defaults. Refused rather than read some other way.
|
|
286
|
+
*/
|
|
287
|
+
"unrepresentable-override": {
|
|
288
|
+
severity: "error",
|
|
289
|
+
messages: {
|
|
290
|
+
default: paramMessage `'${"model"}' redeclares '${"name"}', which '${"base"}' declares as '${"declared"}', and ${"why"}: the document holds the value to both, and no one validator here can. Declare '${"name"}' on '${"model"}' as '${"declared"}', or as a type derived from it.`,
|
|
291
|
+
recursive: paramMessage `'${"model"}.${"name"}' is held to '${"base"}.${"name"}' as well, through 'allOf', and merging the two declarations refers back to itself. This emitter declares no recursive merge, so the value is refused rather than validated against one of the two. Declare '${"name"}' on '${"model"}' as '${"declared"}', or as a type derived from it.`,
|
|
292
|
+
},
|
|
293
|
+
},
|
|
223
294
|
"unknown-key-vocabulary": {
|
|
224
295
|
severity: "error",
|
|
225
296
|
messages: {
|
|
@@ -287,14 +358,25 @@ const diagnostics = {
|
|
|
287
358
|
},
|
|
288
359
|
},
|
|
289
360
|
/**
|
|
361
|
+
* **A constraint `@encode` has moved onto a JSON type it does not apply to.**
|
|
362
|
+
*
|
|
363
|
+
* openapi3 applies a property's or a scalar's constraints and then lets the encoding replace
|
|
364
|
+
* `type`, so `@encode(string)` on a numeric property publishes `minimum` beside `type: string`,
|
|
365
|
+
* and `@encode("x", int32)` on a string publishes `pattern` beside `type: integer`. JSON Schema
|
|
366
|
+
* ignores each there, so the document asserts nothing, and the validator drops it too: applied,
|
|
367
|
+
* a numeric bound on text became a LENGTH check, and `.regex()` on an integer or `.min()` on a
|
|
368
|
+
* boolean threw when the module loaded. A default of the declared type under an encoding that
|
|
369
|
+
* changes it is the same case: it is no value of the encoded type.
|
|
370
|
+
*
|
|
290
371
|
* **A diagnostic, not a linter rule**, because here the emitter cannot do what the spec asks: it
|
|
291
|
-
* drops a
|
|
292
|
-
* diagnostics, where a linter is for a program that is correct but could be better.
|
|
372
|
+
* drops a rule the author wrote. That is the case TypeSpec reserves for `$onValidate`-style
|
|
373
|
+
* diagnostics, where a linter is for a program that is correct but could be better. It replaces
|
|
374
|
+
* `unenforceable-encoded-bound`, which reported numeric bounds on text and nothing else.
|
|
293
375
|
*/
|
|
294
|
-
"unenforceable-encoded-
|
|
376
|
+
"unenforceable-encoded-constraint": {
|
|
295
377
|
severity: "warning",
|
|
296
378
|
messages: {
|
|
297
|
-
default: paramMessage
|
|
379
|
+
default: paramMessage `${"keywords"} on '${"name"}' is not emitted: '@encode' makes the wire value a JSON ${"type"}, and ${"keywords"} is written for the declared type. '@typespec/openapi3' publishes it beside 'type: ${"type"}', where JSON Schema ignores a keyword meant for another type and a default is no value of it, so a validator applying it would disagree with the published contract. Declare the rule in a form the encoded value carries, such as a '@pattern' on a string, or remove '@encode'.`,
|
|
298
380
|
},
|
|
299
381
|
},
|
|
300
382
|
/**
|
|
@@ -303,7 +385,7 @@ const diagnostics = {
|
|
|
303
385
|
* Zod skips a declared `__proto__` key on purpose, so prototype pollution cannot reach through a
|
|
304
386
|
* parse (colinhacks/zod#6386): a value of any type, or none, is accepted for it and dropped from
|
|
305
387
|
* the result. The document publishes it like any other property, and nothing emitted can enforce
|
|
306
|
-
* it - which is the case `unenforceable-encoded-
|
|
388
|
+
* it - which is the case `unenforceable-encoded-constraint` reports for a constraint, for the same reason:
|
|
307
389
|
* the spec is representable, so refusing it would make it emit under one emitter and not the
|
|
308
390
|
* other, and serving it silently would leave a validator looser than its contract unsaid.
|
|
309
391
|
*
|
|
@@ -381,21 +463,77 @@ const diagnostics = {
|
|
|
381
463
|
},
|
|
382
464
|
},
|
|
383
465
|
/**
|
|
384
|
-
* **
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
466
|
+
* **A body declared `never`.** The type has no values, so the body cannot exist, and
|
|
467
|
+
* `@typespec/openapi3` - whose body paths test only for `void` - publishes it as
|
|
468
|
+
* `content: { "application/json": { schema: {} } }`, any value at all. A validator could agree
|
|
469
|
+
* with the spec or with the document, not both, so the declaration is refused. `void` declares no
|
|
470
|
+
* body, and the document publishes it as none.
|
|
471
|
+
*/
|
|
472
|
+
"never-body": {
|
|
473
|
+
severity: "error",
|
|
474
|
+
messages: {
|
|
475
|
+
default: paramMessage `'${"operationId"}' declares a ${"position"} body of type 'never', which has no values, and '@typespec/openapi3' publishes it as any JSON value. Replace it with 'void' to declare no body.`,
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
/**
|
|
479
|
+
* **A `null` the OpenAPI 3.0 document cannot publish.** OAS 3.0.4: `nullable` "only takes effect if
|
|
480
|
+
* `type` is explicitly defined within the same Schema Object", and "Other Schema Object constraints
|
|
481
|
+
* retain their defined behavior, and therefore may disallow the use of `null`". `@typespec/openapi3`
|
|
482
|
+
* publishes `Inner | null` as `{ type: object, allOf: [Inner], nullable: true }`, `Color | null` as
|
|
483
|
+
* `{ allOf: [Color], nullable: true }` and `"a" | "b" | null` as `{ type: string, enum: [a, b],
|
|
484
|
+
* nullable: true }`, and each refuses `null` by that text; a bare `null` is `{ nullable: true }`,
|
|
485
|
+
* any value. Refused where openapi3 publishes 3.0, since a validator agreeing with the spec's
|
|
486
|
+
* `| null` disagrees with the document; at 3.1 each is `anyOf` with `{ type: "null" }`.
|
|
487
|
+
*/
|
|
488
|
+
"unpublishable-null": {
|
|
489
|
+
severity: "error",
|
|
490
|
+
messages: {
|
|
491
|
+
default: paramMessage `'${"type"}' admits null, and the OpenAPI 3.0 document '@typespec/openapi3' publishes does not: ${"why"}. Set 'openapi-versions: ["3.1.0"]' under '@typespec/openapi3' in tspconfig.yaml's options, whether or not it is emitted, to publish OpenAPI 3.1, which says null.`,
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
/**
|
|
495
|
+
* **A sealed model whose base is a record, where openapi3 publishes 3.0.** openapi3's 3.0 sealing
|
|
496
|
+
* is `additionalProperties: { not: {} }` beside the model's own `properties`, which cannot see the
|
|
497
|
+
* record its base declares in `allOf`, so the document refuses every key the record admits; 3.1
|
|
498
|
+
* seals with `unevaluatedProperties`, which can.
|
|
499
|
+
*/
|
|
500
|
+
"unpublishable-record": {
|
|
501
|
+
severity: "error",
|
|
502
|
+
messages: {
|
|
503
|
+
default: paramMessage `'${"type"}' extends the record '${"base"}' and is sealed, and the OpenAPI 3.0 document '@typespec/openapi3' publishes seals it with 'additionalProperties: { not: {} }', which does not see the record in its 'allOf': every key the record admits is refused. Set 'openapi-versions: ["3.1.0"]' under '@typespec/openapi3', which seals with 'unevaluatedProperties', or spread the record onto the model, '...${"base"}', which is published open.`,
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
/**
|
|
507
|
+
* **A required read-only property in a request, where openapi3 publishes 3.0 and a later version.**
|
|
508
|
+
* OAS 3.0.4 says such a `required` "will take effect on the response only"; 3.1 made `readOnly`
|
|
509
|
+
* an annotation, so `required` holds in a request too. One request validator cannot agree with both
|
|
510
|
+
* documents.
|
|
394
511
|
*/
|
|
395
|
-
"
|
|
512
|
+
"ambiguous-read-only": {
|
|
396
513
|
severity: "error",
|
|
397
514
|
messages: {
|
|
398
|
-
default: paramMessage `'${"type"}' is
|
|
515
|
+
default: paramMessage `'${"type"}' is sent in a request and holds the required read-only property '${"property"}', and '@typespec/openapi3' publishes OpenAPI 3.0, where a request may leave it out, and ${"later"}, where it must send it: a request validator agrees with only one. Set one version in 'openapi-versions', or declare the request's own view of it, '${"view"}', which openapi3 publishes without it.`,
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
/**
|
|
519
|
+
* **An `@encode` whose published schema no value satisfies, or which states no type.**
|
|
520
|
+
*
|
|
521
|
+
* `@typespec/openapi3` keeps the declared type beside the encoded one, so `@encode("x", string)`
|
|
522
|
+
* on a model publishes `{ $ref: Filter, type: "string" }`: an object and a string at once, which
|
|
523
|
+
* nothing is. The same holds for a list model, an integer enum, or a string scalar encoded as
|
|
524
|
+
* `int32`. An encoding to a scalar outside the standard library publishes no `type` at all, which
|
|
525
|
+
* is also how two scalars whose `@encode` name each other once overflowed the stack. This emitter
|
|
526
|
+
* validated the encoded scalar beside each, so a value the document refuses was accepted, or one
|
|
527
|
+
* it accepts refused.
|
|
528
|
+
*
|
|
529
|
+
* **An error, as `undeclared-discriminator` is**: the published contract is not one a validator
|
|
530
|
+
* can agree with, so the declaration is refused rather than read some other way, and the property
|
|
531
|
+
* validates nothing until the spec says what it means.
|
|
532
|
+
*/
|
|
533
|
+
"unsatisfiable-encoding": {
|
|
534
|
+
severity: "error",
|
|
535
|
+
messages: {
|
|
536
|
+
default: paramMessage `'${"name"}' is declared '${"declared"}' and encoded as '${"encoded"}': ${"why"}. Encode it as a standard scalar its declared type admits, or remove '@encode'.`,
|
|
399
537
|
},
|
|
400
538
|
},
|
|
401
539
|
/**
|
|
@@ -412,16 +550,67 @@ const diagnostics = {
|
|
|
412
550
|
* validated as both declarations at once, and the contract types still compile. A server emitter
|
|
413
551
|
* reading `EmittedRoute.sharedInputKeys` declines to mount the operation.
|
|
414
552
|
*/
|
|
553
|
+
/**
|
|
554
|
+
* **Two header parameters whose names differ only in case**, which HTTP reads as one header and
|
|
555
|
+
* openapi3 publishes as two parameters of that one value. Refused; see `headerCaseCollisionOf`.
|
|
556
|
+
*/
|
|
557
|
+
"header-case-collision": {
|
|
558
|
+
severity: "error",
|
|
559
|
+
messages: {
|
|
560
|
+
default: paramMessage `'${"operation"}' declares the header '${"first"}' and the header '${"second"}', which HTTP reads as one header - a header's name is case-insensitive - and openapi3 publishes as two parameters of the one value it carries. Neither is validated. Give one of them a different name.`,
|
|
561
|
+
},
|
|
562
|
+
},
|
|
415
563
|
"shared-input-key": {
|
|
416
564
|
severity: "warning",
|
|
417
565
|
messages: {
|
|
418
566
|
default: paramMessage `'${"operation"}' takes the ${"first"} '${"key"}' and the ${"second"} '${"key"}', and a handler's input holds one value under each key, so '${"key"}' is stated as both declarations at once and accepts only a value satisfying both. Give one of them a different name.`,
|
|
567
|
+
repeated: paramMessage `'${"operation"}' takes the ${"first"} '${"key"}' twice, and the document publishes only the first: the wire carries one value, which is validated and typed as the first declaration, and the second states nothing. Give one of them a different name.`,
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
/**
|
|
571
|
+
* **A schema `@typespec/openapi3` publishes with its properties and its `required` read at two
|
|
572
|
+
* visibilities.** openapi3 writes a component from the first reference that reaches it; where that
|
|
573
|
+
* reference is a request's, and the model has nothing to reshape there, its `required` is read at
|
|
574
|
+
* `Read` and its properties at the request's visibility - a read-only property is then required and
|
|
575
|
+
* not declared, or, optional, left out - and every use of the component shares it. Refused, by the
|
|
576
|
+
* maintainer's decision, rather than validated against a schema the document does not hold. See
|
|
577
|
+
* `publicationOf` in `published.ts` for the measured mechanism.
|
|
578
|
+
*/
|
|
579
|
+
"split-visibility": {
|
|
580
|
+
severity: "error",
|
|
581
|
+
messages: {
|
|
582
|
+
default: paramMessage `'@typespec/openapi3' publishes '${"name"}' as ${"via"} first reaches it: there it reads the properties of ${"model"} at '${"at"}' and its 'required' at 'Read', so ${"property"} is ${"effect"}, in every use of '${"name"}'. Refused rather than validated against a schema the document does not hold. Use the request's own view, which openapi3 publishes apart: ${"remedy"}.`,
|
|
583
|
+
inherited: paramMessage `'${"name"}' holds ${"model"} through 'allOf', and '@typespec/openapi3' publishes ${"model"} as ${"via"} first reaches it: there it reads the properties of ${"model"} at '${"at"}' and its 'required' at 'Read', so ${"property"} is ${"effect"}, in every model holding it. Refused rather than validated against a schema the document does not hold. Use the request's own view, which openapi3 publishes apart: ${"remedy"}.`,
|
|
584
|
+
inline: paramMessage `'@typespec/openapi3' publishes ${"model"} with its properties read at '${"at"}' and its 'required' at 'Read', so ${"property"} is ${"effect"}. Refused rather than validated against a schema the document does not hold. Use the request's own view, which openapi3 publishes apart: ${"remedy"}.`,
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
/**
|
|
588
|
+
* **A `@oneOf` body sent as a URL-encoded form, where a member may read one text two ways and
|
|
589
|
+
* another member accepts one and not the other.** The document accepts the form where some
|
|
590
|
+
* reading of its text matches exactly one member, and which reading that is depends on every key
|
|
591
|
+
* and member - finding it means trying every combination, exponential in the keys, and a record's
|
|
592
|
+
* keys are the sender's to choose. Refused rather than validated by a reading the document need
|
|
593
|
+
* not take. See `ambiguousFormKeyOf`.
|
|
594
|
+
*/
|
|
595
|
+
"ambiguous-form-reading": {
|
|
596
|
+
severity: "error",
|
|
597
|
+
messages: {
|
|
598
|
+
default: paramMessage `'${"operation"}' accepts the '@oneOf' '${"union"}' as a URL-encoded form, where '${"member"}' may read the text of ${"key"} as ${"first"} and as ${"second"}, and '${"other"}' accepts only the first. The document takes the form where some reading of its text matches exactly one member, and which reading that is depends on every key and member, so the body is not validated. Give ${"key"} one reading in '${"member"}', declare it alike in '${"other"}', or publish the union as 'anyOf' by removing '@oneOf'.`,
|
|
419
599
|
},
|
|
420
600
|
},
|
|
421
601
|
"duplicate-declaration": {
|
|
422
602
|
severity: "error",
|
|
423
603
|
messages: {
|
|
424
604
|
default: paramMessage `Two different types are both named '${"name"}', and a TypeScript module cannot declare one name twice. The document tells them apart by namespace; this file cannot. Give one of them a different name, or set an explicit one with '@friendlyName'.`,
|
|
605
|
+
renamed: paramMessage `Two different types, '${"first"}' and '${"second"}', are both declared as '${"name"}': a character an identifier cannot hold is written '_', and a TypeScript module cannot declare one name twice, so neither is validated. Give one of them a different name, or set an explicit one with '@friendlyName'.`,
|
|
606
|
+
multipart: paramMessage `'${"name"}' is a multipart body and the name of a schema that differs from it - the same model sent as JSON is one: openapi3 publishes one component under the name, and a TypeScript module cannot declare one name twice, so neither is validated. Send the multipart body as a model of its own.`,
|
|
607
|
+
envelope: paramMessage `'${"name"}' is the name openapi3 gives an envelope of a '@discriminated' union, after the union and the variant, and the name of another type: a TypeScript module cannot declare one name twice, so neither is validated. Rename the type, the union or the variant.`,
|
|
608
|
+
instances: paramMessage `Two instances of the template '${"name"}' are both named '${"name"}', as openapi3 names a union instance after its template alone: it refuses its own document for them as 'duplicate-type-name', and a TypeScript module cannot declare one name twice, so neither is validated. Declare a named union for each, or set an explicit name with '@friendlyName'.`,
|
|
609
|
+
binary: paramMessage `'${"name"}' holds 'bytes' and is sent as a multipart part, where openapi3 publishes 'bytes' as any value, and used elsewhere, where it is a base64 string: openapi3 publishes both under the name and refuses its own document as 'duplicate-type-name', and a TypeScript module cannot declare one name twice, so neither is validated. Give the part a union of its own.`,
|
|
610
|
+
cased: paramMessage `Two different types, '${"first"}' and '${"second"}', are both validated by '${"name"}': a validator is named for its type with the first letter lowered, and a TypeScript module cannot declare one name twice, so neither is validated. Give one of them a different name, or set an explicit one with '@friendlyName'.`,
|
|
611
|
+
suffixed: paramMessage `'${"name"}' is the name openapi3 gives '${"first"}' at '${"at"}', a type it suffixes for being used at more than one visibility, and the name of '${"second"}': openapi3 refuses its own document for it as 'duplicate-type-name', and a TypeScript module cannot declare one name twice, so neither is validated. Rename '${"second"}', or set an explicit name with '@friendlyName'.`,
|
|
612
|
+
visibility: paramMessage `'${"name"}' is the document's one name for two different schemas of one model, at '${"first"}' and at '${"second"}': openapi3 publishes both under it and refuses its own document as 'duplicate-type-name', and this module cannot declare one name twice, so neither is validated. ${"remedy"}`,
|
|
613
|
+
context: paramMessage `'${"name"}' is published inside an explicit '@body', where '@typespec/http' ignores its metadata annotations and the document holds them as data, and outside one, where they are metadata: two shapes under one name, which '@typespec/openapi3' refuses too, as 'duplicate-type-name'. Mark the body '@bodyRoot', or give the body a model of its own.`,
|
|
425
614
|
},
|
|
426
615
|
},
|
|
427
616
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Program } from "@typespec/compiler";
|
|
2
|
+
/**
|
|
3
|
+
* **The OpenAPI versions `@typespec/openapi3` publishes from this compile**: its `openapi-versions`
|
|
4
|
+
* option, read from the compile's options as `typespec-openapi-document` reads it, and openapi3's own
|
|
5
|
+
* default, `3.0.0`, where nothing sets it - whether or not openapi3 is emitted, since a project
|
|
6
|
+
* serving no document is still described by the one openapi3 would write.
|
|
7
|
+
*
|
|
8
|
+
* **The versions are two dialects, and a validator agrees with the one published.** 0.31.0 read every
|
|
9
|
+
* schema as OpenAPI 3.1 reads it while openapi3 published 3.0 by default, and the two answer one
|
|
10
|
+
* schema differently in two places: a required read-only property is required "on the response only"
|
|
11
|
+
* at 3.0 and in both directions at 3.1, and 3.0's `nullable` takes effect only beside `type` - see
|
|
12
|
+
* {@link publishesOpenApi30Only} and `unpublishable-null`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function openApiVersionsOf(program: Program): readonly string[];
|
|
15
|
+
/** Whether every document openapi3 publishes here is OpenAPI 3.0, which reads a request as 3.0 does. */
|
|
16
|
+
export declare function publishesOpenApi30Only(program: Program): boolean;
|
|
17
|
+
/** Whether openapi3 publishes an OpenAPI 3.0 document here, beside any other. */
|
|
18
|
+
export declare function publishesOpenApi30(program: Program): boolean;
|
|
19
|
+
/** Whether openapi3 publishes OpenAPI 3.0 and a later version here, two documents a request reads apart. */
|
|
20
|
+
export declare function publishesOpenApi30AndLater(program: Program): boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **The OpenAPI versions `@typespec/openapi3` publishes from this compile**: its `openapi-versions`
|
|
3
|
+
* option, read from the compile's options as `typespec-openapi-document` reads it, and openapi3's own
|
|
4
|
+
* default, `3.0.0`, where nothing sets it - whether or not openapi3 is emitted, since a project
|
|
5
|
+
* serving no document is still described by the one openapi3 would write.
|
|
6
|
+
*
|
|
7
|
+
* **The versions are two dialects, and a validator agrees with the one published.** 0.31.0 read every
|
|
8
|
+
* schema as OpenAPI 3.1 reads it while openapi3 published 3.0 by default, and the two answer one
|
|
9
|
+
* schema differently in two places: a required read-only property is required "on the response only"
|
|
10
|
+
* at 3.0 and in both directions at 3.1, and 3.0's `nullable` takes effect only beside `type` - see
|
|
11
|
+
* {@link publishesOpenApi30Only} and `unpublishable-null`.
|
|
12
|
+
*/
|
|
13
|
+
export function openApiVersionsOf(program) {
|
|
14
|
+
const options = program.compilerOptions.options?.["@typespec/openapi3"];
|
|
15
|
+
const versions = options?.["openapi-versions"];
|
|
16
|
+
return versions === undefined || versions.length === 0 ? ["3.0.0"] : versions;
|
|
17
|
+
}
|
|
18
|
+
const is30 = (version) => version.startsWith("3.0.");
|
|
19
|
+
/** Whether every document openapi3 publishes here is OpenAPI 3.0, which reads a request as 3.0 does. */
|
|
20
|
+
export function publishesOpenApi30Only(program) {
|
|
21
|
+
return openApiVersionsOf(program).every(is30);
|
|
22
|
+
}
|
|
23
|
+
/** Whether openapi3 publishes an OpenAPI 3.0 document here, beside any other. */
|
|
24
|
+
export function publishesOpenApi30(program) {
|
|
25
|
+
return openApiVersionsOf(program).some(is30);
|
|
26
|
+
}
|
|
27
|
+
/** Whether openapi3 publishes OpenAPI 3.0 and a later version here, two documents a request reads apart. */
|
|
28
|
+
export function publishesOpenApi30AndLater(program) {
|
|
29
|
+
const versions = openApiVersionsOf(program);
|
|
30
|
+
return versions.some(is30) && versions.some((version) => !is30(version));
|
|
31
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { type Model, type ModelProperty, type Namespace, type Program, type Type } from "@typespec/compiler";
|
|
2
|
+
import { Visibility, type HttpOperation } from "@typespec/http";
|
|
3
|
+
export declare function withServiceView<T>(program: Program, service: Namespace, run: () => T): T;
|
|
4
|
+
/** The same scope, held across an `await` - returns what puts the previous one back. */
|
|
5
|
+
export declare function beginServiceView(program: Program, service: Namespace): () => void;
|
|
6
|
+
/**
|
|
7
|
+
* **The types of the service no operation reaches, which openapi3 publishes as components all the
|
|
8
|
+
* same** - its `processUnreferencedSchemas`, with `omitUnreachableTypes` unset: every model, enum and
|
|
9
|
+
* union declared in the service namespace and below it (only the service's own, where the service is
|
|
10
|
+
* the global namespace), but not one an operation reaches, a model whose properties were spread as
|
|
11
|
+
* parameters (published as parameters), a type openapi3 inlines, an HTTP file, or an authentication
|
|
12
|
+
* scheme's model. Rendered at `All` by the caller, as openapi3 renders them. A scalar is validated
|
|
13
|
+
* inline wherever it is used, reached or not, so an unreached one has nothing to declare.
|
|
14
|
+
*/
|
|
15
|
+
export declare function unreachedTypes(): readonly Type[];
|
|
16
|
+
/** Every type the service publishes, reached by an operation or not - read as {@link usageOf} reads it. */
|
|
17
|
+
export declare function publishedTypes(): Iterable<Type>;
|
|
18
|
+
/** Whether openapi3 suffixes this type's component with the visibility it is rendered at. */
|
|
19
|
+
export declare function isSuffixedByUsage(type: Type): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* An operation's body, part or parameter a walk starts from: `where`, as the diagnostic says it, and
|
|
22
|
+
* `root`, the name of its type where it has one - wrapped in a visibility template, as
|
|
23
|
+
* `Create<Outer>`, the request is published from a model of its own and leaves the shared ones alone.
|
|
24
|
+
*/
|
|
25
|
+
interface Via {
|
|
26
|
+
readonly where: string;
|
|
27
|
+
readonly root: string | undefined;
|
|
28
|
+
/** Whether the root is a model, which a visibility template such as `Create<...>` takes. */
|
|
29
|
+
readonly model: boolean;
|
|
30
|
+
/** The root itself, where it is a named model or union - see {@link viewRemedyOf}. */
|
|
31
|
+
readonly type?: Type;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* **The visibility openapi3 reads a model's own properties at, where it publishes the model at
|
|
35
|
+
* `at`: the visibility the reference it writes the schema from arrived at** - see
|
|
36
|
+
* {@link publicationOf}. Where nothing reshapes the model there, the two are one schema - anything
|
|
37
|
+
* else is `split-visibility` - but a type a property refers to is reached at the first, and a list
|
|
38
|
+
* or a union instance is not reduced: `Mid { tags: Tags }` first sent in a create body and also
|
|
39
|
+
* returned is published as `Mid`, its `tags` referring to `TagsCreate`. A component is read as
|
|
40
|
+
* `components.schemas` keeps it, the later of two metadata contexts; a model written out where it
|
|
41
|
+
* is used, as the context `ignore` writes it. Where no reference wrote it, at `at`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function readVisibilityOf(program: Program, model: Model, at: Visibility, ignore: boolean): Visibility;
|
|
44
|
+
/**
|
|
45
|
+
* **The visibility of the component the document keeps for a type rendered at `at`.** Where the type
|
|
46
|
+
* is published at several visibilities under one name - unsuffixed, as openapi3 names a type it
|
|
47
|
+
* counts at one - `components.schemas` keeps the one written last, and every `$ref` to the name is
|
|
48
|
+
* to it. `K extends P`, written at `All` by the pass over types no operation reaches and at `Read` by
|
|
49
|
+
* `P`'s discriminator mapping, is `K` at `Read` in the document; `G extends K`, written out from
|
|
50
|
+
* `K` at `All`, required a create-only `s` the `K` it refers to leaves out. Elsewhere, `at`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function keptVisibilityOf(type: Type, at: Visibility): Visibility;
|
|
53
|
+
/**
|
|
54
|
+
* **Every component openapi3 writes, at the visibility it publishes it at, in the metadata context
|
|
55
|
+
* `components.schemas` keeps** - declared after the routes, so a component no route renders by name
|
|
56
|
+
* is declared all the same. A model a merge holds is written into the merge here, and openapi3
|
|
57
|
+
* publishes it as a component of its own: `L1 extends L0 { m: B }` and `L2 extends L1 { m: Sub }`
|
|
58
|
+
* publish `B` and `Sub`, which had no validator.
|
|
59
|
+
*/
|
|
60
|
+
export declare function publishedComponents(program: Program): readonly {
|
|
61
|
+
readonly type: Type;
|
|
62
|
+
readonly at: Visibility;
|
|
63
|
+
readonly ignore: boolean;
|
|
64
|
+
readonly binary: boolean;
|
|
65
|
+
}[];
|
|
66
|
+
/**
|
|
67
|
+
* **The visibility openapi3 publishes a parameter at, where it is not the operation's.** A parameter
|
|
68
|
+
* spread from a named model is published once, as a shared parameter component, from the first
|
|
69
|
+
* operation that sends it, and every later one refers to it: `...CommonA` sent by a GET and then by a
|
|
70
|
+
* POST is read at `Query` for both. Read at each operation's own, the POST was held to a create view
|
|
71
|
+
* the document does not publish for it.
|
|
72
|
+
*/
|
|
73
|
+
/**
|
|
74
|
+
* Whether openapi3 publishes a shared route's parameter optional though it is declared required -
|
|
75
|
+
* see `resolveSharedRouteParameters` - asked of the declaration, before or after openapi3 sets its
|
|
76
|
+
* `optional`, with one answer.
|
|
77
|
+
*/
|
|
78
|
+
export declare function isSharedRouteOptional(property: ModelProperty): boolean;
|
|
79
|
+
export declare function parameterVisibilityOf(property: ModelProperty): Visibility | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* **The visibility openapi3 publishes an operation's request at.** Its own, unless it shares its
|
|
82
|
+
* verb and path with another: openapi3 publishes one operation for the route, every body and
|
|
83
|
+
* parameter at the first one's visibility, and reports `inconsistent-shared-route-request-visibility`
|
|
84
|
+
* where they differ - graded against what it publishes, as the maintainer decided.
|
|
85
|
+
*/
|
|
86
|
+
export declare function requestVisibilityOf(program: Program, operation: HttpOperation): Visibility;
|
|
87
|
+
/**
|
|
88
|
+
* **How openapi3's schema for a model differs from the schema of that model at the visibility it is
|
|
89
|
+
* rendered at.** openapi3 reads a model's properties at the visibility its first reference arrived
|
|
90
|
+
* at - `document` here - and its `required` at the one it reduces that to; the schema it means, and
|
|
91
|
+
* the one rendered here, reads both at the reduced one - `rendered`. Where no reduction intervenes
|
|
92
|
+
* they are one schema. Only the model's own properties are compared: a type a property refers to has
|
|
93
|
+
* a first reference of its own, and is judged by it where it is rendered - a component wherever it
|
|
94
|
+
* is declared, and a model expression where it is written out, as openapi3 writes it out from its
|
|
95
|
+
* cache. A base is part of the model's schema, through `allOf`.
|
|
96
|
+
*/
|
|
97
|
+
export interface Misreading {
|
|
98
|
+
/** The model whose properties openapi3 reads at {@link at}: the one judged, or a base of it. */
|
|
99
|
+
readonly model: Model;
|
|
100
|
+
/** The property its schema gets wrong. */
|
|
101
|
+
readonly property: ModelProperty;
|
|
102
|
+
/** The visibility openapi3 reads the properties at. */
|
|
103
|
+
readonly at: Visibility;
|
|
104
|
+
/** What openapi3 publishes for the property. */
|
|
105
|
+
readonly effect: "undeclared" | "dropped" | "added" | "reshaped";
|
|
106
|
+
/** Where the schema it is found in was first reached from. */
|
|
107
|
+
readonly via: Via;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* **Whether the schema rendered for `type` here is one openapi3 publishes differently - and if so,
|
|
111
|
+
* `split-visibility`, an error, reported once per declaration.** Refused, as the maintainer decided,
|
|
112
|
+
* rather than validated against a schema the document does not hold: the document's `required` names
|
|
113
|
+
* a property its properties leave out, which no value satisfies under `seal-object-schemas`, or it
|
|
114
|
+
* leaves out an optional one, which a sealed schema then refuses.
|
|
115
|
+
*
|
|
116
|
+
* `declared` is the component's name, for a type published as a component; `type` is then its
|
|
117
|
+
* canonical type and `at` the visibility it is declared at. Otherwise `type` is written out where it
|
|
118
|
+
* is used - a model expression, an inlined template instance - and judged by the emission openapi3
|
|
119
|
+
* writes out in the metadata context `ignore`.
|
|
120
|
+
*/
|
|
121
|
+
export declare function isSplitByVisibility(program: Program, type: Type, at: Visibility, ignore: boolean, declared: string | undefined): boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Whether openapi3 publishes the component of `type` at `at` split - the refusal
|
|
124
|
+
* {@link isSplitByVisibility} reports, asked without reporting it. A dispatch leaves such a subtype
|
|
125
|
+
* out, as it leaves out one no value is: its validator is `z.never()`, which no
|
|
126
|
+
* `z.discriminatedUnion` can hold.
|
|
127
|
+
*/
|
|
128
|
+
export declare function isPublishedSplit(program: Program, type: Type, at: Visibility): boolean;
|
|
129
|
+
/** The standard template that is a model's view at a request visibility, where there is one. */
|
|
130
|
+
/**
|
|
131
|
+
* **The view a remedy names, declared so that it works where it is pasted**: a model of its own,
|
|
132
|
+
* `model OuterCreate is Create<Ns.Outer>;`, with the type spelled from the global namespace. Named
|
|
133
|
+
* `Create<Outer>`, the view inherited `Outer`'s `@friendlyName` and collided with it; spelled by the
|
|
134
|
+
* friendly name, or without its namespace from a sub-namespace, it named nothing; and sent through
|
|
135
|
+
* a spread, `...Create<O1>`, it left the body the document publishes as it was.
|
|
136
|
+
*/
|
|
137
|
+
export declare function viewRemedyOf(template: string, type: Type): string;
|
|
138
|
+
export declare function viewTemplateOf(at: Visibility): string | undefined;
|
|
139
|
+
export {};
|