intrepid_core/
context.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
use crate::Router;

/// An action context, describing any additional context that the action may carry.
/// This is used to distribute system setup information when invoking them as actions,
/// for example, when systems have defined action routes.
#[derive(Clone, Debug, Default)]
pub enum ActionContext<State> {
    /// The action is being invoked as a single unit.
    #[default]
    Unit,
    /// The action is a collection of actions.
    System(SystemContext<State>),
}

impl<State> From<SystemContext<State>> for ActionContext<State> {
    fn from(context: SystemContext<State>) -> Self {
        Self::System(context)
    }
}

/// A router for actions that can be invoked as services.
#[derive(Clone, Debug, Default)]
pub struct SystemContext<State> {
    /// Any routes that have been mounted on a routed system.
    pub router: Router<State>,
}

impl<State> From<Router<State>> for SystemContext<State> {
    fn from(router: Router<State>) -> Self {
        Self { router }
    }
}

/// An error indicating that the state is not ready.
#[derive(Debug, thiserror::Error)]
#[error("Attempted to access state when it was not ready.")]
pub struct StateNotReadyError;

/// The context of the action. This is handed into actions when they're invoked,
/// and is the underpinning mechanism for distributing side-effects, as well as
/// action composition.
///
/// At compile time, the action context grants us, and the consumers, the ability
/// to structure action systems in typesafe ways. At runtime, the context is used
/// to distribute the state of the system, as well as any additional crate-specific
/// plumbing that the action may require.
///
#[derive(Clone, Debug, Default)]
pub struct Context<State> {
    /// The action context, describing any additional context that the action may carry.
    pub action: ActionContext<State>,
    /// The state of the system, determined by the consumer. This can be absent if the
    /// various actions are called before being properly initialized. The type system
    /// should prevent this most of the time by requiring the state to be present before
    /// invoking actions or calling them as services.
    pub state: Option<State>,
}

impl<State> Context<State> {
    /// Create a new context with the given action.
    pub fn from_action(action: ActionContext<State>) -> Self {
        Self {
            action,
            state: None,
        }
    }

    /// Create a new context with the given state.
    pub fn from_state(state: State) -> Self {
        Self {
            action: ActionContext::Unit,
            state: Some(state),
        }
    }

    /// Return the state. Returns an error if the state is not ready.
    pub fn state(&self) -> Result<State, StateNotReadyError>
    where
        State: Clone,
    {
        match self.state.clone() {
            Some(state) => Ok(state),
            None => Err(StateNotReadyError),
        }
    }

    /// Create a new context with the given state and action context.
    pub fn with_action(action: ActionContext<State>, state: State) -> Self {
        Self {
            action,
            state: Some(state),
        }
    }
}

impl<State> From<ActionContext<State>> for Context<State> {
    fn from(action: ActionContext<State>) -> Self {
        Self::from_action(action)
    }
}

impl<State> From<State> for Context<State> {
    fn from(state: State) -> Self {
        Self::from_state(state)
    }
}