typespec-hono 0.3.3 → 0.5.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/app.js +38 -15
- package/dist/src/runtime.d.ts +29 -6
- package/dist/src/runtime.js +71 -4
- package/package.json +2 -2
- package/src/runtime.ts +87 -11
package/dist/src/app.js
CHANGED
|
@@ -279,6 +279,14 @@ securityFor) {
|
|
|
279
279
|
validators.push([targets[0], identifier]);
|
|
280
280
|
continue;
|
|
281
281
|
}
|
|
282
|
+
/**
|
|
283
|
+
* **Recorded as an ordinary body validator as well**, because `byContentType` publishes what
|
|
284
|
+
* it validated under the body target whichever parser produced it. So a dispatched route is
|
|
285
|
+
* the same shape as every other one downstream: the handler's input type includes the body,
|
|
286
|
+
* and the handler reads it with one `c.req.valid` like anywhere else. What differs is only
|
|
287
|
+
* which middleware is emitted, below.
|
|
288
|
+
*/
|
|
289
|
+
validators.push([VALIDATOR_TARGET.body, identifier]);
|
|
282
290
|
dispatched = { byType: body.byType, identifier };
|
|
283
291
|
}
|
|
284
292
|
return [{ route, names, validators, dispatched }];
|
|
@@ -416,21 +424,33 @@ securityFor) {
|
|
|
416
424
|
* from that type -- measured, `hc<typeof app>` resolved the wrapped route's body to `unknown`.
|
|
417
425
|
*/
|
|
418
426
|
/**
|
|
419
|
-
* Where the document declares request media types needing different parsers, the
|
|
420
|
-
*
|
|
421
|
-
*
|
|
427
|
+
* Where the document declares request media types needing different parsers, the parser is
|
|
428
|
+
* chosen from the request's `Content-Type`. `byContentType` takes the body's schema and
|
|
429
|
+
* `deps.invalid` and validates with them, so it stands in for that target's `zValidator`
|
|
430
|
+
* rather than wrapping one, and publishes under the body target either way.
|
|
431
|
+
*
|
|
432
|
+
* **It used to wrap pre-built `zValidator`s and publish under whichever one ran, and that
|
|
433
|
+
* emitted code a consumer could not compile.** A plain `MiddlewareHandler` contributes no
|
|
434
|
+
* `Input` to Hono's chain, so `c.req.valid("json")` in the handler below was
|
|
435
|
+
* `TS2345: Argument of type '"json"' is not assignable to parameter of type '"header"'`, and
|
|
436
|
+
* the handler's declared input type omitted the body entirely because the body was not among
|
|
437
|
+
* the route's validators. One published slot removes both.
|
|
422
438
|
*/
|
|
423
439
|
const dispatch = entry.dispatched === undefined
|
|
424
440
|
? []
|
|
425
441
|
: [
|
|
426
|
-
|
|
427
|
-
...entry.dispatched.byType.map(([type, target]) => `\t\t\t[${JSON.stringify(type)}, ${JSON.stringify(target)}
|
|
442
|
+
`\t\tbyContentType(${identifierOf(entry)}, deps.invalid, [`,
|
|
443
|
+
...entry.dispatched.byType.map(([type, target]) => `\t\t\t[${JSON.stringify(type)}, ${JSON.stringify(target)}],`),
|
|
428
444
|
"\t\t]),",
|
|
429
445
|
];
|
|
430
446
|
const middleware = [
|
|
431
447
|
...(headOnly ? ["\t\theadOnly,"] : []),
|
|
432
448
|
...gate,
|
|
433
|
-
...validators
|
|
449
|
+
...validators
|
|
450
|
+
// The dispatched body's own `zValidator` is `byContentType`; emitting both would parse
|
|
451
|
+
// the body twice and reject every media type but one.
|
|
452
|
+
.filter(([target]) => entry.dispatched === undefined || target !== VALIDATOR_TARGET.body)
|
|
453
|
+
.map(([target, name]) => `\t\tzValidator(${JSON.stringify(target)}, ${name}, deps.invalid),`),
|
|
434
454
|
...dispatch,
|
|
435
455
|
];
|
|
436
456
|
/**
|
|
@@ -467,15 +487,9 @@ securityFor) {
|
|
|
467
487
|
body.push(`\t\t\tconst ctx = deps.context(c, "none");`);
|
|
468
488
|
body.push("\t\t\tif (ctx === null) return deps.noContext(c);");
|
|
469
489
|
}
|
|
490
|
+
// A dispatched body is in `validators` under the body target like any other, so there is
|
|
491
|
+
// nothing extra to spread: `byContentType` published it there whichever parser ran.
|
|
470
492
|
const pieces = validators.map(([target]) => `...c.req.valid(${JSON.stringify(target)})`);
|
|
471
|
-
/**
|
|
472
|
-
* Only one dispatched branch runs, and `zValidator` writes under its own target. Spreading every
|
|
473
|
-
* possible target is how the handler reads whichever one it was: an unset target reads
|
|
474
|
-
* `undefined`, and spreading `undefined` into an object literal contributes nothing.
|
|
475
|
-
*/
|
|
476
|
-
for (const target of new Set((entry.dispatched?.byType ?? []).map(([, name]) => name))) {
|
|
477
|
-
pieces.push(`...c.req.valid(${JSON.stringify(target)})`);
|
|
478
|
-
}
|
|
479
493
|
if (route.rawBodyProperty !== undefined) {
|
|
480
494
|
// The bytes ARE the contract: a signature covers exactly what arrived, so parsing and
|
|
481
495
|
// re-serialising would verify a different string than the sender signed. WHICH reader
|
|
@@ -657,7 +671,16 @@ securityFor) {
|
|
|
657
671
|
const negotiates = [...grouped.values()].some((group) => group.length > 1);
|
|
658
672
|
// Same rule: imported only where a HEAD operation stands alone on its path.
|
|
659
673
|
const guardsHead = registrations.some((registration) => registration.text.includes("headOnly,"));
|
|
660
|
-
|
|
674
|
+
/**
|
|
675
|
+
* Read from the entries rather than from the rendered text, which the two lines above still do.
|
|
676
|
+
*
|
|
677
|
+
* **Matching a substring of generated output is how this import went missing once already.** It
|
|
678
|
+
* tested for `byContentType([`, the emitted call gained arguments before its bracket, the substring
|
|
679
|
+
* stopped matching, the import stopped being written, and the emitted module threw
|
|
680
|
+
* `ReferenceError: byContentType is not defined` at registration. Nothing in the emitter objected,
|
|
681
|
+
* because the check was a string about a string.
|
|
682
|
+
*/
|
|
683
|
+
const dispatchesBody = entries.some((entry) => entry.dispatched !== undefined);
|
|
661
684
|
const runtimeModule = JSON.stringify(emitted.options.runtimeModule);
|
|
662
685
|
/**
|
|
663
686
|
* **One base sub-app, mounted with `app.route()`. Hono's own nesting, not a rewritten path on
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Context, Env, Input, MiddlewareHandler } from "hono";
|
|
2
|
-
import type { ZodType } from "zod";
|
|
2
|
+
import type { input, output, ZodType } from "zod";
|
|
3
3
|
/**
|
|
4
4
|
* One arm of an operation's declared response set, as the document publishes it.
|
|
5
5
|
*
|
|
@@ -116,6 +116,11 @@ export declare function selectContentType(accept: string | undefined, offered: r
|
|
|
116
116
|
* type. Measured: `hc<typeof app>` resolved a wrapped route's body to `unknown`.
|
|
117
117
|
*/
|
|
118
118
|
export declare const headOnly: MiddlewareHandler;
|
|
119
|
+
/**
|
|
120
|
+
* The `zValidator` targets a request BODY can be read from. Hono extracts `"json"` with
|
|
121
|
+
* `c.req.json()` and `"form"` with `c.req.parseBody()`, and those are the only two that read a body.
|
|
122
|
+
*/
|
|
123
|
+
export type BodyTarget = "json" | "form";
|
|
119
124
|
/**
|
|
120
125
|
* Apply the validator that parses the media type the request actually carries.
|
|
121
126
|
*
|
|
@@ -134,12 +139,30 @@ export declare const headOnly: MiddlewareHandler;
|
|
|
134
139
|
* A `Content-Type` matching nothing declared falls through to the first validator, which reproduces
|
|
135
140
|
* the previous behaviour exactly for that case: the body fails to parse and `deps.invalid` answers.
|
|
136
141
|
* No status is invented here that the document does not describe.
|
|
142
|
+
*
|
|
143
|
+
* **The validated body is published under `"json"` whichever parser produced it**, and that is what
|
|
144
|
+
* makes a dispatched route the same shape as every other one downstream. `ValidationTargets` is a
|
|
145
|
+
* closed union in Hono, so there is no seventh name to coin for "the body"; `"json"` is already the
|
|
146
|
+
* slot this emitter reads a body from, and using it here means the handler spreads one
|
|
147
|
+
* `c.req.valid("json")` exactly as it does for a route declaring a single media type.
|
|
148
|
+
*
|
|
149
|
+
* **The alternative was a middleware that publishes under whichever target ran**, and it was worse
|
|
150
|
+
* in two ways that both shipped. `byContentType` was a bare `MiddlewareHandler`, which contributes no
|
|
151
|
+
* `Input` to the chain, so the generated `c.req.valid("json")` did not compile at all
|
|
152
|
+
* (`TS2345: Argument of type '"json"' is not assignable to parameter of type '"header"'`), and the
|
|
153
|
+
* handler's declared input type omitted the body entirely because the body was not among the route's
|
|
154
|
+
* ordinary validators. Publishing to one known slot removes both, rather than typing around them.
|
|
137
155
|
*/
|
|
138
|
-
export declare function byContentType<E extends Env>(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
156
|
+
export declare function byContentType<E extends Env, S extends ZodType>(schema: S, invalid: <P extends string, I extends Input>(result: {
|
|
157
|
+
readonly success: boolean;
|
|
158
|
+
}, c: Context<E, P, I>) => Response | undefined, branches: readonly (readonly [mediaType: string, target: BodyTarget])[]): MiddlewareHandler<E, string, {
|
|
159
|
+
in: {
|
|
160
|
+
json: input<S>;
|
|
161
|
+
};
|
|
162
|
+
out: {
|
|
163
|
+
json: output<S>;
|
|
164
|
+
};
|
|
165
|
+
}>;
|
|
143
166
|
/**
|
|
144
167
|
* What the app provides. One object, passed once, rather than a module the generated file imports by
|
|
145
168
|
* path. A generated server that hard-codes `../../backend.js` is only usable by the project it was
|
package/dist/src/runtime.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HTTPException } from "hono/http-exception";
|
|
1
2
|
/**
|
|
2
3
|
* The arm that applies to a status, preferring an exact code, then its `NXX` range, then `default`.
|
|
3
4
|
*
|
|
@@ -80,6 +81,31 @@ export function selectContentType(accept, offered) {
|
|
|
80
81
|
* type. Measured: `hc<typeof app>` resolved a wrapped route's body to `unknown`.
|
|
81
82
|
*/
|
|
82
83
|
export const headOnly = async (c, next) => c.req.method === "HEAD" ? next() : c.notFound();
|
|
84
|
+
/**
|
|
85
|
+
* The request body, read the way the target says to read it.
|
|
86
|
+
*
|
|
87
|
+
* The same two readers Hono's own `validator` uses, and the same rejection for a body that is not
|
|
88
|
+
* the JSON it claims to be. `parseBody({ all: true })` builds the object Hono's `"form"` target
|
|
89
|
+
* builds: a repeated key and a `key[]` name both become an array, everything else stays a string.
|
|
90
|
+
*/
|
|
91
|
+
async function readBody(c, target) {
|
|
92
|
+
if (target === "form")
|
|
93
|
+
return c.req.parseBody({ all: true });
|
|
94
|
+
try {
|
|
95
|
+
return await c.req.json();
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
throw new HTTPException(400, { message: "Malformed JSON in request body" });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The slot a validated request body is published under, whichever parser produced it.
|
|
103
|
+
*
|
|
104
|
+
* `ValidationTargets` is a closed union in Hono, so a body cannot be given a name of its own. This
|
|
105
|
+
* is the slot the generated code already reads a body from when one media type is declared, so using
|
|
106
|
+
* it for a dispatched body is what keeps the two cases identical downstream.
|
|
107
|
+
*/
|
|
108
|
+
const BODY_TARGET = "json";
|
|
83
109
|
/**
|
|
84
110
|
* Apply the validator that parses the media type the request actually carries.
|
|
85
111
|
*
|
|
@@ -98,12 +124,53 @@ export const headOnly = async (c, next) => c.req.method === "HEAD" ? next() : c.
|
|
|
98
124
|
* A `Content-Type` matching nothing declared falls through to the first validator, which reproduces
|
|
99
125
|
* the previous behaviour exactly for that case: the body fails to parse and `deps.invalid` answers.
|
|
100
126
|
* No status is invented here that the document does not describe.
|
|
127
|
+
*
|
|
128
|
+
* **The validated body is published under `"json"` whichever parser produced it**, and that is what
|
|
129
|
+
* makes a dispatched route the same shape as every other one downstream. `ValidationTargets` is a
|
|
130
|
+
* closed union in Hono, so there is no seventh name to coin for "the body"; `"json"` is already the
|
|
131
|
+
* slot this emitter reads a body from, and using it here means the handler spreads one
|
|
132
|
+
* `c.req.valid("json")` exactly as it does for a route declaring a single media type.
|
|
133
|
+
*
|
|
134
|
+
* **The alternative was a middleware that publishes under whichever target ran**, and it was worse
|
|
135
|
+
* in two ways that both shipped. `byContentType` was a bare `MiddlewareHandler`, which contributes no
|
|
136
|
+
* `Input` to the chain, so the generated `c.req.valid("json")` did not compile at all
|
|
137
|
+
* (`TS2345: Argument of type '"json"' is not assignable to parameter of type '"header"'`), and the
|
|
138
|
+
* handler's declared input type omitted the body entirely because the body was not among the route's
|
|
139
|
+
* ordinary validators. Publishing to one known slot removes both, rather than typing around them.
|
|
101
140
|
*/
|
|
102
|
-
export function byContentType(
|
|
141
|
+
export function byContentType(schema, invalid, branches) {
|
|
103
142
|
return async (c, next) => {
|
|
104
143
|
const declared = (c.req.header("content-type") ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
|
|
105
|
-
const matched =
|
|
106
|
-
const [,
|
|
107
|
-
|
|
144
|
+
const matched = branches.find(([mediaType]) => mediaType.toLowerCase() === declared);
|
|
145
|
+
const [, target] = matched ?? branches[0];
|
|
146
|
+
/**
|
|
147
|
+
* **Registered against the body slot whichever parser runs**, so the validated body is
|
|
148
|
+
* published under one target and the handler reads it exactly as it reads a single-media-type
|
|
149
|
+
* one. Hono's `validator` is what `@hono/zod-validator` is built on, so this is the same
|
|
150
|
+
* extraction, the same `HTTPException` on malformed JSON, and the same default rejection.
|
|
151
|
+
*
|
|
152
|
+
* `validator("json", ...)` hands over `{}` rather than throwing when the request is not JSON,
|
|
153
|
+
* because it checks the `Content-Type` before reading. That is what leaves room for the form
|
|
154
|
+
* branch to read the body itself with `c.req.parseBody({ all: true })`, which builds the same
|
|
155
|
+
* object hono's own `"form"` target builds: a repeated key and a `key[]` name both become an
|
|
156
|
+
* array, everything else stays a string.
|
|
157
|
+
*
|
|
158
|
+
* Annotated rather than inlined because `Context` is invariant in its environment and in its
|
|
159
|
+
* `Input`, and `validator` infers the first as `any`. Same reason `RouteDeps` is parameterised.
|
|
160
|
+
*/
|
|
161
|
+
const parse = async (ctx, proceed) => {
|
|
162
|
+
const result = await schema.safeParseAsync(await readBody(ctx, target));
|
|
163
|
+
const response = invalid(result, ctx);
|
|
164
|
+
if (response !== undefined)
|
|
165
|
+
return response;
|
|
166
|
+
// `zValidator`'s own answer when a hook declines to, kept identical so a dispatched route
|
|
167
|
+
// and a single-media-type one reject a bad body the same way.
|
|
168
|
+
if (!result.success)
|
|
169
|
+
return ctx.json(result, 400);
|
|
170
|
+
ctx.req.addValidatedData(BODY_TARGET, result.data ?? {});
|
|
171
|
+
await proceed();
|
|
172
|
+
return undefined;
|
|
173
|
+
};
|
|
174
|
+
return parse(c, next);
|
|
108
175
|
};
|
|
109
176
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typespec-hono",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "TypeSpec emitter: generate a Hono server, and the Zod validators it enforces, from an HTTP service definition, agreeing with the OpenAPI document @typespec/openapi3 publishes from the same source.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare-workers",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"provenance": true
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"typespec-http-zod": "^0.
|
|
47
|
+
"typespec-http-zod": "^0.5.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@hono/zod-openapi": "^1.4.0",
|
package/src/runtime.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { HTTPException } from "hono/http-exception";
|
|
1
2
|
import type { Context, Env, Input, MiddlewareHandler } from "hono";
|
|
2
|
-
import type { ZodType } from "zod";
|
|
3
|
+
import type { input, output, ZodType } from "zod";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* One arm of an operation's declared response set, as the document publishes it.
|
|
@@ -164,6 +165,37 @@ export function selectContentType(
|
|
|
164
165
|
export const headOnly: MiddlewareHandler = async (c, next) =>
|
|
165
166
|
c.req.method === "HEAD" ? next() : c.notFound();
|
|
166
167
|
|
|
168
|
+
/**
|
|
169
|
+
* The `zValidator` targets a request BODY can be read from. Hono extracts `"json"` with
|
|
170
|
+
* `c.req.json()` and `"form"` with `c.req.parseBody()`, and those are the only two that read a body.
|
|
171
|
+
*/
|
|
172
|
+
export type BodyTarget = "json" | "form";
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The request body, read the way the target says to read it.
|
|
176
|
+
*
|
|
177
|
+
* The same two readers Hono's own `validator` uses, and the same rejection for a body that is not
|
|
178
|
+
* the JSON it claims to be. `parseBody({ all: true })` builds the object Hono's `"form"` target
|
|
179
|
+
* builds: a repeated key and a `key[]` name both become an array, everything else stays a string.
|
|
180
|
+
*/
|
|
181
|
+
async function readBody(c: Context, target: BodyTarget): Promise<unknown> {
|
|
182
|
+
if (target === "form") return c.req.parseBody({ all: true });
|
|
183
|
+
try {
|
|
184
|
+
return await c.req.json();
|
|
185
|
+
} catch {
|
|
186
|
+
throw new HTTPException(400, { message: "Malformed JSON in request body" });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The slot a validated request body is published under, whichever parser produced it.
|
|
192
|
+
*
|
|
193
|
+
* `ValidationTargets` is a closed union in Hono, so a body cannot be given a name of its own. This
|
|
194
|
+
* is the slot the generated code already reads a body from when one media type is declared, so using
|
|
195
|
+
* it for a dispatched body is what keeps the two cases identical downstream.
|
|
196
|
+
*/
|
|
197
|
+
const BODY_TARGET = "json";
|
|
198
|
+
|
|
167
199
|
/**
|
|
168
200
|
* Apply the validator that parses the media type the request actually carries.
|
|
169
201
|
*
|
|
@@ -182,19 +214,63 @@ export const headOnly: MiddlewareHandler = async (c, next) =>
|
|
|
182
214
|
* A `Content-Type` matching nothing declared falls through to the first validator, which reproduces
|
|
183
215
|
* the previous behaviour exactly for that case: the body fails to parse and `deps.invalid` answers.
|
|
184
216
|
* No status is invented here that the document does not describe.
|
|
217
|
+
*
|
|
218
|
+
* **The validated body is published under `"json"` whichever parser produced it**, and that is what
|
|
219
|
+
* makes a dispatched route the same shape as every other one downstream. `ValidationTargets` is a
|
|
220
|
+
* closed union in Hono, so there is no seventh name to coin for "the body"; `"json"` is already the
|
|
221
|
+
* slot this emitter reads a body from, and using it here means the handler spreads one
|
|
222
|
+
* `c.req.valid("json")` exactly as it does for a route declaring a single media type.
|
|
223
|
+
*
|
|
224
|
+
* **The alternative was a middleware that publishes under whichever target ran**, and it was worse
|
|
225
|
+
* in two ways that both shipped. `byContentType` was a bare `MiddlewareHandler`, which contributes no
|
|
226
|
+
* `Input` to the chain, so the generated `c.req.valid("json")` did not compile at all
|
|
227
|
+
* (`TS2345: Argument of type '"json"' is not assignable to parameter of type '"header"'`), and the
|
|
228
|
+
* handler's declared input type omitted the body entirely because the body was not among the route's
|
|
229
|
+
* ordinary validators. Publishing to one known slot removes both, rather than typing around them.
|
|
185
230
|
*/
|
|
186
|
-
export function byContentType<E extends Env>(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
231
|
+
export function byContentType<E extends Env, S extends ZodType>(
|
|
232
|
+
schema: S,
|
|
233
|
+
invalid: <P extends string, I extends Input>(
|
|
234
|
+
result: { readonly success: boolean },
|
|
235
|
+
c: Context<E, P, I>,
|
|
236
|
+
) => Response | undefined,
|
|
237
|
+
branches: readonly (readonly [mediaType: string, target: BodyTarget])[],
|
|
238
|
+
): MiddlewareHandler<E, string, { in: { json: input<S> }; out: { json: output<S> } }> {
|
|
193
239
|
return async (c, next) => {
|
|
194
240
|
const declared = (c.req.header("content-type") ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
|
|
195
|
-
const matched =
|
|
196
|
-
const [,
|
|
197
|
-
|
|
241
|
+
const matched = branches.find(([mediaType]) => mediaType.toLowerCase() === declared);
|
|
242
|
+
const [, target] = matched ?? (branches[0] as (typeof branches)[number]);
|
|
243
|
+
/**
|
|
244
|
+
* **Registered against the body slot whichever parser runs**, so the validated body is
|
|
245
|
+
* published under one target and the handler reads it exactly as it reads a single-media-type
|
|
246
|
+
* one. Hono's `validator` is what `@hono/zod-validator` is built on, so this is the same
|
|
247
|
+
* extraction, the same `HTTPException` on malformed JSON, and the same default rejection.
|
|
248
|
+
*
|
|
249
|
+
* `validator("json", ...)` hands over `{}` rather than throwing when the request is not JSON,
|
|
250
|
+
* because it checks the `Content-Type` before reading. That is what leaves room for the form
|
|
251
|
+
* branch to read the body itself with `c.req.parseBody({ all: true })`, which builds the same
|
|
252
|
+
* object hono's own `"form"` target builds: a repeated key and a `key[]` name both become an
|
|
253
|
+
* array, everything else stays a string.
|
|
254
|
+
*
|
|
255
|
+
* Annotated rather than inlined because `Context` is invariant in its environment and in its
|
|
256
|
+
* `Input`, and `validator` infers the first as `any`. Same reason `RouteDeps` is parameterised.
|
|
257
|
+
*/
|
|
258
|
+
const parse: MiddlewareHandler<
|
|
259
|
+
E,
|
|
260
|
+
string,
|
|
261
|
+
{ in: { json: input<S> }; out: { json: output<S> } }
|
|
262
|
+
> = async (ctx, proceed) => {
|
|
263
|
+
const result = await schema.safeParseAsync(await readBody(ctx, target));
|
|
264
|
+
const response = invalid(result, ctx);
|
|
265
|
+
if (response !== undefined) return response;
|
|
266
|
+
// `zValidator`'s own answer when a hook declines to, kept identical so a dispatched route
|
|
267
|
+
// and a single-media-type one reject a bad body the same way.
|
|
268
|
+
if (!result.success) return ctx.json(result, 400);
|
|
269
|
+
ctx.req.addValidatedData(BODY_TARGET, result.data ?? {});
|
|
270
|
+
await proceed();
|
|
271
|
+
return undefined;
|
|
272
|
+
};
|
|
273
|
+
return parse(c, next);
|
|
198
274
|
};
|
|
199
275
|
}
|
|
200
276
|
|