Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 67 additions & 5 deletions docs/2.utils/1.request.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ You can use a simple function to validate the body or use a Standard-Schema comp
**Example:**

```ts
app.get("/", async (event) => {
app.post("/", async (event) => {
const body = await readValidatedBody(event, (body) => {
return typeof body === "object" && body !== null;
});
Expand All @@ -42,7 +42,7 @@ app.get("/", async (event) => {

```ts
import { z } from "zod";
app.get("/", async (event) => {
app.post("/", async (event) => {
const objectSchema = z.object({
name: z.string().min(3).max(20),
age: z.number({ coerce: true }).positive().int(),
Expand All @@ -51,6 +51,27 @@ app.get("/", async (event) => {
});
```

**Example:**

```ts
import * as v from "valibot";
app.post("/", async (event) => {
const body = await readValidatedBody(
event,
v.object({
name: v.pipe(v.string(), v.minLength(3), v.maxLength(20)),
age: v.pipe(v.number(), v.integer(), v.minValue(1)),
}),
{
onError: (issues) => ({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can input be in { issues } format (or we pass whole result to format error?

Both for forward compatibility and also support custom errors

statusText: "Custom validation error",
message: v.summarize(issues),
}),
},
);
});
```

<!-- /automd -->

## Cache
Expand Down Expand Up @@ -218,7 +239,27 @@ app.get("/", async (event) => {
});
```

### `getValidatedRouterParams(event, validate, opts: { decode? })`
**Example:**

```ts
import * as v from "valibot";
app.get("/", async (event) => {
const params = await getValidatedQuery(
event,
v.object({
key: v.string(),
}),
{
onError: (issues) => ({
statusText: "Custom validation error",
message: v.summarize(issues),
}),
},
);
});
```

### `getValidatedRouterParams(event, validate)`

Get matched route params and validate with validate function.

Expand All @@ -229,7 +270,7 @@ You can use a simple function to validate the params object or use a Standard-Sc
**Example:**

```ts
app.get("/", async (event) => {
app.get("/:key", async (event) => {
const params = await getValidatedRouterParams(event, (data) => {
return "key" in data && typeof data.key === "string";
});
Expand All @@ -240,7 +281,7 @@ app.get("/", async (event) => {

```ts
import { z } from "zod";
app.get("/", async (event) => {
app.get("/:key", async (event) => {
const params = await getValidatedRouterParams(
event,
z.object({
Expand All @@ -250,6 +291,27 @@ app.get("/", async (event) => {
});
```

**Example:**

```ts
import * as v from "valibot";
app.get("/:key", async (event) => {
const params = await getValidatedRouterParams(
event,
v.object({
key: v.pipe(v.string(), v.picklist(["route-1", "route-2", "route-3"])),
}),
{
decode: true,
onError: (issues) => ({
statusText: "Custom validation error",
message: v.summarize(issues),
}),
},
);
});
```

### `isMethod(event, expected, allowHead?)`

Checks if the incoming request method is of the expected type.
Expand Down
11 changes: 10 additions & 1 deletion src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ import type {
StandardSchemaV1,
} from "./utils/internal/standard-schema.ts";
import type { TypedRequest } from "fetchdts";
import { validatedRequest, validatedURL } from "./utils/internal/validate.ts";
import type { ErrorDetails } from "./error.ts";
import {
type ValidateIssues,
validatedRequest,
validatedURL,
} from "./utils/internal/validate.ts";

// --- event handler ---

Expand Down Expand Up @@ -63,6 +68,10 @@ export function defineValidatedHandler<
body?: RequestBody;
headers?: RequestHeaders;
query?: RequestQuery;
onError?: (
issues: ValidateIssues,
source: "headers" | "body" | "query",
) => ErrorDetails;
};
handler: EventHandler<
{
Expand Down
45 changes: 39 additions & 6 deletions src/utils/body.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { HTTPError } from "../error.ts";
import { validateData } from "./internal/validate.ts";
import { type ErrorDetails, HTTPError } from "../error.ts";
import {
type ValidateIssues,
type ValidateError,
validateData,
} from "./internal/validate.ts";
import { parseURLEncodedBody } from "./internal/body.ts";

import type { H3Event } from "../event.ts";
Expand Down Expand Up @@ -52,7 +56,11 @@ export async function readBody<
export async function readValidatedBody<
Event extends H3Event,
S extends StandardSchemaV1,
>(event: Event, validate: S): Promise<InferOutput<S>>;
>(
event: Event,
validate: S,
options?: { onError?: (issues: ValidateIssues) => ErrorDetails },
): Promise<InferOutput<S>>;
export async function readValidatedBody<
Event extends H3Event,
OutputT,
Expand All @@ -62,39 +70,64 @@ export async function readValidatedBody<
validate: (
data: InputT,
) => ValidateResult<OutputT> | Promise<ValidateResult<OutputT>>,
options?: {
onError?: () => ErrorDetails;
},
): Promise<OutputT>;
/**
* Tries to read the request body via `readBody`, then uses the provided validation schema or function and either throws a validation error or returns the result.
*
* You can use a simple function to validate the body or use a Standard-Schema compatible library like `zod` to define a schema.
*
* @example
* app.get("/", async (event) => {
* app.post("/", async (event) => {
* const body = await readValidatedBody(event, (body) => {
* return typeof body === "object" && body !== null;
* });
* });
* @example
* import { z } from "zod";
*
* app.get("/", async (event) => {
* app.post("/", async (event) => {
* const objectSchema = z.object({
* name: z.string().min(3).max(20),
* age: z.number({ coerce: true }).positive().int(),
* });
* const body = await readValidatedBody(event, objectSchema);
* });
* @example
* import * as v from "valibot";
*
* app.post("/", async (event) => {
* const body = await readValidatedBody(
* event,
* v.object({
* name: v.pipe(v.string(), v.minLength(3), v.maxLength(20)),
* age: v.pipe(v.number(), v.integer(), v.minValue(1)),
* }),
* {
* onError: (issues) => ({
* statusText: "Custom validation error",
* message: v.summarize(issues),
* }),
* },
* );
* });
*
* @param event The H3Event passed by the handler.
* @param validate The function to use for body validation. It will be called passing the read request body. If the result is not false, the parsed body will be returned.
* @param options Optional options. If provided, the `onError` function will be called with the validation issues if validation fails.
* @throws If the validation function returns `false` or throws, a validation error will be thrown.
* @return {*} The `Object`, `Array`, `String`, `Number`, `Boolean`, or `null` value corresponding to the request JSON body.
* @see {readBody}
*/
export async function readValidatedBody(
event: H3Event,
validate: any,
options?: {
onError?: ValidateError;
},
): Promise<any> {
const _body = await readBody(event);
return validateData(_body, validate);
return validateData(_body, validate, options);
}
Loading