1import * as Lambda from "@/AWS/Lambda";
2import * as ServiceQuotas from "@/AWS/ServiceQuotas";
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 ServiceQuotasTestFunction extends Lambda.Function<Lambda.Function>()(
13 "ServiceQuotasTestFunction",
14) {}
15
16export default ServiceQuotasTestFunction.make(
17 {
18 main,
19 functionUrl: true,
20 timeout: Duration.seconds(30),
21 },
22 Effect.gen(function* () {
23 const getServiceQuota = yield* ServiceQuotas.GetServiceQuota();
24 const getAWSDefaultServiceQuota =
25 yield* ServiceQuotas.GetAWSDefaultServiceQuota();
26 const listServices = yield* ServiceQuotas.ListServices();
27 const listServiceQuotas = yield* ServiceQuotas.ListServiceQuotas();
28 const listRequestedServiceQuotaChangeHistoryByQuota =
29 yield* ServiceQuotas.ListRequestedServiceQuotaChangeHistoryByQuota();
30 const requestServiceQuotaIncrease =
31 yield* ServiceQuotas.RequestServiceQuotaIncrease();
32
33 return {
34 fetch: Effect.gen(function* () {
35 const request = yield* HttpServerRequest;
36 const url = new URL(request.originalUrl);
37 const pathname = url.pathname;
38
39 // Cheap readiness route — no Service Quotas call.
40 if (request.method === "GET" && pathname === "/ping") {
41 return yield* HttpServerResponse.json({ ok: true });
42 }
43
44 if (request.method === "GET" && pathname === "/quota") {
45 const serviceCode = url.searchParams.get("service");
46 const quotaCode = url.searchParams.get("quota");
47 if (serviceCode === null || quotaCode === null) {
48 return yield* HttpServerResponse.json(
49 { error: "service and quota query params are required" },
50 { status: 400 },
51 );
52 }
53 return yield* getServiceQuota({
54 ServiceCode: serviceCode,
55 QuotaCode: quotaCode,
56 }).pipe(
57 Effect.flatMap((result) =>
58 HttpServerResponse.json({
59 quotaCode: result.Quota?.QuotaCode,
60 quotaName: result.Quota?.QuotaName,
61 value: result.Quota?.Value,
62 adjustable: result.Quota?.Adjustable,
63 }),
64 ),
65 // The typed not-found tag round-trips as a 404 so the test can
66 // assert the binding surfaces distilled's typed error union.
67 Effect.catchTag("NoSuchResourceException", () =>
68 HttpServerResponse.json(
69 { tag: "NoSuchResourceException" },
70 { status: 404 },
71 ),
72 ),
73 );
74 }
75
76 if (request.method === "GET" && pathname === "/default-quota") {
77 const serviceCode = url.searchParams.get("service");
78 const quotaCode = url.searchParams.get("quota");
79 if (serviceCode === null || quotaCode === null) {
80 return yield* HttpServerResponse.json(
81 { error: "service and quota query params are required" },
82 { status: 400 },
83 );
84 }
85 return yield* getAWSDefaultServiceQuota({
86 ServiceCode: serviceCode,
87 QuotaCode: quotaCode,
88 }).pipe(
89 Effect.flatMap((result) =>
90 HttpServerResponse.json({
91 quotaCode: result.Quota?.QuotaCode,
92 value: result.Quota?.Value,
93 }),
94 ),
95 Effect.catchTag("NoSuchResourceException", () =>
96 HttpServerResponse.json(
97 { tag: "NoSuchResourceException" },
98 { status: 404 },
99 ),
100 ),
101 );
102 }
103
104 if (request.method === "GET" && pathname === "/services") {
105 const result = yield* listServices({ MaxResults: 20 });
106 return yield* HttpServerResponse.json({
107 serviceCodes: (result.Services ?? []).map((s) => s.ServiceCode),
108 });
109 }
110
111 if (request.method === "GET" && pathname === "/quotas") {
112 const serviceCode = url.searchParams.get("service");
113 if (serviceCode === null) {
114 return yield* HttpServerResponse.json(
115 { error: "service query param is required" },
116 { status: 400 },
117 );
118 }
119 return yield* listServiceQuotas({
120 ServiceCode: serviceCode,
121 MaxResults: 20,
122 }).pipe(
123 Effect.flatMap((result) =>
124 HttpServerResponse.json({
125 quotaCodes: (result.Quotas ?? []).map((q) => q.QuotaCode),
126 }),
127 ),
128 Effect.catchTag("NoSuchResourceException", () =>
129 HttpServerResponse.json(
130 { tag: "NoSuchResourceException" },
131 { status: 404 },
132 ),
133 ),
134 );
135 }
136
137 if (request.method === "GET" && pathname === "/history") {
138 const serviceCode = url.searchParams.get("service");
139 const quotaCode = url.searchParams.get("quota");
140 if (serviceCode === null || quotaCode === null) {
141 return yield* HttpServerResponse.json(
142 { error: "service and quota query params are required" },
143 { status: 400 },
144 );
145 }
146 return yield* listRequestedServiceQuotaChangeHistoryByQuota({
147 ServiceCode: serviceCode,
148 QuotaCode: quotaCode,
149 }).pipe(
150 Effect.flatMap((result) =>
151 HttpServerResponse.json({
152 count: (result.RequestedQuotas ?? []).length,
153 }),
154 ),
155 Effect.catchTag("NoSuchResourceException", () =>
156 HttpServerResponse.json(
157 { tag: "NoSuchResourceException" },
158 { status: 404 },
159 ),
160 ),
161 );
162 }
163
164 // Exercises the write-path IAM grant without mutating the account:
165 // the bogus quota code fails with the typed NoSuchResourceException
166 // BEFORE any request is submitted. An AccessDenied would surface as
167 // a 500 (die) instead.
168 if (request.method === "POST" && pathname === "/request-increase") {
169 const serviceCode = url.searchParams.get("service");
170 const quotaCode = url.searchParams.get("quota");
171 if (serviceCode === null || quotaCode === null) {
172 return yield* HttpServerResponse.json(
173 { error: "service and quota query params are required" },
174 { status: 400 },
175 );
176 }
177 return yield* requestServiceQuotaIncrease({
178 ServiceCode: serviceCode,
179 QuotaCode: quotaCode,
180 DesiredValue: 1,
181 }).pipe(
182 Effect.flatMap((result) =>
183 HttpServerResponse.json({
184 requestId: result.RequestedQuota?.Id,
185 status: result.RequestedQuota?.Status,
186 }),
187 ),
188 Effect.catchTag("NoSuchResourceException", () =>
189 HttpServerResponse.json(
190 { tag: "NoSuchResourceException" },
191 { status: 404 },
192 ),
193 ),
194 );
195 }
196
197 return yield* HttpServerResponse.json(
198 { error: "Not found", method: request.method, pathname },
199 { status: 404 },
200 );
201 }).pipe(Effect.orDie),
202 };
203 }).pipe(
204 Effect.provide(
205 Layer.mergeAll(
206 ServiceQuotas.GetServiceQuotaHttp,
207 ServiceQuotas.GetAWSDefaultServiceQuotaHttp,
208 ServiceQuotas.ListServicesHttp,
209 ServiceQuotas.ListServiceQuotasHttp,
210 ServiceQuotas.ListRequestedServiceQuotaChangeHistoryByQuotaHttp,
211 ServiceQuotas.RequestServiceQuotaIncreaseHttp,
212 ),
213 ),
214 ),
215);