diff --git a/scripts/core/router/decodeUrl.ts b/scripts/core/decodeUrl.ts similarity index 100% rename from scripts/core/router/decodeUrl.ts rename to scripts/core/decodeUrl.ts diff --git a/scripts/core/functionsBuilders/index.ts b/scripts/core/functionsBuilders/index.ts index f4ebfcc..f173e06 100644 --- a/scripts/core/functionsBuilders/index.ts +++ b/scripts/core/functionsBuilders/index.ts @@ -1,2 +1,3 @@ export * from "./route"; export * from "./steps"; +export * from "./router"; diff --git a/scripts/core/functionsBuilders/route/build.ts b/scripts/core/functionsBuilders/route/build.ts index 42a4fe0..a34e8e9 100644 --- a/scripts/core/functionsBuilders/route/build.ts +++ b/scripts/core/functionsBuilders/route/build.ts @@ -1,12 +1,15 @@ import { E, G, unwrap } from "@duplojs/utils"; -import { buildStepFunction, type BuildStepFunctionParams } from "../steps"; +import { buildStepFunction, type createStepFunctionBuilder } from "../steps"; import { type RouteFunctionBuilderParams, type BuildRouteNotSupportEither, type createRouteFunctionBuilder } from "./create"; import { type HookRouteLifeCycle, type Route } from "@core/route"; import { type ResponseContract } from "@core/response"; +import { type Environment } from "@core/types"; -export interface BuildRouteFunctionParams extends BuildStepFunctionParams { +export interface BuildRouteFunctionParams { readonly routeFunctionBuilders: readonly ReturnType[]; readonly globalHooksRouteLifeCycle: readonly HookRouteLifeCycle[]; + readonly stepFunctionBuilders: readonly ReturnType[]; + readonly environment: Environment; readonly defaultExtractContract: ResponseContract.Contract; } diff --git a/scripts/core/functionsBuilders/route/create.ts b/scripts/core/functionsBuilders/route/create.ts index 0b718f5..5f34e25 100644 --- a/scripts/core/functionsBuilders/route/create.ts +++ b/scripts/core/functionsBuilders/route/create.ts @@ -1,17 +1,12 @@ import { E, type MaybePromise } from "@duplojs/utils"; -import { type HookRouteLifeCycle, type Route } from "@core/route"; -import { type Request } from "@core/request"; +import { type BuildedRoute, type HookRouteLifeCycle, type Route } from "@core/route"; import { type Environment } from "@core/types"; import { type BuildStepSuccessEither, type BuildStepNotSupportEither } from "../steps"; import { type Steps } from "@core/steps"; import { type ResponseContract } from "@core/response"; -export type BuildedRouteFunction = ( - request: Request, -) => Promise; - export type BuildRouteSuccessEither< -> = E.Right<"buildSuccess", BuildedRouteFunction>; +> = E.Right<"buildSuccess", BuildedRoute>; export type BuildRouteNotSupportEither = E.Left<"routeNotSupport", Route>; @@ -28,7 +23,7 @@ export interface RouteFunctionBuilderParams { >; success( - result: BuildedRouteFunction + result: BuildedRoute ): BuildRouteSuccessEither; readonly defaultExtractContract: ResponseContract.Contract; diff --git a/scripts/core/functionsBuilders/route/hook.ts b/scripts/core/functionsBuilders/route/default/hook.ts similarity index 100% rename from scripts/core/functionsBuilders/route/hook.ts rename to scripts/core/functionsBuilders/route/default/hook.ts diff --git a/scripts/core/functionsBuilders/route/default.ts b/scripts/core/functionsBuilders/route/default/index.ts similarity index 97% rename from scripts/core/functionsBuilders/route/default.ts rename to scripts/core/functionsBuilders/route/default/index.ts index 74632b0..5e8602d 100644 --- a/scripts/core/functionsBuilders/route/default.ts +++ b/scripts/core/functionsBuilders/route/default/index.ts @@ -4,8 +4,10 @@ import { A, E, forward, isType, pipe } from "@duplojs/utils"; import { HookResponse, Response } from "@core/response"; import { type Request } from "@core/request"; import { buildHookAfter, buildHookBefore, buildHookErrorBefore, createHookResponse, exitHookFunction, nextHookFunction } from "./hook"; -import { createRouteFunctionBuilder } from "./create"; -import { buildStepsFunction } from "../steps"; +import { createRouteFunctionBuilder } from "../create"; +import { buildStepsFunction } from "../../steps"; + +export * from "./hook"; export const defaultRouteFunctionBuilder = createRouteFunctionBuilder( routeKind.has, diff --git a/scripts/core/functionsBuilders/route/index.ts b/scripts/core/functionsBuilders/route/index.ts index 19ebfd7..622be32 100644 --- a/scripts/core/functionsBuilders/route/index.ts +++ b/scripts/core/functionsBuilders/route/index.ts @@ -1,4 +1,3 @@ export * from "./build"; export * from "./create"; export * from "./default"; -export * from "./hook"; diff --git a/scripts/core/functionsBuilders/router/build.ts b/scripts/core/functionsBuilders/router/build.ts new file mode 100644 index 0000000..821f555 --- /dev/null +++ b/scripts/core/functionsBuilders/router/build.ts @@ -0,0 +1,23 @@ +import { type Environment } from "@core/types"; +import { type createRouterFunctionBuilder } from "./create"; +import { type Request } from "@core/request"; +import { type RouterElementWrapper } from "@core/router/types/routerElementWrapper"; +import { type RouterElementSystem } from "@core/router/types/routerElementSystem"; + +export interface BuildRouterFunctionParams { + readonly routerFunctionBuilder: ReturnType; + readonly environment: Environment; + readonly routerElementWrapper: RouterElementWrapper; + readonly classRequest: typeof Request; + readonly notfoundRouterElement: RouterElementSystem; + readonly malformedUrlRouterElement: RouterElementSystem; +} + +export async function buildRouterFunction( + { + routerFunctionBuilder, + ...params + }: BuildRouterFunctionParams, +) { + return routerFunctionBuilder(params); +} diff --git a/scripts/core/functionsBuilders/router/create.ts b/scripts/core/functionsBuilders/router/create.ts new file mode 100644 index 0000000..4b76e7d --- /dev/null +++ b/scripts/core/functionsBuilders/router/create.ts @@ -0,0 +1,24 @@ +import { type Request } from "@core/request"; +import { type BuildedRouter } from "@core/router"; +import { type RouterElementSystem } from "@core/router/types/routerElementSystem"; +import { type RouterElementWrapper } from "@core/router/types/routerElementWrapper"; +import { type Environment } from "@core/types"; +import { type MaybePromise } from "bun"; + +export interface RouterFunctionBuilderParams { + readonly environment: Environment; + readonly routerElementWrapper: RouterElementWrapper; + readonly classRequest: typeof Request; + readonly notfoundRouterElement: RouterElementSystem; + readonly malformedUrlRouterElement: RouterElementSystem; +} + +export type RouterFunctionBuilder = ( + params: RouterFunctionBuilderParams +) => MaybePromise; + +export function createRouterFunctionBuilder( + theFunction: RouterFunctionBuilder, +) { + return theFunction; +} diff --git a/scripts/core/functionsBuilders/router/default/index.ts b/scripts/core/functionsBuilders/router/default/index.ts new file mode 100644 index 0000000..c46dba3 --- /dev/null +++ b/scripts/core/functionsBuilders/router/default/index.ts @@ -0,0 +1,69 @@ +import { decodeUrl } from "@core/decodeUrl"; +import { createRouterFunctionBuilder } from "../create"; + +export const defaultRouterFunctionBuilder = createRouterFunctionBuilder( + ({ + routerElementWrapper, + malformedUrlRouterElement, + notfoundRouterElement, + classRequest, + }) => (initializationData) => { + const routerElements = routerElementWrapper[initializationData.method]; + const decodedUrl = decodeUrl(initializationData.url); + + if (!decodedUrl) { + return malformedUrlRouterElement.buildedRoute( + new classRequest({ + ...initializationData, + params: {}, + path: "", + query: {}, + matchedPath: null, + bodyReader: malformedUrlRouterElement.bodyReader, + }), + ); + } + + if (!routerElements) { + return notfoundRouterElement.buildedRoute( + new classRequest({ + ...initializationData, + ...decodedUrl, + params: {}, + matchedPath: null, + bodyReader: notfoundRouterElement.bodyReader, + }), + ); + } + + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let index = 0; index < routerElements.length; index++) { + const routerElement = routerElements[index]!; + const result = routerElement.pattern.exec(decodedUrl.path); + + if (!result) { + continue; + } + + return routerElement.buildedRoute( + new classRequest({ + ...initializationData, + ...decodedUrl, + params: result.groups ?? {}, + matchedPath: routerElement.matchedPath, + bodyReader: routerElement.bodyReader, + }), + ); + } + + return notfoundRouterElement.buildedRoute( + new classRequest({ + ...initializationData, + ...decodedUrl, + params: {}, + matchedPath: null, + bodyReader: notfoundRouterElement.bodyReader, + }), + ); + }, +); diff --git a/scripts/core/functionsBuilders/router/index.ts b/scripts/core/functionsBuilders/router/index.ts new file mode 100644 index 0000000..e8da4a9 --- /dev/null +++ b/scripts/core/functionsBuilders/router/index.ts @@ -0,0 +1,3 @@ +export * from "./create"; +export * from "./default"; +export * from "./build"; diff --git a/scripts/core/functionsBuilders/steps/build.ts b/scripts/core/functionsBuilders/steps/build.ts index 0e7f8ab..fd4c9ce 100644 --- a/scripts/core/functionsBuilders/steps/build.ts +++ b/scripts/core/functionsBuilders/steps/build.ts @@ -6,9 +6,7 @@ import { type ResponseContract } from "@core/response"; export interface BuildStepFunctionParams { readonly stepFunctionBuilders: readonly ReturnType[]; - readonly environment: Environment; - readonly defaultExtractContract: ResponseContract.Contract; } diff --git a/scripts/core/functionsBuilders/steps/create.ts b/scripts/core/functionsBuilders/steps/create.ts index 377dfd1..cc6de3f 100644 --- a/scripts/core/functionsBuilders/steps/create.ts +++ b/scripts/core/functionsBuilders/steps/create.ts @@ -1,18 +1,11 @@ import { E, type MaybePromise } from "@duplojs/utils"; -import { type Steps } from "../../steps/types"; -import { type Floor } from "@core/floor"; +import { type BuildedStep, type Steps } from "../../steps/types"; import { type HookRouteLifeCycle } from "@core/route"; -import { type Request } from "@core/request"; -import { type ResponseContract, type Response } from "@core/response"; +import { type ResponseContract } from "@core/response"; import { type Environment } from "@core/types"; -export type BuildedStepFunction = ( - request: Request, - floor: Floor -) => MaybePromise; - export interface BuildStepResult { - readonly buildedFunction: BuildedStepFunction; + readonly buildedFunction: BuildedStep; readonly hooksRouteLifeCycle: readonly HookRouteLifeCycle[]; } diff --git a/scripts/core/hub/hooks.ts b/scripts/core/hub/hooks.ts index 9df6759..91b8042 100644 --- a/scripts/core/hub/hooks.ts +++ b/scripts/core/hub/hooks.ts @@ -2,7 +2,7 @@ import { type Route } from "@core/route"; import { type EscapeVoid, G, type Kind, type MaybePromise } from "@duplojs/utils"; import { type Hub } from "."; import { createCoreLibKind } from "@core/kind"; -import { type RouterInitializationData } from "@core/router"; +import { type RouterParams } from "@core/router"; import { type HttpServerParams } from "@core/types"; export const hookServerExitKind = createCoreLibKind("server-hook-exit"); @@ -65,7 +65,7 @@ export interface HttpServerErrorParams { readonly error: unknown; next(): ServerHookNext; exit(): ServerHookExit; - routerInitializationData: RouterInitializationData; + routerInitializationData: RouterParams; } export type HookServerError = ( diff --git a/scripts/core/hub/index.ts b/scripts/core/hub/index.ts index 7e65bc1..5cb10fd 100644 --- a/scripts/core/hub/index.ts +++ b/scripts/core/hub/index.ts @@ -13,6 +13,7 @@ import { type createRouteFunctionBuilder } from "@core/functionsBuilders/route"; import { defaultBodyController } from "./defaultBodyController"; import { defaultMalformedUrlHandler } from "./defaultMalformedUrlHandler"; import { defaultEmptyReaderImplementation } from "./defaultEmptyReaderImplementation"; +import { type createRouterFunctionBuilder } from "@core/functionsBuilders/router"; export * from "./hooks"; export * from "./defaultNotfoundHandler"; @@ -51,6 +52,8 @@ export class Hub< public routes = new Set(); + public routerFunctionBuilder: ReturnType | undefined = undefined; + public routeFunctionBuilders: ReturnType[] = []; public stepFunctionBuilders: ReturnType[] = []; @@ -97,6 +100,13 @@ export class Hub< return this; } + public setRouterFunctionBuilder( + functionBuilder: ReturnType, + ) { + this.routerFunctionBuilder = functionBuilder; + return this; + } + public addRouteFunctionBuilder( functionBuilder: MaybeArray>, ) { diff --git a/scripts/core/implementHttpServer.ts b/scripts/core/implementHttpServer.ts index 764ae5f..5bea220 100644 --- a/scripts/core/implementHttpServer.ts +++ b/scripts/core/implementHttpServer.ts @@ -1,6 +1,5 @@ - import { type Hub, launchHookServer, launchHookServerError, serverErrorExitHookFunction, serverErrorNextHookFunction } from "./hub"; -import { buildRouter, type RouterInitializationData } from "./router"; +import { createRouter, type RouterParams } from "./router"; import { type AnyTuple, forward, type MaybePromise } from "@duplojs/utils"; import { type HttpServerParams } from "./types"; import { type HookRouteLifeCycle } from "./route"; @@ -18,10 +17,10 @@ export interface ImplementHttpServerParams { } export type ExecRouteSystem = ( - routerInitializationData: RouterInitializationData, + routerInitializationData: RouterParams, whenUncaughtError: ( error: unknown, - routerInitializationData: RouterInitializationData + routerInitializationData: RouterParams ) => MaybePromise ) => Promise; @@ -47,7 +46,7 @@ export async function implementHttpServer< ...params.getInterfaceHooks(params), ]); - const router = await buildRouter( + const router = await createRouter( params.hub, ); diff --git a/scripts/core/router/buildSystemRoute.ts b/scripts/core/router/createRouterElementSystem.ts similarity index 77% rename from scripts/core/router/buildSystemRoute.ts rename to scripts/core/router/createRouterElementSystem.ts index fcdd27d..ada1d3d 100644 --- a/scripts/core/router/buildSystemRoute.ts +++ b/scripts/core/router/createRouterElementSystem.ts @@ -3,15 +3,16 @@ import { buildRouteFunction, type BuildRouteFunctionParams } from "@core/functio import { controlBodyAsEmpty } from "@core/request"; import { createRoute } from "@core/route"; import { RouterBuildError } from "./buildError"; -import { defaultEmptyReaderImplementation } from "@core/hub/defaultEmptyReaderImplementation"; +import { defaultEmptyReaderImplementation } from "@core/hub"; import { type HandlerStep } from "@core/steps"; +import { type RouterElementSystem } from "./types"; -interface BuildSystemRouteParams { +interface CreateRouterElementSystemParams { handlerStep: HandlerStep; buildParams: BuildRouteFunctionParams; } -export async function buildSystemRoute(params: BuildSystemRouteParams) { +export async function createRouterElementSystem(params: CreateRouterElementSystemParams): Promise { const bodyController = controlBodyAsEmpty(); const bodyReader = bodyController.createReaderOrThrow( defaultEmptyReaderImplementation, diff --git a/scripts/core/router/index.ts b/scripts/core/router/index.ts index b925044..2f0dc0d 100644 --- a/scripts/core/router/index.ts +++ b/scripts/core/router/index.ts @@ -1,36 +1,19 @@ import { type Hub, launchHookBeforeBuildRoute } from "@core/hub"; -import { type BuildedRouter } from "./types"; import { A, E, forward, G, isType, justReturn, O, pipe, unwrap } from "@duplojs/utils"; -import { type BuildedRoute } from "@core/route/types"; import { pathToRegExp } from "./pathToRegExp"; import { RouterBuildError } from "./buildError"; -import { buildRouteFunction, type createRouteFunctionBuilder, defaultRouteFunctionBuilder, type BuildRouteFunctionParams } from "@core/functionsBuilders/route"; -import { decodeUrl } from "./decodeUrl"; -import { type BodyReader } from "@core/request"; import { NotFoundBodyReaderImplementationError } from "./notFoundBodyReaderImplementationError"; -import { type createStepFunctionBuilder, defaultCheckerStepFunctionBuilder, defaultCutStepFunctionBuilder, defaultExtractStepFunctionBuilder, defaultHandlerStepFunctionBuilder, defaultProcessStepFunctionBuilder } from "@core/functionsBuilders"; -import { buildSystemRoute } from "./buildSystemRoute"; +import { buildRouteFunction, type BuildRouteFunctionParams, buildRouterFunction, type createRouteFunctionBuilder, type createStepFunctionBuilder, defaultCheckerStepFunctionBuilder, defaultCutStepFunctionBuilder, defaultExtractStepFunctionBuilder, defaultHandlerStepFunctionBuilder, defaultProcessStepFunctionBuilder, defaultRouteFunctionBuilder, defaultRouterFunctionBuilder } from "@core/functionsBuilders"; +import { createRouterElementSystem } from "./createRouterElementSystem"; +import { type RouterElementWrapper, type Router } from "./types"; export * from "./types"; export * from "./pathToRegExp"; export * from "./buildError"; -export * from "./decodeUrl"; export * from "./notFoundBodyReaderImplementationError"; -export * from "./buildSystemRoute"; +export * from "./createRouterElementSystem"; -interface RouterElement { - pattern: RegExp; - matchedPath: string; - bodyReader: BodyReader; - buildedRoute: BuildedRoute; -} - -type GroupedRoute = Record< - string, - RouterElement[] ->; - -export async function buildRouter(hub: Hub): Promise { +export async function createRouter(hub: Hub): Promise { const { environment } = hub.config; const { hooksRouteLifeCycle, @@ -66,9 +49,9 @@ export async function buildRouter(hub: Hub): Promise { defaultExtractContract: hub.defaultExtractContract, }; - const groupedRoute = await G.asyncReduce( + const routerElementWrapper = await G.asyncReduce( routes, - G.reduceFrom({}), + G.reduceFrom({}), async({ lastValue, element: route, @@ -133,78 +116,25 @@ export async function buildRouter(hub: Hub): Promise { }, ); - const defaultNotfoundRoute = await buildSystemRoute({ + const notfoundRouterElement = await createRouterElementSystem({ handlerStep: hub.notfoundHandler, buildParams, }); - const defaultMalformedUrlRoute = await buildSystemRoute({ + const malformedUrlRouterElement = await createRouterElementSystem({ handlerStep: hub.malformedUrlHandler, buildParams, }); - const Request = hub.classRequest; - return { - exec: (initializationData) => { - const routerElements = groupedRoute[initializationData.method]; - const decodedUrl = decodeUrl(initializationData.url); - - if (!decodedUrl) { - return defaultMalformedUrlRoute.buildedRoute( - new Request({ - ...initializationData, - params: {}, - path: "", - query: {}, - matchedPath: null, - bodyReader: defaultMalformedUrlRoute.bodyReader, - }), - ); - } - - if (!routerElements) { - return defaultNotfoundRoute.buildedRoute( - new Request({ - ...initializationData, - ...decodedUrl, - params: {}, - matchedPath: null, - bodyReader: defaultNotfoundRoute.bodyReader, - }), - ); - } - - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let index = 0; index < routerElements.length; index++) { - const routerElement = routerElements[index]!; - const result = routerElement.pattern.exec(decodedUrl.path); - - if (!result) { - continue; - } - - return routerElement.buildedRoute( - new Request({ - ...initializationData, - ...decodedUrl, - params: result.groups ?? {}, - matchedPath: routerElement.matchedPath, - bodyReader: routerElement.bodyReader, - }), - ); - } - - return defaultNotfoundRoute.buildedRoute( - new Request({ - ...initializationData, - ...decodedUrl, - params: {}, - matchedPath: null, - bodyReader: defaultNotfoundRoute.bodyReader, - }), - ); - }, + exec: await buildRouterFunction({ + environment: hub.config.environment, + routerElementWrapper, + notfoundRouterElement: notfoundRouterElement, + malformedUrlRouterElement: malformedUrlRouterElement, + classRequest: hub.classRequest, + routerFunctionBuilder: hub.routerFunctionBuilder ?? defaultRouterFunctionBuilder, + }), hooksRouteLifeCycle, routeFunctionBuilders, routes, diff --git a/scripts/core/router/types/buildedRouter.ts b/scripts/core/router/types/buildedRouter.ts index 730f3d9..7a5856c 100644 --- a/scripts/core/router/types/buildedRouter.ts +++ b/scripts/core/router/types/buildedRouter.ts @@ -1,9 +1,7 @@ -import { type createStepFunctionBuilder, type createRouteFunctionBuilder } from "@core/functionsBuilders"; -import { type HookHubLifeCycle } from "@core/hub"; import { type RequestInitializationData } from "@core/request"; -import { type HookRouteLifeCycle, type Route } from "@core/route"; -export type RouterInitializationData = Omit< +// need omit to correct override +export type RouterParams = Omit< RequestInitializationData, | "matchedPath" | "bodyReader" @@ -12,15 +10,6 @@ export type RouterInitializationData = Omit< | "query" >; -export interface BuildedRouter { - exec( - initializationData: RouterInitializationData - ): Promise; - readonly routes: ReadonlySet; - readonly hooksRouteLifeCycle: readonly HookRouteLifeCycle[]; - readonly routeFunctionBuilders: readonly ReturnType[]; - readonly stepFunctionBuilders: readonly ReturnType[]; - readonly hooksHubLifeCycle: readonly HookHubLifeCycle[]; - -} - +export type BuildedRouter = ( + params: RouterParams +) => Promise; diff --git a/scripts/core/router/types/index.ts b/scripts/core/router/types/index.ts index c2d0e97..9c3567f 100644 --- a/scripts/core/router/types/index.ts +++ b/scripts/core/router/types/index.ts @@ -1 +1,5 @@ export * from "./buildedRouter"; +export * from "./router"; +export * from "./routerElement"; +export * from "./routerElementSystem"; +export * from "./routerElementWrapper"; diff --git a/scripts/core/router/types/router.ts b/scripts/core/router/types/router.ts new file mode 100644 index 0000000..3ea7798 --- /dev/null +++ b/scripts/core/router/types/router.ts @@ -0,0 +1,14 @@ +import { type createRouteFunctionBuilder, type createStepFunctionBuilder } from "@core/functionsBuilders"; +import { type HookHubLifeCycle } from "@core/hub"; +import { type HookRouteLifeCycle, type Route } from "@core/route"; +import { type BuildedRouter } from "./buildedRouter"; + +export interface Router { + exec: BuildedRouter; + readonly routes: ReadonlySet; + readonly hooksRouteLifeCycle: readonly HookRouteLifeCycle[]; + readonly routeFunctionBuilders: readonly ReturnType[]; + readonly stepFunctionBuilders: readonly ReturnType[]; + readonly hooksHubLifeCycle: readonly HookHubLifeCycle[]; + +} diff --git a/scripts/core/router/types/routerElement.ts b/scripts/core/router/types/routerElement.ts new file mode 100644 index 0000000..afac446 --- /dev/null +++ b/scripts/core/router/types/routerElement.ts @@ -0,0 +1,9 @@ +import { type BodyReader } from "@core/request"; +import { type BuildedRoute } from "@core/route/types"; + +export interface RouterElement { + readonly pattern: RegExp; + readonly matchedPath: string; + readonly bodyReader: BodyReader; + readonly buildedRoute: BuildedRoute; +} diff --git a/scripts/core/router/types/routerElementSystem.ts b/scripts/core/router/types/routerElementSystem.ts new file mode 100644 index 0000000..13d5b3b --- /dev/null +++ b/scripts/core/router/types/routerElementSystem.ts @@ -0,0 +1,7 @@ +import { type BodyReader } from "@core/request"; +import { type BuildedRoute } from "@core/route/types"; + +export interface RouterElementSystem { + readonly bodyReader: BodyReader; + readonly buildedRoute: BuildedRoute; +} diff --git a/scripts/core/router/types/routerElementWrapper.ts b/scripts/core/router/types/routerElementWrapper.ts new file mode 100644 index 0000000..2272695 --- /dev/null +++ b/scripts/core/router/types/routerElementWrapper.ts @@ -0,0 +1,6 @@ +import { type RouterElement } from "./routerElement"; + +export type RouterElementWrapper = Record< + string, + RouterElement[] +>; diff --git a/scripts/interfaces/node/createHttpServer.ts b/scripts/interfaces/node/createHttpServer.ts index f907f91..a4e6182 100644 --- a/scripts/interfaces/node/createHttpServer.ts +++ b/scripts/interfaces/node/createHttpServer.ts @@ -1,5 +1,4 @@ import { type Hub } from "@core/hub"; -import { type RouterInitializationData } from "@core/router"; import http from "http"; import https from "https"; import { initNodeHook } from "./hooks"; @@ -7,6 +6,7 @@ import { implementHttpServer } from "@core/implementHttpServer"; import { O } from "@duplojs/utils"; import { type HttpServerParams } from "@core/types"; import { createFormDataBodyReaderImplementation, createTextBodyReaderImplementation } from "./bodyReaders"; +import { type RouterParams } from "@core/router"; declare module "@core/types" { interface HttpServerParams { @@ -58,9 +58,9 @@ export function createHttpServer( function whenUncaughtError( error: unknown, - routerInitializationData: RouterInitializationData, + routerParams: RouterParams, ) { - const serverResponse = routerInitializationData.raw.response; + const serverResponse = routerParams.raw.response; if (!serverResponse.headersSent && !serverResponse.writableEnded) { serverResponse.writeHead(500, { diff --git a/tests/core/router/decodeUrl.test.ts b/tests/core/decodeUrl.test.ts similarity index 97% rename from tests/core/router/decodeUrl.test.ts rename to tests/core/decodeUrl.test.ts index e32f57d..de7f266 100644 --- a/tests/core/router/decodeUrl.test.ts +++ b/tests/core/decodeUrl.test.ts @@ -1,4 +1,4 @@ -import { decodeUrl } from "@core/router"; +import { decodeUrl } from "@core/decodeUrl"; describe("decodeUrl", () => { it("decodes paths and query parameters", () => { diff --git a/tests/core/functionsBuilders/route/default.test.ts b/tests/core/functionsBuilders/route/default/index.test.ts similarity index 100% rename from tests/core/functionsBuilders/route/default.test.ts rename to tests/core/functionsBuilders/route/default/index.test.ts diff --git a/tests/core/functionsBuilders/router/build.test.ts b/tests/core/functionsBuilders/router/build.test.ts new file mode 100644 index 0000000..41707ae --- /dev/null +++ b/tests/core/functionsBuilders/router/build.test.ts @@ -0,0 +1,23 @@ +import { buildRouterFunction, type BuildRouterFunctionParams, Request } from "@core"; +import { O } from "@duplojs/utils"; + +describe("buildRouterFunction", () => { + it("correct call function", async() => { + const params: BuildRouterFunctionParams = { + routerFunctionBuilder: vi.fn(() => Promise.resolve(true) as never), + classRequest: Request, + environment: "DEV", + malformedUrlRouterElement: {} as never, + notfoundRouterElement: {} as never, + routerElementWrapper: {}, + }; + + const result = await buildRouterFunction(params); + + expect(result).toBe(true); + + expect(params.routerFunctionBuilder).toHaveBeenCalledWith( + O.omit(params, ["routerFunctionBuilder"]), + ); + }); +}); diff --git a/tests/core/functionsBuilders/router/create.test.ts b/tests/core/functionsBuilders/router/create.test.ts new file mode 100644 index 0000000..b4fff6b --- /dev/null +++ b/tests/core/functionsBuilders/router/create.test.ts @@ -0,0 +1,12 @@ +import { createRouterFunctionBuilder } from "@core"; + +describe("createRouterFunctionBuilder", () => { + it("correct help", () => { + const theFunction = vi.fn(); + const result = createRouterFunctionBuilder( + theFunction, + ); + + expect(result).toBe(theFunction); + }); +}); diff --git a/tests/core/functionsBuilders/router/default/index.test.ts b/tests/core/functionsBuilders/router/default/index.test.ts new file mode 100644 index 0000000..a7cfeaf --- /dev/null +++ b/tests/core/functionsBuilders/router/default/index.test.ts @@ -0,0 +1,215 @@ +import { defaultRouterFunctionBuilder, Request, type RouterFunctionBuilderParams } from "@core"; + +describe("defaultRouterFunctionBuilder", () => { + const spyMalformed = vi.fn(); + const spyNotfound = vi.fn(); + + const baseParams: RouterFunctionBuilderParams = { + classRequest: Request, + environment: "DEV", + malformedUrlRouterElement: { + bodyReader: {} as never, + buildedRoute: spyMalformed, + }, + notfoundRouterElement: { + bodyReader: {} as never, + buildedRoute: spyNotfound, + }, + routerElementWrapper: {}, + }; + + afterEach(() => { + spyMalformed.mockClear(); + spyNotfound.mockClear(); + }); + + it("buildedRouter use malformed url route with fallback request values", async() => { + const buildedRouter = await defaultRouterFunctionBuilder(baseParams); + + await buildedRouter({ + headers: {}, + host: "", + method: "GET", + origin: "", + url: "/%E0%A4%A", + }); + + expect(spyMalformed).toHaveBeenCalledWith( + new Request({ + bodyReader: {} as never, + headers: {}, + host: "", + method: "GET", + origin: "", + url: "/%E0%A4%A", + matchedPath: null, + params: {}, + path: "", + query: {}, + }), + ); + expect(spyNotfound).not.toHaveBeenCalled(); + }); + + it("buildedRouter use notfound route when method group does not exist", async() => { + const buildedRouter = await defaultRouterFunctionBuilder(baseParams); + + await buildedRouter({ + headers: {}, + host: "", + method: "POST", + origin: "", + url: "/users?role=admin", + }); + + expect(spyNotfound).toHaveBeenCalledWith( + new Request({ + bodyReader: {} as never, + headers: {}, + host: "", + method: "POST", + origin: "", + url: "/users?role=admin", + matchedPath: null, + params: {}, + path: "/users", + query: { role: "admin" }, + }), + ); + expect(spyMalformed).not.toHaveBeenCalled(); + }); + + it("buildedRouter use notfound route after search route", async() => { + const spyRoute = vi.fn(); + const buildedRouter = await defaultRouterFunctionBuilder({ + ...baseParams, + routerElementWrapper: { + POST: [ + { + bodyReader: {} as never, + buildedRoute: spyRoute, + matchedPath: "/test", + pattern: /^\/test/, + }, + ], + }, + }); + + await buildedRouter({ + headers: {}, + host: "", + method: "POST", + origin: "", + url: "/users?role=admin", + }); + + expect(spyNotfound).toHaveBeenCalledWith( + new Request({ + bodyReader: {} as never, + headers: {}, + host: "", + method: "POST", + origin: "", + url: "/users?role=admin", + matchedPath: null, + params: {}, + path: "/users", + query: { role: "admin" }, + }), + ); + expect(spyRoute).not.toHaveBeenCalled(); + expect(spyMalformed).not.toHaveBeenCalled(); + }); + + it("buildedRouter exec route", async() => { + const spyRoute = vi.fn(); + const buildedRouter = await defaultRouterFunctionBuilder({ + ...baseParams, + routerElementWrapper: { + POST: [ + { + bodyReader: {} as never, + buildedRoute: spyRoute, + matchedPath: "/users", + pattern: /^\/users/, + }, + ], + }, + }); + + await buildedRouter({ + headers: {}, + host: "", + method: "POST", + origin: "", + url: "/users?role=admin", + }); + + expect(spyRoute).toHaveBeenCalledWith( + new Request({ + bodyReader: {} as never, + headers: {}, + host: "", + method: "POST", + origin: "", + url: "/users?role=admin", + matchedPath: "/users", + params: {}, + path: "/users", + query: { role: "admin" }, + }), + ); + expect(spyNotfound).not.toHaveBeenCalled(); + expect(spyMalformed).not.toHaveBeenCalled(); + }); + + it("buildedRouter skip route when pattern not match and execute next", async() => { + const spyRouteSkip = vi.fn(); + const spyRoute = vi.fn(); + const buildedRouter = await defaultRouterFunctionBuilder({ + ...baseParams, + routerElementWrapper: { + POST: [ + { + bodyReader: {} as never, + buildedRoute: spyRouteSkip, + matchedPath: "/test", + pattern: /^\/test/, + }, + { + bodyReader: {} as never, + buildedRoute: spyRoute, + matchedPath: "/users", + pattern: /^\/users/, + }, + ], + }, + }); + + await buildedRouter({ + headers: {}, + host: "", + method: "POST", + origin: "", + url: "/users?role=admin", + }); + + expect(spyRoute).toHaveBeenCalledWith( + new Request({ + bodyReader: {} as never, + headers: {}, + host: "", + method: "POST", + origin: "", + url: "/users?role=admin", + matchedPath: "/users", + params: {}, + path: "/users", + query: { role: "admin" }, + }), + ); + expect(spyNotfound).not.toHaveBeenCalled(); + expect(spyMalformed).not.toHaveBeenCalled(); + expect(spyRouteSkip).not.toHaveBeenCalled(); + }); +}); diff --git a/tests/core/hub/index.test.ts b/tests/core/hub/index.test.ts index 707882a..d98eebd 100644 --- a/tests/core/hub/index.test.ts +++ b/tests/core/hub/index.test.ts @@ -17,6 +17,7 @@ describe("hub", () => { routes: new Set(), stepFunctionBuilders: [], bodyReaderImplementations: [defaultEmptyReaderImplementation], + routerFunctionBuilder: undefined, }; it("hub shape", () => { @@ -167,6 +168,19 @@ describe("hub", () => { }); }); + it("hub set route hooks", () => { + const routerFunctionBuilder = vi.fn(); + const hub = createHub({ + environment: "DEV", + }) + .setRouterFunctionBuilder(routerFunctionBuilder); + + expect({ ...hub }).toStrictEqual({ + ...baseHub, + routerFunctionBuilder, + }); + }); + it("hub add hub hooks", () => { const hubHook: HookHubLifeCycle = {}; const hub = createHub({ @@ -210,7 +224,7 @@ describe("hub", () => { ]); }); - it("hub set not found handler", () => { + it("hub set not found handler", async() => { const contract = ResponseContract.notFound("test"); const hub = createHub({ @@ -229,9 +243,15 @@ describe("hub", () => { }), }), }); + + const spyNotfound = vi.fn(() => true as never); + const result = await hub.notfoundHandler.definition.theFunction({}, { response: spyNotfound } as never); + + expect(result).toBe(true); + expect(spyNotfound).toHaveBeenCalledWith("test"); }); - it("hub set malformed url handler", () => { + it("hub set malformed url handler", async() => { const contract = ResponseContract.badRequest("malformed"); const hub = createHub({ @@ -250,6 +270,12 @@ describe("hub", () => { }), }), }); + + const spyMalformed = vi.fn(() => true as never); + const result = await hub.malformedUrlHandler.definition.theFunction({}, { response: spyMalformed } as never); + + expect(result).toBe(true); + expect(spyMalformed).toHaveBeenCalledWith("malformed"); }); it("hub set default extract contract", () => { diff --git a/tests/core/implementHttpServer.test.ts b/tests/core/implementHttpServer.test.ts index 914a4de..ba12890 100644 --- a/tests/core/implementHttpServer.test.ts +++ b/tests/core/implementHttpServer.test.ts @@ -1,5 +1,4 @@ -import { type HttpServerParams, ResponseContract, TextBodyController, createHub, implementHttpServer, serverErrorExitHookFunction, serverErrorNextHookFunction, useRouteBuilder } from "@core"; -import { type RouterInitializationData } from "@core/router"; +import { type HttpServerParams, ResponseContract, type RouterParams, TextBodyController, createHub, implementHttpServer, serverErrorExitHookFunction, serverErrorNextHookFunction, useRouteBuilder } from "@core"; import { E, type AnyFunction } from "@duplojs/utils"; describe("implementHttpServer", () => { @@ -127,7 +126,7 @@ describe("implementHttpServer", () => { }, ); - const routerInitializationData: RouterInitializationData = { + const routerInitializationData: RouterParams = { method: "GET", headers: {}, host: "", diff --git a/tests/core/router/buildSystemRoute.test.ts b/tests/core/router/buildSystemRoute.test.ts deleted file mode 100644 index 0daeee5..0000000 --- a/tests/core/router/buildSystemRoute.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { E } from "@duplojs/utils"; -import { - Request, - ResponseContract, - RouterBuildError, - createHandlerStep, - createHub, - defaultCheckerStepFunctionBuilder, - defaultCutStepFunctionBuilder, - defaultExtractStepFunctionBuilder, - defaultHandlerStepFunctionBuilder, - defaultProcessStepFunctionBuilder, - defaultRouteFunctionBuilder, - stepKind, - buildSystemRoute, -} from "@core"; - -function makeBuildParams() { - const hub = createHub({ - environment: "DEV", - }); - - return { - environment: hub.config.environment, - globalHooksRouteLifeCycle: hub.hooksRouteLifeCycle, - routeFunctionBuilders: [ - ...hub.routeFunctionBuilders, - defaultRouteFunctionBuilder, - ], - stepFunctionBuilders: [ - ...hub.stepFunctionBuilders, - defaultCheckerStepFunctionBuilder, - defaultCutStepFunctionBuilder, - defaultHandlerStepFunctionBuilder, - defaultExtractStepFunctionBuilder, - defaultProcessStepFunctionBuilder, - ], - defaultExtractContract: hub.defaultExtractContract, - }; -} - -describe("buildSystemRoute", () => { - it("build system route with empty body reader", async() => { - const spy = vi.fn(); - const handlerStep = createHandlerStep({ - responseContract: ResponseContract.noContent("system.route"), - theFunction: async(__, { request, response }) => { - spy(await request.getBody()); - return response("system.route", undefined as never); - }, - metadata: [], - }); - - const { bodyReader, buildedRoute } = await buildSystemRoute({ - handlerStep, - buildParams: makeBuildParams(), - }); - - await buildedRoute( - new Request({ - headers: {}, - host: "", - matchedPath: null, - method: "GET", - origin: "", - params: {}, - path: "/", - query: {}, - url: "/", - bodyReader, - }), - ); - - expect(spy).toHaveBeenCalledWith(E.success(undefined)); - }); - - it("throw RouterBuildError when handler step cannot be build", async() => { - await expect( - buildSystemRoute({ - handlerStep: stepKind.setTo({}) as never, - buildParams: makeBuildParams(), - }), - ).rejects.instanceof(RouterBuildError); - }); -}); diff --git a/tests/core/router/createRouterElementSystem.test.ts b/tests/core/router/createRouterElementSystem.test.ts new file mode 100644 index 0000000..91fd806 --- /dev/null +++ b/tests/core/router/createRouterElementSystem.test.ts @@ -0,0 +1,71 @@ +import { E } from "@duplojs/utils"; +import { Request, ResponseContract, RouterBuildError, createHandlerStep, createHub, defaultCheckerStepFunctionBuilder, defaultCutStepFunctionBuilder, defaultExtractStepFunctionBuilder, defaultHandlerStepFunctionBuilder, defaultProcessStepFunctionBuilder, defaultRouteFunctionBuilder, stepKind, createRouterElementSystem } from "@core"; + +describe("createRouterElementSystem", () => { + function makeBuildParams() { + const hub = createHub({ + environment: "DEV", + }); + + return { + environment: hub.config.environment, + globalHooksRouteLifeCycle: hub.hooksRouteLifeCycle, + routeFunctionBuilders: [ + ...hub.routeFunctionBuilders, + defaultRouteFunctionBuilder, + ], + stepFunctionBuilders: [ + ...hub.stepFunctionBuilders, + defaultCheckerStepFunctionBuilder, + defaultCutStepFunctionBuilder, + defaultHandlerStepFunctionBuilder, + defaultExtractStepFunctionBuilder, + defaultProcessStepFunctionBuilder, + ], + defaultExtractContract: hub.defaultExtractContract, + }; + } + + it("build system route with empty body reader", async() => { + const spy = vi.fn(); + const handlerStep = createHandlerStep({ + responseContract: ResponseContract.noContent("system.route"), + theFunction: async(__, { request, response }) => { + spy(await request.getBody()); + return response("system.route", undefined as never); + }, + metadata: [], + }); + + const { bodyReader, buildedRoute } = await createRouterElementSystem({ + handlerStep, + buildParams: makeBuildParams(), + }); + + await buildedRoute( + new Request({ + headers: {}, + host: "", + matchedPath: null, + method: "GET", + origin: "", + params: {}, + path: "/", + query: {}, + url: "/", + bodyReader, + }), + ); + + expect(spy).toHaveBeenCalledWith(E.success(undefined)); + }); + + it("throw RouterBuildError when handler step cannot be build", async() => { + await expect( + createRouterElementSystem({ + handlerStep: stepKind.setTo({}) as never, + buildParams: makeBuildParams(), + }), + ).rejects.instanceof(RouterBuildError); + }); +}); diff --git a/tests/core/router/index.test.ts b/tests/core/router/index.test.ts index 11749d7..5aac2e5 100644 --- a/tests/core/router/index.test.ts +++ b/tests/core/router/index.test.ts @@ -1,4 +1,4 @@ -import { buildRouter, createHub, defaultCheckerStepFunctionBuilder, defaultCutStepFunctionBuilder, defaultExtractStepFunctionBuilder, defaultHandlerStepFunctionBuilder, defaultProcessStepFunctionBuilder, defaultRouteFunctionBuilder, NotFoundBodyReaderImplementationError, ResponseContract, RouterBuildError, stepKind, TextBodyController, useRouteBuilder } from "@core"; +import { createRouter, createHub, defaultCheckerStepFunctionBuilder, defaultCutStepFunctionBuilder, defaultExtractStepFunctionBuilder, defaultHandlerStepFunctionBuilder, defaultProcessStepFunctionBuilder, defaultRouteFunctionBuilder, NotFoundBodyReaderImplementationError, ResponseContract, RouterBuildError, stepKind, TextBodyController, useRouteBuilder, Request } from "@core"; import { DP, E } from "@duplojs/utils"; import { testRoute } from "@test-utils/route"; @@ -8,7 +8,8 @@ describe("buildRouter", () => { it("correct build router", async() => { const otherRoute = { ...testRoute }; - const router = await buildRouter( + const spyRouterBuilder = vi.fn(() => Promise.resolve(true) as never); + const router = await createRouter( createHub({ environment: "DEV", }) @@ -25,11 +26,12 @@ describe("buildRouter", () => { stepFunctionBuilders: [defaultCutStepFunctionBuilder], routes: [otherRoute], }) + .setRouterFunctionBuilder(spyRouterBuilder) .addBodyReaderImplementation(textBodyReaderImplementation), ); expect(router).toStrictEqual({ - exec: expect.any(Function), + exec: true, hooksHubLifeCycle: [{}, {}], hooksRouteLifeCycle: [{}, {}], routeFunctionBuilders: [ @@ -48,11 +50,28 @@ describe("buildRouter", () => { defaultProcessStepFunctionBuilder, ], }); + + expect(spyRouterBuilder).toHaveBeenCalledWith({ + classRequest: Request, + environment: "DEV", + malformedUrlRouterElement: expect.any(Object), + notfoundRouterElement: expect.any(Object), + routerElementWrapper: { + GET: [ + expect.objectContaining({ + matchedPath: "/test", + }), + expect.objectContaining({ + matchedPath: "/test", + }), + ], + }, + }); }); it("throw error when build route", async() => { await expect( - buildRouter( + createRouter( createHub({ environment: "DEV", }) @@ -64,7 +83,7 @@ describe("buildRouter", () => { it("throw error when not found body reader implementation", async() => { await expect( - buildRouter( + createRouter( createHub({ environment: "DEV", }) @@ -80,220 +99,9 @@ describe("buildRouter", () => { (hub as any).notfoundHandler = stepKind.setTo({}) as any; await expect( - buildRouter( + createRouter( hub, ), ).rejects.instanceof(RouterBuildError); }); - - it("buildedRouter use malformed url route with fallback request values", async() => { - const spy = vi.fn(); - const buildedRouter = await buildRouter( - createHub({ environment: "DEV" }) - .setMalformedUrlHandler( - ResponseContract.badRequest("malformed"), - async({ request, response }) => { - spy({ - matchedPath: request.matchedPath, - params: request.params, - path: request.path, - query: request.query, - body: await request.getBody(), - }); - return response("malformed"); - }, - ) - .addBodyReaderImplementation(textBodyReaderImplementation), - ); - - await buildedRouter.exec({ - headers: {}, - host: "", - method: "GET", - origin: "", - url: "/%E0%A4%A", - }); - - expect(spy).toHaveBeenCalledWith({ - matchedPath: null, - params: {}, - path: "", - query: {}, - body: E.success(undefined), - }); - }); - - it("buildedRouter use notfound route when method group does not exist", async() => { - const spy = vi.fn(); - const route = useRouteBuilder("GET", "/users") - .handler( - ResponseContract.ok("users.find", DP.empty()), - (__, { response }) => response("users.find"), - ); - - const buildedRouter = await buildRouter( - createHub({ environment: "DEV" }) - .register(route) - .setNotfoundHandler( - ResponseContract.notFound("notfound"), - async({ request, response }) => { - spy({ - matchedPath: request.matchedPath, - params: request.params, - path: request.path, - query: request.query, - body: await request.getBody(), - }); - return response("notfound"); - }, - ) - .addBodyReaderImplementation(textBodyReaderImplementation), - ); - - await buildedRouter.exec({ - headers: {}, - host: "", - method: "POST", - origin: "", - url: "/users?role=admin", - }); - - expect(spy).toHaveBeenCalledWith({ - matchedPath: null, - params: {}, - path: "/users", - query: { - role: "admin", - }, - body: E.success(undefined), - }); - }); - - it("buildedRouter use notfound route after search route", async() => { - const spyRoute = vi.fn(); - const route = useRouteBuilder("GET", "/users") - .handler( - ResponseContract.ok("users.find", DP.empty()), - (floor, { response }) => { - spyRoute(); - return response("users.find"); - }, - ); - - const spy = vi.fn(); - const buildedRouter = await buildRouter( - createHub({ - environment: "DEV", - }) - .register(route) - .setNotfoundHandler( - ResponseContract.notFound("notfound"), - ({ response }) => { - spy(); - return response("notfound"); - }, - ) - .addBodyReaderImplementation(textBodyReaderImplementation), - ); - - await buildedRouter.exec({ - headers: {}, - host: "", - method: "GET", - origin: "", - url: "/admins", - }); - - expect(spyRoute).not.toHaveBeenCalled(); - expect(spy).toHaveBeenCalled(); - }); - - it("buildedRouter exec route", async() => { - const spyRoute = vi.fn(); - const route = useRouteBuilder("GET", "/users") - .handler( - ResponseContract.ok("users.find", DP.empty()), - (floor, { response }) => { - spyRoute(); - return response("users.find"); - }, - ); - - const spyNotfound = vi.fn(); - const buildedRouter = await buildRouter( - createHub({ - environment: "DEV", - }) - .register(route) - .setNotfoundHandler( - ResponseContract.notFound("notfound"), - ({ response }) => { - spyNotfound(); - return response("notfound"); - }, - ) - .addBodyReaderImplementation(textBodyReaderImplementation), - ); - - await buildedRouter.exec({ - headers: {}, - host: "", - method: "GET", - origin: "", - url: "/users", - }); - - expect(spyRoute).toHaveBeenCalled(); - expect(spyNotfound).not.toHaveBeenCalled(); - }); - - it("buildedRouter skip route when pattern not match and execute next", async() => { - const spyRoute = vi.fn(); - const route = useRouteBuilder("GET", "/users") - .handler( - ResponseContract.ok("users.find", DP.empty()), - (floor, { response }) => { - spyRoute(); - return response("users.find"); - }, - ); - - const spyRoute2 = vi.fn(); - const route2 = useRouteBuilder("GET", "/admins") - .handler( - ResponseContract.ok("admins.find", DP.empty()), - (floor, { response }) => { - spyRoute2(); - return response("admins.find"); - }, - ); - - const spyNotfound = vi.fn(); - const buildedRouter = await buildRouter( - createHub({ - environment: "DEV", - }) - .register([route, route2]) - .setNotfoundHandler( - ResponseContract.notFound("notfound"), - ({ response }) => { - spyNotfound(); - return response("notfound"); - }, - ) - .addBodyReaderImplementation(textBodyReaderImplementation), - ); - - await buildedRouter.exec({ - headers: {}, - host: "", - method: "GET", - origin: "", - url: "/admins", - }); - - expect(spyRoute).not.toHaveBeenCalled(); - expect(spyRoute2).toHaveBeenCalled(); - expect(spyNotfound).not.toHaveBeenCalled(); - }); });