featureflag/
context.rs

1//! Context values for context-aware features.
2
3mod stack;
4
5use std::{fmt, sync::Arc};
6
7use crate::{
8    context::stack::GLOBAL_CONTEXT_STACK,
9    evaluator::{Evaluator, EvaluatorRef, WeakEvaluatorRef, get_default},
10    extensions::Extensions,
11    fields::Fields,
12};
13
14/// A context for evaluating feature flags.
15///
16/// A context contains an [`EvaluatorRef`], a parent context, and a set of custom
17/// extensions.
18///
19/// When a context is created, the evaluator can store custom data in the context
20/// based on the context fields.
21#[derive(Clone)]
22pub struct Context {
23    data: Option<Arc<Data>>,
24}
25
26struct Data {
27    evaluator: WeakEvaluatorRef,
28    parent: Option<Context>,
29    extensions: Extensions,
30}
31
32impl Context {
33    /// Creates a new context with the given fields.
34    ///
35    /// The context is associated with the current evaluator.
36    ///
37    /// In most cases, you should use the [`context!`] macro to create a context
38    /// instead of using this constructor.
39    pub fn new(fields: Fields<'_>) -> Context {
40        Context::new_with_parent(Context::current().as_ref(), fields)
41    }
42
43    /// Creates a new context with the given parent context and fields.
44    ///
45    /// The context is associated with the current evaluator.
46    ///
47    /// In most cases, you should use the [`context!`] macro to create a context
48    /// instead of using this constructor.
49    pub fn new_with_parent(mut parent: Option<&Context>, fields: Fields<'_>) -> Context {
50        if parent.is_some_and(|p| p.is_root()) {
51            parent = None;
52        }
53
54        get_default(|evaluator| {
55            let data = match evaluator {
56                Some(evaluator) => {
57                    let mut data = Data {
58                        evaluator: evaluator.downgrade(),
59                        parent: parent.cloned(),
60                        extensions: Extensions::new(),
61                    };
62
63                    evaluator.on_new_context(ContextRef { data: &mut data }, fields);
64
65                    data
66                }
67                _ => Data {
68                    evaluator: WeakEvaluatorRef::new(),
69                    parent: parent.cloned(),
70                    extensions: Extensions::new(),
71                },
72            };
73
74            Context {
75                data: Some(Arc::new(data)),
76            }
77        })
78    }
79
80    /// Get the root context.
81    ///
82    /// The root context has no parent and is always associated with the current
83    /// evaluator.
84    pub const fn root() -> Context {
85        Context { data: None }
86    }
87
88    /// Check if this context is the root context.
89    pub fn is_root(&self) -> bool {
90        self.data.is_none()
91    }
92
93    /// Get the current context.
94    pub fn current() -> Option<Context> {
95        GLOBAL_CONTEXT_STACK.current()
96    }
97
98    /// Get the current context or the root context if no current context is set.
99    pub fn current_or_root() -> Context {
100        Context::current().unwrap_or(Context::root())
101    }
102
103    /// Get the parent context of this context.
104    ///
105    /// All contexts except the root context have a parent context, so this only
106    /// returns `None` for the root context.
107    pub fn parent(&self) -> Option<&Context> {
108        self.data
109            .as_ref()?
110            .parent
111            .as_ref()
112            .or(Some(const { &Context::root() }))
113    }
114
115    /// Get a read-only reference to the extensions of this context.
116    pub fn extensions(&self) -> &Extensions {
117        self.data
118            .as_ref()
119            .map(|data| &data.extensions)
120            .unwrap_or(const { &Extensions::new() })
121    }
122
123    /// Iterate over this context and its parents.
124    pub fn iter(&self) -> impl Iterator<Item = &Context> {
125        std::iter::successors(Some(self), |context| context.parent())
126    }
127
128    /// Run a function with this context as the current context.
129    pub fn in_scope<F: FnOnce() -> R, R>(&self, f: F) -> R {
130        GLOBAL_CONTEXT_STACK.in_scope(self, f)
131    }
132
133    /// Get the evaluator associated with this context.
134    pub(crate) fn evaluator(&self) -> Option<EvaluatorRef> {
135        match &self.data {
136            Some(data) => data.evaluator.upgrade(),
137            None => {
138                // root context always uses the current default evaluator
139                get_default(|evaluator| evaluator.cloned())
140            }
141        }
142    }
143}
144
145impl fmt::Debug for Context {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        f.debug_struct("Context").finish_non_exhaustive()
148    }
149}
150
151impl Drop for Data {
152    fn drop(&mut self) {
153        if let Some(evaluator) = self.evaluator.upgrade() {
154            evaluator.on_close_context(ContextRef { data: self })
155        }
156    }
157}
158
159/// A mutable reference to a context being created or destroyed.
160pub struct ContextRef<'a> {
161    data: &'a mut Data,
162}
163
164impl ContextRef<'_> {
165    /// Get the parent context of this context.
166    ///
167    /// See [`Context::parent`] for more details.
168    pub fn parent(&self) -> Option<&Context> {
169        self.data.parent.as_ref()
170    }
171
172    /// Get a read-only reference to the extensions of this context.
173    pub fn extensions(&self) -> &Extensions {
174        &self.data.extensions
175    }
176
177    /// Get a mutable reference to the extensions of this context.
178    pub fn extensions_mut(&mut self) -> &mut Extensions {
179        &mut self.data.extensions
180    }
181
182    /// Recursively iterate over this context's parents.
183    ///
184    /// Because the `ContextRef` is used before the context is created, and
185    /// after it is destroyed, the iterator will not include the context itself.
186    pub fn iter(&self) -> impl Iterator<Item = &Context> {
187        self.data.parent.iter().flat_map(Context::iter)
188    }
189
190    pub(crate) fn by_mut(&mut self) -> ContextRef<'_> {
191        ContextRef { data: self.data }
192    }
193}
194
195/// Create a new context with the given fields.
196///
197/// The fields are specified as a comma-separated list of `key = value` pairs.
198/// Field values can be any type that implements the [`ToValue`](crate::value::ToValue) trait.
199///
200/// A parent context can be specified with `parent: <parent>`.
201///
202/// # Examples
203///
204/// ```
205/// let a = context!(foo = 1, bar = "baz");
206/// let b = context!(parent: a, foo = 2);
207/// let c = context!(parent: None, foo = 3);
208/// ```
209#[macro_export]
210macro_rules! context {
211    (parent: $parent:expr $(, $($fields:tt)*)?) => {
212        $crate::context::Context::new_with_parent(
213            $crate::context::AsContextParam::as_context_param(
214                &$parent
215            ),
216            $crate::fields!($($($fields)*)?),
217        )
218    };
219    ($($fields:tt)*) => {
220        $crate::context::Context::new($crate::fields!($($fields)*))
221    };
222}
223
224// Allow references from doc comments before the macro definition.
225#[allow(unused_imports)]
226use crate::context;
227
228/// Helper trait for macros to accept different types as context parameters.
229#[doc(hidden)]
230pub trait AsContextParam {
231    fn as_context_param(&self) -> Option<&Context>;
232}
233
234impl AsContextParam for Context {
235    fn as_context_param(&self) -> Option<&Context> {
236        Some(self)
237    }
238}
239
240impl AsContextParam for Option<Context> {
241    fn as_context_param(&self) -> Option<&Context> {
242        self.as_ref()
243    }
244}
245
246impl AsContextParam for () {
247    fn as_context_param(&self) -> Option<&Context> {
248        None
249    }
250}