Skip to content

Commit b1265ee

Browse files
feat: add renaming mechanism for EnvVarProvider (#75)
Signed-off-by: Matteo Joliveau <[email protected]>
1 parent cbaa8db commit b1265ee

File tree

2 files changed

+104
-8
lines changed

2 files changed

+104
-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: 93 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
EvaluationContext, EvaluationError, EvaluationErrorCode, EvaluationReason, EvaluationResult,
@@ -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
@@ -73,7 +83,7 @@ impl FeatureProvider for EnvVarProvider {
7383
flag_key: &str,
7484
evaluation_context: &EvaluationContext,
7585
) -> EvaluationResult<ResolutionDetails<bool>> {
76-
return evaluate_environment_variable(flag_key, evaluation_context);
86+
return evaluate_environment_variable(&self.rename, flag_key, evaluation_context);
7787
}
7888

7989
/// The 64-bit signed integer type.
@@ -97,7 +107,7 @@ impl FeatureProvider for EnvVarProvider {
97107
flag_key: &str,
98108
evaluation_context: &EvaluationContext,
99109
) -> EvaluationResult<ResolutionDetails<i64>> {
100-
return evaluate_environment_variable(flag_key, evaluation_context);
110+
return evaluate_environment_variable(&self.rename, flag_key, evaluation_context);
101111
}
102112

103113
/// A 64-bit floating point type
@@ -125,7 +135,7 @@ impl FeatureProvider for EnvVarProvider {
125135
flag_key: &str,
126136
evaluation_context: &EvaluationContext,
127137
) -> EvaluationResult<ResolutionDetails<f64>> {
128-
return evaluate_environment_variable(flag_key, evaluation_context);
138+
return evaluate_environment_variable(&self.rename, flag_key, evaluation_context);
129139
}
130140

131141
/// A UTF-8 encoded string.
@@ -152,7 +162,7 @@ impl FeatureProvider for EnvVarProvider {
152162
flag_key: &str,
153163
evaluation_context: &EvaluationContext,
154164
) -> EvaluationResult<ResolutionDetails<String>> {
155-
return evaluate_environment_variable(flag_key, evaluation_context);
165+
return evaluate_environment_variable(&self.rename, flag_key, evaluation_context);
156166
}
157167

158168
/// Structured data, presented however is idiomatic in the implementation language, such as JSON or YAML.
@@ -179,11 +189,13 @@ impl FeatureProvider for EnvVarProvider {
179189
/// assert_eq!(res.unwrap_err().code, EvaluationErrorCode::FlagNotFound);
180190
/// }
181191
/// ```
182-
fn evaluate_environment_variable<T: std::str::FromStr>(
192+
fn evaluate_environment_variable<R: Rename, T: std::str::FromStr>(
193+
rename: &R,
183194
flag_key: &str,
184195
_evaluation_context: &EvaluationContext,
185196
) -> EvaluationResult<ResolutionDetails<T>> {
186-
match std::env::var(flag_key) {
197+
let env_var = rename.rename(flag_key);
198+
match std::env::var(env_var.as_ref()) {
187199
Ok(value) => match value.parse::<T>() {
188200
Ok(parsed_value) => EvaluationResult::Ok(
189201
ResolutionDetails::builder()
@@ -215,6 +227,51 @@ fn error<T>(evaluation_error_code: EvaluationErrorCode) -> EvaluationResult<T> {
215227
.build())
216228
}
217229

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

@@ -237,4 +294,32 @@ mod tests {
237294
assert!(provider.resolve_string_value("", &context).await.is_err());
238295
assert!(provider.resolve_struct_value("", &context).await.is_err());
239296
}
297+
298+
#[test]
299+
fn noop_rename_does_nothing() {
300+
let flag_key = "test-key";
301+
assert_eq!(NoopRename.rename(flag_key), flag_key);
302+
}
303+
304+
fn underscore(flag_key: &str) -> Cow<'_, str> {
305+
flag_key.replace("-", "_").to_uppercase().into()
306+
}
307+
308+
#[tokio::test]
309+
async fn resolves_with_a_custom_rename() {
310+
let provider = EnvVarProvider::new(underscore);
311+
let context = EvaluationContext::default();
312+
313+
unsafe {
314+
std::env::set_var("HELLO_WORLD", "true");
315+
}
316+
317+
assert!(
318+
provider
319+
.resolve_bool_value("hello-world", &context)
320+
.await
321+
.unwrap()
322+
.value
323+
);
324+
}
240325
}

0 commit comments

Comments
 (0)