about summary refs log tree commit diff
path: root/finito-door
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2018-09-26T16·00+0200
committerVincent Ambo <mail@tazj.in>2018-09-26T16·00+0200
commit401486d124154e8fb33e319814648e4a10b30e94 (patch)
treea1cb2286d76dc41547deb86598837e64f55fcf60 /finito-door
parent40caa5ffa23cdd482b7c97e74891fb41269e8076 (diff)
docs(door): Port over documentation from finito-hs
Diffstat (limited to 'finito-door')
-rw-r--r--finito-door/src/lib.rs180
1 files changed, 172 insertions, 8 deletions
diff --git a/finito-door/src/lib.rs b/finito-door/src/lib.rs
index cfbaa4abe617..0137a6026a78 100644
--- a/finito-door/src/lib.rs
+++ b/finito-door/src/lib.rs
@@ -1,68 +1,210 @@
-//! TODO: port the door docs
+//! # What & why?
+//!
+//! This module serves as a (hopefully simple) example of how to
+//! implement finite-state machines using Finito. Note that the
+//! concepts of Finito itself won't be explained in detail here,
+//! consult its library documentation for that.
+//!
+//! Reading through this module should give you a rough idea of how to
+//! work with Finito and get you up and running modeling things
+//! *quickly*.
+//!
+//! Note: The generated documentation for this module will display the
+//! various components of the door, but it will not inform you about
+//! the actual transition logic and all that stuff. Read the source,
+//! too!
+//!
+//! # The Door
+//!
+//! My favourite example when explaining these state-machines
+//! conceptually has been to use a simple, lockable door. Our door has
+//! a keypad next to it which can be used to lock the door by entering
+//! a code, after which the same code must be entered to unlock it
+//! again.
+//!
+//! The door can only be locked if it is closed. Oh, and it has a few
+//! extra features:
+//!
+//! * whenever the door's state changes, an IRC channel receives a
+//!   message about that
+//!
+//! * the door calls the police if the code is intered incorrectly more
+//!   than a specified number of times (mhm, lets say, three)
+//!
+//! * if the police is called the door can not be interacted with
+//!   anymore (and honestly, for the sake of this example, we don't
+//!   care how its functionality is restored)
+//!
+//! ## The Door - Visualized
+//!
+//! Here's a rough attempt at drawing a state diagram in ASCII. The
+//! bracketed words denote states, the arrows denote events:
+//!
+//! ```text
+//!          <--Open---    <--Unlock-- correct code? --Unlock-->
+//!      [Opened]    [Closed]            [Locked]            [Disabled]
+//!          --Close-->    ----Lock-->
+//! ```
+//!
+//! I'm so sorry for that drawing.
+//!
+//! ## The Door - Usage example
+//!
+//! An interaction session with our final door could look like this:
+//!
+//! ```rust,ignore
+//! use finito_postgres::{insert_machine, advance};
+//!
+//! let door = insert_machine(&conn, &DoorState::Opened)?;
+//!
+//! advance(&conn, &door, DoorEvent::Close)?;
+//! advance(&conn, &door, DoorEvent::Lock(1337))?;
+//!
+//! format!("Door is now: {}", get_machine(&conn, &door)?);
+//! ```
+//!
+//! Here we have created, closed and then locked a door and inspected
+//! its state. We will see that it is locked, has the locking code we
+//! gave it and three remaining attempts to open it.
+//!
+//! Alright, enough foreplay, lets dive in!
 
 extern crate finito;
 
 use finito::FSM;
 
+/// Type synonym to represent the code with which the door is locked. This
+/// exists only for clarity in the signatures below and please do not email me
+/// about the fact that an integer is not actually a good representation of
+/// numerical digits. Thanks!
 type Code = usize;
+
+/// Type synonym to represent the remaining number of unlock attempts.
 type Attempts = usize;
 
+/// This type represents the possible door states and the data that they carry.
+/// We can infer this from the "diagram" in the documentation above.
+///
+/// This type is the one for which `finito::FSM` will be implemented, making it
+/// the wooden (?) heart of our door.
 #[derive(Debug, PartialEq)]
 pub enum DoorState {
-    /// This state represents an open door.
+    /// In `Opened` state, the door is wide open and anyone who fits through can
+    /// go through.
     Opened,
 
-    /// This state represents a closed door.
+    /// In `Closed` state, the door is shut but does not prevent anyone from
+    /// opening it.
     Closed,
 
-    /// This state represents a locked door on which a given code
-    /// is set. It also carries a number of remaining attempts
-    /// before the door is permanently disabled.
+    /// In `Locked` state, the door is locked and waiting for someone to enter
+    /// its locking code on the keypad.
+    ///
+    /// This state contains the code that the door is locked with, as well as
+    /// the remaining number of attempts before the door calls the police and
+    /// becomes unusable.
     Locked { code: Code, attempts: Attempts },
 
-    /// This state represents a disabled door. The police will
-    /// need to unlock it manually!
+    /// This state represents a disabled door after the police has been called.
+    /// The police will need to unlock it manually!
     Disabled,
 }
 
+/// This type represents the events that can occur in our door, i.e. the input
+/// and interactions it receives.
 #[derive(Debug)]
 pub enum DoorEvent {
+    /// `Open` means someone is opening the door!
     Open,
+
+    /// `Close` means, you guessed it, the exact opposite.
     Close,
+
+    /// `Lock` means somebody has entered a locking code on the
+    /// keypad.
     Lock(Code),
+
+    /// `Unlock` means someone has attempted to unlock the door.
     Unlock(Code),
 }
 
+/// This type represents the possible actions, a.k.a. everything our door "does"
+/// that does not just impact itself, a.k.a. side-effects.
+///
+/// **Note**: This type by itself *is not* a collection of side-effects, it
+/// merely describes the side-effects we want to occur (which are then
+/// interpreted by the machinery later).
 #[derive(Debug, PartialEq)]
 pub enum DoorAction {
+    /// `NotifyIRC` is used to display some kind of message on the
+    /// aforementioned IRC channel that is, for some reason, very interested in
+    /// the state of the door.
     NotifyIRC(String),
+
+    /// `CallThePolice` does what you think it does.
+    ///
+    /// **Note**: For safety reasons, causing this action is not recommended for
+    /// users inside the US!
     CallThePolice,
 }
 
+/// This trait implementation turns our 'DoorState' into a type actually
+/// representing a finite-state machine. To implement it, we need to do three
+/// main things:
+///
+/// * Define what our associated `Event` and `Action` type should be
+///
+/// * Define the event-handling and state-entering logic (i.e. the meat of the
+/// ... door)
+///
+/// * Implement the interpretation of our actions, i.e. implement actual
+///   side-effects
 impl FSM for DoorState {
     const FSM_NAME: &'static str = "door";
+
+    // As you might expect, our `Event` type is 'DoorEvent' and our `Action`
+    // type is 'DoorAction'.
     type Event = DoorEvent;
     type Action = DoorAction;
 
+    // The implementation of `handle` provides us with the actual transition
+    // logic of the door.
+    //
+    // The door is conceptually not that complicated so it is relatively short.
     fn handle(self, event: DoorEvent) -> (Self, Vec<DoorAction>) {
         match (self, event) {
+            // An opened door can be closed:
             (DoorState::Opened, DoorEvent::Close) => return (DoorState::Closed, vec![]),
 
+            // A closed door can be opened:
             (DoorState::Closed, DoorEvent::Open) => return (DoorState::Opened, vec![]),
 
+            // A closed door can also be locked, in which case the locking code
+            // is stored with the next state and the unlock attempts default to
+            // three:
             (DoorState::Closed, DoorEvent::Lock(code)) => {
                 return (DoorState::Locked { code, attempts: 3 }, vec![])
             }
 
+            // A locked door receiving an `Unlock`-event can do several
+            // different things ...
             (DoorState::Locked { code, attempts }, DoorEvent::Unlock(unlock_code)) => {
+                // In the happy case, entry of a correct code leads to the door
+                // becoming unlocked (i.e. transitioning back to `Closed`).
                 if code == unlock_code {
                     return (DoorState::Closed, vec![]);
                 }
 
+                // If the code wasn't correct and the fraudulent unlocker ran
+                // out of attempts (i.e. there was only one attempt remaining),
+                // it's time for some consequences.
                 if attempts == 1 {
                     return (DoorState::Disabled, vec![DoorAction::CallThePolice]);
                 }
 
+                // If the code wasn't correct, but there are still some
+                // remaining attempts, the user doesn't have to face the police
+                // quite yet but IRC gets to laugh about it.
                 return (
                     DoorState::Locked {
                         code,
@@ -72,10 +214,23 @@ impl FSM for DoorState {
                 );
             }
 
+            // This actually already concludes our event-handling logic. Our
+            // uncaring door does absolutely nothing if you attempt to do
+            // something with it that it doesn't support, so the last handler is
+            // a simple fallback.
+            //
+            // In a real-world state machine, especially one that receives
+            // events from external sources, you may want fallback handlers to
+            // actually do something. One example could be creating an action
+            // that logs information about unexpected events, alerts a
+            // monitoring service, or whatever else.
             (current, _) => (current, vec![]),
         }
     }
 
+    // The implementation of `enter` lets door states cause additional actions
+    // they are transitioned to. In the door example we use this only to notify
+    // IRC about what is going on.
     fn enter(&self) -> Vec<DoorAction> {
         let msg = match self {
             DoorState::Opened => "door was opened",
@@ -87,6 +242,15 @@ impl FSM for DoorState {
         vec![DoorAction::NotifyIRC(msg.into())]
     }
 
+    // The implementation of `act` lets us perform actual side-effects.
+    //
+    // Again, for the sake of educational simplicity, this does not deal with
+    // all potential (or in fact any) error cases that can occur during this toy
+    // implementation of actions.
+    //
+    // Additionally the `act` function can return new events. This is useful for
+    // a sort of "callback-like" pattern (cause an action to fetch some data,
+    // receive it as an event) but is not used in this example.
     fn act(action: DoorAction) -> Vec<DoorEvent> {
         match action {
             DoorAction::NotifyIRC(msg) => {