1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import type { PolicyStatement } from "../IAM/Policy.ts";
4import { isBindingHost } from "../Lambda/Function.ts";
5import type { Group } from "./Group.ts";
6
7/**
8 * Shared scaffolding for the AWS Resource Groups HTTP bindings.
9 *
10 * NOT exported from `index.ts` — every thin `{Op}Http.ts` in this service is
11 * a `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the two builders
12 * below. Everything except the operation, the IAM action list, and (for
13 * group-scoped operations) the injected group identifier is boilerplate.
14 */
15
16/**
17 * Build the impl Effect for a Resource Groups operation scoped to a
18 * {@link Group}: the deploy-time half grants `actions` on the bound group's
19 * ARN (plus any `supportingActions` on `*` — e.g. the Resource Groups
20 * Tagging API / CloudFormation read-through permissions that member
21 * enumeration fans out to), and the runtime half injects the group's name
22 * into every request as `Group`.
23 */
24export const makeResourceGroupsGroupHttpBinding = <
25 I extends { Group?: string },
26 A,
27 E,
28 R,
29>(options: {
30 /** Fully-qualified binding tag, e.g. `AWS.ResourceGroups.GroupResources`. */
31 tag: string;
32 /** The distilled operation. */
33 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
34 /** IAM actions granted on the group ARN. */
35 actions: readonly string[];
36 /**
37 * IAM actions granted on `*` that the operation fans out to — member
38 * resources are arbitrary ARNs unknowable at deploy time (e.g.
39 * `tag:GetResources` for `ListGroupResources`).
40 */
41 supportingActions?: readonly string[];
42}) =>
43 Effect.gen(function* () {
44 const op = yield* options.operation;
45
46 return Effect.fn(function* (group: Group) {
47 const GroupName = yield* group.groupName;
48 if (!globalThis.__ALCHEMY_RUNTIME__) {
49 const host = yield* Binding.Host;
50 if (isBindingHost(host)) {
51 const supporting: PolicyStatement[] =
52 options.supportingActions !== undefined
53 ? [
54 {
55 Effect: "Allow",
56 Action: [...options.supportingActions],
57 // Member resources are arbitrary ARNs chosen per request.
58 Resource: ["*"],
59 },
60 ]
61 : [];
62 yield* host.bind`Allow(${host}, ${options.tag}(${group}))`({
63 policyStatements: [
64 {
65 Effect: "Allow",
66 Action: [...options.actions],
67 Resource: [group.groupArn],
68 },
69 ...supporting,
70 ],
71 });
72 }
73 }
74 return Effect.fn(`${options.tag}(${group.LogicalId})`)(function* (
75 request?: Omit<I, "Group" | "GroupName">,
76 ) {
77 const groupName = yield* GroupName;
78 return yield* op({ ...request, Group: groupName } as I);
79 });
80 });
81 });
82
83/**
84 * Build the impl Effect for an account-level Resource Groups operation
85 * (search, account settings, tag-sync task audit). The deploy-time half
86 * grants `actions` on `*` — the targets (queries, task ARNs) are chosen per
87 * request at runtime and unknowable at deploy time.
88 */
89export const makeResourceGroupsAccountHttpBinding = <I, A, E, R>(options: {
90 /** Fully-qualified binding tag, e.g. `AWS.ResourceGroups.SearchResources`. */
91 tag: string;
92 /** The distilled operation, invoked with the caller's request as-is. */
93 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
94 /** IAM actions granted on `*`. */
95 actions: readonly string[];
96}) =>
97 Effect.gen(function* () {
98 const op = yield* options.operation;
99
100 return Effect.fn(function* () {
101 if (!globalThis.__ALCHEMY_RUNTIME__) {
102 const host = yield* Binding.Host;
103 if (isBindingHost(host)) {
104 yield* host.bind`Allow(${host}, ${options.tag}())`({
105 policyStatements: [
106 {
107 Effect: "Allow",
108 Action: [...options.actions],
109 Resource: ["*"],
110 },
111 ],
112 });
113 }
114 }
115 return Effect.fn(options.tag)(function* (request?: I) {
116 return yield* op((request ?? {}) as I);
117 });
118 });
119 });