typespec-http-zod 0.15.1 → 0.16.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/dist/src/api.js +34 -4
- package/dist/src/types.js +22 -1
- package/package.json +1 -1
package/dist/src/api.js
CHANGED
|
@@ -601,6 +601,29 @@ function alternateSchemaFor(operation, status, registry) {
|
|
|
601
601
|
* document does not state AND reject what a server actually receives, since `c.req.parseBody()`
|
|
602
602
|
* hands back a `File`. The type of a value is positional, exactly as its visibility is.
|
|
603
603
|
*/
|
|
604
|
+
/**
|
|
605
|
+
* The TS type for a **multipart** request body, built from the same parts as its validator.
|
|
606
|
+
*
|
|
607
|
+
* **`requestBodyOf` returns a type only for `bodyKind === "single"`, so a multipart body reached the
|
|
608
|
+
* request type not at all.** Measured: `UploadInput` was `{ "Content-Type": "multipart/form-data" }`
|
|
609
|
+
* while the validator beside it was `z.object({ file: ..., alt: ... })` and the document published
|
|
610
|
+
* every part. Two artefacts from one program, contradicting each other.
|
|
611
|
+
*
|
|
612
|
+
* **Built from `body.parts`, not from the parts model.** Walking the model types each part as its
|
|
613
|
+
* `HttpPart<T>` wrapper - `file: {}` - where the validator unwraps to `T`. Mirroring
|
|
614
|
+
* {@link multipartSchemaOf} part for part, including `multi` as an array, `optional`, and a binary
|
|
615
|
+
* part as `unknown`, is what makes the two agree by construction rather than by coincidence.
|
|
616
|
+
*/
|
|
617
|
+
function multipartTsOf(body, registry) {
|
|
618
|
+
const entries = body.parts.map((part) => {
|
|
619
|
+
const type = part.body?.type;
|
|
620
|
+
const inner = type === undefined || isBinaryPart(type) ? "unknown" : registry.expressionFor(type);
|
|
621
|
+
const value = part.multi === true ? `(${inner})[]` : inner;
|
|
622
|
+
const optional = part.optional === true ? "?" : "";
|
|
623
|
+
return `\t${objectKey(part.name ?? "")}${optional}: ${value};`;
|
|
624
|
+
});
|
|
625
|
+
return entries.length === 0 ? "{}" : `{\n${entries.join("\n")}\n}`;
|
|
626
|
+
}
|
|
604
627
|
function multipartSchemaOf(body, registry) {
|
|
605
628
|
const entries = body.parts.map((part) => {
|
|
606
629
|
const type = part.body?.type;
|
|
@@ -660,7 +683,9 @@ export function isRawBinaryMediaType(contentTypes) {
|
|
|
660
683
|
/** The declared request body type, if the operation takes one. */
|
|
661
684
|
function requestBodyOf(operation) {
|
|
662
685
|
const body = operation.parameters.body;
|
|
663
|
-
|
|
686
|
+
if (body?.bodyKind === "single")
|
|
687
|
+
return body.type;
|
|
688
|
+
return undefined;
|
|
664
689
|
}
|
|
665
690
|
/**
|
|
666
691
|
* Parameters are emitted as their **own** schemas rather than folded into the body, because the two
|
|
@@ -1421,6 +1446,9 @@ function collectRequestTypes(program, registry, service) {
|
|
|
1421
1446
|
if (successStatusOf(operation) === undefined)
|
|
1422
1447
|
continue;
|
|
1423
1448
|
const body = requestBodyOf(operation);
|
|
1449
|
+
/** The parts of a multipart body, which `requestBodyOf` deliberately does not return. */
|
|
1450
|
+
const multipartBody = operation.parameters.body;
|
|
1451
|
+
const multipart = multipartBody?.bodyKind === "multipart" ? { parts: multipartBody.parts } : undefined;
|
|
1424
1452
|
/**
|
|
1425
1453
|
* Headers are part of the input too.
|
|
1426
1454
|
*
|
|
@@ -1466,9 +1494,11 @@ function collectRequestTypes(program, registry, service) {
|
|
|
1466
1494
|
operationId: operationIdOf(program, operation.operation),
|
|
1467
1495
|
bodyRef: rawBody !== undefined
|
|
1468
1496
|
? `{\n\t${rawBody}: ${rawBodyType};\n}`
|
|
1469
|
-
:
|
|
1470
|
-
?
|
|
1471
|
-
:
|
|
1497
|
+
: multipart !== undefined
|
|
1498
|
+
? multipartTsOf(multipart, registry)
|
|
1499
|
+
: body === undefined
|
|
1500
|
+
? undefined
|
|
1501
|
+
: registry.expressionFor(body),
|
|
1472
1502
|
extraProperties,
|
|
1473
1503
|
});
|
|
1474
1504
|
}
|
package/dist/src/types.js
CHANGED
|
@@ -207,7 +207,28 @@ function modelToTs(program, model) {
|
|
|
207
207
|
* Permissiveness is a statement about the VALIDATOR - what it lets through - not about what a
|
|
208
208
|
* producer must contain. The contract type is the floor: these fields, at least.
|
|
209
209
|
*/
|
|
210
|
-
|
|
210
|
+
/**
|
|
211
|
+
* **An open model carries its catchall here too, or the two walks disagree.**
|
|
212
|
+
*
|
|
213
|
+
* `modelToZod` emits `.loose()` for a `...Record<unknown>` indexer and `.catchall(T)` for a typed
|
|
214
|
+
* one, so `z.infer` gains an index signature. This walk emitted the declared properties alone, and
|
|
215
|
+
* the emitter's OWN assertion caught it:
|
|
216
|
+
* `TS2344: the emitted validator and the shared wire type describe different shapes`. Any spec with
|
|
217
|
+
* `...Record<unknown>` on a request model shipped a `wire-contract.gen.ts` that does not compile.
|
|
218
|
+
*
|
|
219
|
+
* **Emitted rather than compared away.** The document says `additionalProperties`, the validator
|
|
220
|
+
* says `.loose()`, so the type saying it too is what makes all three agree. Relaxing the comparison
|
|
221
|
+
* instead would hide the disagreement rather than remove it, and a looser comparison stops catching
|
|
222
|
+
* the drift it exists for.
|
|
223
|
+
*
|
|
224
|
+
* The dictionary case above returns before this: a model with an indexer and NO declared properties
|
|
225
|
+
* is a `Record<...>` outright rather than an object with a catchall.
|
|
226
|
+
*/
|
|
227
|
+
const catchall = indexerKey === "string" && indexer !== undefined && !isNeverType(indexer.value)
|
|
228
|
+
? [`\t[key: string]: ${typeToTs(program, indexer.value)};`]
|
|
229
|
+
: [];
|
|
230
|
+
const all = [...entries, ...catchall];
|
|
231
|
+
return all.length === 0 ? "{}" : `{\n${all.join("\n")}\n}`;
|
|
211
232
|
}
|
|
212
233
|
/**
|
|
213
234
|
* A property line, including its optionality marker.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typespec-http-zod",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "TypeSpec emitter and library: turn an HTTP service definition into Zod validators and framework-free contract types that agree, keyword for keyword, with the OpenAPI document @typespec/openapi3 publishes from the same source.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codegen",
|