1import * as Lambda from "@/AWS/Lambda";
2import * as OSIS from "@/AWS/OSIS";
3import * as Duration from "effect/Duration";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6import { HttpServerRequest } from "effect/http/HttpServerRequest";
7import * as HttpServerResponse from "effect/http/HttpServerResponse";
8import path from "pathe";
9
10const main = path.resolve(import.meta.dirname, "handler.ts");
11
12export class OsisTestFunction extends Lambda.Function<Lambda.Function>()(
13 "OsisTestFunction",
14) {}
15
16// A syntactically-valid Data Prepper configuration (http source -> s3 sink).
17// ValidatePipeline is a static check; the role/bucket are never assumed.
18const validConfig = `version: "2"
19log-pipeline:
20 source:
21 http:
22 path: "/logs/ingest"
23 sink:
24 - s3:
25 aws:
26 sts_role_arn: "arn:aws:iam::123456789012:role/osis-validate-fixture"
27 region: "us-west-2"
28 bucket: "osis-validate-fixture"
29 threshold:
30 event_collect_timeout: "60s"
31 codec:
32 ndjson:
33`;
34
35// Structurally broken: the sink references a plugin that does not exist.
36const invalidConfig = `version: "2"
37bad-pipeline:
38 source:
39 http:
40 path: "/logs/ingest"
41 sink:
42 - not-a-real-sink-plugin:
43 some_option: true
44`;
45
46export default OsisTestFunction.make(
47 {
48 main,
49 functionUrl: true,
50 timeout: Duration.seconds(30),
51 },
52 Effect.gen(function* () {
53 // Account-level bindings — no resource argument.
54 const validatePipeline = yield* OSIS.ValidatePipeline();
55 const listPipelineBlueprints = yield* OSIS.ListPipelineBlueprints();
56 const getPipelineBlueprint = yield* OSIS.GetPipelineBlueprint();
57 const listPipelineEndpointConnections =
58 yield* OSIS.ListPipelineEndpointConnections();
59
60 const bound = {
61 validatePipeline,
62 listPipelineBlueprints,
63 getPipelineBlueprint,
64 listPipelineEndpointConnections,
65 };
66
67 // ValidatePipeline surfaces some invalid configs as a typed
68 // ValidationException rather than `isValid: false`; normalize both.
69 const validate = (body: string) =>
70 validatePipeline({ PipelineConfigurationBody: body }).pipe(
71 Effect.map((response) => ({
72 isValid: response.isValid === true,
73 errors: (response.Errors ?? []).flatMap((e) =>
74 e.Message !== undefined ? [e.Message] : [],
75 ),
76 })),
77 Effect.catchTag("ValidationException", (error) =>
78 Effect.succeed({
79 isValid: false,
80 errors: [error.message ?? "ValidationException"],
81 }),
82 ),
83 );
84
85 return {
86 fetch: Effect.gen(function* () {
87 const request = yield* HttpServerRequest;
88 const url = new URL(request.originalUrl);
89 const pathname = url.pathname;
90
91 if (request.method === "GET" && pathname === "/bindings") {
92 return yield* HttpServerResponse.json({
93 bound: Object.keys(bound),
94 });
95 }
96
97 if (request.method === "GET" && pathname === "/validate-good") {
98 return yield* HttpServerResponse.json(yield* validate(validConfig));
99 }
100
101 if (request.method === "GET" && pathname === "/validate-bad") {
102 return yield* HttpServerResponse.json(yield* validate(invalidConfig));
103 }
104
105 if (request.method === "GET" && pathname === "/blueprints") {
106 const { Blueprints } = yield* listPipelineBlueprints();
107 return yield* HttpServerResponse.json({
108 names: (Blueprints ?? []).flatMap((blueprint) =>
109 blueprint.BlueprintName !== undefined
110 ? [blueprint.BlueprintName]
111 : [],
112 ),
113 });
114 }
115
116 if (request.method === "GET" && pathname === "/blueprint") {
117 const name = url.searchParams.get("name") ?? "";
118 const { Blueprint } = yield* getPipelineBlueprint({
119 BlueprintName: name,
120 });
121 return yield* HttpServerResponse.json({
122 name: Blueprint?.BlueprintName,
123 hasBody:
124 typeof Blueprint?.PipelineConfigurationBody === "string" &&
125 Blueprint.PipelineConfigurationBody.length > 0,
126 });
127 }
128
129 if (request.method === "GET" && pathname === "/endpoint-connections") {
130 const { PipelineEndpointConnections } =
131 yield* listPipelineEndpointConnections();
132 return yield* HttpServerResponse.json({
133 count: (PipelineEndpointConnections ?? []).length,
134 });
135 }
136
137 return yield* HttpServerResponse.json(
138 { error: "Not found", method: request.method, pathname },
139 { status: 404 },
140 );
141 }).pipe(Effect.orDie),
142 };
143 }).pipe(
144 Effect.provide(
145 Layer.mergeAll(
146 OSIS.ValidatePipelineHttp,
147 OSIS.ListPipelineBlueprintsHttp,
148 OSIS.GetPipelineBlueprintHttp,
149 OSIS.ListPipelineEndpointConnectionsHttp,
150 ),
151 ),
152 ),
153);