open_feature/provider/
no_op_provider.rs

1use async_trait::async_trait;
2
3use crate::{
4    EvaluationContext, EvaluationError, EvaluationErrorCode, EvaluationResult, StructValue,
5};
6
7use super::{FeatureProvider, ProviderMetadata, ProviderStatus, ResolutionDetails};
8
9// ============================================================
10//  NoOpProvider
11// ============================================================
12
13/// The default provider that does nothing.
14///
15/// It always returns [`EvaluationError`] for all the given flag keys.
16#[derive(Debug)]
17pub struct NoOpProvider {
18    metadata: ProviderMetadata,
19}
20
21impl Default for NoOpProvider {
22    fn default() -> Self {
23        Self {
24            metadata: ProviderMetadata::new("No-op Provider"),
25        }
26    }
27}
28
29#[async_trait]
30impl FeatureProvider for NoOpProvider {
31    fn metadata(&self) -> &ProviderMetadata {
32        &self.metadata
33    }
34
35    fn status(&self) -> ProviderStatus {
36        ProviderStatus::NotReady
37    }
38
39    async fn resolve_bool_value(
40        &self,
41        _flag_key: &str,
42        _evaluation_context: &EvaluationContext,
43    ) -> EvaluationResult<ResolutionDetails<bool>> {
44        just_error()
45    }
46
47    async fn resolve_int_value(
48        &self,
49        _flag_key: &str,
50        _evaluation_context: &EvaluationContext,
51    ) -> EvaluationResult<ResolutionDetails<i64>> {
52        just_error()
53    }
54
55    async fn resolve_float_value(
56        &self,
57        _flag_key: &str,
58        _evaluation_context: &EvaluationContext,
59    ) -> EvaluationResult<ResolutionDetails<f64>> {
60        just_error()
61    }
62
63    async fn resolve_string_value(
64        &self,
65        _flag_key: &str,
66        _evaluation_context: &EvaluationContext,
67    ) -> EvaluationResult<ResolutionDetails<String>> {
68        just_error()
69    }
70
71    async fn resolve_struct_value(
72        &self,
73        _flag_key: &str,
74        _evaluation_context: &EvaluationContext,
75    ) -> Result<ResolutionDetails<StructValue>, EvaluationError> {
76        just_error()
77    }
78}
79
80fn just_error<T>() -> EvaluationResult<T> {
81    Err(EvaluationError::builder()
82        .code(EvaluationErrorCode::ProviderNotReady)
83        .message("No-op provider is never ready")
84        .build())
85}
86
87// ============================================================
88//  Tests
89// ============================================================
90
91#[cfg(test)]
92mod tests {
93    use spec::spec;
94
95    use super::*;
96    use crate::{provider::ProviderStatus, *};
97
98    #[spec(
99        number = "2.1.1",
100        text = "The provider interface MUST define a metadata member or accessor, containing a name field or accessor of type string, which identifies the provider implementation."
101    )]
102    #[test]
103    fn metadata_name() {
104        let provider = NoOpProvider::default();
105
106        assert_eq!(provider.metadata().name, "No-op Provider");
107    }
108
109    #[spec(
110        number = "2.2.1",
111        text = "The feature provider interface MUST define methods to resolve flag values, with parameters flag key (string, required), default value (boolean | number | string | structure, required) and evaluation context (optional), which returns a resolution details structure."
112    )]
113    #[spec(
114        number = "2.2.2.1",
115        text = "The feature provider interface MUST define methods for typed flag resolution, including boolean, numeric, string, and structure."
116    )]
117    #[spec(
118        number = "2.2.3",
119        text = "In cases of normal execution, the provider MUST populate the resolution details structure's value field with the resolved flag value."
120    )]
121    #[spec(
122        number = "2.2.4",
123        text = "In cases of normal execution, the provider SHOULD populate the resolution details structure's variant field with a string identifier corresponding to the returned flag value."
124    )]
125    #[spec(
126        number = "2.2.5",
127        text = r###"The provider SHOULD populate the resolution details structure's reason field with "STATIC", "DEFAULT", "TARGETING_MATCH", "SPLIT", "CACHED", "DISABLED", "UNKNOWN", "STALE", "ERROR" or some other string indicating the semantic reason for the returned flag value."###
128    )]
129    #[spec(
130        number = "2.2.6",
131        text = "In cases of normal execution, the provider MUST NOT populate the resolution details structure's error code field, or otherwise must populate it with a null or falsy value."
132    )]
133    #[spec(
134        number = "2.2.9",
135        text = "The provider SHOULD populate the resolution details structure's flag metadata field. "
136    )]
137    #[spec(
138        number = "2.2.10",
139        text = "flag metadata MUST be a structure supporting the definition of arbitrary properties, with keys of type string, and values of type boolean | string | number."
140    )]
141    #[tokio::test]
142    async fn resolve_value() {
143        let provider = NoOpProvider::default();
144        let context = EvaluationContext::default();
145
146        assert!(provider.resolve_bool_value("", &context).await.is_err());
147        assert!(provider.resolve_int_value("", &context).await.is_err());
148        assert!(provider.resolve_float_value("", &context).await.is_err());
149        assert!(provider.resolve_string_value("", &context).await.is_err());
150        assert!(provider.resolve_struct_value("", &context).await.is_err());
151    }
152
153    #[spec(
154        number = "2.2.7",
155        text = "In cases of abnormal execution, the provider MUST indicate an error using the idioms of the implementation language, with an associated error code and optional associated error message."
156    )]
157    #[test]
158    fn error_code_message_provided_checked_by_type_system() {}
159
160    #[spec(
161        number = "2.2.8.1",
162        text = "The resolution details structure SHOULD accept a generic argument (or use an equivalent language feature) which indicates the type of the wrapped value field."
163    )]
164    #[test]
165    fn resolution_details_generic_checked_by_type_system() {}
166
167    #[spec(
168        number = "2.4.1",
169        text = "The provider MAY define an initialize function which accepts the global evaluation context as an argument and performs initialization logic relevant to the provider."
170    )]
171    #[tokio::test]
172    async fn initialize() {
173        let mut provider = NoOpProvider::default();
174
175        provider.initialize(&EvaluationContext::default()).await;
176    }
177
178    #[spec(
179        number = "2.4.2",
180        text = "The provider MAY define a status field/accessor which indicates the readiness of the provider, with possible values NOT_READY, READY, or ERROR."
181    )]
182    #[spec(
183        number = "2.4.3",
184        text = "The provider MUST set its status field/accessor to READY if its initialize function terminates normally."
185    )]
186    #[spec(
187        number = "2.4.4",
188        text = "The provider MUST set its status field to ERROR if its initialize function terminates abnormally."
189    )]
190    #[spec(
191        number = "2.4.5",
192        text = "The provider SHOULD indicate an error if flag resolution is attempted before the provider is ready."
193    )]
194    #[tokio::test]
195    async fn status() {
196        let provider = NoOpProvider::default();
197        assert_eq!(provider.status(), ProviderStatus::NotReady);
198    }
199
200    #[spec(
201        number = "2.5.1",
202        text = "The provider MAY define a mechanism to gracefully shutdown and dispose of resources."
203    )]
204    #[test]
205    fn shutdown_covered_by_drop_trait() {}
206}