-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spike for constraints #27
base: main
Are you sure you want to change the base?
Conversation
src/strategy.rs
Outdated
expression: ConstraintExpression, | ||
inverted: Option<bool>, | ||
getter: F, | ||
) -> Evaluate | ||
where | ||
F: Fn(&Context) -> Option<&String> + Clone + Sync + Send + 'static, | ||
{ | ||
match &expression { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This match is getting a little bit long. I'm struggling to break it out into more granular functions due to rust marking closures as unique types. I'm very open to suggestions if someone feels the same as me and has clever ideas here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://docs.rs/enum_dispatch/latest/enum_dispatch/ is probably the thing to investigate.
We'd want to refactor the api type as well to have layers:
#[serde(flatten)]
enum ConstraintExpression{
NotIn(NotInExpression),
...
Or something like that.
I suggest not trying that though, until we've removed the code duplication below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the tl;dr is:
- the test suite is too long and not interestingly-different: lets bring in proptest
- there is too much code duplication in the new code paths
I've made some concrete suggestions on how to address these issues inline in the review.
It will be very nice to have this feature!
#[serde(flatten)] | ||
pub expression: ConstraintExpression, | ||
} | ||
|
||
#[derive(Clone, Serialize, Deserialize, Debug)] | ||
#[serde(tag = "operator", content = "values")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a shame this needs to change: the enums are much less ergonomic now :( - single attribute structs :(.
I wonder if struct flattening + an internally tagged representation could work: a family of related enums:
- one with values
- one with value
@@ -40,4 +41,10 @@ pub struct Context { | |||
pub app_name: String, | |||
#[serde(default)] | |||
pub environment: String, | |||
#[serde(default = "current_time", rename = "currentTime")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should test #[serde(rename_all = "camelCase")] at some point, might be sufficient.
@@ -40,4 +41,10 @@ pub struct Context { | |||
pub app_name: String, | |||
#[serde(default)] | |||
pub environment: String, | |||
#[serde(default = "current_time", rename = "currentTime")] | |||
pub current_time: String, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't be a string.
IIRC correctly chrono doesn't have a sensible default, so I suggest:
a Chrono tuple newtype with serde derived impls, a std::default::Default implementation for that, then inclusion here, which as the chrono::serde default is rfc3339 should just work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact, I'm not sure whether we should be capturing the current time at all. For the proxy use case, clients should not be permitted to time-travel; for the non-proxy use case access to the current time is probably cheap enough, since time access is such a well known thing.
We could compile rules that relate to get compiled into a unix time expression for even faster evaluation, if scaling the proxy is required - though I suspect only unleash themselves would hit that scale.
fn _compile_constraint_string<F>(expression: ConstraintExpression, getter: F) -> Evaluate | ||
fn _compile_constraint_string<F>( | ||
expression: ConstraintExpression, | ||
inverted: Option<bool>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than inverted being Option, I think it wants to be a type parameter: we want to have unchanging code in this function whose types change depending on the parameter.
fn _compile_constraint_string<F, B>(expression: ConstraintExpression, getter: F, boxer:B) -> Evaluate where
F: ...,
B: Fn(Fn(&Context) -> bool) -> Evaluate
{
...
ConstraintExpression::In { values } => {
let as_set: HashSet<String> = values.iter().cloned().collect();
boxer(move |context: &Context| {
getter(context).map(|v| as_set.contains(v)).unwrap_or(false)
})
}
....
May need a little code golf to make it work, but I think this would be a nice place to get to.
src/strategy.rs
Outdated
expression: ConstraintExpression, | ||
inverted: Option<bool>, | ||
getter: F, | ||
) -> Evaluate | ||
where | ||
F: Fn(&Context) -> Option<&String> + Clone + Sync + Send + 'static, | ||
{ | ||
match &expression { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://docs.rs/enum_dispatch/latest/enum_dispatch/ is probably the thing to investigate.
We'd want to refactor the api type as well to have layers:
#[serde(flatten)]
enum ConstraintExpression{
NotIn(NotInExpression),
...
Or something like that.
I suggest not trying that though, until we've removed the code duplication below.
src/strategy.rs
Outdated
}) | ||
} | ||
} | ||
ConstraintExpression::StrEndsWith { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
theres lots of duplication with StrStartsWith: I think a helper function generic over the predicate would be super useful here.
src/strategy.rs
Outdated
}) | ||
} | ||
} | ||
ConstraintExpression::StrContains { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
src/strategy.rs
Outdated
}) | ||
} | ||
} | ||
ConstraintExpression::NumEq { value } => match value.parse::<f64>() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and this family of contraints should also be extractable into composable helpers
src/strategy.rs
Outdated
@@ -332,16 +561,17 @@ where | |||
} | |||
false | |||
}) | |||
.unwrap_or(false) | |||
.unwrap_or(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative idea is a helper trait on Bool adding .inverted(inverted) - that will allow
.unwrap_or(false).inverted(inverted)
..Default::default() | ||
}; | ||
// starts with matches string start | ||
assert!(super::constrain( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we probably want to bring proptest in at this point
This implements extended support for constraint operators, reference implementation on the node SDK is here: https://github.com/Unleash/unleash-client-node/pull/289/files
Note that the client specs have not been rolled forward to cover these cases, since the specs also include cases for custom stickiness, which isn't as of yet implemented in this SDK, this was covered by local testing against the spec