Skip to content

Commit 031f9c5

Browse files
committed
refactor: Update finally hook signature to be compliant to Requirement 4.3.8 of OpenFeature specification
1 parent 60ca477 commit 031f9c5

File tree

5 files changed

+93
-18
lines changed

5 files changed

+93
-18
lines changed

examples/hooks.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,12 @@ impl Hook for DummyLoggingHook {
114114
);
115115
}
116116

117-
async fn finally<'a>(&self, context: &HookContext<'a>, _hints: Option<&'a HookHints>) {
117+
async fn finally<'a>(
118+
&self,
119+
context: &HookContext<'a>,
120+
_: &EvaluationDetails<Value>,
121+
_hints: Option<&'a HookHints>,
122+
) {
118123
log::info!(
119124
"Finally({}) evaluating flag {} of type {}",
120125
self.0,

src/api/client.rs

+25-5
Original file line numberDiff line numberDiff line change
@@ -338,11 +338,21 @@ impl Client {
338338
.await;
339339
hook_context.evaluation_context = &context;
340340

341+
// INFO: Result of the resolution or error reson with default value
342+
// This bind is defined here to minimize cloning of the `Value`
343+
let evaluation_details;
344+
341345
if let Err(error) = result {
342346
self.error_hooks(after_hooks.clone(), &hook_context, &error, hints)
343347
.await;
344-
self.finally_hooks(after_hooks.into_iter(), &hook_context, hints)
345-
.await;
348+
evaluation_details = EvaluationDetails::error_reason(flag_key, T::default());
349+
self.finally_hooks(
350+
after_hooks.into_iter(),
351+
&hook_context,
352+
&evaluation_details,
353+
hints,
354+
)
355+
.await;
346356

347357
return Err(error);
348358
}
@@ -360,18 +370,27 @@ impl Client {
360370
.after_hooks(after_hooks.clone(), &hook_context, &details, hints)
361371
.await
362372
{
373+
evaluation_details = EvaluationDetails::error_reason(flag_key, T::default());
363374
self.error_hooks(after_hooks.clone(), &hook_context, &error, hints)
364375
.await;
376+
} else {
377+
evaluation_details = details;
365378
}
366379
}
367380
Err(ref error) => {
381+
evaluation_details = EvaluationDetails::error_reason(flag_key, T::default());
368382
self.error_hooks(after_hooks.clone(), &hook_context, error, hints)
369383
.await;
370384
}
371385
}
372386

373-
self.finally_hooks(after_hooks.into_iter(), &hook_context, hints)
374-
.await;
387+
self.finally_hooks(
388+
after_hooks.into_iter(),
389+
&hook_context,
390+
&evaluation_details,
391+
hints,
392+
)
393+
.await;
375394

376395
result
377396
}
@@ -441,12 +460,13 @@ impl Client {
441460
&self,
442461
hooks: I,
443462
hook_context: &HookContext<'_>,
463+
evaluation_details: &EvaluationDetails<Value>,
444464
hints: Option<&HookHints>,
445465
) where
446466
I: Iterator<Item = &'a HookWrapper>,
447467
{
448468
for hook in hooks {
449-
hook.finally(hook_context, hints).await;
469+
hook.finally(hook_context, evaluation_details, hints).await;
450470
}
451471
}
452472
}

src/evaluation/details.rs

+13
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ pub struct EvaluationDetails<T> {
3333
pub flag_metadata: FlagMetadata,
3434
}
3535

36+
impl EvaluationDetails<Value> {
37+
/// Creates a new `EvaluationDetails` instance with an error reason.
38+
pub fn error_reason(flag_key: impl Into<String>, value: impl Into<Value>) -> Self {
39+
Self {
40+
value: value.into(),
41+
flag_key: flag_key.into(),
42+
reason: Some(EvaluationReason::Error),
43+
variant: None,
44+
flag_metadata: FlagMetadata::default(),
45+
}
46+
}
47+
}
48+
3649
impl<T> EvaluationDetails<T>
3750
where
3851
T: Into<Value>,

src/hooks/logging.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ impl Hook for LoggingHook {
3434
) {
3535
log::error!("Error hook for flag {}: {:?}", context.flag_key, error);
3636
}
37-
async fn finally<'a>(&self, context: &HookContext<'a>, _: Option<&'a HookHints>) {
37+
async fn finally<'a>(
38+
&self,
39+
context: &HookContext<'a>,
40+
_: &EvaluationDetails<Value>,
41+
_: Option<&'a HookHints>,
42+
) {
3843
log::trace!("Finally hook for flag {}", context.flag_key);
3944
}
4045
}

src/hooks/mod.rs

+43-11
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ pub trait Hook: Send + Sync + 'static {
4444
);
4545

4646
/// This method is called after the flag evaluation, regardless of the result.
47-
async fn finally<'a>(&self, context: &HookContext<'a>, hints: Option<&'a HookHints>);
47+
async fn finally<'a>(
48+
&self,
49+
context: &HookContext<'a>,
50+
evaluation_details: &EvaluationDetails<Value>,
51+
hints: Option<&'a HookHints>,
52+
);
4853
}
4954

5055
// ============================================================
@@ -102,7 +107,7 @@ mod tests {
102107

103108
use crate::{
104109
provider::{MockFeatureProvider, ResolutionDetails},
105-
EvaluationErrorCode, EvaluationOptions, OpenFeature, StructValue,
110+
EvaluationErrorCode, EvaluationOptions, EvaluationReason, OpenFeature, StructValue,
106111
};
107112

108113
use super::*;
@@ -437,7 +442,22 @@ mod tests {
437442
.in_sequence(&mut seq)
438443
.return_const(());
439444

440-
mock_hook.expect_finally().return_const(());
445+
mock_hook
446+
.expect_finally()
447+
.withf(|ctx, details, _| {
448+
assert_eq!(ctx.flag_key, "flag");
449+
assert_eq!(ctx.flag_type, Type::Bool);
450+
assert_eq!(
451+
ctx.evaluation_context,
452+
&EvaluationContext::default().with_custom_field("is", "a test")
453+
);
454+
assert_eq!(ctx.default_value, Some(Value::Bool(false)));
455+
assert_eq!(details.flag_key, "flag");
456+
assert_eq!(details.value, Value::Bool(false));
457+
assert_eq!(details.reason, Some(EvaluationReason::Error));
458+
true
459+
})
460+
.return_const(());
441461

442462
// evaluation
443463
client = client.with_hook(mock_hook);
@@ -531,6 +551,18 @@ mod tests {
531551
.expect_finally()
532552
.once()
533553
.in_sequence(&mut seq)
554+
.withf(|ctx, details, _| {
555+
assert_eq!(ctx.flag_key, "flag");
556+
assert_eq!(ctx.flag_type, Type::Bool);
557+
assert_eq!(
558+
ctx.evaluation_context,
559+
&EvaluationContext::default().with_custom_field("is", "a test")
560+
);
561+
assert_eq!(ctx.default_value, Some(Value::Bool(false)));
562+
assert_eq!(details.flag_key, "flag");
563+
assert_eq!(details.value, Value::Bool(true));
564+
true
565+
})
534566
.return_const(());
535567

536568
// evaluation
@@ -621,22 +653,22 @@ mod tests {
621653
.expect_finally()
622654
.once()
623655
.in_sequence(&mut seq)
624-
.returning(|_, _| {});
656+
.returning(|_, _, _| {});
625657
mock_invocation_hook
626658
.expect_finally()
627659
.once()
628660
.in_sequence(&mut seq)
629-
.returning(|_, _| {});
661+
.returning(|_, _, _| {});
630662
mock_client_hook
631663
.expect_finally()
632664
.once()
633665
.in_sequence(&mut seq)
634-
.returning(|_, _| {});
666+
.returning(|_, _, _| {});
635667
mock_api_hook
636668
.expect_finally()
637669
.once()
638670
.in_sequence(&mut seq)
639-
.returning(|_, _| {});
671+
.returning(|_, _, _| {});
640672

641673
provider
642674
.expect_hooks()
@@ -779,22 +811,22 @@ mod tests {
779811
.expect_finally()
780812
.once()
781813
.in_sequence(&mut seq)
782-
.returning(|_, _| {});
814+
.returning(|_, _, _| {});
783815
mock_invocation_hook
784816
.expect_finally()
785817
.once()
786818
.in_sequence(&mut seq)
787-
.returning(|_, _| {});
819+
.returning(|_, _, _| {});
788820
mock_client_hook
789821
.expect_finally()
790822
.once()
791823
.in_sequence(&mut seq)
792-
.returning(|_, _| {});
824+
.returning(|_, _, _| {});
793825
mock_api_hook
794826
.expect_finally()
795827
.once()
796828
.in_sequence(&mut seq)
797-
.returning(|_, _| {});
829+
.returning(|_, _, _| {});
798830

799831
provider
800832
.expect_hooks()

0 commit comments

Comments
 (0)