Skip to content

Commit b162290

Browse files
authored
delegate body validation (#4897)
* initial working BodyValidator * delegate body generic validation * enable previously failing test * update failing tests * delegate to BodyValidator * add validator in private dts * add notes * add changeset
1 parent 0c38bd3 commit b162290

File tree

5 files changed

+52
-19
lines changed

5 files changed

+52
-19
lines changed

.changeset/large-cows-suffer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@sveltejs/kit": patch
3+
---
4+
5+
delegate `RequestHandler` generics `Body` validation

packages/kit/src/core/sync/write_types.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { ${imports} } from '@sveltejs/kit';`;
88

99
/** @param {string} arg */
1010
const endpoint = (arg) => `
11-
export type RequestHandler<Output extends ResponseBody = ResponseBody> = GenericRequestHandler<${arg}, Output>;`;
11+
export type RequestHandler<Output = ResponseBody> = GenericRequestHandler<${arg}, Output>;`;
1212

1313
/** @param {string} arg */
1414
const page = (arg) => `

packages/kit/test/typings/endpoint.test.ts

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,30 +68,53 @@ export const differential_headers_assignment: RequestHandler = () => {
6868
}
6969
};
7070

71-
// TODO https://github.com/sveltejs/kit/issues/1997
72-
// interface ExamplePost {
73-
// title: string;
74-
// description: string;
75-
// published_date?: string;
76-
// author_name?: string;
77-
// author_link?: string;
78-
// }
79-
// // valid - should not be any different
80-
// export const generic_case: RequestHandler<Record<string, string>, ExamplePost> = () => {
81-
// return {
82-
// body: {} as ExamplePost
83-
// };
84-
// };
71+
/**
72+
* NOTE about type casting in body returned
73+
*
74+
* tests below with `{} as Interface` casts are there only for
75+
* convenience purposes so we won't have to actually fill in the
76+
* required data, it serves the exact same purpose and doesn't
77+
* make the tests invalid
78+
*/
79+
80+
/** example json-serializable POJO */
81+
interface ExamplePost {
82+
title: string;
83+
description: string;
84+
published_date?: string;
85+
author_name?: string;
86+
author_link?: string;
87+
}
88+
// valid - should not be any different
89+
export const generic_case: RequestHandler<Record<string, string>, ExamplePost> = () => {
90+
return {
91+
body: {} as ExamplePost
92+
};
93+
};
8594

8695
// --- invalid cases ---
8796

8897
// @ts-expect-error - body must be JSON serializable
89-
export const error_body_must_be_serializable: RequestHandler = () => {
98+
export const error_unserializable_literal: RequestHandler = () => {
9099
return {
91100
body: () => {}
92101
};
93102
};
94103

104+
/** example object that isn't serializable */
105+
interface InvalidPost {
106+
sorter(a: any, b: any): number;
107+
}
108+
// @ts-expect-error - body must be JSON serializable with Generic passed
109+
export const error_unserializable_generic: RequestHandler<
110+
Record<string, string>,
111+
InvalidPost
112+
> = () => {
113+
return {
114+
body: {} as InvalidPost
115+
};
116+
};
117+
95118
// @ts-expect-error - body typed array must only be Uint8Array
96119
export const error_other_typed_array_instances: RequestHandler = () => {
97120
return {

packages/kit/types/index.d.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import './ambient';
66
import { CompileOptions } from 'svelte/types/compiler/interfaces';
77
import {
88
AdapterEntry,
9+
BodyValidator,
910
CspDirectives,
1011
JSONValue,
1112
Logger,
@@ -241,15 +242,15 @@ export interface ParamMatcher {
241242
*/
242243
export interface RequestHandler<
243244
Params extends Record<string, string> = Record<string, string>,
244-
Output extends ResponseBody = ResponseBody
245+
Output = ResponseBody
245246
> {
246247
(event: RequestEvent<Params>): MaybePromise<RequestHandlerOutput<Output>>;
247248
}
248249

249-
export interface RequestHandlerOutput<Output extends ResponseBody = ResponseBody> {
250+
export interface RequestHandlerOutput<Output = ResponseBody> {
250251
status?: number;
251252
headers?: Headers | Partial<ResponseHeaders>;
252-
body?: Output;
253+
body?: Output extends ResponseBody ? Output : BodyValidator<Output>;
253254
}
254255

255256
export type ResponseBody = JSONValue | Uint8Array | ReadableStream | import('stream').Readable;

packages/kit/types/private.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export interface AdapterEntry {
2626
}) => MaybePromise<void>;
2727
}
2828

29+
export type BodyValidator<T> = {
30+
[P in keyof T]: T[P] extends JSONValue ? BodyValidator<T[P]> : never;
31+
};
32+
2933
// Based on https://github.com/josh-hemphill/csp-typed-directives/blob/latest/src/csp.types.ts
3034
//
3135
// MIT License

0 commit comments

Comments
 (0)