about summary refs log tree commit diff
path: root/finito-door/src/lib.rs
blob: a4aad8dcbbe31227e7eb1fbca4885a4c4e5dd7d9 (plain) (blame)
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//! TODO: port the door docs

extern crate finito;

use finito::FSM;

type Code = usize;
type Attempts = usize;

#[derive(Debug, PartialEq)]
pub enum DoorState {
    /// This state represents an open door.
    Opened,

    /// This state represents a closed door.
    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.
    Locked { code: Code, attempts: Attempts },

    /// This state represents a disabled door. The police will
    /// need to unlock it manually!
    Disabled,
}

#[derive(Debug)]
pub enum DoorEvent {
    Open,
    Close,
    Lock(Code),
    Unlock(Code),
}

#[derive(Debug, PartialEq)]
pub enum DoorAction {
    NotifyIRC(String),
    CallThePolice,
}

impl FSM for DoorState {
    type Event = DoorEvent;
    type Action = DoorAction;

    fn handle(self, event: DoorEvent) -> (Self, Vec<DoorAction>) {
        match (self, event) {
            (DoorState::Opened, DoorEvent::Close) => return (DoorState::Closed, vec![]),

            (DoorState::Closed, DoorEvent::Open) => return (DoorState::Opened, vec![]),

            (DoorState::Closed, DoorEvent::Lock(code)) => {
                return (DoorState::Locked { code, attempts: 3 }, vec![])
            }

            (DoorState::Locked { code, attempts }, DoorEvent::Unlock(unlock_code)) => {
                if code == unlock_code {
                    return (DoorState::Closed, vec![]);
                }

                if attempts == 1 {
                    return (DoorState::Disabled, vec![DoorAction::CallThePolice]);
                }

                return (
                    DoorState::Locked {
                        code,
                        attempts: attempts - 1,
                    },
                    vec![DoorAction::NotifyIRC("invalid code entered".into())],
                );
            }

            (current, _) => (current, vec![]),
        }
    }

    fn enter(&self) -> Vec<DoorAction> {
        let msg = match self {
            DoorState::Opened => "door was opened",
            DoorState::Closed => "door was closed",
            DoorState::Locked { .. } => "door was locked",
            DoorState::Disabled => "door was disabled",
        };

        vec![DoorAction::NotifyIRC(msg.into())]
    }

    fn act(action: DoorAction) -> Vec<DoorEvent> {
        match action {
            DoorAction::NotifyIRC(msg) => {
                // TODO: write to file in example
                println!("IRC: {}", msg);
                vec![]
            }

            DoorAction::CallThePolice => {
                // TODO: call the police
                println!("The police was called! For real!");
                vec![]
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use finito::advance;

    fn test_fsm<S: FSM>(initial: S, events: Vec<S::Event>) -> (S, Vec<S::Action>) {
        events.into_iter().fold((initial, vec![]), |(state, mut actions), event| {
            let (new_state, mut new_actions) = advance(state, event);
            actions.append(&mut new_actions);
            (new_state, actions)
        })
    }

    #[test]
    fn test_door() {
        let initial = DoorState::Opened;
        let events = vec![
            DoorEvent::Close,
            DoorEvent::Open,
            DoorEvent::Close,
            DoorEvent::Lock(1234),
            DoorEvent::Unlock(1234),
            DoorEvent::Lock(4567),
            DoorEvent::Unlock(1234),
        ];
        let (final_state, actions) = test_fsm(initial, events);

        assert_eq!(final_state, DoorState::Locked { code: 4567, attempts: 2 });
        assert_eq!(actions, vec![
            DoorAction::NotifyIRC("door was closed".into()),
            DoorAction::NotifyIRC("door was opened".into()),
            DoorAction::NotifyIRC("door was closed".into()),
            DoorAction::NotifyIRC("door was locked".into()),
            DoorAction::NotifyIRC("door was closed".into()),
            DoorAction::NotifyIRC("door was locked".into()),
            DoorAction::NotifyIRC("invalid code entered".into()),
        ]);
    }
}