1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import * as Output from "../../Output.ts";
4import type { Role } from "../IAM/Role.ts";
5import { isBindingHost } from "../Lambda/Function.ts";
6
7/**
8 * Shared scaffolding for AWS Comprehend HTTP bindings.
9 *
10 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
11 * thin `Layer.effect(Cap, make…HttpBinding({ … }))` over one of the two
12 * builders below. Everything except the operation and the IAM action list is
13 * boilerplate: Comprehend's real-time detect APIs and async job APIs have no
14 * resource-level IAM, so every grant is on `*`. The `Start*Job` operations
15 * additionally inject the bound data-access role and a scoped `iam:PassRole`
16 * grant.
17 */
18
19/**
20 * Build the impl Effect for a Comprehend operation with no bound resource
21 * (real-time detection, job describe/list/stop). The deploy-time half grants
22 * `actions` on `*` — Comprehend detection and job IAM actions do not support
23 * resource-level scoping.
24 */
25export const makeComprehendHttpBinding = <I, A, E, R>(options: {
26 /** Fully-qualified binding tag, e.g. `AWS.Comprehend.DetectEntities`. */
27 tag: string;
28 /** The distilled operation, invoked with the caller's request as-is. */
29 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
30 /** IAM actions granted on `*`. */
31 actions: readonly string[];
32}) =>
33 Effect.gen(function* () {
34 const op = yield* options.operation;
35
36 return Effect.fn(function* () {
37 if (!globalThis.__ALCHEMY_RUNTIME__) {
38 const host = yield* Binding.Host;
39 if (isBindingHost(host)) {
40 yield* host.bind`Allow(${host}, ${options.tag}())`({
41 policyStatements: [
42 {
43 Effect: "Allow",
44 Action: [...options.actions],
45 Resource: ["*"],
46 },
47 ],
48 });
49 }
50 }
51 return Effect.fn(options.tag)(function* (request?: I) {
52 return yield* op((request ?? {}) as I);
53 });
54 });
55 });
56
57/**
58 * Build the impl Effect for a `Start*Job` operation: the binding is
59 * constructed with the **data-access role** (the IAM role Amazon Comprehend
60 * assumes to read input documents from S3 and write results; its trust
61 * policy must allow `comprehend.amazonaws.com`). The role's ARN is injected
62 * as `DataAccessRoleArn` on every runtime request, and the deploy-time half
63 * grants `actions` on `*` plus `iam:PassRole` on the role — without the
64 * PassRole grant, `Start*Job` fails only at runtime with an AccessDenied.
65 */
66export const makeComprehendStartJobHttpBinding = <
67 I extends { DataAccessRoleArn: string },
68 A,
69 E,
70 R,
71>(options: {
72 /** Fully-qualified binding tag, e.g. `AWS.Comprehend.StartSentimentDetectionJob`. */
73 tag: string;
74 /** The distilled operation; `DataAccessRoleArn` is injected from the role. */
75 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
76 /** IAM actions granted on `*`. */
77 actions: readonly string[];
78}) =>
79 Effect.gen(function* () {
80 const op = yield* options.operation;
81
82 return Effect.fn(function* <Rl extends Role>(dataAccessRole: Rl) {
83 const RoleArn = yield* dataAccessRole.roleArn;
84 if (!globalThis.__ALCHEMY_RUNTIME__) {
85 const host = yield* Binding.Host;
86 if (isBindingHost(host)) {
87 yield* host.bind`Allow(${host}, ${options.tag}(${dataAccessRole}))`({
88 policyStatements: [
89 {
90 Effect: "Allow",
91 Action: [...options.actions],
92 Resource: ["*"],
93 },
94 // CRITICAL: without iam:PassRole on the data-access role,
95 // Start*Job fails only at runtime with an AccessDenied.
96 {
97 Effect: "Allow",
98 Action: ["iam:PassRole"],
99 Resource: [Output.interpolate`${dataAccessRole.roleArn}`],
100 Condition: {
101 StringEquals: {
102 "iam:PassedToService": "comprehend.amazonaws.com",
103 },
104 },
105 },
106 ],
107 });
108 }
109 }
110 return Effect.fn(`${options.tag}(${dataAccessRole.LogicalId})`)(
111 function* (
112 request: Omit<I, "DataAccessRoleArn"> & {
113 DataAccessRoleArn?: string;
114 },
115 ) {
116 return yield* op({
117 ...request,
118 DataAccessRoleArn: request.DataAccessRoleArn ?? (yield* RoleArn),
119 } as I);
120 },
121 );
122 });
123 });