108 lines
2.9 KiB
TypeScript
108 lines
2.9 KiB
TypeScript
/*!
|
|
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
|
* obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
import { Schema as S } from "@effect/schema";
|
|
import * as Api from "@make-api/api/Api";
|
|
import * as Query from "@make-api/api/Query";
|
|
import * as RequestBody from "@make-api/api/RequestBody";
|
|
import * as Route from "@make-api/api/Route";
|
|
import { Array as A, Effect, Match, Option as O, Record as R, pipe } from "effect";
|
|
|
|
const getUsers = pipe(
|
|
Api.make("GET", "/users"),
|
|
Api.responseBodyJson(200, S.Array(S.Struct({
|
|
userId: S.UUID,
|
|
username: S.String,
|
|
email: S.String,
|
|
}))),
|
|
Api.responseBodyJson(500, S.Struct({
|
|
error: S.String,
|
|
stackTrace: S.optional(S.String, { exact: true }),
|
|
})),
|
|
);
|
|
|
|
type RouteParams<T extends Route.Route.Any> = { readonly [_: string]: unknown }; // TODO
|
|
|
|
const matchesRoute = <T extends Route.Route.Any>(route: T, pathComponents: readonly string[]): boolean => {
|
|
if (route.length !== pathComponents.length) return false;
|
|
return pipe(
|
|
A.zip(route, pathComponents),
|
|
A.every(([token, pathComponent]) => token._tag !== "Literal" || token.literal === pathComponent),
|
|
);
|
|
};
|
|
|
|
const makeRouteParser = <T extends Route.Route.Any>(route: T) => {
|
|
const decoder = pipe(
|
|
route,
|
|
A.filterMap((r) => r._tag === "Param" ? O.some([r.name, r.schema] as const) : O.none()),
|
|
R.fromEntries,
|
|
S.Struct,
|
|
(_) => S.decode(_, { errors: "all" }),
|
|
);
|
|
|
|
return (pathComponents: readonly string[]) => pipe(
|
|
pathComponents,
|
|
A.filterMap((c, i) => {
|
|
const r = route[i];
|
|
return r._tag === "Param" ? O.some([r.name, c] as const) : O.none();
|
|
}),
|
|
R.fromEntries,
|
|
decoder,
|
|
);
|
|
};
|
|
|
|
const makeQueryParser = <T extends Query.Query.Any>(query: T) => {
|
|
const decoder = pipe();
|
|
|
|
return (searchParams: URLSearchParams) => pipe();
|
|
};
|
|
|
|
const makeBodyParser = <T extends RequestBody.RequestBody.Any>(requestBody: T) => {
|
|
const decoder = pipe(
|
|
Match.value<RequestBody.RequestBody.Any>(requestBody),
|
|
Match.tags({
|
|
None: () => {},
|
|
Text: () => {},
|
|
Json: () => {},
|
|
UrlEncoded: () => {},
|
|
Multipart: () => {},
|
|
File: () => {},
|
|
}),
|
|
);
|
|
|
|
return (req: Request) => pipe();
|
|
};
|
|
|
|
const routeParser = makeRouteParser(getUsers.props.route);
|
|
const queryParser = makeQueryParser(getUsers.props.query);
|
|
const bodyParser = makeBodyParser(getUsers.props.request);
|
|
|
|
Bun.serve({
|
|
fetch: (req) => {
|
|
const url = new URL(req.url);
|
|
|
|
const pathname = url.pathname;
|
|
const pathComponents = pathname.replaceAll(/^\/|\/$/g, "").split("/");
|
|
|
|
const searchParams = url.searchParams;
|
|
|
|
if (matchesRoute(getUsers.props.route, pathComponents)) {
|
|
const paramsEffect = routeParser(pathComponents);
|
|
const queryEffect = queryParser(searchParams);
|
|
const bodyEffect = bodyParser(req);
|
|
|
|
const effect = Effect.all({
|
|
params: paramsEffect,
|
|
query: queryEffect,
|
|
body: bodyEffect,
|
|
});
|
|
}
|
|
},
|
|
websocket: {
|
|
message: (ws, message) => {}
|
|
},
|
|
});
|