Skip to content

Commit 40d66c0

Browse files
committed
Updated logic with the latest changes in issue description
1 parent 34ad6dd commit 40d66c0

File tree

6 files changed

+175
-282
lines changed

6 files changed

+175
-282
lines changed

packages/event-handler/src/http/Router.ts

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -574,8 +574,8 @@ class Router {
574574
method: HttpMethod,
575575
path: Path,
576576
middlewareOrHandler?: Middleware[] | RouteHandler,
577-
handlerOrOptions?: RouteHandler | Omit<RestRouteOptions, 'method' | 'path'>,
578-
options?: Omit<RestRouteOptions, 'method' | 'path' | 'middleware'>
577+
handlerOrOptions?: RouteHandler | Omit<HttpRouteOptions, 'method' | 'path'>,
578+
options?: Omit<HttpRouteOptions, 'method' | 'path' | 'middleware'>
579579
): MethodDecorator | undefined {
580580
// Case 1: post(path, [middleware], handler, { validation })
581581
if (Array.isArray(middlewareOrHandler)) {
@@ -625,21 +625,21 @@ class Router {
625625
public get(
626626
path: Path,
627627
handler: RouteHandler,
628-
options?: Omit<RestRouteOptions, 'method' | 'path'>
628+
options?: Omit<HttpRouteOptions, 'method' | 'path'>
629629
): void;
630630
public get(
631631
path: Path,
632632
middleware: Middleware[],
633633
handler: RouteHandler,
634-
options?: Omit<RestRouteOptions, 'method' | 'path' | 'middleware'>
634+
options?: Omit<HttpRouteOptions, 'method' | 'path' | 'middleware'>
635635
): void;
636636
public get(path: Path): MethodDecorator;
637637
public get(path: Path, middleware: Middleware[]): MethodDecorator;
638638
public get(
639639
path: Path,
640640
middlewareOrHandler?: Middleware[] | RouteHandler,
641-
handlerOrOptions?: RouteHandler | Omit<RestRouteOptions, 'method' | 'path'>,
642-
options?: Omit<RestRouteOptions, 'method' | 'path' | 'middleware'>
641+
handlerOrOptions?: RouteHandler | Omit<HttpRouteOptions, 'method' | 'path'>,
642+
options?: Omit<HttpRouteOptions, 'method' | 'path' | 'middleware'>
643643
): MethodDecorator | undefined {
644644
return this.#handleHttpMethod(
645645
HttpVerbs.GET,
@@ -653,21 +653,21 @@ class Router {
653653
public post(
654654
path: Path,
655655
handler: RouteHandler,
656-
options?: Omit<RestRouteOptions, 'method' | 'path'>
656+
options?: Omit<HttpRouteOptions, 'method' | 'path'>
657657
): void;
658658
public post(
659659
path: Path,
660660
middleware: Middleware[],
661661
handler: RouteHandler,
662-
options?: Omit<RestRouteOptions, 'method' | 'path' | 'middleware'>
662+
options?: Omit<HttpRouteOptions, 'method' | 'path' | 'middleware'>
663663
): void;
664664
public post(path: Path): MethodDecorator;
665665
public post(path: Path, middleware: Middleware[]): MethodDecorator;
666666
public post(
667667
path: Path,
668668
middlewareOrHandler?: Middleware[] | RouteHandler,
669-
handlerOrOptions?: RouteHandler | Omit<RestRouteOptions, 'method' | 'path'>,
670-
options?: Omit<RestRouteOptions, 'method' | 'path' | 'middleware'>
669+
handlerOrOptions?: RouteHandler | Omit<HttpRouteOptions, 'method' | 'path'>,
670+
options?: Omit<HttpRouteOptions, 'method' | 'path' | 'middleware'>
671671
): MethodDecorator | undefined {
672672
return this.#handleHttpMethod(
673673
HttpVerbs.POST,
@@ -681,21 +681,21 @@ class Router {
681681
public put(
682682
path: Path,
683683
handler: RouteHandler,
684-
options?: Omit<RestRouteOptions, 'method' | 'path'>
684+
options?: Omit<HttpRouteOptions, 'method' | 'path'>
685685
): void;
686686
public put(
687687
path: Path,
688688
middleware: Middleware[],
689689
handler: RouteHandler,
690-
options?: Omit<RestRouteOptions, 'method' | 'path' | 'middleware'>
690+
options?: Omit<HttpRouteOptions, 'method' | 'path' | 'middleware'>
691691
): void;
692692
public put(path: Path): MethodDecorator;
693693
public put(path: Path, middleware: Middleware[]): MethodDecorator;
694694
public put(
695695
path: Path,
696696
middlewareOrHandler?: Middleware[] | RouteHandler,
697-
handlerOrOptions?: RouteHandler | Omit<RestRouteOptions, 'method' | 'path'>,
698-
options?: Omit<RestRouteOptions, 'method' | 'path' | 'middleware'>
697+
handlerOrOptions?: RouteHandler | Omit<HttpRouteOptions, 'method' | 'path'>,
698+
options?: Omit<HttpRouteOptions, 'method' | 'path' | 'middleware'>
699699
): MethodDecorator | undefined {
700700
return this.#handleHttpMethod(
701701
HttpVerbs.PUT,
@@ -709,21 +709,21 @@ class Router {
709709
public patch(
710710
path: Path,
711711
handler: RouteHandler,
712-
options?: Omit<RestRouteOptions, 'method' | 'path'>
712+
options?: Omit<HttpRouteOptions, 'method' | 'path'>
713713
): void;
714714
public patch(
715715
path: Path,
716716
middleware: Middleware[],
717717
handler: RouteHandler,
718-
options?: Omit<RestRouteOptions, 'method' | 'path' | 'middleware'>
718+
options?: Omit<HttpRouteOptions, 'method' | 'path' | 'middleware'>
719719
): void;
720720
public patch(path: Path): MethodDecorator;
721721
public patch(path: Path, middleware: Middleware[]): MethodDecorator;
722722
public patch(
723723
path: Path,
724724
middlewareOrHandler?: Middleware[] | RouteHandler,
725-
handlerOrOptions?: RouteHandler | Omit<RestRouteOptions, 'method' | 'path'>,
726-
options?: Omit<RestRouteOptions, 'method' | 'path' | 'middleware'>
725+
handlerOrOptions?: RouteHandler | Omit<HttpRouteOptions, 'method' | 'path'>,
726+
options?: Omit<HttpRouteOptions, 'method' | 'path' | 'middleware'>
727727
): MethodDecorator | undefined {
728728
return this.#handleHttpMethod(
729729
HttpVerbs.PATCH,
@@ -737,21 +737,21 @@ class Router {
737737
public delete(
738738
path: Path,
739739
handler: RouteHandler,
740-
options?: Omit<RestRouteOptions, 'method' | 'path'>
740+
options?: Omit<HttpRouteOptions, 'method' | 'path'>
741741
): void;
742742
public delete(
743743
path: Path,
744744
middleware: Middleware[],
745745
handler: RouteHandler,
746-
options?: Omit<RestRouteOptions, 'method' | 'path' | 'middleware'>
746+
options?: Omit<HttpRouteOptions, 'method' | 'path' | 'middleware'>
747747
): void;
748748
public delete(path: Path): MethodDecorator;
749749
public delete(path: Path, middleware: Middleware[]): MethodDecorator;
750750
public delete(
751751
path: Path,
752752
middlewareOrHandler?: Middleware[] | RouteHandler,
753-
handlerOrOptions?: RouteHandler | Omit<RestRouteOptions, 'method' | 'path'>,
754-
options?: Omit<RestRouteOptions, 'method' | 'path' | 'middleware'>
753+
handlerOrOptions?: RouteHandler | Omit<HttpRouteOptions, 'method' | 'path'>,
754+
options?: Omit<HttpRouteOptions, 'method' | 'path' | 'middleware'>
755755
): MethodDecorator | undefined {
756756
return this.#handleHttpMethod(
757757
HttpVerbs.DELETE,
@@ -765,21 +765,21 @@ class Router {
765765
public head(
766766
path: Path,
767767
handler: RouteHandler,
768-
options?: Omit<RestRouteOptions, 'method' | 'path'>
768+
options?: Omit<HttpRouteOptions, 'method' | 'path'>
769769
): void;
770770
public head(
771771
path: Path,
772772
middleware: Middleware[],
773773
handler: RouteHandler,
774-
options?: Omit<RestRouteOptions, 'method' | 'path' | 'middleware'>
774+
options?: Omit<HttpRouteOptions, 'method' | 'path' | 'middleware'>
775775
): void;
776776
public head(path: Path): MethodDecorator;
777777
public head(path: Path, middleware: Middleware[]): MethodDecorator;
778778
public head(
779779
path: Path,
780780
middlewareOrHandler?: Middleware[] | RouteHandler,
781-
handlerOrOptions?: RouteHandler | Omit<RestRouteOptions, 'method' | 'path'>,
782-
options?: Omit<RestRouteOptions, 'method' | 'path' | 'middleware'>
781+
handlerOrOptions?: RouteHandler | Omit<HttpRouteOptions, 'method' | 'path'>,
782+
options?: Omit<HttpRouteOptions, 'method' | 'path' | 'middleware'>
783783
): MethodDecorator | undefined {
784784
return this.#handleHttpMethod(
785785
HttpVerbs.HEAD,
@@ -793,21 +793,21 @@ class Router {
793793
public options(
794794
path: Path,
795795
handler: RouteHandler,
796-
options?: Omit<RestRouteOptions, 'method' | 'path'>
796+
options?: Omit<HttpRouteOptions, 'method' | 'path'>
797797
): void;
798798
public options(
799799
path: Path,
800800
middleware: Middleware[],
801801
handler: RouteHandler,
802-
options?: Omit<RestRouteOptions, 'method' | 'path' | 'middleware'>
802+
options?: Omit<HttpRouteOptions, 'method' | 'path' | 'middleware'>
803803
): void;
804804
public options(path: Path): MethodDecorator;
805805
public options(path: Path, middleware: Middleware[]): MethodDecorator;
806806
public options(
807807
path: Path,
808808
middlewareOrHandler?: Middleware[] | RouteHandler,
809-
handlerOrOptions?: RouteHandler | Omit<RestRouteOptions, 'method' | 'path'>,
810-
options?: Omit<RestRouteOptions, 'method' | 'path' | 'middleware'>
809+
handlerOrOptions?: RouteHandler | Omit<HttpRouteOptions, 'method' | 'path'>,
810+
options?: Omit<HttpRouteOptions, 'method' | 'path' | 'middleware'>
811811
): MethodDecorator | undefined {
812812
return this.#handleHttpMethod(
813813
HttpVerbs.OPTIONS,
Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { StandardSchemaV1 } from '@standard-schema/spec';
2-
import type { Middleware, RestRouteOptions } from '../../types/rest.js';
2+
import type { HttpRouteOptions, Middleware } from '../../types/http.js';
33
import { RequestValidationError, ResponseValidationError } from '../errors.js';
44

55
/**
@@ -9,7 +9,7 @@ import { RequestValidationError, ResponseValidationError } from '../errors.js';
99
* @returns Middleware function that validates request/response
1010
*/
1111
export const createValidationMiddleware = (
12-
config: RestRouteOptions['validation']
12+
config: HttpRouteOptions['validation']
1313
): Middleware => {
1414
const reqSchemas = config?.req;
1515
const resSchemas = config?.res;
@@ -18,32 +18,35 @@ export const createValidationMiddleware = (
1818
// Validate request
1919
if (reqSchemas) {
2020
if (reqSchemas.body) {
21-
let bodyData: unknown = reqCtx.event.body;
21+
const clonedRequest = reqCtx.req.clone();
2222
const contentType = reqCtx.req.headers.get('content-type');
23-
if (
24-
contentType?.includes('application/json') &&
25-
typeof bodyData === 'string'
26-
) {
23+
let bodyData: unknown;
24+
25+
if (contentType?.includes('application/json')) {
2726
try {
28-
bodyData = JSON.parse(bodyData);
27+
bodyData = await clonedRequest.json();
2928
} catch {
30-
// If parsing fails, validate the raw string
29+
// If JSON parsing fails, get as text and let validator handle it
30+
bodyData = await reqCtx.req.clone().text();
3131
}
32+
} else {
33+
bodyData = await clonedRequest.text();
3234
}
33-
await validateComponent(reqSchemas.body, bodyData, 'body', true);
35+
36+
await validateRequest(reqSchemas.body, bodyData, 'body');
3437
}
3538
if (reqSchemas.headers) {
3639
const headers = Object.fromEntries(reqCtx.req.headers.entries());
37-
await validateComponent(reqSchemas.headers, headers, 'headers', true);
40+
await validateRequest(reqSchemas.headers, headers, 'headers');
3841
}
3942
if (reqSchemas.path) {
40-
await validateComponent(reqSchemas.path, reqCtx.params, 'path', true);
43+
await validateRequest(reqSchemas.path, reqCtx.params, 'path');
4144
}
4245
if (reqSchemas.query) {
4346
const query = Object.fromEntries(
4447
new URL(reqCtx.req.url).searchParams.entries()
4548
);
46-
await validateComponent(reqSchemas.query, query, 'query', true);
49+
await validateRequest(reqSchemas.query, query, 'query');
4750
}
4851
}
4952

@@ -54,47 +57,74 @@ export const createValidationMiddleware = (
5457
if (resSchemas) {
5558
const response = reqCtx.res;
5659

57-
if (resSchemas.body) {
60+
if (resSchemas.body && response.body) {
5861
const clonedResponse = response.clone();
5962
const contentType = response.headers.get('content-type');
63+
const bodyData = contentType?.includes('application/json')
64+
? await clonedResponse.json()
65+
: await clonedResponse.text();
6066

61-
let bodyData: unknown;
62-
if (contentType?.includes('application/json')) {
63-
bodyData = await clonedResponse.json();
64-
} else {
65-
bodyData = await clonedResponse.text();
66-
}
67-
68-
await validateComponent(resSchemas.body, bodyData, 'body', false);
67+
await validateResponse(resSchemas.body, bodyData, 'body');
6968
}
7069

7170
if (resSchemas.headers) {
7271
const headers = Object.fromEntries(response.headers.entries());
73-
await validateComponent(resSchemas.headers, headers, 'headers', false);
72+
await validateResponse(resSchemas.headers, headers, 'headers');
7473
}
7574
}
7675
};
7776
};
7877

79-
async function validateComponent(
78+
async function validateRequest(
8079
schema: StandardSchemaV1,
8180
data: unknown,
82-
component: 'body' | 'headers' | 'path' | 'query',
83-
isRequest: boolean
81+
component: 'body' | 'headers' | 'path' | 'query'
8482
): Promise<void> {
85-
const result = await schema['~standard'].validate(data);
86-
87-
if ('issues' in result) {
88-
const message = `Validation failed for ${isRequest ? 'request' : 'response'} ${component}`;
89-
const error = new Error('Validation failed');
83+
try {
84+
const result = await schema['~standard'].validate(data);
9085

91-
if (isRequest) {
86+
if ('issues' in result) {
87+
const message = `Validation failed for request ${component}`;
88+
const error = new Error('Validation failed');
9289
throw new RequestValidationError(message, component, error);
9390
}
91+
} catch (error) {
92+
// Handle schemas that throw errors instead of returning issues
93+
if (error instanceof RequestValidationError) {
94+
throw error;
95+
}
96+
const message = `Validation failed for request ${component}`;
97+
throw new RequestValidationError(
98+
message,
99+
component,
100+
error instanceof Error ? error : new Error(String(error))
101+
);
102+
}
103+
}
104+
105+
async function validateResponse(
106+
schema: StandardSchemaV1,
107+
data: unknown,
108+
component: 'body' | 'headers'
109+
): Promise<void> {
110+
try {
111+
const result = await schema['~standard'].validate(data);
112+
113+
if ('issues' in result) {
114+
const message = `Validation failed for response ${component}`;
115+
const error = new Error('Validation failed');
116+
throw new ResponseValidationError(message, component, error);
117+
}
118+
} catch (error) {
119+
// Handle schemas that throw errors instead of returning issues
120+
if (error instanceof ResponseValidationError) {
121+
throw error;
122+
}
123+
const message = `Validation failed for response ${component}`;
94124
throw new ResponseValidationError(
95125
message,
96-
component as 'body' | 'headers',
97-
error
126+
component,
127+
error instanceof Error ? error : new Error(String(error))
98128
);
99129
}
100130
}

packages/event-handler/src/types/http.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -267,17 +267,21 @@ type RequestValidationConfig<T = unknown> = {
267267
/**
268268
* Configuration for response validation
269269
*/
270-
type ResponseValidationConfig<T = unknown> = {
271-
body?: StandardSchemaV1<unknown, T>;
272-
headers?: StandardSchemaV1<unknown, Record<string, string>>;
270+
type ResponseValidationConfig<T extends HandlerResponse = HandlerResponse> = {
271+
body?: StandardSchemaV1<HandlerResponse, T>;
272+
headers?: StandardSchemaV1<Record<string, string>, Record<string, string>>;
273273
};
274274

275275
/**
276-
* Validation configuration for request and response
276+
* Validation configuration for request and response.
277+
* At least one of req or res should be provided.
277278
*/
278-
type ValidationConfig<TReq = unknown, TRes = unknown> = {
279-
req?: RequestValidationConfig<TReq>;
280-
res?: ResponseValidationConfig<TRes>;
279+
type ValidationConfig<
280+
TReqBody = unknown,
281+
TResBody extends HandlerResponse = HandlerResponse,
282+
> = {
283+
req?: RequestValidationConfig<TReqBody>;
284+
res?: ResponseValidationConfig<TResBody>;
281285
};
282286

283287
/**

0 commit comments

Comments
 (0)