-
Notifications
You must be signed in to change notification settings - Fork 10
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
feat: Add hooks implementation #95
base: main
Are you sure you want to change the base?
Conversation
1d86d02
to
fafe675
Compare
Signed-off-by: Maxim Fischuk <[email protected]> Signed-off-by: MaximFischuk <[email protected]>
Signed-off-by: MaximFischuk <[email protected]>
Signed-off-by: MaximFischuk <[email protected]>
Signed-off-by: MaximFischuk <[email protected]>
aa8b329
to
60ca477
Compare
src/hooks/logging.rs
Outdated
) { | ||
log::error!("Error hook for flag {}: {:?}", context.flag_key, error); | ||
} | ||
async fn finally<'a>(&self, context: &HookContext<'a>, _: Option<&'a HookHints>) { |
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.
async fn finally<'a>(&self, context: &HookContext<'a>, _: Option<&'a HookHints>) { | |
async fn finally<'a>(&self, context: &HookContext<'a>, _: &EvaluationDetails<Value>, _: Option<&'a HookHints>) { |
Hey @MaximFischuk, the finally
stage is hooks changed slightly in a recent spec update. Thanks!
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.
Thank you for the information! Seems like I've missed it, I'll update it soon :)
…ment 4.3.8` of OpenFeature specification Signed-off-by: MaximFischuk <[email protected]>
031f9c5
to
a2b6497
Compare
Hey @MaximFischuk, reviews may be slower than usual around the holidays. Hopefully, someone can take a look soon. I'll do my best to review, but my Rust skills are basically non-existent 😄 |
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.
overall great job 🍻
Cargo.toml
Outdated
@@ -2,7 +2,7 @@ | |||
name = "open-feature" | |||
version = "0.2.4" | |||
edition = "2021" | |||
rust-version = "1.71.1" # MSRV | |||
rust-version = "1.71.1" # MSRV |
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 something went wrong 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.
Yep, TOML formatter did weird thing :)
src/api/api.rs
Outdated
@@ -54,6 +56,12 @@ impl OpenFeature { | |||
self.provider_registry.set_named(name, provider).await; | |||
} | |||
|
|||
/// Add a new hook to the global list of hooks. | |||
pub async fn add_hook<T: Hook>(&mut self, hook: T) { | |||
let mut lock = self.hooks.get_mut().await; // TODO: Remove async context to avoid deadlock and enforce initialization before usage. |
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.
not sure about the TODO here. didn't you RwLock
guard hooks
anyway?
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.
Some of my notes were accidentally published to PR, sorry, I'll remove them
@@ -11,7 +11,7 @@ pub enum EvaluationContextFieldValue { | |||
Float(f64), | |||
String(String), | |||
DateTime(OffsetDateTime), | |||
Struct(Arc<dyn Any + Send + Sync>), | |||
Struct(Arc<dyn Any + Send + Sync>), // TODO: better to make structure in similar way as serde_json::Value |
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.
you mean to create a nested HashMap
struct?
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.
Exactly! Type information is completely cleared here, so the client cannot restore the initial type without workarounds. My suggestion is to add a recursion Map like it was implemented in serde_json.
src/api/client.rs
Outdated
@@ -23,20 +25,26 @@ pub struct Client { | |||
provider_registry: ProviderRegistry, | |||
evaluation_context: EvaluationContext, | |||
global_evaluation_context: GlobalEvaluationContext, | |||
global_hooks: GlobalHooks, | |||
|
|||
hooks: Vec<HookWrapper>, |
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.
could you rename it to client_hooks
?
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.
Sure :)
src/api/client.rs
Outdated
.await; | ||
hook_context.evaluation_context = &context; | ||
|
||
// INFO: Result of the resolution or error reson with default value |
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.
// INFO: Result of the resolution or error reson with default value | |
// INFO: Result of the resolution or error reason with default value |
context: &HookContext<'a>, | ||
_: Option<&'a HookHints>, | ||
) -> Result<Option<EvaluationContext>, EvaluationError> { | ||
log::debug!("Before hook for flag {}", context.flag_key); |
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.
just thinking, wouldn't it be better to raise the log levels, debug
-> info
and the trace
-> debug
?
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's debatable, enterprise clients usually hide debug
level of logs in production. Flag evaluating could generate extremely many logs in large applications so it may force a client to disable logs specifically for the OpenFeature library because just setting log level info
is not enough.
But, I don't insist :)
src/serde_json.rs
Outdated
@@ -20,7 +20,7 @@ fn json_value_to_value(value: &serde_json::Value) -> EvaluationResult<Value> { | |||
match value { | |||
serde_json::Value::Bool(value) => Ok(Value::Bool(*value)), | |||
serde_json::Value::Number(value) if value.is_i64() => { | |||
Ok(Value::Int(value.as_i64().unwrap())) | |||
Ok(Value::Int(value.as_i64().unwrap())) //TODO: remove unwrap or use expect |
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.
can you explain the TODO? how do you wan to remove the unwrap()
?
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 general, it is better to avoid any potential panic in libraries, so in my opinion, the error could be returned to the caller as EvaluationResult::Err(...), or when any error is impossible then expect
will make it clearer to anyone who gets an unexpected panic.
…ncy, remove accidentally committed todos Signed-off-by: MaximFischuk <[email protected]>
Signed-off-by: MaximFischuk <[email protected]>
use super::{Hook, HookContext, HookHints}; | ||
|
||
/// A hook that logs the evaluation lifecycle of a flag. | ||
pub struct LoggingHook; |
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.
@MaximFischuk please consider adding a print option to the LoggingHook struct for evaluation context; it's important to print the context in a readable JSON format if it is set according to the spec
_: &EvaluationDetails<Value>, | ||
_: Option<&'a HookHints>, | ||
) -> Result<(), EvaluationError> { | ||
log::debug!("After hook for flag {}", context.flag_key); |
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.
@MaximFischuk please consider adding fields (Logged data) to the log messages (serialized, opt-in) according to the specification in each Stage.
This PR
This pull request introduces the implementation of Hooks in the OpenFeature SDK following the OpenFeature standard. The changes include the addition of hooks to various stages in the feature flag evaluation process, such as before, after, error, and finally. The implementation provides capabilities to add hooks at the global, client, provider, and invocation levels, which allows for extensive customizability and control over the feature flag lifecycle.
Related Issues
Fixes #5
Notes
Follow-up Tasks
How to test
examples/hooks.rs
to see a working implementation of the hooks in action.src/hooks/mod.rs
to ensure they cover expected behaviors and edge cases.