Skip to main content

waterui_core/foundation/
env.rs

1//! Environment management module for sharing data across views.
2//!
3//! This module provides functionality for creating and managing environment contexts
4//! that can be passed through the view hierarchy. The environment is a type-based
5//! key-value store where types serve as unique keys.
6//!
7//! The main components are:
8//! - `Environment`: A store for typed values that can be passed between views
9//! - `UseEnv`: A view that allows consuming environment values
10//! - `With`: A view that extends the environment with additional values
11//!
12//! # Example
13//!
14//! ```rust
15//! use waterui_core::{Environment,env::use_env};
16//!
17//! // Create an environment with a string value
18//! let env = Environment::new().with(String::from("Hello, world!"));
19//!
20//! // Access the value in a child view
21//! // let view = use_env(|env: &Environment| {
22//! //     if let Some(message) = env.get::<String>() {
23//! //         message
24//! //     } else {
25//! //         "No message found".to_string()
26//! //     }
27//! // });
28//! ```
29
30use core::{
31    any::{Any, TypeId},
32    fmt::Debug,
33    marker::PhantomData,
34};
35
36use alloc::{collections::BTreeMap, rc::Rc, vec::Vec};
37
38/// An `Environment` stores a map of types to values.
39///
40/// Each type can have at most one value in the environment. The environment
41/// is used to pass contextual information from parent views to child views.
42///
43/// # Examples
44///
45/// ```
46/// use waterui_core::Environment;
47///
48/// let mut env = Environment::new();
49/// env.insert(String::from("hello"));
50///
51/// // Get the value back
52/// assert_eq!(env.get::<String>(), Some(&String::from("hello")));
53///
54/// // Remove the value
55/// env.remove::<String>();
56/// assert_eq!(env.get::<String>(), None);
57/// ```
58#[derive(Debug, Clone)]
59pub struct Environment {
60    state: Rc<EnvironmentState>,
61}
62
63#[derive(Debug, Clone)]
64enum EnvironmentState {
65    Map(BTreeMap<TypeId, Rc<dyn Any>>),
66    Overlay {
67        parent: Rc<Self>,
68        key: TypeId,
69        entry: EnvironmentEntry,
70    },
71}
72
73#[derive(Debug, Clone)]
74enum EnvironmentEntry {
75    Present(Rc<dyn Any>),
76    Removed,
77}
78
79impl MetadataKey for Environment {}
80
81impl Default for Environment {
82    fn default() -> Self {
83        Self::new()
84    }
85}
86
87use crate::{
88    View,
89    components::Metadata,
90    extract::Extractor,
91    metadata::MetadataKey,
92    plugin::Plugin,
93    view::{Hook, ViewConfiguration},
94};
95
96/// A type-indexed storage container for values in an environment.
97///
98/// This struct allows storing values of any type `V` indexed by a type key `K`.
99#[derive(Debug)]
100pub struct Store<K, V> {
101    key: PhantomData<K>,
102    value: V,
103}
104
105impl<K, V> Store<K, V> {
106    /// Creates a new store with the given value.
107    #[must_use]
108    pub const fn new(value: V) -> Self {
109        Self {
110            key: PhantomData,
111            value,
112        }
113    }
114
115    /// Returns a reference to the stored value.
116    #[must_use]
117    pub const fn value(&self) -> &V {
118        &self.value
119    }
120}
121
122impl Environment {
123    /// Returns a stable identity for this environment overlay chain.
124    #[must_use]
125    pub fn identity(&self) -> usize {
126        Rc::as_ptr(&self.state) as usize
127    }
128
129    fn insert_any(&mut self, key: TypeId, value: Rc<dyn Any>) {
130        match Rc::get_mut(&mut self.state) {
131            Some(EnvironmentState::Map(map)) => {
132                map.insert(key, value);
133            }
134            Some(EnvironmentState::Overlay {
135                key: overlay_key,
136                entry,
137                ..
138            }) if *overlay_key == key => {
139                *entry = EnvironmentEntry::Present(value);
140            }
141            _ => self.push_overlay(key, EnvironmentEntry::Present(value)),
142        }
143    }
144
145    fn lookup_any_in_state(state: &EnvironmentState, key: TypeId) -> Option<&Rc<dyn Any>> {
146        match state {
147            EnvironmentState::Map(map) => map.get(&key),
148            EnvironmentState::Overlay {
149                parent,
150                key: overlay_key,
151                entry,
152            } => {
153                if *overlay_key == key {
154                    match entry {
155                        EnvironmentEntry::Present(value) => Some(value),
156                        EnvironmentEntry::Removed => None,
157                    }
158                } else {
159                    Self::lookup_any_in_state(parent.as_ref(), key)
160                }
161            }
162        }
163    }
164
165    fn lookup_any(&self, key: TypeId) -> Option<&Rc<dyn Any>> {
166        Self::lookup_any_in_state(self.state.as_ref(), key)
167    }
168
169    fn push_overlay(&mut self, key: TypeId, entry: EnvironmentEntry) {
170        self.state = Rc::new(EnvironmentState::Overlay {
171            parent: self.state.clone(),
172            key,
173            entry,
174        });
175    }
176
177    fn extend_from_state(&mut self, state: &EnvironmentState) {
178        match state {
179            EnvironmentState::Map(map) => {
180                for (key, value) in map {
181                    self.insert_any(*key, value.clone());
182                }
183            }
184            EnvironmentState::Overlay { parent, key, entry } => {
185                self.extend_from_state(parent.as_ref());
186                self.push_overlay(*key, entry.clone());
187            }
188        }
189    }
190
191    fn collect_matches_in_state<'a, T: 'static>(
192        state: &'a EnvironmentState,
193        matches: &mut Vec<&'a T>,
194    ) {
195        match state {
196            EnvironmentState::Map(map) => {
197                if let Some(value) = map.get(&TypeId::of::<T>()) {
198                    matches.push(
199                        value
200                            .downcast_ref::<T>()
201                            .expect("failed to downcast value while collecting environment state"),
202                    );
203                }
204            }
205            EnvironmentState::Overlay { parent, key, entry } => {
206                Self::collect_matches_in_state(parent.as_ref(), matches);
207                if *key != TypeId::of::<T>() {
208                    return;
209                }
210                match entry {
211                    EnvironmentEntry::Present(value) => matches.push(
212                        value
213                            .downcast_ref::<T>()
214                            .expect("failed to downcast value while collecting environment state"),
215                    ),
216                    EnvironmentEntry::Removed => matches.clear(),
217                }
218            }
219        }
220    }
221
222    /// Creates a new empty environment.
223    #[must_use]
224    pub fn new() -> Self {
225        Self {
226            state: Rc::new(EnvironmentState::Map(BTreeMap::new())),
227        }
228    }
229
230    /// Stores a value in the environment indexed by type `K`.
231    ///
232    /// # Arguments
233    /// * `value` - The value to store
234    #[must_use]
235    pub fn store<K: 'static, V: 'static>(mut self, value: V) -> Self {
236        self.insert(Store {
237            key: PhantomData::<K>,
238            value,
239        });
240        self
241    }
242
243    /// Queries for a value in the environment indexed by type `K`.
244    ///
245    /// # Returns
246    /// An optional reference to the stored value
247    #[must_use]
248    pub fn query<K: 'static, V: 'static>(&self) -> Option<&V> {
249        self.get::<Store<K, V>>().map(|s| &s.value)
250    }
251
252    /// Installs a plugin into the environment.
253    ///
254    /// Plugins can register values or modifiers that will be available to all views.
255    pub fn install(&mut self, plugin: impl Plugin) -> &mut Self {
256        plugin.install(self);
257        self
258    }
259
260    /// Inserts a value into the environment.
261    ///
262    /// If a value of the same type already exists, it will be replaced.
263    pub fn insert<T: 'static>(&mut self, value: T) {
264        let key = TypeId::of::<T>();
265        let value = Rc::new(value) as Rc<dyn Any>;
266        self.insert_any(key, value);
267    }
268
269    /// Inserts a view configuration hook into the environment.
270    ///
271    /// Hooks allow you to intercept and modify view configurations globally.
272    pub fn insert_hook<T: ViewConfiguration, V: View>(
273        &mut self,
274        hook: impl Fn(&Self, T) -> V + 'static,
275    ) {
276        self.insert(Hook::new(hook));
277    }
278
279    /// Removes a value from the environment by its type.
280    pub fn remove<T: 'static>(&mut self) {
281        let key = TypeId::of::<T>();
282        match Rc::get_mut(&mut self.state) {
283            Some(EnvironmentState::Map(map)) => {
284                map.remove(&key);
285            }
286            Some(EnvironmentState::Overlay {
287                key: overlay_key,
288                entry,
289                ..
290            }) if *overlay_key == key => {
291                *entry = EnvironmentEntry::Removed;
292            }
293            _ => self.push_overlay(key, EnvironmentEntry::Removed),
294        }
295    }
296
297    /// Adds a value to the environment and returns the modified environment.
298    ///
299    /// This is a fluent interface for chaining multiple additions.
300    pub fn with<T: 'static>(&mut self, value: T) -> &mut Self {
301        self.insert(value);
302        self
303    }
304
305    /// Returns a new environment that overlays a value on top of the current state.
306    ///
307    /// This is an O(1) operation backed by structural sharing and avoids copying
308    /// the underlying map.
309    #[must_use]
310    pub fn extending<T: 'static>(&self, value: T) -> Self {
311        Self {
312            state: Rc::new(EnvironmentState::Overlay {
313                parent: self.state.clone(),
314                key: TypeId::of::<T>(),
315                entry: EnvironmentEntry::Present(Rc::new(value) as Rc<dyn Any>),
316            }),
317        }
318    }
319
320    /// Retrieves a reference to a value from the environment by its type.
321    ///
322    /// Returns `None` if no value of the requested type exists.
323    ///
324    /// # Panics
325    ///
326    /// This function will panic if a value of the requested type exists in the environment,
327    /// but the stored value cannot be downcast to the requested type. This should never happen
328    /// if only `insert` and `with` are used to add values.
329    #[must_use]
330    #[allow(clippy::coerce_container_to_any)]
331    pub fn get<T: 'static>(&self) -> Option<&T> {
332        self.lookup_any(TypeId::of::<T>())
333            .map(|v| v.downcast_ref::<T>().expect("failed to downcast value"))
334    }
335
336    /// Retrieves the `index`-th visible value of type `T`, counting from the
337    /// nearest (most recently overlaid) value outwards.
338    ///
339    /// `get_nth(0)` therefore always agrees with [`get`](Self::get).
340    ///
341    /// This is primarily used by action extractors to support repeated
342    /// same-typed [`crate::extract::State`] values in a single handler, and
343    /// nearest-first is what gives those handlers positional correspondence:
344    /// `.state(&a).state(&b)` wraps `b` outermost, so `b`'s overlay is applied
345    /// first and `a` ends up nearest — and `a` is what the first `State<T>`
346    /// handler parameter must bind to.
347    #[must_use]
348    pub fn get_nth<T: 'static>(&self, index: usize) -> Option<&T> {
349        let mut matches = Vec::new();
350        Self::collect_matches_in_state(self.state.as_ref(), &mut matches);
351        matches.into_iter().nth_back(index)
352    }
353
354    /// Retrieves a reference to a value from the environment by its type,
355    /// inserting a new value if it does not already exist.
356    ///
357    /// The new value is created by calling the provided closure `f`.
358    ///
359    /// # Panics
360    ///
361    /// Panics if insertion succeeds but the inserted value cannot be retrieved
362    /// back as `T`, which indicates a corrupted environment entry.
363    #[must_use]
364    pub fn get_or_insert_with<T: 'static, F: FnOnce() -> T>(&mut self, f: F) -> &T {
365        if self.lookup_any(TypeId::of::<T>()).is_none() {
366            self.insert(f());
367        }
368        self.get::<T>()
369            .expect("value missing from environment after insertion")
370    }
371
372    /// Extracts a value from the environment using the `Extractor` trait.
373    ///
374    /// This is a convenience method for extracting values that implement `Extractor`.
375    ///
376    /// # Errors
377    ///
378    /// Returns an error if extraction fails (e.g., value not found).
379    pub fn extract<T: Extractor>(&self) -> Result<T, anyhow::Error> {
380        T::extract(self)
381    }
382
383    /// Replays this environment's overlays on top of `parent`.
384    ///
385    /// This preserves repeated same-typed overlay entries instead of collapsing
386    /// them into a single visible value.
387    #[must_use]
388    pub fn layered_on(&self, parent: &Self) -> Self {
389        let mut layered = parent.clone();
390        layered.extend_from_state(self.state.as_ref());
391        layered
392    }
393}
394
395/// A view that provides access to the environment.
396///
397/// `UseEnv` allows child views to access values stored in the environment
398/// through a handler function.
399#[derive(Debug, Clone)]
400pub struct UseEnv<F> {
401    handler: F,
402}
403
404impl<F> UseEnv<F> {
405    /// Creates a new `UseEnv` with the provided handler.
406    #[must_use]
407    pub const fn new(handler: F) -> Self {
408        Self { handler }
409    }
410}
411
412/// Creates a view that can access the environment.
413///
414/// This function takes a closure that receives a value extracted from the environment
415/// and returns a view. The closure parameter type must implement `Extractor`.
416///
417/// # Example
418///
419/// ```
420/// use nami::Binding;
421/// use waterui_core::{State, env::use_env, raw_view};
422///
423/// // A leaf view that keeps the binding rather than reading it, so the value
424/// // stays reactive.
425/// struct Counter(Binding<i32>);
426/// raw_view!(Counter);
427///
428/// // Extract one value from the environment.
429/// let view = use_env(|State(count): State<Binding<i32>>| Counter(count));
430///
431/// // Extract several at once with a tuple.
432/// let pair = use_env(|(State(count), State(step)): (State<Binding<i32>>, State<Binding<i32>>)| {
433///     let _ = step;
434///     Counter(count)
435/// });
436/// ```
437///
438/// # Panics
439///
440/// Panics if [`Extractor::extract`] fails for `E`.
441#[must_use]
442pub fn use_env<E, V, F>(f: F) -> UseEnv<impl FnOnce(&Environment) -> V>
443where
444    E: Extractor,
445    V: View,
446    F: FnOnce(E) -> V + 'static,
447{
448    UseEnv::new(move |env: &Environment| {
449        let extracted = E::extract(env).expect("failed to extract value from environment");
450        f(extracted)
451    })
452}
453
454impl<V, F> View for UseEnv<F>
455where
456    V: View,
457    F: FnOnce(&Environment) -> V + 'static,
458{
459    fn body(self, env: &Environment) -> impl View {
460        (self.handler)(env)
461    }
462}
463
464/// A view that extends the environment with an additional value.
465///
466/// `With` wraps a child view and provides an extended environment that
467/// includes a new value of type `T`.
468#[derive(Debug, Clone)]
469pub struct With<V, T> {
470    content: V,
471    value: T,
472}
473
474impl<V: View, T: 'static> With<V, T> {
475    /// Creates a new `With` view that wraps the provided content and adds
476    /// the given value to the environment for all child views.
477    pub const fn new(content: V, value: T) -> Self {
478        Self { content, value }
479    }
480}
481
482/// Wraps a view and provides an extended environment value.
483pub const fn with<V: View, T: 'static>(view: V, value: T) -> With<V, T> {
484    With::new(view, value)
485}
486
487impl<V: View, T: 'static> View for With<V, T> {
488    fn body(self, env: &Environment) -> impl View {
489        let env = env.extending(self.value);
490        Metadata::new(self.content, env)
491    }
492}
493
494#[cfg(test)]
495mod tests {
496    use alloc::string::String;
497
498    use super::*;
499
500    #[test]
501    fn extending_reuses_parent_state_via_overlay() {
502        let mut base = Environment::new();
503        base.insert(7_u32);
504        let parent_state = base.state.clone();
505
506        let extended = base.extending(11_u64);
507        match extended.state.as_ref() {
508            EnvironmentState::Overlay { parent, key, entry } => {
509                assert!(Rc::ptr_eq(parent, &parent_state));
510                assert_eq!(*key, TypeId::of::<u64>());
511                match entry {
512                    EnvironmentEntry::Present(value) => {
513                        assert_eq!(value.downcast_ref::<u64>(), Some(&11_u64));
514                    }
515                    EnvironmentEntry::Removed => {
516                        panic!("overlay entry unexpectedly removed");
517                    }
518                }
519            }
520            EnvironmentState::Map(_) => panic!("extending must create overlay state"),
521        }
522    }
523
524    #[test]
525    fn get_nth_counts_from_the_nearest_overlay_outwards() {
526        let env = Environment::new()
527            .extending(1_i32)
528            .extending(2_i32)
529            .extending(3_i32);
530
531        assert_eq!(env.get_nth::<i32>(0), Some(&3_i32));
532        assert_eq!(env.get_nth::<i32>(1), Some(&2_i32));
533        assert_eq!(env.get_nth::<i32>(2), Some(&1_i32));
534        assert_eq!(env.get_nth::<i32>(3), None);
535    }
536
537    #[test]
538    fn get_nth_zero_agrees_with_get() {
539        let env = Environment::new().extending(1_i32).extending(2_i32);
540
541        assert_eq!(env.get_nth::<i32>(0), env.get::<i32>());
542    }
543
544    #[test]
545    fn deep_overlay_chain_preserves_parent_visibility() {
546        let mut env = Environment::new();
547        env.insert(String::from("root"));
548        let env = env.extending(3_i32).extending(true).extending(9_u8);
549
550        assert_eq!(env.get::<String>(), Some(&String::from("root")));
551        assert_eq!(env.get::<i32>(), Some(&3_i32));
552        assert_eq!(env.get::<bool>(), Some(&true));
553        assert_eq!(env.get::<u8>(), Some(&9_u8));
554    }
555}