Skip to content

Commit

Permalink
feat: expose references to Inner
Browse files Browse the repository at this point in the history
Problem: Mutable access to `Inner` is required for ergonomic unit
testing.

Solution: Add `inner` and `inner_mut` to:
- `awaitable::state_machine{UninitializedStateMachine,
  InitializedStateMachine}`
- `blocking::state_machine{UninitializedStateMachine,
  InitializedStateMachine}`
Mark the methods as `unsafe` for the case of an
`InitializedStateMachine` to force users to think critically about
mutating in thise case.

Testing: `cargo test`

Issue: mdeloof#34
  • Loading branch information
Christian Heussy committed Jan 7, 2025
1 parent 3014f00 commit 55d61df
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 9 deletions.
119 changes: 114 additions & 5 deletions statig/src/awaitable/state_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,6 @@ where
{
self.handle_with_context(&(), context).await;
}

/// Get the current state.
pub fn state(&self) -> &M::State {
&self.inner.state
}
}

impl<M> Clone for StateMachine<M>
Expand Down Expand Up @@ -286,6 +281,66 @@ where
pub fn state(&self) -> &M::State {
&self.inner.state
}

/// Get a reference to the [Inner] storage.
///
/// ```
/// # use statig::prelude::*;
/// # #[derive(Default)]
/// # pub struct Blinky {
/// # led: bool,
/// # }
/// #
/// # pub struct Event;
/// #
/// # #[state_machine(
/// # initial = "State::on()",
/// # state(derive(Debug, PartialEq, Eq))
/// # )]
/// # impl Blinky {
/// # #[state]
/// # fn on(event: &Event) -> Response<State> { Handled }
/// # }
/// #
/// # let uninitialized_state_machine = Blinky::default().uninitialized_state_machine();
/// let initialized_state_machine = uninitialized_state_machine.init();
/// assert_eq!(initialized_state_machine.inner().shared_storage.led, false);
/// assert_eq!(initialized_state_machine.inner().state, State::on());
/// ```
pub fn inner(&self) -> &Inner<M> {
&self.inner
}

/// Get a mutable reference to the [Inner] storage.
/// # Safety
///
/// The users is responsible for validating that mutating an
/// [InitializedStateMachine] does not break any invariants.
///
/// ```
/// # use statig::prelude::*;
/// # #[derive(Default)]
/// # pub struct Blinky {
/// # led: bool,
/// # }
/// #
/// # pub struct Event;
/// #
/// # #[state_machine(initial = "State::on()")]
/// # impl Blinky {
/// # #[state]
/// # fn on(event: &Event) -> Response<State> { Handled }
/// # }
/// #
/// # let mut uninitialized_state_machine = Blinky::default().uninitialized_state_machine();
/// let mut initialized_state_machine = uninitialized_state_machine.init();
/// unsafe {
/// initialized_state_machine.inner_mut().shared_storage.led = true;
/// }
/// ```
pub unsafe fn inner_mut(&mut self) -> &mut Inner<M> {
&mut self.inner
}
}

impl<M> Clone for InitializedStateMachine<M>
Expand Down Expand Up @@ -458,6 +513,60 @@ where
state_machine.inner.async_init_with_context(context).await;
state_machine
}
/// Get a reference to the [Inner] storage.
///
/// ```
/// # use statig::prelude::*;
/// # #[derive(Default)]
/// # pub struct Blinky {
/// # led: bool,
/// # }
/// #
/// # pub struct Event;
/// #
/// # #[state_machine(
/// # initial = "State::on()",
/// # state(derive(Debug, PartialEq, Eq))
/// # )]
/// # impl Blinky {
/// # #[state]
/// # fn on(event: &Event) -> Response<State> { Handled }
/// # }
/// #
/// let mut uninitialized_state_machine = Blinky::default().uninitialized_state_machine();
///
/// assert_eq!(uninitialized_state_machine.inner().shared_storage.led, false);
/// assert_eq!(uninitialized_state_machine.inner().state, State::on());
/// ```
pub fn inner(&mut self) -> &Inner<M> {
&self.inner
}

/// Get a mutable reference to the [Inner] storage.
///
/// ```
/// # use statig::prelude::*;
/// # #[derive(Default)]
/// # pub struct Blinky {
/// # led: bool,
/// # }
/// #
/// # pub struct Event;
/// #
/// # #[state_machine(initial = "State::on()")]
/// # impl Blinky {
/// # #[state]
/// # fn on(event: &Event) -> Response<State> { Handled }
/// # }
/// #
/// let mut uninitialized_state_machine = Blinky::default().uninitialized_state_machine();
///
/// uninitialized_state_machine.inner_mut().shared_storage.led = true;
/// uninitialized_state_machine.inner_mut().state = State::on();
/// ```
pub fn inner_mut(&mut self) -> &mut Inner<M> {
&mut self.inner
}
}

impl<M> Clone for UninitializedStateMachine<M>
Expand Down
115 changes: 115 additions & 0 deletions statig/src/blocking/state_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,66 @@ where
pub fn state(&self) -> &M::State {
&self.inner.state
}

/// Get a reference to the [Inner] storage.
///
/// ```
/// # use statig::prelude::*;
/// # #[derive(Default)]
/// # pub struct Blinky {
/// # led: bool,
/// # }
/// #
/// # pub struct Event;
/// #
/// # #[state_machine(
/// # initial = "State::on()",
/// # state(derive(Debug, PartialEq, Eq))
/// # )]
/// # impl Blinky {
/// # #[state]
/// # fn on(event: &Event) -> Response<State> { Handled }
/// # }
/// #
/// # let uninitialized_state_machine = Blinky::default().uninitialized_state_machine();
/// let initialized_state_machine = uninitialized_state_machine.init();
/// assert_eq!(initialized_state_machine.inner().shared_storage.led, false);
/// assert_eq!(initialized_state_machine.inner().state, State::on());
/// ```
pub fn inner(&self) -> &Inner<M> {
&self.inner
}

/// Get a mutable reference to the [Inner] storage.
/// # Safety
///
/// The users is responsible for validating that mutating an
/// [InitializedStateMachine] does not break any invariants.
///
/// ```
/// # use statig::prelude::*;
/// # #[derive(Default)]
/// # pub struct Blinky {
/// # led: bool,
/// # }
/// #
/// # pub struct Event;
/// #
/// # #[state_machine(initial = "State::on()")]
/// # impl Blinky {
/// # #[state]
/// # fn on(event: &Event) -> Response<State> { Handled }
/// # }
/// #
/// # let uninitialized_state_machine = Blinky::default().uninitialized_state_machine();
/// let mut initialized_state_machine = uninitialized_state_machine.init();
/// unsafe {
/// initialized_state_machine.inner_mut().shared_storage.led = true;
/// }
/// ```
pub unsafe fn inner_mut(&mut self) -> &mut Inner<M> {
&mut self.inner
}
}

impl<M> Clone for InitializedStateMachine<M>
Expand Down Expand Up @@ -434,6 +494,61 @@ where
state_machine.inner.init_with_context(context);
state_machine
}

/// Get a reference to the [Inner] storage.
///
/// ```
/// # use statig::prelude::*;
/// # #[derive(Default)]
/// # pub struct Blinky {
/// # led: bool,
/// # }
/// #
/// # pub struct Event;
/// #
/// # #[state_machine(
/// # initial = "State::on()",
/// # state(derive(Debug, PartialEq, Eq))
/// # )]
/// # impl Blinky {
/// # #[state]
/// # fn on(event: &Event) -> Response<State> { Handled }
/// # }
/// #
/// let mut uninitialized_state_machine = Blinky::default().uninitialized_state_machine();
///
/// assert_eq!(uninitialized_state_machine.inner().shared_storage.led, false);
/// assert_eq!(uninitialized_state_machine.inner().state, State::on());
/// ```
pub fn inner(&mut self) -> &Inner<M> {
&self.inner
}

/// Get a mutable reference to the [Inner] storage.
///
/// ```
/// # use statig::prelude::*;
/// # #[derive(Default)]
/// # pub struct Blinky {
/// # led: bool,
/// # }
/// #
/// # pub struct Event;
/// #
/// # #[state_machine(initial = "State::on()")]
/// # impl Blinky {
/// # #[state]
/// # fn on(event: &Event) -> Response<State> { Handled }
/// # }
/// #
/// let mut uninitialized_state_machine = Blinky::default().uninitialized_state_machine();
///
/// uninitialized_state_machine.inner_mut().shared_storage.led = true;
/// uninitialized_state_machine.inner_mut().state = State::on();
/// ```
pub fn inner_mut(&mut self) -> &mut Inner<M> {
&mut self.inner
}
}

impl<M> Clone for UninitializedStateMachine<M>
Expand Down
2 changes: 1 addition & 1 deletion statig/src/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::blocking::{self, StateExt as _};
use crate::{IntoStateMachine, Response};

/// Private internal representation of a state machine that is used for the public types.
pub(crate) struct Inner<M>
pub struct Inner<M>
where
M: IntoStateMachine,
{
Expand Down
6 changes: 3 additions & 3 deletions statig/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,7 @@
//! I highly recommend it if you want to learn how to use state machines to design
//! complex systems.
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(all(not(feature = "std"), not(doc)), no_std)]
#![allow(incomplete_features)]

mod inner;
Expand Down Expand Up @@ -815,7 +815,7 @@ pub use statig_macro::action;

/// Prelude containing the necessary imports for use with macro.
pub mod prelude {
#[cfg(feature = "async")]
#[cfg(any(feature = "async", doc))]
pub use crate::awaitable::{IntoStateMachineExt as _, StateExt as _, *};
pub use crate::blocking::{IntoStateMachineExt as _, StateExt as _, *};
pub use crate::Response::{self, *};
Expand All @@ -826,7 +826,7 @@ pub mod prelude {

pub mod blocking;

#[cfg(feature = "async")]
#[cfg(any(feature = "async", doc))]
pub mod awaitable;

pub(crate) use inner::*;
Expand Down

0 comments on commit 55d61df

Please sign in to comment.