1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//! Feature-flag capability trait and types.
//!
//! A `FeatureFlagPlugin` answers the question *"what does flag `X` evaluate
//! to for this caller, right now?"*. Backends fall into two broad groups:
//!
//! 1. **Local** (`@bext/flags-static`) — flag definitions live in a config
//! file loaded at construction time; evaluation is a pure lookup.
//! 2. **Remote** (`@bext/flags-openfeature`, `@bext/flags-unleash`,
//! `@bext/flags-launchdarkly`) — flag state is fetched from an external
//! provider; `refresh` pulls a fresh snapshot.
//!
//! The trait is deliberately sync to match the rest of `bext-plugin-api`.
//! Backends that need network I/O (OpenFeature providers, LaunchDarkly SDK)
//! either use a blocking client or block on a runtime handle the same way
//! the JWKS fetcher in the JWT middleware does — plugins cannot expose
//! native async across the WASM boundary, so the host-facing shape stays
//! sync.
//!
//! `FlagValue` is kept small and typed so that the host-function shims
//! (`flags.bool`, `flags.string`, `flags.int`, `flags.json`) can return
//! the same data without reaching back into the plugin for re-decoding.
//! JSON-shaped values are carried as a JSON-encoded `String` — this keeps
//! the ABI flat and matches the way `session.rs` carries session data and
//! the way `lifecycle.rs` carries event payloads.
use HashMap;
/// A single flag value. Four concrete shapes cover every flag provider the
/// plan lists (OpenFeature, Unleash, LaunchDarkly, Statsig) without making
/// any of them special.
///
/// `Json` carries a JSON-encoded string rather than a `serde_json::Value`
/// so the whole enum round-trips through the WASM ABI as plain bytes. The
/// caller parses the string in its own code if it needs structured access.
/// Evaluation context handed to a flag provider on every call.
///
/// Pure data, no framework types — matches the `AuthRequestContext` and
/// `RequestContext` conventions so the shape travels across the sandbox
/// boundary. `attributes` is the same escape-hatch map as
/// `AuthUser::attributes`: arbitrary provider-specific targeting inputs
/// (country, device class, plan tier, cohort) land here as flat string
/// pairs so the trait itself never grows vendor-specific fields.
/// A feature-flag provider.
///
/// The runtime holds one instance per configured backend and dispatches
/// `flags.bool` / `flags.string` / `flags.int` / `flags.json` host calls
/// through it. `evaluate` is the core: callers pass a flag key and a
/// context, the provider returns the current value.
///
/// `evaluate_batch` exists for the common "render-time prefetch" pattern
/// where a page needs to resolve a dozen flags before it can render. A
/// default implementation walks the keys and calls `evaluate` one at a
/// time — backends with native batch APIs (LaunchDarkly, OpenFeature's
/// `ProviderEvaluation` list) override for efficiency.
///
/// `refresh` exists for backends that cache flag state (virtually all
/// remote providers). Local providers like `@bext/flags-static` override
/// it to re-read the config file off disk; on-change detection is out of
/// scope for the trait — the runtime schedules `refresh` itself.