//! Finito's core finite-state machine abstraction. //! //! # What & why? //! //! Most processes that occur in software applications can be modeled //! as finite-state machines (FSMs), however the actual states, the //! transitions between them and the model's interaction with the //! external world is often implicit. //! //! Making the states of a process explicit using a simple language //! that works for both software developers and other people who may //! have opinions on processes makes it easier to synchronise thoughts, //! extend software and keep a good level of control over what is going //! on. //! //! This library aims to provide functionality for implementing //! finite-state machines in a way that balances expressivity and //! safety. //! //! Finito does not aim to prevent every possible incorrect //! transition, but aims for somewhere "safe-enough" (please don't //! lynch me) that is still easily understood. //! //! # Conceptual overview //! //! The core idea behind Finito can be expressed in a single line and //! will potentially look familiar if you have used Erlang in a //! previous life. The syntax used here is the type-signature notation //! of Haskell. //! //! ```text //! advance :: state -> event -> (state, [action]) //! ``` //! //! In short, every FSM is made up of three distinct types: //! //! * a state type representing all possible states of the machine //! //! * an event type representing all possible events in the machine //! //! * an action type representing a description of all possible //! side-effects of the machine //! //! Using the definition above we can now say that a transition in a //! state-machine, involving these three types, takes an initial state //! and an event to apply it to and returns a new state and a list of //! actions to execute. //! //! With this definition most processes can already be modeled quite //! well. Two additional functions are required to make it all work: //! //! ```text //! -- | The ability to cause additional side-effects after entering //! -- a new state. //! > enter :: state -> [action] //! ``` //! //! as well as //! //! ```text //! -- | An interpreter for side-effects //! act :: action -> m [event] //! ``` //! //! **Note**: This library is based on an original Haskell library. In //! Haskell, side-effects can be controlled via the type system which //! is impossible in Rust. //! //! Some parts of Finito make assumptions about the programmer not //! making certain kinds of mistakes, which are pointed out in the //! documentation. Unfortunately those assumptions are not //! automatically verifiable in Rust. //! //! ## Example //! //! Please consult `finito-door` for an example representing a simple, //! lockable door as a finite-state machine. This gives an overview //! over Finito's primary features. //! //! If you happen to be the kind of person who likes to learn about //! libraries by reading code, you should familiarise yourself with the //! door as it shows up as the example in other finito-related //! libraries, too. //! //! # Persistence, side-effects and mud //! //! These three things are inescapable in the fateful realm of //! computers, but Finito separates them out into separate libraries //! that you can drag in as you need them. //! //! Currently, those libraries include: //! //! * `finito`: Core components and classes of Finito //! //! * `finito-in-mem`: In-memory implementation of state machines //! that do not need to live longer than an application using //! standard library concurrency primitives. //! //! * `finito-postgres`: Postgres-backed, persistent implementation //! of state machines that, well, do need to live longer. Uses //! Postgres for concurrency synchronisation, so keep that in //! mind. //! //! Which should cover most use-cases. Okay, enough prose, lets dive //! in. //! //! # Does Finito make you want to scream? //! //! Please reach out! I want to know why! extern crate serde; use serde::Serialize; use serde::de::DeserializeOwned; use std::fmt::Debug; use std::mem; /// Primary trait that needs to be implemented for every state type /// representing the states of an FSM. /// /// This trait is used to implement transition logic and to "tie the /// room together", with the room being our triplet of types. pub trait FSM where Self: Sized { /// A human-readable string uniquely describing what this FSM /// models. This is used in log messages, database tables and /// various other things throughout Finito. const FSM_NAME: &'static str; /// The associated event type of an FSM represents all possible /// events that can occur in the state-machine. type Event; /// The associated action type of an FSM represents all possible /// actions that can occur in the state-machine. type Action; /// The associated error type of an FSM represents failures that /// can occur during action processing. type Error: Debug; /// The associated state type of an FSM describes the state that /// is made available to the implementation of action /// interpretations. type State; /// `handle` deals with any incoming events to cause state /// transitions and emit actions. This function is the core logic /// of any state machine. /// /// Implementations of this function **must not** cause any /// side-effects to avoid breaking the guarantees of Finitos /// conceptual model. fn handle(self, event: Self::Event) -> (Self, Vec<Self::Action>); /// `enter` is called when a new state is entered, allowing a /// state to produce additional side-effects. /// /// This is useful for side-effects that event handlers do not /// need to know about and for resting assured that a certain /// action has been caused when a state is entered. /// /// FSM state types are expected to be enum (i.e. sum) types. A /// state is considered "new" and enter calls are run if is of a /// different enum variant. fn enter(&self) -> Vec<Self::Action>; /// `act` interprets and executes FSM actions. This is the only /// part of an FSM in which side-effects are allowed. fn act(Self::Action, &Self::State) -> Result<Vec<Self::Event>, Self::Error>; } /// This function is the primary function used to advance a state /// machine. It takes care of both running the event handler as well /// as possible state-enter calls and returning the result. /// /// Users of Finito should basically always use this function when /// advancing state-machines manually, and never call FSM-trait /// methods directly. pub fn advance<S: FSM>(state: S, event: S::Event) -> (S, Vec<S::Action>) { // Determine the enum variant of the initial state (used to // trigger enter calls). let old_discriminant = mem::discriminant(&state); let (new_state, mut actions) = state.handle(event); // Compare the enum variant of the resulting state to the old one // and run `enter` if they differ. let new_discriminant = mem::discriminant(&new_state); let mut enter_actions = if old_discriminant != new_discriminant { new_state.enter() } else { vec![] }; actions.append(&mut enter_actions); (new_state, actions) } /// This trait is implemented by Finito backends. Backends are /// expected to be able to keep track of the current state of an FSM /// and retrieve it / apply updates transactionally. /// /// See the `finito-postgres` and `finito-in-mem` crates for example /// implementations of this trait. /// /// Backends must be parameterised over an additional (user-supplied) /// state type which can be used to track application state that must /// be made available to action handlers, for example to pass along /// database connections. pub trait FSMBackend<S: 'static> { /// Key type used to identify individual state machines in this /// backend. /// /// TODO: Should be parameterised over FSM type after rustc /// #44265. type Key; /// Error type for all potential failures that can occur when /// interacting with this backend. type Error: Debug; /// Insert a new state-machine into the backend's storage and /// return its newly allocated key. fn insert_machine<F>(&self, initial: F) -> Result<Self::Key, Self::Error> where F: FSM + Serialize + DeserializeOwned; /// Retrieve the current state of an FSM by its key. fn get_machine<F: FSM>(&self, key: Self::Key) -> Result<F, Self::Error> where F: FSM + Serialize + DeserializeOwned; /// Advance a state machine by applying an event and persisting it /// as well as any resulting actions. /// /// **Note**: Whether actions are automatically executed depends /// on the backend used. Please consult the backend's /// documentation for details. fn advance<'a, F: FSM>(&'a self, key: Self::Key, event: F::Event) -> Result<F, Self::Error> where F: FSM + Serialize + DeserializeOwned, F::State: From<&'a S>, F::Event: Serialize + DeserializeOwned, F::Action: Serialize + DeserializeOwned; }