Skip to content

Commit 0a63cce

Browse files
committed
feat: support multiple/number expected flag type
1 parent 87523cb commit 0a63cce

File tree

7 files changed

+149
-72
lines changed

7 files changed

+149
-72
lines changed

datadog-ffe-ffi/src/assignment.rs

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33

44
use std::ffi::{c_char, CStr};
55

6+
use datadog_ffe::rules_based as ffe;
67
use datadog_ffe::rules_based::{
78
now, Assignment, AssignmentReason, AssignmentValue, Configuration, EvaluationContext,
8-
EvaluationError, Str, VariationType,
9+
EvaluationError, Str,
910
};
1011

1112
use crate::Handle;
@@ -82,35 +83,47 @@ impl AsRef<Result<Assignment, EvaluationError>> for ResolutionDetails {
8283
}
8384

8485
#[repr(C)]
85-
pub enum FlagType {
86-
Unknown,
86+
pub enum ExpectedFlagType {
8787
String,
8888
Integer,
8989
Float,
9090
Boolean,
9191
Object,
92+
Number,
93+
Any,
9294
}
93-
impl From<FlagType> for Option<VariationType> {
94-
fn from(value: FlagType) -> Self {
95+
impl From<ExpectedFlagType> for ffe::ExpectedFlagType {
96+
fn from(value: ExpectedFlagType) -> ffe::ExpectedFlagType {
9597
match value {
96-
FlagType::Unknown => None,
97-
FlagType::String => Some(VariationType::String),
98-
FlagType::Integer => Some(VariationType::Integer),
99-
FlagType::Float => Some(VariationType::Numeric),
100-
FlagType::Boolean => Some(VariationType::Boolean),
101-
FlagType::Object => Some(VariationType::Json),
98+
ExpectedFlagType::String => ffe::ExpectedFlagType::String,
99+
ExpectedFlagType::Integer => ffe::ExpectedFlagType::Integer,
100+
ExpectedFlagType::Float => ffe::ExpectedFlagType::Float,
101+
ExpectedFlagType::Boolean => ffe::ExpectedFlagType::Boolean,
102+
ExpectedFlagType::Object => ffe::ExpectedFlagType::Object,
103+
ExpectedFlagType::Number => ffe::ExpectedFlagType::Number,
104+
ExpectedFlagType::Any => ffe::ExpectedFlagType::Any,
102105
}
103106
}
104107
}
105108

106-
impl From<VariationType> for FlagType {
107-
fn from(value: VariationType) -> Self {
109+
#[repr(C)]
110+
pub enum FlagType {
111+
Unknown,
112+
String,
113+
Integer,
114+
Float,
115+
Boolean,
116+
Object,
117+
}
118+
119+
impl From<ffe::FlagType> for FlagType {
120+
fn from(value: ffe::FlagType) -> Self {
108121
match value {
109-
VariationType::String => FlagType::String,
110-
VariationType::Integer => FlagType::Integer,
111-
VariationType::Numeric => FlagType::Float,
112-
VariationType::Boolean => FlagType::Boolean,
113-
VariationType::Json => FlagType::Object,
122+
ffe::FlagType::String => FlagType::String,
123+
ffe::FlagType::Integer => FlagType::Integer,
124+
ffe::FlagType::Float => FlagType::Float,
125+
ffe::FlagType::Boolean => FlagType::Boolean,
126+
ffe::FlagType::Object => FlagType::Object,
114127
}
115128
}
116129
}
@@ -153,7 +166,7 @@ pub enum Reason {
153166
pub unsafe extern "C" fn ddog_ffe_get_assignment(
154167
config: Handle<Configuration>,
155168
flag_key: *const c_char,
156-
expected_type: FlagType,
169+
expected_type: ExpectedFlagType,
157170
context: Handle<EvaluationContext>,
158171
) -> Handle<ResolutionDetails> {
159172
if flag_key.is_null() {

datadog-ffe/src/rules_based/error.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use serde::{Deserialize, Serialize};
55

6-
use crate::rules_based::{ufc::VariationType, Str};
6+
use crate::rules_based::{ExpectedFlagType, FlagType, Str};
77

88
/// Enum representing all possible reasons that could result in evaluation returning an error or
99
/// default assignment.
@@ -18,9 +18,9 @@ pub enum EvaluationError {
1818
#[error("invalid flag type (expected: {expected:?}, found: {found:?})")]
1919
TypeMismatch {
2020
/// Expected type of the flag.
21-
expected: VariationType,
21+
expected: ExpectedFlagType,
2222
/// Actual type of the flag.
23-
found: VariationType,
23+
found: FlagType,
2424
},
2525

2626
/// Failed to parse configuration. This should normally never happen and is likely a signal

datadog-ffe/src/rules_based/eval/eval_assignment.rs

Lines changed: 28 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@ use chrono::{DateTime, Utc};
55

66
use crate::rules_based::{
77
error::EvaluationError,
8-
ufc::{
9-
Allocation, Assignment, AssignmentReason, CompiledFlagsConfig, Flag, Shard, Split,
10-
VariationType,
11-
},
12-
Configuration, EvaluationContext, Timestamp,
8+
ufc::{Allocation, Assignment, AssignmentReason, CompiledFlagsConfig, Flag, Shard, Split},
9+
Configuration, EvaluationContext, ExpectedFlagType, Timestamp,
1310
};
1411

1512
/// Evaluate the specified feature flag for the given subject and return assigned variation and
@@ -18,7 +15,7 @@ pub fn get_assignment(
1815
configuration: Option<&Configuration>,
1916
flag_key: &str,
2017
subject: &EvaluationContext,
21-
expected_type: Option<VariationType>,
18+
expected_type: ExpectedFlagType,
2219
now: DateTime<Utc>,
2320
) -> Result<Assignment, EvaluationError> {
2421
let Some(config) = configuration else {
@@ -37,7 +34,7 @@ impl Configuration {
3734
&self,
3835
flag_key: &str,
3936
context: &EvaluationContext,
40-
expected_type: Option<VariationType>,
37+
expected_type: ExpectedFlagType,
4138
now: DateTime<Utc>,
4239
) -> Result<Assignment, EvaluationError> {
4340
let result = self
@@ -72,16 +69,10 @@ impl CompiledFlagsConfig {
7269
&self,
7370
flag_key: &str,
7471
subject: &EvaluationContext,
75-
expected_type: Option<VariationType>,
72+
expected_type: ExpectedFlagType,
7673
now: DateTime<Utc>,
7774
) -> Result<Assignment, EvaluationError> {
78-
let flag = self.get_flag(flag_key)?;
79-
80-
if let Some(ty) = expected_type {
81-
flag.verify_type(ty)?;
82-
}
83-
84-
flag.eval(subject, now)
75+
self.get_flag(flag_key)?.eval(subject, expected_type, now)
8576
}
8677

8778
fn get_flag(&self, flag_key: &str) -> Result<&Flag, EvaluationError> {
@@ -94,22 +85,19 @@ impl CompiledFlagsConfig {
9485
}
9586

9687
impl Flag {
97-
fn verify_type(&self, ty: VariationType) -> Result<(), EvaluationError> {
98-
if self.variation_type == ty {
99-
Ok(())
100-
} else {
101-
Err(EvaluationError::TypeMismatch {
102-
expected: ty,
103-
found: self.variation_type,
104-
})
105-
}
106-
}
107-
10888
fn eval(
10989
&self,
11090
subject: &EvaluationContext,
91+
expected_type: ExpectedFlagType,
11192
now: DateTime<Utc>,
11293
) -> Result<Assignment, EvaluationError> {
94+
if !expected_type.is_compatible(self.variation_type.into()) {
95+
return Err(EvaluationError::TypeMismatch {
96+
expected: expected_type,
97+
found: self.variation_type.into(),
98+
});
99+
}
100+
113101
let Some((allocation, (split, reason))) = self.allocations.iter().find_map(|allocation| {
114102
let result = allocation.get_matching_split(subject, now);
115103
result
@@ -212,15 +200,15 @@ mod tests {
212200

213201
use crate::rules_based::{
214202
eval::get_assignment,
215-
ufc::{AssignmentValue, UniversalFlagConfig, VariationType},
216-
Attribute, Configuration, EvaluationContext, Str,
203+
ufc::{AssignmentValue, UniversalFlagConfig},
204+
Attribute, Configuration, EvaluationContext, FlagType, Str,
217205
};
218206

219207
#[derive(Debug, Serialize, Deserialize)]
220208
#[serde(rename_all = "camelCase")]
221209
struct TestCase {
222210
flag: String,
223-
variation_type: VariationType,
211+
variation_type: FlagType,
224212
default_value: Arc<serde_json::value::RawValue>,
225213
targeting_key: Str,
226214
attributes: Arc<HashMap<Str, Attribute>>,
@@ -251,27 +239,31 @@ mod tests {
251239
let test_cases: Vec<TestCase> = serde_json::from_reader(f).unwrap();
252240

253241
for test_case in test_cases {
254-
let default_assignment =
255-
AssignmentValue::from_wire(test_case.variation_type, test_case.default_value)
256-
.unwrap();
242+
let default_assignment = AssignmentValue::from_wire(
243+
test_case.variation_type.into(),
244+
test_case.default_value,
245+
)
246+
.unwrap();
257247

258248
print!("test subject {:?} ... ", test_case.targeting_key);
259249
let subject = EvaluationContext::new(test_case.targeting_key, test_case.attributes);
260250
let result = get_assignment(
261251
Some(&config),
262252
&test_case.flag,
263253
&subject,
264-
Some(test_case.variation_type),
254+
test_case.variation_type.into(),
265255
now,
266256
);
267257

268258
let result_assingment = result
269259
.as_ref()
270260
.map(|assignment| &assignment.value)
271261
.unwrap_or(&default_assignment);
272-
let expected_assignment =
273-
AssignmentValue::from_wire(test_case.variation_type, test_case.result.value)
274-
.unwrap();
262+
let expected_assignment = AssignmentValue::from_wire(
263+
test_case.variation_type.into(),
264+
test_case.result.value,
265+
)
266+
.unwrap();
275267

276268
assert_eq!(result_assingment, &expected_assignment);
277269
println!("ok");
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4+
#[serde(rename_all = "snake_case")]
5+
#[repr(u8)]
6+
pub enum FlagType {
7+
#[serde(alias = "BOOLEAN")]
8+
Boolean = 1,
9+
#[serde(alias = "STRING")]
10+
String = 1 << 1,
11+
#[serde(alias = "NUMERIC")]
12+
Float = 1 << 2,
13+
#[serde(alias = "INTEGER")]
14+
Integer = 1 << 3,
15+
#[serde(alias = "JSON")]
16+
Object = 1 << 4,
17+
}
18+
19+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20+
#[serde(rename_all = "snake_case")]
21+
#[repr(u8)]
22+
#[non_exhaustive]
23+
pub enum ExpectedFlagType {
24+
Boolean = FlagType::Boolean as u8,
25+
String = FlagType::String as u8,
26+
Float = FlagType::Float as u8,
27+
Integer = FlagType::Integer as u8,
28+
Object = FlagType::Object as u8,
29+
Number = (FlagType::Integer as u8) | (FlagType::Float as u8),
30+
Any = 0xff,
31+
}
32+
33+
impl From<FlagType> for ExpectedFlagType {
34+
fn from(value: FlagType) -> Self {
35+
match value {
36+
FlagType::String => ExpectedFlagType::String,
37+
FlagType::Integer => ExpectedFlagType::Integer,
38+
FlagType::Float => ExpectedFlagType::Float,
39+
FlagType::Boolean => ExpectedFlagType::Boolean,
40+
FlagType::Object => ExpectedFlagType::Object,
41+
}
42+
}
43+
}
44+
45+
impl ExpectedFlagType {
46+
pub(crate) fn is_compatible(self, ty: FlagType) -> bool {
47+
(self as u8) & (ty as u8) != 0
48+
}
49+
}

datadog-ffe/src/rules_based/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod attributes;
55
mod configuration;
66
mod error;
77
mod eval;
8+
mod flag_type;
89
mod sharder;
910
mod str;
1011
mod timestamp;
@@ -14,6 +15,7 @@ pub use attributes::Attribute;
1415
pub use configuration::Configuration;
1516
pub use error::EvaluationError;
1617
pub use eval::{get_assignment, EvaluationContext};
18+
pub use flag_type::{ExpectedFlagType, FlagType};
1719
pub use str::Str;
1820
pub use timestamp::{now, Timestamp};
19-
pub use ufc::{Assignment, AssignmentReason, AssignmentValue, UniversalFlagConfig, VariationType};
21+
pub use ufc::{Assignment, AssignmentReason, AssignmentValue, UniversalFlagConfig};

datadog-ffe/src/rules_based/ufc/assignment.rs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ use std::sync::Arc;
66

77
use serde::{Deserialize, Serialize};
88

9-
use crate::rules_based::Str;
10-
11-
use super::VariationType;
9+
use crate::rules_based::{ufc::VariationType, FlagType, Str};
1210

1311
/// Reason for assignment evaluation result.
1412
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
@@ -278,18 +276,17 @@ impl AssignmentValue {
278276
///
279277
/// # Examples
280278
/// ```
281-
/// # use datadog_ffe::rules_based::AssignmentValue;
282-
/// # use datadog_ffe::rules_based::VariationType;
279+
/// # use datadog_ffe::rules_based::{AssignmentValue, FlagType};
283280
/// let value = AssignmentValue::String("example".into());
284-
/// assert_eq!(value.variation_type(), VariationType::String);
281+
/// assert_eq!(value.variation_type(), FlagType::String);
285282
/// ```
286-
pub fn variation_type(&self) -> VariationType {
283+
pub fn variation_type(&self) -> FlagType {
287284
match self {
288-
AssignmentValue::String(_) => VariationType::String,
289-
AssignmentValue::Integer(_) => VariationType::Integer,
290-
AssignmentValue::Float(_) => VariationType::Numeric,
291-
AssignmentValue::Boolean(_) => VariationType::Boolean,
292-
AssignmentValue::Json { .. } => VariationType::Json,
285+
AssignmentValue::String(_) => FlagType::String,
286+
AssignmentValue::Integer(_) => FlagType::Integer,
287+
AssignmentValue::Float(_) => FlagType::Float,
288+
AssignmentValue::Boolean(_) => FlagType::Boolean,
289+
AssignmentValue::Json { .. } => FlagType::Object,
293290
}
294291
}
295292

datadog-ffe/src/rules_based/ufc/models.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{collections::HashMap, sync::Arc};
66
use regex::Regex;
77
use serde::{Deserialize, Serialize};
88

9-
use crate::rules_based::{EvaluationError, Str, Timestamp};
9+
use crate::rules_based::{EvaluationError, FlagType, Str, Timestamp};
1010

1111
/// Universal Flag Configuration attributes. This contains the actual flag configuration data.
1212
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -91,6 +91,30 @@ pub enum VariationType {
9191
Json,
9292
}
9393

94+
impl From<VariationType> for FlagType {
95+
fn from(value: VariationType) -> FlagType {
96+
match value {
97+
VariationType::String => FlagType::String,
98+
VariationType::Integer => FlagType::Integer,
99+
VariationType::Numeric => FlagType::Float,
100+
VariationType::Boolean => FlagType::Boolean,
101+
VariationType::Json => FlagType::Object,
102+
}
103+
}
104+
}
105+
106+
impl From<FlagType> for VariationType {
107+
fn from(value: FlagType) -> VariationType {
108+
match value {
109+
FlagType::String => VariationType::String,
110+
FlagType::Integer => VariationType::Integer,
111+
FlagType::Float => VariationType::Numeric,
112+
FlagType::Boolean => VariationType::Boolean,
113+
FlagType::Object => VariationType::Json,
114+
}
115+
}
116+
}
117+
94118
#[derive(Debug, Serialize, Deserialize, Clone)]
95119
#[serde(rename_all = "camelCase")]
96120
#[allow(missing_docs)]

0 commit comments

Comments
 (0)