Skip to main content

waterui_core/foundation/
extract.rs

1//! This module provides mechanisms for extracting values from the Environment.
2//!
3//! It defines the `Extractor` trait for types that can be extracted from an
4//! Environment, along with implementations for common types.
5//! The `Use<T>` wrapper provides a convenient way to extract specific types
6//! from the environment, while [`State<T>`] supports cloneable state values
7//! injected into the environment for action handlers.
8
9use core::any::{TypeId, type_name};
10use core::ops::{Deref, DerefMut};
11
12use crate::Environment;
13use alloc::{collections::BTreeMap, format};
14use anyhow::Error;
15
16/// A trait for extracting values from an Environment.
17///
18/// Types implementing this trait can be extracted from an Environment instance.
19/// This is useful for dependency injection and accessing shared resources.
20pub trait Extractor: 'static + Sized {
21    /// Attempts to extract an instance of `Self` from the given environment.
22    ///
23    /// # Errors
24    /// Returns an error if extraction fails, for example if the required value is not present in the environment.
25    fn extract(env: &Environment) -> Result<Self, Error>;
26
27    /// Attempts to extract an instance of `Self` from the given environment for
28    /// an action handler invocation.
29    ///
30    /// This variant can track per-type extraction order when a single action
31    /// asks for multiple instances of the same extractor, such as repeated
32    /// [`State<T>`] parameters.
33    ///
34    /// # Errors
35    ///
36    /// Returns an error if extraction fails for the current action invocation.
37    fn extract_from_action(env: &Environment, state: &mut ExtractionState) -> Result<Self, Error> {
38        let _ = state;
39        Self::extract(env)
40    }
41}
42
43/// Wrapper struct for values that need to be used from the Environment.
44///
45/// This wrapper enables extracting values by type from an Environment.
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct Use<T: 'static>(pub T);
48
49/// Wrapper for cloneable state values injected into the environment.
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct State<T: 'static>(pub T);
52
53/// Per-action extraction state used to disambiguate repeated extractor types.
54#[derive(Debug, Clone, Default)]
55pub struct ExtractionState {
56    positions: BTreeMap<TypeId, usize>,
57}
58
59impl ExtractionState {
60    /// Returns the next extraction index for the requested type.
61    #[must_use]
62    pub fn take_next<T: 'static>(&mut self) -> usize {
63        let position = self.positions.entry(TypeId::of::<T>()).or_insert(0);
64        let current = *position;
65        *position += 1;
66        current
67    }
68}
69
70impl Extractor for Environment {
71    /// Extracts the Environment itself by creating a clone.
72    fn extract(env: &Environment) -> Result<Self, Error> {
73        Ok(env.clone())
74    }
75}
76
77impl<T> Deref for Use<T> {
78    type Target = T;
79
80    fn deref(&self) -> &Self::Target {
81        &self.0
82    }
83}
84
85impl<T> DerefMut for Use<T> {
86    fn deref_mut(&mut self) -> &mut Self::Target {
87        &mut self.0
88    }
89}
90
91impl<T> Deref for State<T> {
92    type Target = T;
93
94    fn deref(&self) -> &Self::Target {
95        &self.0
96    }
97}
98
99impl<T> DerefMut for State<T> {
100    fn deref_mut(&mut self) -> &mut Self::Target {
101        &mut self.0
102    }
103}
104
105impl<T: Extractor> Extractor for Option<T> {
106    /// Converts a regular extraction into an optional extraction.
107    ///
108    /// This implementation allows for graceful handling of extraction failures
109    /// by converting the error case into a `None` value.
110    fn extract(env: &Environment) -> Result<Self, Error> {
111        Ok(<T as Extractor>::extract(env).ok())
112    }
113
114    fn extract_from_action(env: &Environment, state: &mut ExtractionState) -> Result<Self, Error> {
115        let snapshot = state.clone();
116        T::extract_from_action(env, state).map_or_else(
117            |_| {
118                *state = snapshot;
119                Ok(None)
120            },
121            |value| Ok(Some(value)),
122        )
123    }
124}
125
126impl<T: 'static + Clone> Extractor for Use<T> {
127    /// Extracts a value of type T from the Environment.
128    ///
129    /// # Errors
130    /// Returns an error if the requested type is not present in the Environment.
131    fn extract(env: &Environment) -> Result<Self, Error> {
132        env.get::<T>().map_or_else(
133            || {
134                Err(Error::msg(format!(
135                    "Environment value `{}` not found",
136                    type_name::<T>()
137                )))
138            },
139            |value| Ok(Self(value.clone())),
140        )
141    }
142}
143
144impl<T: 'static + Clone> Extractor for State<T> {
145    fn extract(env: &Environment) -> Result<Self, Error> {
146        env.get::<Self>().map_or_else(
147            || {
148                Err(Error::msg(format!(
149                    "Environment state `{}` not found",
150                    type_name::<T>()
151                )))
152            },
153            |value| Ok(value.clone()),
154        )
155    }
156
157    fn extract_from_action(env: &Environment, state: &mut ExtractionState) -> Result<Self, Error> {
158        let position = state.take_next::<Self>();
159        env.get_nth::<Self>(position).map_or_else(
160            || {
161                Err(Error::msg(format!(
162                    "Environment state `{}` not found at position {}",
163                    type_name::<T>(),
164                    position
165                )))
166            },
167            |value| Ok(value.clone()),
168        )
169    }
170}
171
172// Tuple extractors for combining multiple extractions
173macro_rules! impl_tuple_extractor {
174    ($($T:ident),+) => {
175        impl<$($T: Extractor),+> Extractor for ($($T,)+) {
176            fn extract(env: &Environment) -> Result<Self, Error> {
177                Ok(($($T::extract(env)?,)+))
178            }
179
180            fn extract_from_action(
181                env: &Environment,
182                state: &mut ExtractionState,
183            ) -> Result<Self, Error> {
184                Ok(($($T::extract_from_action(env, state)?,)+))
185            }
186        }
187    };
188}
189
190impl_tuple_extractor!(A, B);
191impl_tuple_extractor!(A, B, C);
192impl_tuple_extractor!(A, B, C, D);
193impl_tuple_extractor!(A, B, C, D, E);
194impl_tuple_extractor!(A, B, C, D, E, F);
195impl_tuple_extractor!(A, B, C, D, E, F, G);
196impl_tuple_extractor!(A, B, C, D, E, F, G, H);