From c47862f37614a877e3698bd201834de076e81a78 Mon Sep 17 00:00:00 2001 From: qpismont Date: Thu, 11 Jan 2024 23:03:46 +0100 Subject: [PATCH] add zod validation for internal Request and Response message --- mod.ts | 2 + src/messages.ts | 18 ++++++-- src/service.ts | 102 ++++++++++++++++++++++++++++++------------ src/types.ts | 4 +- tests/service.test.ts | 4 +- 5 files changed, 93 insertions(+), 37 deletions(-) diff --git a/mod.ts b/mod.ts index 444650f..f9d23f5 100644 --- a/mod.ts +++ b/mod.ts @@ -1,2 +1,4 @@ export * from "./src/service.ts"; export * from "./src/adaptors/nats.ts"; +export * from "./src/error.ts"; +export type { Request, Response } from "./src/messages.ts"; diff --git a/src/messages.ts b/src/messages.ts index 9318246..fa52838 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -1,13 +1,23 @@ +import { z } from "zod"; + export interface Request { service: string; subject: string; data?: T; } -export interface Message { - from: string; - data?: T; -} +export const InternalRequestSchema = z.object({ + from: z.string(), + data: z.optional(z.any()), +}); + +export const InternalResponseSchema = z.object({ + data: z.optional(z.any()), + statusCode: z.number(), +}); + +export type InternalRequest = z.infer; +export type InternalResponse = z.infer; export interface Response { data?: T; diff --git a/src/service.ts b/src/service.ts index 89eef49..eda7631 100644 --- a/src/service.ts +++ b/src/service.ts @@ -1,7 +1,13 @@ -import { ServiceError } from "nats"; import Adaptor from "./adaptors/adaptor.ts"; import { RequestError } from "./error.ts"; -import { Message, Request, Response } from "./messages.ts"; +import { + InternalRequest, + InternalRequestSchema, + InternalResponse, + InternalResponseSchema, + Request, + Response, +} from "./messages.ts"; import { RouteSubscribeTypeFn } from "./types.ts"; import { z } from "zod"; @@ -28,40 +34,62 @@ export default class Service { this.adaptors[adaptor].subscribe( `${this.name}.${subject}`, async (rawReq) => { - const msg: Request = JSON.parse(rawReq); + const rawReqJson = JSON.parse(rawReq); + const internalRequestJson = InternalRequestSchema.safeParse(rawReqJson); + if (!internalRequestJson.success) { + return JSON.stringify( + { + statusCode: 400, + data: "bad request structure", + } satisfies InternalResponse, + ); + } - const message: Message> = { from: this.name }; + const internalRequest = internalRequestJson.data; + const req = { + service: internalRequest.from, + subject: subject, + } as Request>; - if (msg.data && schema) { - const validate = schema.safeParse(msg.data); + if (internalRequest.data && schema) { + const validate = schema.safeParse(internalRequest.data); if (!validate.success) { return JSON.stringify( { statusCode: 400, - data: validate.error.toString(), - } satisfies Response, + data: validate.error, + } satisfies InternalResponse, ); } else { - message.data = validate.data; + req.data = validate.data; } } - let res; try { - res = await fn(message); + const res = await fn(req); + const internalResponse = { + statusCode: res.statusCode, + data: res.data, + } satisfies InternalResponse; + + return JSON.stringify(internalResponse); } catch (err) { - if (err instanceof ServiceError) { - res = { data: err.message, statusCode: err.code } as Response< - string - >; + if (err instanceof RequestError) { + return JSON.stringify( + { + statusCode: err.statusCode, + data: err.message, + } satisfies InternalResponse, + ); } else { - res = { data: "unknow error append", statusCode: 500 } as Response< - string - >; + return JSON.stringify( + { + statusCode: 500, + data: err?.message || "unknow error apend", + } satisfies InternalResponse, + ); } } - - return JSON.stringify(res); }, ); } @@ -75,24 +103,40 @@ export default class Service { throw new Error(`${adaptor} adaptor not exist`); } - const rawReq = JSON.stringify(req); + const internalRequest = { + from: this.name, + data: req.data, + } satisfies InternalRequest; + const internalRequestJson = JSON.stringify(internalRequest); try { const rawRes = await this.adaptors[adaptor].request( `${req.service}.${req.subject}`, - rawReq, + internalRequestJson, ); - const res: Response> = JSON.parse(rawRes); - - if (res.statusCode < 200 || res.statusCode >= 299) { - throw new RequestError("error while request", res.statusCode); + const rawResJson: unknown = JSON.parse(rawRes); + const internalResponseJson = InternalResponseSchema.safeParse(rawResJson); + if (!internalResponseJson.success) { + throw new RequestError(internalResponseJson.error.toString(), 500); } - if (res.data && schema) { - const validate = schema.safeParse(res.data); + const internalResponse = internalResponseJson.data; + if ( + internalResponse.statusCode < 200 || internalResponse.statusCode >= 299 + ) { + throw new RequestError( + internalResponse.data, + internalResponse.statusCode, + ); + } + const res: Response> = { + statusCode: internalResponse.statusCode, + }; + if (internalResponse.data && schema) { + const validate = schema.safeParse(internalResponse.data); if (!validate.success) { - throw new ServiceError(400, validate.error.toString()); + throw new RequestError(validate.error.message, 400); } else { res.data = validate.data; } diff --git a/src/types.ts b/src/types.ts index f22df5a..acae320 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,6 @@ -import { Message, Response } from "./messages.ts"; +import { Request, Response } from "./messages.ts"; export type AdaptorSubscribeTypeFn = (msg: string) => Promise; export type RouteSubscribeTypeFn = ( - msg: Message, + msg: Request, ) => Promise>; diff --git a/tests/service.test.ts b/tests/service.test.ts index 4f3af08..1d2d4a1 100644 --- a/tests/service.test.ts +++ b/tests/service.test.ts @@ -1,9 +1,9 @@ import { z } from "zod"; import NatsAdaptor from "../src/adaptors/nats.ts"; -import { Message } from "../src/messages.ts"; import Service from "../src/service.ts"; import { assertEquals, assertRejects, assertThrows } from "std/assert/mod.ts"; import { afterEach, beforeEach, it } from "std/testing/bdd.ts"; +import { RequestError } from "../src/error.ts"; let srv!: Service; @@ -55,7 +55,7 @@ it("request error", { srv.addAdaptor(adaptorName, new NatsAdaptor({ servers: [natsServer] })); srv.subscribe(adaptorName, subject, async (msg) => { - return { data: msg.data, statusCode: statusCodeExpected }; + throw new RequestError("request error", 500); }, z.string()); await srv.listen();