waterui-core 0.5.1

Core functionality for the WaterUI framework
Documentation
//! This module provides mechanisms for extracting values from the Environment.
//!
//! It defines the `Extractor` trait for types that can be extracted from an
//! Environment, along with implementations for common types.
//! The `Use<T>` wrapper provides a convenient way to extract specific types
//! from the environment, while [`State<T>`] supports cloneable state values
//! injected into the environment for action handlers.

use core::any::{TypeId, type_name};
use core::ops::{Deref, DerefMut};

use crate::Environment;
use alloc::{collections::BTreeMap, format};
use anyhow::Error;

/// A trait for extracting values from an Environment.
///
/// Types implementing this trait can be extracted from an Environment instance.
/// This is useful for dependency injection and accessing shared resources.
#[diagnostic::on_unimplemented(
    message = "`{Self}` cannot be extracted from a WaterUI environment",
    label = "expected a type implementing `Extractor`",
    note = "Handler and `use_env` parameters must be extractors: `#[state]` makes an owned `Clone` type extractable from `.state(&value)` injections, `State<T>` wraps an injected value of a foreign type, `Use<T>` reads an environment value, `Option<E>` tolerates a missing one, tuples combine extractors, and `impl_extractor!` marks a `Clone` type installed as an environment value."
)]
pub trait Extractor: 'static + Sized {
    /// Attempts to extract an instance of `Self` from the given environment.
    ///
    /// # Errors
    /// Returns an error if extraction fails, for example if the required value is not present in the environment.
    fn extract(env: &Environment) -> Result<Self, Error>;

    /// Attempts to extract an instance of `Self` from the given environment for
    /// an action handler invocation.
    ///
    /// This variant can track per-type extraction order when a single action
    /// asks for multiple instances of the same extractor, such as repeated
    /// [`State<T>`] parameters.
    ///
    /// # Errors
    ///
    /// Returns an error if extraction fails for the current action invocation.
    fn extract_from_action(env: &Environment, state: &mut ExtractionState) -> Result<Self, Error> {
        let _ = state;
        Self::extract(env)
    }
}

/// Wrapper struct for values that need to be used from the Environment.
///
/// This wrapper enables extracting values by type from an Environment.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Use<T: 'static>(pub T);

/// Wrapper for cloneable state values injected into the environment.
///
/// `.state(&value)` installs `State(value)` so handlers can recover it. For a
/// `Clone` type the app owns, `#[state]` implements [`Extractor`] on the type
/// itself and the wrapper disappears from handler signatures; `State<T>`
/// remains the extractor for values of foreign types such as `Binding<T>`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct State<T: 'static>(pub T);

/// Per-action extraction state used to disambiguate repeated extractor types.
#[derive(Debug, Clone, Default)]
pub struct ExtractionState {
    positions: BTreeMap<TypeId, usize>,
}

impl ExtractionState {
    /// Returns the next extraction index for the requested type.
    #[must_use]
    pub fn take_next<T: 'static>(&mut self) -> usize {
        let position = self.positions.entry(TypeId::of::<T>()).or_insert(0);
        let current = *position;
        *position += 1;
        current
    }
}

impl Extractor for Environment {
    /// Extracts the Environment itself by creating a clone.
    fn extract(env: &Environment) -> Result<Self, Error> {
        Ok(env.clone())
    }
}

impl<T> Deref for Use<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T> DerefMut for Use<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl<T> Deref for State<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T> DerefMut for State<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl<T: Extractor> Extractor for Option<T> {
    /// Converts a regular extraction into an optional extraction.
    ///
    /// This implementation allows for graceful handling of extraction failures
    /// by converting the error case into a `None` value.
    fn extract(env: &Environment) -> Result<Self, Error> {
        Ok(<T as Extractor>::extract(env).ok())
    }

    fn extract_from_action(env: &Environment, state: &mut ExtractionState) -> Result<Self, Error> {
        let snapshot = state.clone();
        T::extract_from_action(env, state).map_or_else(
            |_| {
                *state = snapshot;
                Ok(None)
            },
            |value| Ok(Some(value)),
        )
    }
}

impl<T: 'static + Clone> Extractor for Use<T> {
    /// Extracts a value of type T from the Environment.
    ///
    /// # Errors
    /// Returns an error if the requested type is not present in the Environment.
    fn extract(env: &Environment) -> Result<Self, Error> {
        env.get::<T>().map_or_else(
            || {
                Err(Error::msg(format!(
                    "Environment value `{}` not found",
                    type_name::<T>()
                )))
            },
            |value| Ok(Self(value.clone())),
        )
    }
}

impl<T: 'static + Clone> Extractor for State<T> {
    fn extract(env: &Environment) -> Result<Self, Error> {
        env.get::<Self>().map_or_else(
            || {
                Err(Error::msg(format!(
                    "Environment state `{}` not found",
                    type_name::<T>()
                )))
            },
            |value| Ok(value.clone()),
        )
    }

    fn extract_from_action(env: &Environment, state: &mut ExtractionState) -> Result<Self, Error> {
        let position = state.take_next::<Self>();
        env.get_nth::<Self>(position).map_or_else(
            || {
                Err(Error::msg(format!(
                    "Environment state `{}` not found at position {}",
                    type_name::<T>(),
                    position
                )))
            },
            |value| Ok(value.clone()),
        )
    }
}

// Tuple extractors for combining multiple extractions
macro_rules! impl_tuple_extractor {
    ($($T:ident),+) => {
        impl<$($T: Extractor),+> Extractor for ($($T,)+) {
            fn extract(env: &Environment) -> Result<Self, Error> {
                Ok(($($T::extract(env)?,)+))
            }

            fn extract_from_action(
                env: &Environment,
                state: &mut ExtractionState,
            ) -> Result<Self, Error> {
                Ok(($($T::extract_from_action(env, state)?,)+))
            }
        }
    };
}

impl_tuple_extractor!(A, B);
impl_tuple_extractor!(A, B, C);
impl_tuple_extractor!(A, B, C, D);
impl_tuple_extractor!(A, B, C, D, E);
impl_tuple_extractor!(A, B, C, D, E, F);
impl_tuple_extractor!(A, B, C, D, E, F, G);
impl_tuple_extractor!(A, B, C, D, E, F, G, H);

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Environment;
    use waterui_macros::state;

    /// The real `#[state]` expansion — inside `waterui-core` the macro resolves
    /// `crate::extract::…` through `proc_macro_crate`'s `Itself` branch.
    #[state]
    #[derive(Clone)]
    struct Editor {
        title: &'static str,
    }

    #[test]
    fn state_injected_owned_type_extracts_bare() {
        let env = Environment::new().extending(State(Editor { title: "draft" }));

        let editor = env.extract::<Editor>().expect("injected state extracts");
        assert_eq!(editor.title, "draft");
    }

    #[test]
    fn same_type_states_bind_positionally_and_share_positions() {
        // `.state(&first).state(&second)` layers the outer call's value below
        // the inner one's, so the nearest `State<Editor>` is `first`.
        let env = Environment::new()
            .extending(State(Editor { title: "second" }))
            .extending(State(Editor { title: "first" }));

        let mut state = ExtractionState::default();
        let bare = Editor::extract_from_action(&env, &mut state).expect("first parameter");
        // A `State<Editor>` parameter after a bare `Editor` draws the next
        // position of the same channel.
        let wrapped = <State<Editor> as Extractor>::extract_from_action(&env, &mut state)
            .expect("second parameter");

        assert_eq!(bare.title, "first");
        assert_eq!(wrapped.0.title, "second");
    }
}