open_feature/hooks/
mod.rs

1use std::{collections::HashMap, ops::Deref, sync::Arc};
2
3use crate::{
4    provider::ProviderMetadata, ClientMetadata, EvaluationContext, EvaluationDetails,
5    EvaluationError, Type, Value,
6};
7
8mod logging;
9pub use logging::LoggingHook;
10
11// ============================================================
12//  Hook
13// ============================================================
14
15/// Hook allows application developers to add arbitrary behavior to the flag evaluation lifecycle.
16/// They operate similarly to middleware in many web frameworks.
17///
18/// https://github.com/open-feature/spec/blob/main/specification/sections/04-hooks.md
19#[cfg_attr(
20    feature = "test-util",
21    mockall::automock,
22    allow(clippy::ref_option_ref)
23)] // Specified lifetimes manually to make it work with mockall
24#[async_trait::async_trait]
25pub trait Hook: Send + Sync + 'static {
26    /// This method is called before the flag evaluation.
27    async fn before<'a>(
28        &self,
29        context: &HookContext<'a>,
30        hints: Option<&'a HookHints>,
31    ) -> Result<Option<EvaluationContext>, EvaluationError>;
32
33    /// This method is called after the successful flag evaluation.
34    async fn after<'a>(
35        &self,
36        context: &HookContext<'a>,
37        details: &EvaluationDetails<Value>,
38        hints: Option<&'a HookHints>,
39    ) -> Result<(), EvaluationError>;
40
41    /// This method is called on error during flag evaluation or error in before hook or after hook.
42    async fn error<'a>(
43        &self,
44        context: &HookContext<'a>,
45        error: &EvaluationError,
46        hints: Option<&'a HookHints>,
47    );
48
49    /// This method is called after the flag evaluation, regardless of the result.
50    async fn finally<'a>(
51        &self,
52        context: &HookContext<'a>,
53        evaluation_details: &EvaluationDetails<Value>,
54        hints: Option<&'a HookHints>,
55    );
56}
57
58// ============================================================
59//  HookWrapper
60// ============================================================
61
62#[allow(missing_docs)]
63#[derive(Clone)]
64pub struct HookWrapper(Arc<dyn Hook>);
65
66impl HookWrapper {
67    #[allow(missing_docs)]
68    pub fn new(hook: impl Hook) -> Self {
69        Self(Arc::new(hook))
70    }
71}
72
73impl Deref for HookWrapper {
74    type Target = dyn Hook;
75
76    fn deref(&self) -> &Self::Target {
77        &*self.0
78    }
79}
80
81// ============================================================
82//  HookHints
83// ============================================================
84
85#[allow(missing_docs)]
86#[derive(Clone, Default, PartialEq, Debug)]
87pub struct HookHints {
88    hints: HashMap<String, Value>,
89}
90
91// ============================================================
92//  HookContext
93// ============================================================
94
95/// Context for hooks.
96#[allow(missing_docs)]
97#[derive(Clone, PartialEq, Debug)]
98pub struct HookContext<'a> {
99    pub flag_key: &'a str,
100    pub flag_type: Type,
101    pub evaluation_context: &'a EvaluationContext,
102    pub provider_metadata: ProviderMetadata,
103    pub default_value: Option<Value>,
104    pub client_metadata: ClientMetadata,
105}
106
107#[cfg(test)]
108mod tests {
109
110    use spec::spec;
111
112    use crate::{
113        provider::{MockFeatureProvider, ResolutionDetails},
114        EvaluationErrorCode, EvaluationOptions, EvaluationReason, OpenFeature, StructValue,
115    };
116
117    use super::*;
118
119    #[spec(
120        number = "4.1.1",
121        text = "Hook context MUST provide: the flag key, flag value type, evaluation context, and the default value."
122    )]
123    #[spec(
124        number = "4.1.2",
125        text = "The hook context SHOULD provide: access to the client metadata and the provider metadata fields."
126    )]
127    #[spec(
128        number = "4.1.3",
129        text = "The flag key, flag type, and default value properties MUST be immutable. If the language does not support immutability, the hook MUST NOT modify these properties."
130    )]
131    #[test]
132    fn hook_context() {
133        let context = HookContext {
134            flag_key: "flag_key",
135            flag_type: Type::Bool,
136            evaluation_context: &EvaluationContext::default(),
137            provider_metadata: ProviderMetadata::default(),
138            default_value: Some(Value::Bool(true)),
139            client_metadata: ClientMetadata::default(),
140        };
141
142        assert_eq!(context.flag_key, "flag_key");
143        assert_eq!(context.flag_type, Type::Bool);
144        assert_eq!(context.evaluation_context, &EvaluationContext::default());
145        assert_eq!(context.provider_metadata, ProviderMetadata::default());
146        assert_eq!(context.default_value, Some(Value::Bool(true)));
147        assert_eq!(context.client_metadata, ClientMetadata::default());
148    }
149
150    #[spec(
151        number = "4.2.1",
152        text = "hook hints MUST be a structure supports definition of arbitrary properties, with keys of type string, and values of type boolean | string | number | datetime | structure."
153    )]
154    #[test]
155    fn hook_hints() {
156        let mut hints = HookHints::default();
157        hints.hints.insert("key".to_string(), Value::Bool(true));
158        hints
159            .hints
160            .insert("key2".to_string(), Value::String("value".to_string()));
161        hints.hints.insert("key3".to_string(), Value::Int(42));
162        hints.hints.insert("key4".to_string(), Value::Float(3.14));
163        hints.hints.insert("key5".to_string(), Value::Array(vec![]));
164        hints
165            .hints
166            .insert("key6".to_string(), Value::Struct(StructValue::default()));
167
168        assert_eq!(hints.hints.len(), 6);
169        assert_eq!(hints.hints.get("key"), Some(&Value::Bool(true)));
170        assert_eq!(
171            hints.hints.get("key2"),
172            Some(&Value::String("value".to_string()))
173        );
174        assert_eq!(hints.hints.get("key3"), Some(&Value::Int(42)));
175        assert_eq!(hints.hints.get("key4"), Some(&Value::Float(3.14)));
176        assert_eq!(hints.hints.get("key5"), Some(&Value::Array(vec![])));
177        assert_eq!(
178            hints.hints.get("key6"),
179            Some(&Value::Struct(StructValue::default()))
180        );
181    }
182
183    #[spec(number = "4.2.2.1", text = "Hook hints MUST be immutable.")]
184    #[test]
185    fn hook_hints_mutability_checked_by_type_system() {}
186
187    #[spec(
188        number = "4.2.2.2",
189        text = "The client metadata field in the hook context MUST be immutable."
190    )]
191    #[test]
192    fn client_metadata_mutability_checked_by_type_system() {}
193
194    #[spec(
195        number = "4.2.2.3",
196        text = "The provider metadata field in the hook context MUST be immutable."
197    )]
198    #[test]
199    fn provider_metadata_mutability_checked_by_type_system() {}
200
201    #[spec(number = "4.3.1", text = "Hooks MUST specify at least one stage.")]
202    #[test]
203    fn hook_interface_implementation_checked_by_type_system() {}
204
205    #[spec(
206        number = "4.3.2.1",
207        text = "The before stage MUST run before flag resolution occurs. It accepts a hook context (required) and hook hints (optional) as parameters and returns either an evaluation context or nothing."
208    )]
209    #[test]
210    fn hook_before_function_interface_implementation_checked_by_type_system() {}
211
212    #[spec(
213        number = "4.3.4",
214        text = "Any evaluation context returned from a before hook MUST be passed to subsequent before hooks (via HookContext)."
215    )]
216    #[tokio::test]
217    async fn before_hook_context_passing() {
218        let mut mock_hook_1 = MockHook::new();
219        let mut mock_hook_2 = MockHook::new();
220
221        let mut api = OpenFeature::default();
222        let mut client = api.create_named_client("test");
223        let mut mock_provider = MockFeatureProvider::default();
224
225        mock_provider.expect_hooks().return_const(vec![]);
226        mock_provider.expect_initialize().return_const(());
227        mock_provider
228            .expect_metadata()
229            .return_const(ProviderMetadata::default());
230        mock_provider
231            .expect_resolve_bool_value()
232            .withf(|_, ctx| {
233                assert_eq!(
234                    ctx,
235                    &EvaluationContext::default()
236                        .with_targeting_key("mock_hook_1")
237                        .with_custom_field("is", "a test")
238                );
239                true
240            })
241            .return_const(Ok(ResolutionDetails::new(true)));
242
243        api.set_provider(mock_provider).await;
244        drop(api);
245
246        let flag_key = "flag";
247
248        let eval_ctx = EvaluationContext::default().with_custom_field("is", "a test");
249
250        let expected_eval_ctx = eval_ctx.clone();
251        let client_metadata = client.metadata().clone();
252        mock_hook_1
253            .expect_before()
254            .withf(move |ctx, _| {
255                let hook_ctx_1 = HookContext {
256                    flag_key,
257                    flag_type: Type::Bool,
258                    evaluation_context: &expected_eval_ctx,
259                    default_value: Some(Value::Bool(false)),
260                    provider_metadata: ProviderMetadata::default(),
261                    client_metadata: client_metadata.clone(),
262                };
263
264                assert_eq!(ctx, &hook_ctx_1);
265                true
266            })
267            .once()
268            .returning(move |_, _| {
269                Ok(Some(
270                    EvaluationContext::default().with_targeting_key("mock_hook_1"),
271                ))
272            });
273
274        let expected_eval_ctx_2 = EvaluationContext::default().with_targeting_key("mock_hook_1");
275        let client_metadata = client.metadata().clone();
276        mock_hook_2
277            .expect_before()
278            .withf(move |ctx, _| {
279                let hook_ctx_1 = HookContext {
280                    flag_key,
281                    flag_type: Type::Bool,
282                    evaluation_context: &expected_eval_ctx_2,
283                    default_value: Some(Value::Bool(false)),
284                    provider_metadata: ProviderMetadata::default(),
285                    client_metadata: client_metadata.clone(),
286                };
287
288                assert_eq!(ctx, &hook_ctx_1);
289                true
290            })
291            .once()
292            .returning(move |_, _| Ok(None));
293
294        mock_hook_1.expect_after().return_const(Ok(()));
295        mock_hook_2.expect_after().return_const(Ok(()));
296        mock_hook_1.expect_finally().return_const(());
297        mock_hook_2.expect_finally().return_const(());
298
299        // evaluation
300        client = client.with_hook(mock_hook_1).with_hook(mock_hook_2);
301
302        let result = client.get_bool_value(flag_key, Some(&eval_ctx), None).await;
303
304        assert!(result.is_ok());
305    }
306
307    #[spec(
308        number = "4.3.5",
309        text = "When before hooks have finished executing, any resulting evaluation context MUST be merged with the existing evaluation context."
310    )]
311    #[tokio::test]
312    async fn before_hook_context_merging() {
313        let mut mock_hook = MockHook::new();
314
315        let mut api = OpenFeature::default();
316        api.set_evaluation_context(
317            EvaluationContext::default()
318                .with_custom_field("key", "api context")
319                .with_custom_field("lowestPriority", true),
320        )
321        .await;
322
323        let mut client = api.create_named_client("test");
324        client.set_evaluation_context(
325            EvaluationContext::default()
326                .with_custom_field("key", "client context")
327                .with_custom_field("lowestPriority", false)
328                .with_custom_field("beatsClient", false),
329        );
330
331        mock_hook.expect_before().once().returning(move |_, _| {
332            Ok(Some(
333                EvaluationContext::default()
334                    .with_custom_field("key", "hook value")
335                    .with_custom_field("multiplier", 3),
336            ))
337        });
338
339        mock_hook.expect_after().return_const(Ok(()));
340        mock_hook.expect_finally().return_const(());
341
342        let flag_key = "flag";
343        let eval_ctx = EvaluationContext::default()
344            .with_custom_field("key", "invocation context")
345            .with_custom_field("on", true)
346            .with_custom_field("beatsClient", true);
347
348        let expected_ctx = EvaluationContext::default()
349            .with_custom_field("key", "hook value")
350            .with_custom_field("multiplier", 3)
351            .with_custom_field("on", true)
352            .with_custom_field("lowestPriority", false)
353            .with_custom_field("beatsClient", true);
354
355        let mut mock_provider = MockFeatureProvider::default();
356
357        mock_provider.expect_hooks().return_const(vec![]);
358        mock_provider.expect_initialize().return_const(());
359        mock_provider
360            .expect_metadata()
361            .return_const(ProviderMetadata::default());
362        mock_provider
363            .expect_resolve_string_value()
364            .withf(move |_, ctx| {
365                assert_eq!(ctx, &expected_ctx);
366                true
367            })
368            .return_const(Ok(ResolutionDetails::new("value")));
369
370        api.set_provider(mock_provider).await;
371        drop(api);
372
373        client = client.with_hook(mock_hook);
374
375        let result = client
376            .get_string_value(flag_key, Some(&eval_ctx), None)
377            .await;
378
379        assert!(result.is_ok());
380    }
381
382    #[spec(
383        number = "4.3.6",
384        text = "The after stage MUST run after flag resolution occurs. It accepts a hook context (required), evaluation details (required) and hook hints (optional). It has no return value."
385    )]
386    #[tokio::test]
387    async fn after_hook() {
388        let mut mock_hook = MockHook::new();
389
390        let mut api = OpenFeature::default();
391        let mut client = api.create_client();
392        let mut mock_provider = MockFeatureProvider::default();
393
394        let mut seq = mockall::Sequence::new();
395
396        mock_provider.expect_hooks().return_const(vec![]);
397        mock_provider.expect_initialize().return_const(());
398        mock_provider
399            .expect_metadata()
400            .return_const(ProviderMetadata::default());
401        mock_provider
402            .expect_resolve_bool_value()
403            .once()
404            .in_sequence(&mut seq)
405            .return_const(Ok(ResolutionDetails::new(true)));
406
407        api.set_provider(mock_provider).await;
408        drop(api);
409
410        mock_hook.expect_before().returning(|_, _| Ok(None));
411
412        mock_hook
413            .expect_after()
414            .once()
415            .in_sequence(&mut seq)
416            .return_const(Ok(()));
417
418        mock_hook.expect_finally().return_const(());
419
420        // evaluation
421        client = client.with_hook(mock_hook);
422
423        let flag_key = "flag";
424        let eval_ctx = EvaluationContext::default().with_custom_field("is", "a test");
425
426        let result = client.get_bool_value(flag_key, Some(&eval_ctx), None).await;
427
428        assert!(result.is_ok());
429    }
430
431    #[spec(
432        number = "4.3.7",
433        text = "The error hook MUST run when errors are encountered in the before stage, the after stage or during flag resolution. It accepts hook context (required), exception representing what went wrong (required), and hook hints (optional). It has no return value."
434    )]
435    #[tokio::test]
436    async fn error_hook() {
437        // error on `before` hook
438        {
439            let mut mock_hook = MockHook::new();
440
441            let mut api = OpenFeature::default();
442            let mut client = api.create_client();
443            let mut mock_provider = MockFeatureProvider::default();
444
445            let mut seq = mockall::Sequence::new();
446
447            mock_provider.expect_hooks().return_const(vec![]);
448            mock_provider.expect_initialize().return_const(());
449            mock_provider.expect_resolve_bool_value().never();
450            mock_provider
451                .expect_metadata()
452                .return_const(ProviderMetadata::default());
453
454            api.set_provider(mock_provider).await;
455            drop(api);
456
457            mock_hook.expect_before().returning(|_, _| error());
458
459            mock_hook
460                .expect_error()
461                .once()
462                .in_sequence(&mut seq)
463                .return_const(());
464
465            mock_hook
466                .expect_finally()
467                .withf(|ctx, details, _| {
468                    assert_eq!(ctx.flag_key, "flag");
469                    assert_eq!(ctx.flag_type, Type::Bool);
470                    assert_eq!(
471                        ctx.evaluation_context,
472                        &EvaluationContext::default().with_custom_field("is", "a test")
473                    );
474                    assert_eq!(ctx.default_value, Some(Value::Bool(false)));
475                    assert_eq!(details.flag_key, "flag");
476                    assert_eq!(details.value, Value::Bool(false));
477                    assert_eq!(details.reason, Some(EvaluationReason::Error));
478                    true
479                })
480                .return_const(());
481
482            // evaluation
483            client = client.with_hook(mock_hook);
484
485            let flag_key = "flag";
486            let eval_ctx = EvaluationContext::default().with_custom_field("is", "a test");
487
488            let result = client.get_bool_value(flag_key, Some(&eval_ctx), None).await;
489
490            assert!(result.is_err());
491        }
492
493        // error on evaluation
494        {
495            let mut mock_hook = MockHook::new();
496
497            let mut api = OpenFeature::default();
498            let mut client = api.create_client();
499            let mut mock_provider = MockFeatureProvider::default();
500
501            let mut seq = mockall::Sequence::new();
502
503            mock_provider.expect_hooks().return_const(vec![]);
504            mock_provider.expect_initialize().return_const(());
505            mock_provider
506                .expect_metadata()
507                .return_const(ProviderMetadata::default());
508
509            mock_hook.expect_before().returning(|_, _| Ok(None));
510
511            mock_provider
512                .expect_resolve_bool_value()
513                .once()
514                .in_sequence(&mut seq)
515                .return_const(error());
516
517            mock_hook
518                .expect_error()
519                .once()
520                .in_sequence(&mut seq)
521                .return_const(());
522
523            mock_hook.expect_finally().return_const(());
524
525            api.set_provider(mock_provider).await;
526            drop(api);
527
528            // evaluation
529            client = client.with_hook(mock_hook);
530
531            let flag_key = "flag";
532            let eval_ctx = EvaluationContext::default().with_custom_field("is", "a test");
533
534            let result = client.get_bool_value(flag_key, Some(&eval_ctx), None).await;
535
536            assert!(result.is_err());
537        }
538    }
539
540    #[spec(
541        number = "4.3.8",
542        text = "The finally hook MUST run after the before, after, and error stages. It accepts a hook context (required), evaluation details (required) and hook hints (optional). It has no return value."
543    )]
544    #[tokio::test]
545    async fn finally_hook() {
546        let mut mock_hook = MockHook::new();
547
548        let mut api = OpenFeature::default();
549        let mut client = api.create_client();
550        let mut mock_provider = MockFeatureProvider::default();
551
552        let mut seq = mockall::Sequence::new();
553
554        mock_provider.expect_hooks().return_const(vec![]);
555        mock_provider.expect_initialize().return_const(());
556        mock_provider
557            .expect_metadata()
558            .return_const(ProviderMetadata::default());
559        mock_provider
560            .expect_resolve_bool_value()
561            .return_const(Ok(ResolutionDetails::new(true)));
562
563        api.set_provider(mock_provider).await;
564
565        mock_hook
566            .expect_before()
567            .once()
568            .in_sequence(&mut seq)
569            .returning(|_, _| Ok(None));
570        mock_hook
571            .expect_after()
572            .once()
573            .in_sequence(&mut seq)
574            .return_const(Ok(()));
575
576        mock_hook
577            .expect_finally()
578            .once()
579            .in_sequence(&mut seq)
580            .withf(|ctx, details, _| {
581                assert_eq!(ctx.flag_key, "flag");
582                assert_eq!(ctx.flag_type, Type::Bool);
583                assert_eq!(
584                    ctx.evaluation_context,
585                    &EvaluationContext::default().with_custom_field("is", "a test")
586                );
587                assert_eq!(ctx.default_value, Some(Value::Bool(false)));
588                assert_eq!(details.flag_key, "flag");
589                assert_eq!(details.value, Value::Bool(true));
590                true
591            })
592            .return_const(());
593
594        // evaluation
595        client = client.with_hook(mock_hook);
596
597        let flag_key = "flag";
598        let eval_ctx = EvaluationContext::default().with_custom_field("is", "a test");
599
600        let result = client.get_bool_value(flag_key, Some(&eval_ctx), None).await;
601
602        assert!(result.is_ok());
603    }
604
605    #[spec(
606        number = "4.4.1",
607        text = "The API, Client, Provider, and invocation MUST have a method for registering hooks."
608    )]
609    #[spec(
610        number = "4.4.2",
611        text = "Hooks MUST be evaluated in the following order -> before: API, Client, Invocation, Provider. after: Provider, Invocation, Client, API. error(if applicable): Provider, Invocation, Client, API. finally: Provider, Invocation, Client, API."
612    )]
613    #[tokio::test]
614    async fn hook_evaluation_order() {
615        let mut mock_api_hook = MockHook::new();
616        let mut mock_client_hook = MockHook::new();
617        let mut mock_provider_hook = MockHook::new();
618        let mut mock_invocation_hook = MockHook::new();
619
620        let mut api = OpenFeature::default();
621        let mut client = api.create_client();
622        let mut provider = MockFeatureProvider::default();
623
624        let mut seq = mockall::Sequence::new();
625
626        // before: API, Client, Invocation, Provider
627        mock_api_hook
628            .expect_before()
629            .once()
630            .in_sequence(&mut seq)
631            .returning(|_, _| Ok(None));
632        mock_client_hook
633            .expect_before()
634            .once()
635            .in_sequence(&mut seq)
636            .returning(|_, _| Ok(None));
637        mock_invocation_hook
638            .expect_before()
639            .once()
640            .in_sequence(&mut seq)
641            .returning(|_, _| Ok(None));
642        mock_provider_hook
643            .expect_before()
644            .once()
645            .in_sequence(&mut seq)
646            .returning(|_, _| Ok(None));
647
648        // evaluation
649        provider
650            .expect_resolve_bool_value()
651            .once()
652            .in_sequence(&mut seq)
653            .return_const(Ok(ResolutionDetails::new(true)));
654
655        // after: Provider, Invocation, Client, API
656        mock_provider_hook
657            .expect_after()
658            .once()
659            .in_sequence(&mut seq)
660            .returning(|_, _, _| Ok(()));
661        mock_invocation_hook
662            .expect_after()
663            .once()
664            .in_sequence(&mut seq)
665            .returning(|_, _, _| Ok(()));
666        mock_client_hook
667            .expect_after()
668            .once()
669            .in_sequence(&mut seq)
670            .returning(|_, _, _| Ok(()));
671        mock_api_hook
672            .expect_after()
673            .once()
674            .in_sequence(&mut seq)
675            .returning(|_, _, _| Ok(()));
676
677        // finally: Provider, Invocation, Client, API
678        mock_provider_hook
679            .expect_finally()
680            .once()
681            .in_sequence(&mut seq)
682            .returning(|_, _, _| {});
683        mock_invocation_hook
684            .expect_finally()
685            .once()
686            .in_sequence(&mut seq)
687            .returning(|_, _, _| {});
688        mock_client_hook
689            .expect_finally()
690            .once()
691            .in_sequence(&mut seq)
692            .returning(|_, _, _| {});
693        mock_api_hook
694            .expect_finally()
695            .once()
696            .in_sequence(&mut seq)
697            .returning(|_, _, _| {});
698
699        provider
700            .expect_hooks()
701            .return_const(vec![HookWrapper::new(mock_provider_hook)]);
702        provider.expect_initialize().return_const(());
703        provider
704            .expect_metadata()
705            .return_const(ProviderMetadata::default());
706
707        api.set_provider(provider).await;
708        api.add_hook(mock_api_hook).await;
709        client = client.with_hook(mock_client_hook);
710
711        let eval = EvaluationOptions::default().with_hook(mock_invocation_hook);
712        let _ = client.get_bool_value("flag", None, Some(&eval)).await;
713    }
714
715    #[spec(
716        number = "4.4.3",
717        text = "If a finally hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining finally hooks."
718    )]
719    #[test]
720    fn finally_hook_not_throw_checked_by_type_system() {}
721
722    #[spec(
723        number = "4.4.4",
724        text = "If an error hook abnormally terminates, evaluation MUST proceed, including the execution of any remaining error hooks."
725    )]
726    #[test]
727    fn error_hook_not_throw_checked_by_type_system() {}
728
729    #[spec(
730        number = "4.4.5",
731        text = "If an error occurs in the before or after hooks, the error hooks MUST be invoked."
732    )]
733    #[tokio::test]
734    async fn error_hook_invoked_on_error() {
735        let mut mock_hook = MockHook::new();
736
737        let mut api = OpenFeature::default();
738        let mut client = api.create_client();
739        let mut mock_provider = MockFeatureProvider::default();
740
741        let mut seq = mockall::Sequence::new();
742
743        mock_provider.expect_hooks().return_const(vec![]);
744        mock_provider.expect_initialize().return_const(());
745        mock_provider.expect_resolve_bool_value().never();
746        mock_provider
747            .expect_metadata()
748            .return_const(ProviderMetadata::default());
749
750        api.set_provider(mock_provider).await;
751
752        mock_hook
753            .expect_before()
754            .once()
755            .in_sequence(&mut seq)
756            .returning(|_, _| error());
757
758        mock_hook
759            .expect_error()
760            .once()
761            .in_sequence(&mut seq)
762            .return_const(());
763
764        mock_hook.expect_finally().return_const(());
765
766        // evaluation
767        client = client.with_hook(mock_hook);
768
769        let flag_key = "flag";
770        let eval_ctx = EvaluationContext::default().with_custom_field("is", "a test");
771
772        let result = client.get_bool_value(flag_key, Some(&eval_ctx), None).await;
773
774        assert!(result.is_err());
775    }
776
777    #[spec(
778        number = "4.4.6",
779        text = "If an error occurs during the evaluation of before or after hooks, any remaining hooks in the before or after stages MUST NOT be invoked."
780    )]
781    #[tokio::test]
782    async fn do_not_evaluate_remaining_hooks_on_error() {
783        let mut mock_api_hook = MockHook::new();
784        let mut mock_client_hook = MockHook::new();
785        let mut mock_provider_hook = MockHook::new();
786        let mut mock_invocation_hook = MockHook::new();
787
788        let mut api = OpenFeature::default();
789        let mut client = api.create_client();
790        let mut provider = MockFeatureProvider::default();
791
792        let mut seq = mockall::Sequence::new();
793
794        // before: API, Client, Invocation, Provider
795        mock_api_hook
796            .expect_before()
797            .once()
798            .in_sequence(&mut seq)
799            .returning(|_, _| Ok(None));
800        mock_client_hook
801            .expect_before()
802            .once()
803            .in_sequence(&mut seq)
804            .returning(|_, _| error());
805
806        // Remaining `before` and `after` hooks should not be called
807        mock_invocation_hook.expect_before().never();
808        mock_provider_hook.expect_before().never();
809
810        // evaluation should not be called
811        provider.expect_resolve_bool_value().never();
812
813        // after: Provider, Invocation, Client, API
814        mock_provider_hook.expect_after().never();
815        mock_invocation_hook.expect_after().never();
816        mock_client_hook.expect_after().never();
817        mock_api_hook.expect_after().never();
818
819        // error: Provider, Invocation, Client, API
820        mock_provider_hook
821            .expect_error()
822            .once()
823            .in_sequence(&mut seq)
824            .returning(|_, _, _| {});
825        mock_invocation_hook
826            .expect_error()
827            .once()
828            .in_sequence(&mut seq)
829            .returning(|_, _, _| {});
830        mock_client_hook
831            .expect_error()
832            .once()
833            .in_sequence(&mut seq)
834            .returning(|_, _, _| {});
835        mock_api_hook
836            .expect_error()
837            .once()
838            .in_sequence(&mut seq)
839            .returning(|_, _, _| {});
840
841        // finally: Provider, Invocation, Client, API
842        mock_provider_hook
843            .expect_finally()
844            .once()
845            .in_sequence(&mut seq)
846            .returning(|_, _, _| {});
847        mock_invocation_hook
848            .expect_finally()
849            .once()
850            .in_sequence(&mut seq)
851            .returning(|_, _, _| {});
852        mock_client_hook
853            .expect_finally()
854            .once()
855            .in_sequence(&mut seq)
856            .returning(|_, _, _| {});
857        mock_api_hook
858            .expect_finally()
859            .once()
860            .in_sequence(&mut seq)
861            .returning(|_, _, _| {});
862
863        provider
864            .expect_hooks()
865            .return_const(vec![HookWrapper::new(mock_provider_hook)]);
866        provider.expect_initialize().return_const(());
867        provider
868            .expect_metadata()
869            .return_const(ProviderMetadata::default());
870
871        api.set_provider(provider).await;
872        api.add_hook(mock_api_hook).await;
873        client = client.with_hook(mock_client_hook);
874
875        let eval = EvaluationOptions::default().with_hook(mock_invocation_hook);
876        let result = client.get_bool_value("flag", None, Some(&eval)).await;
877
878        assert!(result.is_err());
879    }
880
881    #[spec(
882        number = "4.4.7",
883        text = "If an error occurs in the before hooks, the default value MUST be returned."
884    )]
885    #[test]
886    fn default_value_covered_by_implementing_default_trait() {}
887
888    fn error<T>() -> Result<T, EvaluationError> {
889        Err(EvaluationError {
890            code: EvaluationErrorCode::General("error".to_string()),
891            message: None,
892        })
893    }
894}