Skip to content

Commit 7983981

Browse files
feat: add renaming mechanism for EnvVarProvider
EnvVarProvider now supports injecting a custom renaming strategy, which allows users to customize how environment variables are selected by transforming the requested flag key. Signed-off-by: Matteo Joliveau <[email protected]>
1 parent 5eadcd6 commit 7983981

File tree

3 files changed

+103
-8
lines changed

3 files changed

+103
-8
lines changed

crates/env-var/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,17 @@ if is_feature_enabled {
4343
}
4444
```
4545

46+
The environment variable names can be customized by injecting a custom `Rename` implementation:
47+
48+
```rust
49+
/// Transforms env-flag-key to ENV_FLAG_KEY
50+
fn underscore(flag_key: &str) -> Cow<'_, str> {
51+
flag_key.replace("-", "_").to_uppercase().into()
52+
}
53+
54+
let provider = EnvVarProvider::new(underscore);
55+
```
56+
4657
## Testing
4758

4859
Run `cargo test` to execute tests.

crates/env-var/src/lib.rs

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::borrow::Cow;
2+
13
use async_trait::async_trait;
24
use open_feature::{
35
provider::{FeatureProvider, ProviderMetadata, ResolutionDetails},
@@ -19,22 +21,30 @@ const METADATA: &str = "Environment Variables Provider";
1921
///
2022
/// The provider will return [`EvaluationResult::Err(EvaluationError)`] if the flag is not found or if the value is not of the expected type.
2123
#[derive(Debug)]
22-
pub struct EnvVarProvider {
24+
pub struct EnvVarProvider<R = NoopRename> {
2325
metadata: ProviderMetadata,
26+
rename: R,
2427
}
2528

2629
/// Default implementation for the Environment Variables Provider
2730
impl Default for EnvVarProvider {
2831
fn default() -> Self {
32+
Self::new(NoopRename)
33+
}
34+
}
35+
36+
impl<R> EnvVarProvider<R> {
37+
pub fn new(rename: R) -> Self {
2938
Self {
3039
metadata: ProviderMetadata::new(METADATA),
40+
rename,
3141
}
3242
}
3343
}
3444

3545
/// Implementation of the FeatureProvider trait for the Environment Variables Provider
3646
#[async_trait]
37-
impl FeatureProvider for EnvVarProvider {
47+
impl<R: Rename> FeatureProvider for EnvVarProvider<R> {
3848
/// Returns the provider metadata
3949
/// # Example
4050
/// ```rust
@@ -71,7 +81,7 @@ impl FeatureProvider for EnvVarProvider {
7181
flag_key: &str,
7282
evaluation_context: &EvaluationContext,
7383
) -> EvaluationResult<ResolutionDetails<bool>> {
74-
return evaluate_environment_variable(flag_key, evaluation_context);
84+
return evaluate_environment_variable(&self.rename, flag_key, evaluation_context);
7585
}
7686

7787
/// The 64-bit signed integer type.
@@ -93,7 +103,7 @@ impl FeatureProvider for EnvVarProvider {
93103
flag_key: &str,
94104
evaluation_context: &EvaluationContext,
95105
) -> EvaluationResult<ResolutionDetails<i64>> {
96-
return evaluate_environment_variable(flag_key, evaluation_context);
106+
return evaluate_environment_variable(&self.rename, flag_key, evaluation_context);
97107
}
98108

99109
/// A 64-bit floating point type
@@ -120,7 +130,7 @@ impl FeatureProvider for EnvVarProvider {
120130
flag_key: &str,
121131
evaluation_context: &EvaluationContext,
122132
) -> EvaluationResult<ResolutionDetails<f64>> {
123-
return evaluate_environment_variable(flag_key, evaluation_context);
133+
return evaluate_environment_variable(&self.rename, flag_key, evaluation_context);
124134
}
125135

126136
/// A UTF-8 encoded string.
@@ -145,7 +155,7 @@ impl FeatureProvider for EnvVarProvider {
145155
flag_key: &str,
146156
evaluation_context: &EvaluationContext,
147157
) -> EvaluationResult<ResolutionDetails<String>> {
148-
return evaluate_environment_variable(flag_key, evaluation_context);
158+
return evaluate_environment_variable(&self.rename, flag_key, evaluation_context);
149159
}
150160

151161
/// Structured data, presented however is idiomatic in the implementation language, such as JSON or YAML.
@@ -172,11 +182,13 @@ impl FeatureProvider for EnvVarProvider {
172182
/// assert_eq!(res.unwrap_err().code, EvaluationErrorCode::FlagNotFound);
173183
/// }
174184
/// ```
175-
fn evaluate_environment_variable<T: std::str::FromStr>(
185+
fn evaluate_environment_variable<R: Rename, T: std::str::FromStr>(
186+
rename: &R,
176187
flag_key: &str,
177188
_evaluation_context: &EvaluationContext,
178189
) -> EvaluationResult<ResolutionDetails<T>> {
179-
match std::env::var(flag_key) {
190+
let env_var = rename.rename(flag_key);
191+
match std::env::var(env_var.as_ref()) {
180192
Ok(value) => match value.parse::<T>() {
181193
Ok(parsed_value) => EvaluationResult::Ok(
182194
ResolutionDetails::builder()
@@ -208,6 +220,51 @@ fn error<T>(evaluation_error_code: EvaluationErrorCode) -> EvaluationResult<T> {
208220
.build())
209221
}
210222

223+
/// Rename helps converting flag keys to environment variable names
224+
///
225+
/// # Example
226+
/// ```rust
227+
/// fn underscore(flag_key: &str) -> std::borrow::Cow<'_, str> {
228+
/// flag_key.replace("-", "_").to_uppercase().into()
229+
/// }
230+
///
231+
/// #[tokio::test]
232+
/// async fn test_rename() {
233+
/// let flag_key = "test-rename-key";
234+
/// let flag_value = std::f64::consts::PI.to_string();
235+
/// let provider = EnvVarProvider::new(underscore);
236+
///
237+
/// std::env::set_var("TEST_RENAME_KEY", &flag_value);
238+
///
239+
/// let result = provider
240+
/// .resolve_float_value(flag_key, &EvaluationContext::default())
241+
/// .await;
242+
/// assert!(result.is_ok());
243+
/// assert_eq!(result.unwrap().value, flag_value.parse::<f64>().unwrap());
244+
/// }
245+
/// ```
246+
pub trait Rename: Send + Sync + 'static {
247+
fn rename<'a>(&self, flag_key: &'a str) -> Cow<'a, str>;
248+
}
249+
250+
#[derive(Copy, Clone, Default, Debug)]
251+
pub struct NoopRename;
252+
253+
impl Rename for NoopRename {
254+
fn rename<'a>(&self, flag_key: &'a str) -> Cow<'a, str> {
255+
flag_key.into()
256+
}
257+
}
258+
259+
impl<F> Rename for F
260+
where
261+
F: Fn(&str) -> Cow<'_, str> + Send + Sync + 'static,
262+
{
263+
fn rename<'a>(&self, flag_key: &'a str) -> Cow<'a, str> {
264+
(self)(flag_key)
265+
}
266+
}
267+
211268
#[cfg(test)]
212269
mod tests {
213270

@@ -230,4 +287,30 @@ mod tests {
230287
assert!(provider.resolve_string_value("", &context).await.is_err());
231288
assert!(provider.resolve_struct_value("", &context).await.is_err());
232289
}
290+
291+
#[test]
292+
fn noop_rename_does_nothing() {
293+
let flag_key = "test-key";
294+
assert_eq!(NoopRename.rename(flag_key), flag_key);
295+
}
296+
297+
fn underscore(flag_key: &str) -> Cow<'_, str> {
298+
flag_key.replace("-", "_").to_uppercase().into()
299+
}
300+
301+
#[tokio::test]
302+
async fn resolves_with_a_custom_rename() {
303+
let provider = EnvVarProvider::new(underscore);
304+
let context = EvaluationContext::default();
305+
306+
std::env::set_var("HELLO_WORLD", "true");
307+
308+
assert!(
309+
provider
310+
.resolve_bool_value("hello-world", &context)
311+
.await
312+
.unwrap()
313+
.value
314+
);
315+
}
233316
}

crates/env-var/tests/example.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,5 @@ fn setup() {
182182
std::env::set_var("string-flag", "hi");
183183
std::env::set_var("integer-flag", "10");
184184
std::env::set_var("float-flag", "0.5");
185+
std::env::set_var("underscore_boolean_flag", "true");
185186
}

0 commit comments

Comments
 (0)