Skip to content

Commit

Permalink
delegate body validation (#4897)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ignatiusmb authored May 13, 2022
1 parent 0c38bd3 commit b162290
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/large-cows-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sveltejs/kit": patch
---

delegate `RequestHandler` generics `Body` validation
2 changes: 1 addition & 1 deletion packages/kit/src/core/sync/write_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { ${imports} } from '@sveltejs/kit';`;

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

/** @param {string} arg */
const page = (arg) => `
Expand Down
53 changes: 38 additions & 15 deletions packages/kit/test/typings/endpoint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,30 +68,53 @@ export const differential_headers_assignment: RequestHandler = () => {
}
};

// TODO https://github.com/sveltejs/kit/issues/1997
// interface ExamplePost {
// title: string;
// description: string;
// published_date?: string;
// author_name?: string;
// author_link?: string;
// }
// // valid - should not be any different
// export const generic_case: RequestHandler<Record<string, string>, ExamplePost> = () => {
// return {
// body: {} as ExamplePost
// };
// };
/**
* NOTE about type casting in body returned
*
* tests below with `{} as Interface` casts are there only for
* convenience purposes so we won't have to actually fill in the
* required data, it serves the exact same purpose and doesn't
* make the tests invalid
*/

/** example json-serializable POJO */
interface ExamplePost {
title: string;
description: string;
published_date?: string;
author_name?: string;
author_link?: string;
}
// valid - should not be any different
export const generic_case: RequestHandler<Record<string, string>, ExamplePost> = () => {
return {
body: {} as ExamplePost
};
};

// --- invalid cases ---

// @ts-expect-error - body must be JSON serializable
export const error_body_must_be_serializable: RequestHandler = () => {
export const error_unserializable_literal: RequestHandler = () => {
return {
body: () => {}
};
};

/** example object that isn't serializable */
interface InvalidPost {
sorter(a: any, b: any): number;
}
// @ts-expect-error - body must be JSON serializable with Generic passed
export const error_unserializable_generic: RequestHandler<
Record<string, string>,
InvalidPost
> = () => {
return {
body: {} as InvalidPost
};
};

// @ts-expect-error - body typed array must only be Uint8Array
export const error_other_typed_array_instances: RequestHandler = () => {
return {
Expand Down
7 changes: 4 additions & 3 deletions packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import './ambient';
import { CompileOptions } from 'svelte/types/compiler/interfaces';
import {
AdapterEntry,
BodyValidator,
CspDirectives,
JSONValue,
Logger,
Expand Down Expand Up @@ -241,15 +242,15 @@ export interface ParamMatcher {
*/
export interface RequestHandler<
Params extends Record<string, string> = Record<string, string>,
Output extends ResponseBody = ResponseBody
Output = ResponseBody
> {
(event: RequestEvent<Params>): MaybePromise<RequestHandlerOutput<Output>>;
}

export interface RequestHandlerOutput<Output extends ResponseBody = ResponseBody> {
export interface RequestHandlerOutput<Output = ResponseBody> {
status?: number;
headers?: Headers | Partial<ResponseHeaders>;
body?: Output;
body?: Output extends ResponseBody ? Output : BodyValidator<Output>;
}

export type ResponseBody = JSONValue | Uint8Array | ReadableStream | import('stream').Readable;
Expand Down
4 changes: 4 additions & 0 deletions packages/kit/types/private.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export interface AdapterEntry {
}) => MaybePromise<void>;
}

export type BodyValidator<T> = {
[P in keyof T]: T[P] extends JSONValue ? BodyValidator<T[P]> : never;
};

// Based on https://github.com/josh-hemphill/csp-typed-directives/blob/latest/src/csp.types.ts
//
// MIT License
Expand Down

0 comments on commit b162290

Please sign in to comment.