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}