-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix(flags): False positive when variant rule is an async function #876
base: main
Are you sure you want to change the base?
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ import JsonViewer from "../components/JsonViewer.tsx"; | |
import type { Block, BlockModule, InstanceOf } from "../engine/block.ts"; | ||
import { isDeferred } from "../engine/core/resolver.ts"; | ||
import { type Device, deviceOf } from "../utils/userAgent.ts"; | ||
import { isAwaitable } from "../utils/mod.ts"; | ||
export type Flag = InstanceOf<typeof flagBlock, "#/root/flags">; | ||
|
||
export interface FlagObj<TVariant = unknown> { | ||
|
@@ -64,7 +65,7 @@ const flagBlock: Block<BlockModule<FlagFunc>> = { | |
>(func: { | ||
default: FlagFunc<TConfig>; | ||
}) => | ||
($live: TConfig, { request }: HttpContext) => { | ||
async ($live: TConfig, { request }: HttpContext) => { | ||
const flag = func.default($live); | ||
let device: Device | null = null; | ||
const ctx = { | ||
|
@@ -75,12 +76,33 @@ const flagBlock: Block<BlockModule<FlagFunc>> = { | |
}, | ||
}; | ||
if (isMultivariate(flag)) { | ||
const value = ((flag?.variants ?? []).find((variant) => | ||
typeof variant?.rule === "function" && variant?.rule(ctx) | ||
) as Variant<unknown>)?.value ?? | ||
((flag?.variants ?? [])[flag?.variants?.length - 1] as Variant<unknown>) | ||
?.value; | ||
return isDeferred(value) ? value() : value; | ||
let match: Variant<unknown> | null = null; | ||
|
||
for (const variant of flag.variants || []) { | ||
if (typeof variant?.rule !== "function") { | ||
continue; | ||
} | ||
let ruleResult = variant?.rule(ctx); | ||
// the rule can sometimes be a promise and we need to await it to check if it's truthy or not | ||
if (isAwaitable(ruleResult)) { | ||
try { | ||
ruleResult = await ruleResult; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in the case you provided (when site.ts has an app middleware) this code runs on serial (one await for each matcher) instead of parallel since all matchers will returns a promise, right? Given that, you should instead, parallelize the rule evaluation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not at all, matchers coming from "website" app continue as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean, anyways introducing a middleware will cause all your matchers to be async, so it worth to make parallel instead, wdyt? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. make sense, your proposal is to always use Promise.all(flags.variants.map((variant) => variant.rule(ctx)).find((result) => result) |
||
} catch (_) { | ||
// if the rule throws an error, we don't match this variant | ||
vitoUwu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ruleResult = false; | ||
} | ||
} | ||
if (ruleResult) { | ||
match = variant; | ||
break; | ||
} | ||
} | ||
|
||
if (!match) { | ||
match = flag.variants[flag.variants.length - 1]; | ||
} | ||
|
||
return isDeferred(match?.value) ? match?.value() : match?.value; | ||
} | ||
const matchValue = typeof flag?.matcher === "function" | ||
? flag.matcher(ctx) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will impact tracings as only async functions are included on tracing. This shouldn't be though but we should keep an eye on it.