Skip to content
This repository was archived by the owner on Jul 1, 2024. It is now read-only.

Commit 00c2435

Browse files
committed
sdk: memoize OPA fallback decision
Signed-off-by: Stephan Renatus <[email protected]>
1 parent bc15388 commit 00c2435

File tree

1 file changed

+65
-50
lines changed

1 file changed

+65
-50
lines changed

src/opaclient.ts

+65-50
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export interface BatchRequestOptions<Res> extends RequestOptions<Res> {
6161
*/
6262
export class OPAClient {
6363
private opa: Opa;
64+
private opaFallback: boolean = false;
6465

6566
/** Create a new `OPA` instance.
6667
* @param serverURL - The OPA URL, e.g. `https://opa.internal.corp:8443/`.
@@ -167,59 +168,30 @@ export class OPAClient {
167168
);
168169
let res: BatchMixedResults | BatchSuccessfulPolicyEvaluation | undefined;
169170

170-
try {
171-
const resp = await this.opa.executeBatchPolicyWithInput(
172-
{ path, requestBody: { inputs: inps } },
173-
opts,
174-
);
175-
176-
res = resp.batchMixedResults || resp.batchSuccessfulPolicyEvaluation;
177-
} catch (err) {
178-
// TODO(sr): memoize fallback
179-
if (
180-
err instanceof SDKError &&
181-
err.httpMeta.response.status == 404 &&
182-
opts?.fallback
183-
) {
184-
// run a sequence of evaluatePolicyWithInput() instead, via Promise.all/Promise.allSettled
185-
let items: [string, ServerError | SuccessfulPolicyResponse][];
186-
const inputs = Object.values(inps);
187-
const keys = Object.keys(inps);
188-
const ps = inputs.map((input) =>
189-
this.opa
190-
.executePolicyWithInput({ path, requestBody: { input } })
191-
.then(({ successfulPolicyResponse: res }) => res),
171+
if (this.opaFallback && opts?.fallback) {
172+
// memoized fallback: we have hit a 404 here before
173+
const responses = await this.fallbackBatch(path, inps, opts);
174+
res = { responses };
175+
} else {
176+
try {
177+
const resp = await this.opa.executeBatchPolicyWithInput(
178+
{ path, requestBody: { inputs: inps } },
179+
opts,
192180
);
193-
if (opts?.rejectMixed) {
194-
items = await Promise.all(ps).then((results) =>
195-
results.map((result, i) => {
196-
if (!result) throw `no result in API response`;
197-
return [
198-
keys[i] as string, // can't be undefined
199-
result,
200-
];
201-
}),
202-
);
181+
182+
res = resp.batchMixedResults || resp.batchSuccessfulPolicyEvaluation;
183+
} catch (err) {
184+
if (
185+
err instanceof SDKError &&
186+
err.httpMeta.response.status == 404 &&
187+
opts?.fallback
188+
) {
189+
this.opaFallback = true;
190+
const responses = await this.fallbackBatch(path, inps, opts);
191+
res = { responses };
203192
} else {
204-
const settled = await Promise.allSettled(ps).then((results) => {
205-
return results.map((res, i) => {
206-
if (res.status === "rejected") {
207-
return [
208-
keys[i],
209-
{
210-
...(res.reason as ServerError_).data$,
211-
httpStatusCode: "500",
212-
},
213-
] as [string, ServerError];
214-
}
215-
return [keys[i], res.value] as [string, SuccessfulPolicyResponse];
216-
});
217-
});
218-
items = settled;
193+
throw err;
219194
}
220-
res = { responses: Object.fromEntries(items) };
221-
} else {
222-
throw err;
223195
}
224196
}
225197

@@ -231,6 +203,49 @@ export class OPAClient {
231203
}
232204
return Object.fromEntries(entries);
233205
}
206+
207+
// run a sequence of evaluatePolicyWithInput(), via Promise.all/Promise.allSettled
208+
async fallbackBatch<Res>(
209+
path: string,
210+
inputs: { [k: string]: Input },
211+
opts?: BatchRequestOptions<Res>,
212+
): Promise<{ [k: string]: ServerError | SuccessfulPolicyResponse }> {
213+
let items: [string, ServerError | SuccessfulPolicyResponse][];
214+
const keys = Object.keys(inputs);
215+
const ps = Object.values(inputs).map((input) =>
216+
this.opa
217+
.executePolicyWithInput({ path, requestBody: { input } })
218+
.then(({ successfulPolicyResponse: res }) => res),
219+
);
220+
if (opts?.rejectMixed) {
221+
items = await Promise.all(ps).then((results) =>
222+
results.map((result, i) => {
223+
if (!result) throw `no result in API response`;
224+
return [
225+
keys[i] as string, // can't be undefined
226+
result,
227+
];
228+
}),
229+
);
230+
} else {
231+
const settled = await Promise.allSettled(ps).then((results) => {
232+
return results.map((res, i) => {
233+
if (res.status === "rejected") {
234+
return [
235+
keys[i],
236+
{
237+
...(res.reason as ServerError_).data$,
238+
httpStatusCode: "500",
239+
},
240+
] as [string, ServerError];
241+
}
242+
return [keys[i], res.value] as [string, SuccessfulPolicyResponse];
243+
});
244+
});
245+
items = settled;
246+
}
247+
return Object.fromEntries(items);
248+
}
234249
}
235250

236251
function processResult<Res>(

0 commit comments

Comments
 (0)