Skip to main content

sofie_flags/
gated.rs

1use sofie_core::Result;
2use sofie_db::{Call, Outcome, Plan, Reply, Stamp};
3
4use crate::{Feature, Flags, Subject};
5
6/// Acts for no principal, so a flag only some principals have is off for it.
7pub fn gated<H>(flags: &Flags, feature: Feature, handler: H) -> Gated<H> {
8    Gated {
9        flags: flags.clone(),
10        feature,
11        handler,
12    }
13}
14
15#[derive(Clone)]
16pub struct Gated<H> {
17    flags: Flags,
18    feature: Feature,
19    handler: H,
20}
21
22impl<C: Send, Args, H: Call<C, Args>> Call<C, Args> for Gated<H> {
23    type Outcome = Skippable<H::Outcome>;
24
25    async fn call(&self, context: &mut C) -> Result<Self::Outcome> {
26        let features = self.flags.evaluate(Subject::default());
27        if !features.enabled(self.feature) {
28            let name = self.feature.name();
29            tracing::info!(feature_flag.key = name, "skipped: its feature is off");
30            return Ok(Skippable(None));
31        }
32        sofie_telemetry::attach_feature_flags(&tracing::Span::current(), move || {
33            features.evaluated()
34        });
35        self.handler
36            .call(context)
37            .await
38            .map(|outcome| Skippable(Some(outcome)))
39    }
40}
41
42pub struct Skippable<O>(Option<O>);
43
44impl<O: Outcome> Outcome for Skippable<O> {
45    fn plan(&self, plan: &mut Plan<'_>) -> Result<()> {
46        self.0.as_ref().map_or(Ok(()), |outcome| outcome.plan(plan))
47    }
48
49    fn committed(&mut self, stamp: Stamp) {
50        if let Some(outcome) = &mut self.0 {
51            outcome.committed(stamp);
52        }
53    }
54
55    fn reply(self) -> Result<Reply> {
56        self.0.map_or_else(|| ().reply(), Outcome::reply)
57    }
58}