open_feature_env_var/
lib.rs

1use async_trait::async_trait;
2use open_feature::{
3    provider::{FeatureProvider, ProviderMetadata, ResolutionDetails},
4    EvaluationContext, EvaluationError, EvaluationErrorCode, EvaluationReason, EvaluationResult,
5    StructValue,
6};
7/// Environment Variables Provider Metadata
8const METADATA: &str = "Environment Variables Provider";
9
10/// Environment Variables Provider
11///
12/// This provider resolves feature flags from environment variables.
13/// The provider supports the following types:
14/// - Int
15/// - Float
16/// - String
17/// - Bool
18/// - Struct (not supported)
19///
20/// The provider will return [`EvaluationResult::Err(EvaluationError)`] if the flag is not found or if the value is not of the expected type.
21#[derive(Debug)]
22pub struct EnvVarProvider {
23    metadata: ProviderMetadata,
24}
25
26/// Default implementation for the Environment Variables Provider
27impl Default for EnvVarProvider {
28    fn default() -> Self {
29        Self {
30            metadata: ProviderMetadata::new(METADATA),
31        }
32    }
33}
34
35/// Implementation of the FeatureProvider trait for the Environment Variables Provider
36#[async_trait]
37impl FeatureProvider for EnvVarProvider {
38    /// Returns the provider metadata
39    /// # Example
40    /// ```rust
41    /// #[tokio::test]
42    /// async fn test_metadata() {
43    ///    let provider = EnvVarProvider::default();
44    ///   assert_eq!(provider.metadata().name, "Environment Variables Provider");
45    /// }
46    /// ```
47    fn metadata(&self) -> &ProviderMetadata {
48        &self.metadata
49    }
50
51    /// A logical true or false, as represented idiomatically in the implementation languages.
52    ///
53    /// # Example
54    /// ```rust
55    /// #[tokio::test]
56    /// async fn test_resolve_string_value() {
57    ///     let provider = EnvVarProvider::default();
58    ///     let flag_key = "TEST_ENV_VAR";
59    ///     let value = "false";
60    ///     std::env::set_var(flag_key, value);
61    ///
62    ///     let res = provider
63    ///         .resolve_string_value(flag_key, &EvaluationContext::default())
64    ///         .await;
65    ///     assert!(res.is_ok());
66    ///     assert_eq!(res.unwrap().value, value);
67    /// }
68    /// ```
69    async fn resolve_bool_value(
70        &self,
71        flag_key: &str,
72        evaluation_context: &EvaluationContext,
73    ) -> EvaluationResult<ResolutionDetails<bool>> {
74        return evaluate_environment_variable(flag_key, evaluation_context);
75    }
76
77    /// The 64-bit signed integer type.
78    /// # Example
79    /// ```rust
80    /// #[tokio::test]
81    /// async fn test_resolve_int_value() {
82    ///     let flag_key = "TEST_INT_ENV_VAR";
83    ///     let flag_value = i64::MAX.to_string();
84    ///     let provider = EnvVarProvider::default();
85    ///     std::env::set_var(flag_key, &flag_value);
86    ///     let result = provider.resolve_int_value(flag_key, &EvaluationContext::default()).await;
87    ///     assert!(result.is_ok());
88    ///     assert_eq!(result.unwrap().value, flag_value.parse::<i64>().unwrap());
89    /// }
90    /// ```
91    async fn resolve_int_value(
92        &self,
93        flag_key: &str,
94        evaluation_context: &EvaluationContext,
95    ) -> EvaluationResult<ResolutionDetails<i64>> {
96        return evaluate_environment_variable(flag_key, evaluation_context);
97    }
98
99    /// A 64-bit floating point type
100    ///
101    /// # Example
102    /// ```rust
103    /// #[tokio::test]
104    /// async fn test_resolve_float_value() {
105    ///     let flag_key = "TEST_FLOAT_ENV_VAR";
106    ///     let flag_value = std::f64::consts::PI.to_string();
107    ///     let provider = EnvVarProvider::default();
108    ///
109    ///     std::env::set_var(flag_key, &flag_value);
110    ///
111    ///     let result = provider
112    ///         .resolve_float_value(flag_key, &EvaluationContext::default())
113    ///         .await;
114    ///     assert!(result.is_ok());
115    ///     assert_eq!(result.unwrap().value, flag_value.parse::<f64>().unwrap());
116    /// }
117    /// ```
118    async fn resolve_float_value(
119        &self,
120        flag_key: &str,
121        evaluation_context: &EvaluationContext,
122    ) -> EvaluationResult<ResolutionDetails<f64>> {
123        return evaluate_environment_variable(flag_key, evaluation_context);
124    }
125
126    /// A UTF-8 encoded string.
127    /// # Example
128    /// ```rust
129    /// #[tokio::test]
130    /// async fn test_resolve_string_value() {
131    ///     let provider = EnvVarProvider::default();
132    ///     let flag_key = "TEST_ENV_VAR";
133    ///     let value = "flag_value";
134    ///     std::env::set_var(flag_key, value);
135    ///
136    ///     let res = provider
137    ///         .resolve_string_value(flag_key, &EvaluationContext::default())
138    ///         .await;
139    ///     assert!(res.is_ok());
140    ///     assert_eq!(res.unwrap().value, value);
141    /// }
142    /// ```
143    async fn resolve_string_value(
144        &self,
145        flag_key: &str,
146        evaluation_context: &EvaluationContext,
147    ) -> EvaluationResult<ResolutionDetails<String>> {
148        return evaluate_environment_variable(flag_key, evaluation_context);
149    }
150
151    /// Structured data, presented however is idiomatic in the implementation language, such as JSON or YAML.
152    async fn resolve_struct_value(
153        &self,
154        _flag_key: &str,
155        _evaluation_context: &EvaluationContext,
156    ) -> EvaluationResult<ResolutionDetails<StructValue>> {
157        return error(EvaluationErrorCode::General(
158            "Structs are not supported".to_string(),
159        ));
160    }
161}
162
163/// Helper function to evaluate the environment variable
164/// # Example
165/// ```rust
166/// #[tokio::test]
167/// async fn test_evaluate_environment_variable() {
168///    let provider = EnvVarProvider::default();
169///    let flag_key = "TEST_ENV_VAR_NOT_FOUND";
170/// let res = evaluate_environment_variable(flag_key, &EvaluationContext::default());
171/// assert!(res.is_err());
172/// assert_eq!(res.unwrap_err().code, EvaluationErrorCode::FlagNotFound);
173/// }
174/// ```
175fn evaluate_environment_variable<T: std::str::FromStr>(
176    flag_key: &str,
177    _evaluation_context: &EvaluationContext,
178) -> EvaluationResult<ResolutionDetails<T>> {
179    match std::env::var(flag_key) {
180        Ok(value) => match value.parse::<T>() {
181            Ok(parsed_value) => EvaluationResult::Ok(
182                ResolutionDetails::builder()
183                    .value(parsed_value)
184                    .reason(EvaluationReason::Static)
185                    .build(),
186            ),
187            Err(_) => error(EvaluationErrorCode::TypeMismatch),
188        },
189        Err(_) => error(EvaluationErrorCode::FlagNotFound),
190    }
191}
192/// Error helper function to return an [`EvaluationResult`] with an [`EvaluationError`]
193/// # Example
194/// ```rust
195/// #[tokio::test]
196/// async fn test_error() {
197///     let provider = EnvVarProvider::default();
198///     let flag_key = "TEST_ENV_VAR_NOT_FOUND";
199///     let res = provider.resolve_string_value(flag_key, &EvaluationContext::default()).await;
200///     assert!(res.is_err());
201///     assert_eq!(res.unwrap_err().code, EvaluationErrorCode::FlagNotFound);
202/// }
203/// ```
204fn error<T>(evaluation_error_code: EvaluationErrorCode) -> EvaluationResult<T> {
205    Err(EvaluationError::builder()
206        .message("Error evaluating environment variable")
207        .code(evaluation_error_code)
208        .build())
209}
210
211#[cfg(test)]
212mod tests {
213
214    use super::*;
215
216    #[test]
217    fn test_metadata() {
218        let provider = EnvVarProvider::default();
219        assert_eq!(provider.metadata().name, "Environment Variables Provider");
220    }
221
222    #[tokio::test]
223    async fn resolve_err_values() {
224        let provider = EnvVarProvider::default();
225        let context = EvaluationContext::default();
226
227        assert!(provider.resolve_bool_value("", &context).await.is_err());
228        assert!(provider.resolve_int_value("", &context).await.is_err());
229        assert!(provider.resolve_float_value("", &context).await.is_err());
230        assert!(provider.resolve_string_value("", &context).await.is_err());
231        assert!(provider.resolve_struct_value("", &context).await.is_err());
232    }
233}