-
-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Http api #83
Comments
To be more precise, I believe two functionalities could be beneficial, although maybe out of scope for this package (especially the latter). (1) A function similar to HttpApiBuilder.toWebHandler and https://github.com/floydspace/effect-aws/tree/main/packages/lambda that will take an Http api and returns a handler function that takes an (2) It should be possible to take the http api definition and derive a CDK api gateway from it that can be deployed, similar to OpenAPI cdk constructs. |
hi @florianbepunkt |
@floydspace Thanks for the link. This is an interesting proof of concept. I believe the CDK construct is the more difficult part. We have several APIs, where we have an OpenAPI spec that uses entities defined by Effect Schema. This OpenAPI spec file is used to create a rest api CDK construct, similar to what the poc does. The problem, which would also apply to the poc you have linked: Effect produces JSON schema v7 while AWS api gateway can only handle draft4. With slightly more complex schemas you will unfortunately run into problems :/ Therefore we had to use a custom JSON schema generator to create draft4 JSON schemas. I opened an issue here: AMar4enko/effect-http-api-gateway#1 Maybe AMar4enko has came up with a way to solve this. I'm not sure whether using OpenAPI is the right approach. If you define the API in effect, you are already doing the validation there. So while there is probably no harm in double validating requests, it is not necessary. Plus you get the limitations mentioned above. With regards to (1): I believe something around the following lines should work (untested): import { DateTime, Effect, Layer, Schema as S } from "effect";
import {
HttpApi,
HttpApiBuilder,
HttpApiEndpoint,
HttpApiGroup,
HttpApp,
HttpServer,
type HttpRouter,
} from "@effect/platform";
import type { Router } from "@effect/platform/HttpApiBuilder";
import type { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda";
const eventToNativeRequest = (event: APIGatewayProxyEvent): Request => {
const { httpMethod, headers, body, path, queryStringParameters } = event;
// Construct the URL
const protocol = headers["X-Forwarded-Proto"] || "https";
const host = headers["Host"] || "localhost";
const queryString = new URLSearchParams(
(queryStringParameters as Record<string, string>) || {},
).toString();
const url = `${protocol}://${host}${path}${queryString ? `?${queryString}` : ""}`;
// Map headers to Headers object
const requestHeaders = new Headers();
for (const [key, value] of Object.entries(headers || {})) {
if (value) {
requestHeaders.append(key, value);
}
}
// Return the Request object
return new Request(url, {
method: httpMethod,
headers: requestHeaders,
body: body || undefined,
});
};
const fromNativeResponse = async (response: Response): Promise<APIGatewayProxyResult> => {
const headers: { [header: string]: string } = {};
response.headers.forEach((value, key) => {
headers[key] = value;
});
const body = response.bodyUsed ? await response.text() : null;
return {
statusCode: response.status,
headers,
body: body || "",
isBase64Encoded: false, // Assume the body is not Base64 encoded by default
};
};
const makeApiLambda = <LA, LE>(
layer: Layer.Layer<LA | HttpApi.Api | HttpRouter.HttpRouter.DefaultServices, LE>,
options?: {
readonly middleware?: (
httpApp: HttpApp.Default,
) => HttpApp.Default<never, HttpApi.Api | Router | HttpRouter.HttpRouter.DefaultServices>;
readonly memoMap?: Layer.MemoMap;
},
) => {
const { handler } = HttpApiBuilder.toWebHandler(layer, options);
return async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
const request = eventToNativeRequest(event);
const response = await handler(request);
return fromNativeResponse(response);
};
};
// Usage example
class User extends S.Class<User>("User")({
id: S.Number,
name: S.String,
createdAt: S.DateTimeUtc,
}) {}
class UsersApi extends HttpApiGroup.make("users").add(
HttpApiEndpoint.get("findById", "/users/:id")
.addSuccess(User)
.setPath(
S.Struct({
id: S.NumberFromString,
}),
),
) {}
class MyApi extends HttpApi.empty.add(UsersApi) {}
const UsersApiLive: Layer.Layer<HttpApiGroup.ApiGroup<"users">> = HttpApiBuilder.group(
MyApi,
"users",
(handlers) =>
handlers
// the parameters & payload are passed to the handler function.
.handle("findById", ({ path: { id } }) =>
Effect.succeed(
new User({
id,
name: "John Doe",
createdAt: DateTime.unsafeNow(),
}),
),
),
);
const MyApiLive: Layer.Layer<HttpApi.Api> = HttpApiBuilder.api(MyApi).pipe(Layer.provide(UsersApiLive));
const handler = makeApiLambda(Layer.mergeAll(MyApiLive, HttpServer.layerContext)); |
@floydspace I sketched a rough prototype: https://github.com/florianbepunkt/effect-aws-api If you find the time, maybe you can have a look. I'm rather unsure how to approach / generalize this in a way that it helps others. The CDK construct make a lot of assumptions about a use case – some might make sense (lambdalith with http api), other might not. Also there are some open questions I noted in the readme and the code. |
I would love to see a way to use the http api from @effect/platform with a lambda fn fronted by api gateway. Currently we use middy and their http router in our code base for some microservices.
The text was updated successfully, but these errors were encountered: