intrepid_core/context.rs
1use crate::Router;
2
3/// An action context, describing any additional context that the action may carry.
4/// This is used to distribute system setup information when invoking them as actions,
5/// for example, when systems have defined action routes.
6#[derive(Clone, Debug, Default)]
7pub enum ActionContext<State> {
8 /// The action is being invoked as a single unit.
9 #[default]
10 Unit,
11 /// The action is a collection of actions.
12 System(SystemContext<State>),
13}
14
15impl<State> From<SystemContext<State>> for ActionContext<State> {
16 fn from(context: SystemContext<State>) -> Self {
17 Self::System(context)
18 }
19}
20
21/// A router for actions that can be invoked as services.
22#[derive(Clone, Debug, Default)]
23pub struct SystemContext<State> {
24 /// Any routes that have been mounted on a routed system.
25 pub router: Router<State>,
26}
27
28impl<State> From<Router<State>> for SystemContext<State> {
29 fn from(router: Router<State>) -> Self {
30 Self { router }
31 }
32}
33
34/// An error indicating that the state is not ready.
35#[derive(Debug, thiserror::Error)]
36#[error("Attempted to access state when it was not ready.")]
37pub struct StateNotReadyError;
38
39/// The context of the action. This is handed into actions when they're invoked,
40/// and is the underpinning mechanism for distributing side-effects, as well as
41/// action composition.
42///
43/// At compile time, the action context grants us, and the consumers, the ability
44/// to structure action systems in typesafe ways. At runtime, the context is used
45/// to distribute the state of the system, as well as any additional crate-specific
46/// plumbing that the action may require.
47///
48#[derive(Clone, Debug, Default)]
49pub struct Context<State> {
50 /// The action context, describing any additional context that the action may carry.
51 pub action: ActionContext<State>,
52 /// The state of the system, determined by the consumer. This can be absent if the
53 /// various actions are called before being properly initialized. The type system
54 /// should prevent this most of the time by requiring the state to be present before
55 /// invoking actions or calling them as services.
56 pub state: Option<State>,
57}
58
59impl<State> Context<State> {
60 /// Create a new context with the given action.
61 pub fn from_action(action: ActionContext<State>) -> Self {
62 Self {
63 action,
64 state: None,
65 }
66 }
67
68 /// Create a new context with the given state.
69 pub fn from_state(state: State) -> Self {
70 Self {
71 action: ActionContext::Unit,
72 state: Some(state),
73 }
74 }
75
76 /// Return the state. Returns an error if the state is not ready.
77 pub fn state(&self) -> Result<State, StateNotReadyError>
78 where
79 State: Clone,
80 {
81 match self.state.clone() {
82 Some(state) => Ok(state),
83 None => Err(StateNotReadyError),
84 }
85 }
86
87 /// Create a new context with the given state and action context.
88 pub fn with_action(action: ActionContext<State>, state: State) -> Self {
89 Self {
90 action,
91 state: Some(state),
92 }
93 }
94}
95
96impl<State> From<ActionContext<State>> for Context<State> {
97 fn from(action: ActionContext<State>) -> Self {
98 Self::from_action(action)
99 }
100}
101
102impl<State> From<State> for Context<State> {
103 fn from(state: State) -> Self {
104 Self::from_state(state)
105 }
106}