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