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 Translate 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. The bindings take terminology/parallel-data/job identifiers
14 * at runtime (not as bound resources), so grants are on `Resource: ["*"]`.
15 */
16
17/**
18 * Build the impl Effect for a Translate operation with no bound resource
19 * (real-time translation, terminology/parallel-data reads, job
20 * describe/list/stop).
21 */
22export const makeTranslateHttpBinding = <I, A, E, R>(options: {
23 /** Fully-qualified binding tag, e.g. `AWS.Translate.TranslateText`. */
24 tag: string;
25 /** The distilled operation, invoked with the caller's request as-is. */
26 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
27 /** IAM actions granted on `*`. */
28 actions: readonly string[];
29}) =>
30 Effect.gen(function* () {
31 const op = yield* options.operation;
32
33 return Effect.fn(function* () {
34 if (!globalThis.__ALCHEMY_RUNTIME__) {
35 const host = yield* Binding.Host;
36 if (isBindingHost(host)) {
37 yield* host.bind`Allow(${host}, ${options.tag}())`({
38 policyStatements: [
39 {
40 Effect: "Allow",
41 Action: [...options.actions],
42 Resource: ["*"],
43 },
44 ],
45 });
46 }
47 }
48 return Effect.fn(options.tag)(function* (request?: I) {
49 return yield* op((request ?? {}) as I);
50 });
51 });
52 });
53
54/**
55 * Build the impl Effect for `StartTextTranslationJob`: the binding is
56 * constructed with the **data-access role** (the IAM role Amazon Translate
57 * assumes to read input documents from S3 and write results; its trust
58 * policy must allow `translate.amazonaws.com`). The role's ARN is injected
59 * as `DataAccessRoleArn` on every runtime request, and the deploy-time half
60 * grants `actions` on `*` plus `iam:PassRole` on the role — without the
61 * PassRole grant, the start call fails only at runtime with an AccessDenied.
62 * `ClientToken` is optional on the runtime request (the AWS SDK also
63 * auto-generates it) — a UUID is generated per call when omitted.
64 */
65export const makeTranslateStartJobHttpBinding = <
66 I extends { DataAccessRoleArn: string; ClientToken: string },
67 A,
68 E,
69 R,
70>(options: {
71 /** Fully-qualified binding tag, e.g. `AWS.Translate.StartTextTranslationJob`. */
72 tag: string;
73 /** The distilled operation; `DataAccessRoleArn` is injected from the role. */
74 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
75 /** IAM actions granted on `*`. */
76 actions: readonly string[];
77}) =>
78 Effect.gen(function* () {
79 const op = yield* options.operation;
80
81 return Effect.fn(function* <Rl extends Role>(dataAccessRole: Rl) {
82 const RoleArn = yield* dataAccessRole.roleArn;
83 if (!globalThis.__ALCHEMY_RUNTIME__) {
84 const host = yield* Binding.Host;
85 if (isBindingHost(host)) {
86 yield* host.bind`Allow(${host}, ${options.tag}(${dataAccessRole}))`({
87 policyStatements: [
88 {
89 Effect: "Allow",
90 Action: [...options.actions],
91 Resource: ["*"],
92 },
93 // CRITICAL: without iam:PassRole on the data-access role, the
94 // start call fails only at runtime with an AccessDenied.
95 {
96 Effect: "Allow",
97 Action: ["iam:PassRole"],
98 Resource: [Output.interpolate`${dataAccessRole.roleArn}`],
99 Condition: {
100 StringEquals: {
101 "iam:PassedToService": "translate.amazonaws.com",
102 },
103 },
104 },
105 ],
106 });
107 }
108 }
109 return Effect.fn(`${options.tag}(${dataAccessRole.LogicalId})`)(
110 function* (
111 request: Omit<I, "DataAccessRoleArn" | "ClientToken"> & {
112 DataAccessRoleArn?: string;
113 ClientToken?: string;
114 },
115 ) {
116 return yield* op({
117 ...request,
118 DataAccessRoleArn: request.DataAccessRoleArn ?? (yield* RoleArn),
119 ClientToken:
120 request.ClientToken ??
121 (yield* Effect.sync(() => crypto.randomUUID())),
122 } as I);
123 },
124 );
125 });
126 });