diff --git a/.gitignore b/.gitignore deleted file mode 100644 index c41cc9e3..00000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index dbea45eb..74639706 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,6 +14,22 @@ version = "0.1.0" name = "complex-type" version = "0.1.0" +[[package]] +name = "error-handling" +version = "0.1.0" + +[[package]] +name = "generic-type" +version = "0.1.0" + [[package]] name = "ownership-borrowing" version = "0.1.0" + +[[package]] +name = "state-machine-atm" +version = "0.1.0" + +[[package]] +name = "traits" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3f7870cb..df71da66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,10 @@ members = [ "./exercises/basic-of-rust", "./exercises/ownership-borrowing", "./exercises/complex-type", + "./exercises/generic-type", + "./exercises/traits", + "./exercises/error-handling", + "./final-project/state-machine-atm", ] @@ -33,5 +37,21 @@ path = "./exercises/complex-type/src/structs.rs" name = "enums" path = "./exercises/complex-type/src/enums.rs" +[[test]] +name = "generic-type" +path = "./exercises/generic-type/src/lib.rs" + +[[test]] +name = "traits" +path = "./exercises/traits/src/lib.rs" + +[[test]] +name = "error-handling" +path = "./exercises/error-handling/src/lib.rs" + + +[[test]] +name = "final-project" +path = "./final-project/state-machine-atm/src/lib.rs" -[dependencies] \ No newline at end of file +[dependencies] diff --git a/exercises/error-handling/Cargo.toml b/exercises/error-handling/Cargo.toml new file mode 100644 index 00000000..f4f30948 --- /dev/null +++ b/exercises/error-handling/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "error-handling" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/exercises/error-handling/README.md b/exercises/error-handling/README.md new file mode 100644 index 00000000..57b947d5 --- /dev/null +++ b/exercises/error-handling/README.md @@ -0,0 +1,10 @@ +## Complete Error Handling exercises +### Error Handling + ++ Make it compile and Complete `Error handlings` exercises in `exercises/error-handling/src/lib.rs` + ++ Run tests to check your implementation + +``` + cargo test --test error-handling +``` \ No newline at end of file diff --git a/exercises/error-handling/src/lib.rs b/exercises/error-handling/src/lib.rs new file mode 100644 index 00000000..7cd97954 --- /dev/null +++ b/exercises/error-handling/src/lib.rs @@ -0,0 +1,82 @@ +//Execise 1 +// Make it compile in unit test +// Run tests +// Hint: Convert Option to Result +fn generate_nametag_text(name: String) -> Option { + if name.is_empty() { + // Empty names aren't allowed. + None + } else { + Some(format!("Hi! My name is {}", name)) + } +} +// Exercise 2 +// Make it compile in unit test +// Run tests +// Hint: &str to integer conversion by using parse method and return Result +use std::num::ParseIntError; + +fn parse_number(s: &str) -> Result { + todo!() +} + +// Exercise 3 +// Make it compile in unit test +// Run tests +// Hint: Custom Error +#[derive(PartialEq, Debug)] +struct PositiveNonzeroInteger(u64); + +#[derive(PartialEq, Debug)] +enum CreationError { + Negative, + Zero, +} + +impl PositiveNonzeroInteger { + fn new(value: i64) -> Result { + // Hmm...? Why is this only returning an Ok value? + Ok(PositiveNonzeroInteger(value as u64)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Test for exercise 1 + #[test] + fn exercise1_should_work() { + assert_eq!( + generate_nametag_text("Beyoncé".into()), + Ok("Hi! My name is Beyoncé".into()) + ); + + assert_eq!( + generate_nametag_text("".into()), + // Don't change this line + Err("`name` was empty; it must be nonempty.".into()) + ); + } + + /// Test for exercise 2 + #[test] + fn exercise2_should_work() { + assert_eq!(parse_number("42"), Ok(42)); + assert_eq!( + parse_number("invalid"), + Err("invalid digit found in string".parse().unwrap()) + ); + } + + /// Test for exercise 3 + #[test] + fn exercise3_should_work() { + assert!(PositiveNonzeroInteger::new(10).is_ok()); + assert_eq!( + Err(CreationError::Negative), + PositiveNonzeroInteger::new(-10) + ); + assert_eq!(Err(CreationError::Zero), PositiveNonzeroInteger::new(0)); + } +} diff --git a/exercises/generic-type/Cargo.toml b/exercises/generic-type/Cargo.toml new file mode 100644 index 00000000..8f1e556b --- /dev/null +++ b/exercises/generic-type/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "generic-type" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/exercises/generic-type/README.md b/exercises/generic-type/README.md new file mode 100644 index 00000000..9f9caeee --- /dev/null +++ b/exercises/generic-type/README.md @@ -0,0 +1,4 @@ +## Complete Generic type exercises +### Generic type + ++ Make it compile, add logic code `Generic Type` exercises in `exercises/generic-type/src/lib.rs` diff --git a/exercises/generic-type/src/lib.rs b/exercises/generic-type/src/lib.rs new file mode 100644 index 00000000..9015fd09 --- /dev/null +++ b/exercises/generic-type/src/lib.rs @@ -0,0 +1,144 @@ +// Exercise 1 +// Implement struct Point to make it work. +// Make it compile +fn exercise1() { + let integer = Position { x: 5, y: 10 }; + let float = Position { x: 1.0, y: 4.0 }; +} + + + +// Exercise 2 +// Modify this struct to make the code work +// Make it compile +struct Point { + x: T, + y: T, +} + +fn exercise2() { + // DON'T modify this code. + let p = Point{x: 5, y : "hello".to_string()}; +} + + + +// Exercise 3 +// Make it compile +// Add generic for Val to make the code work, DON'T modify the code in `main`. +struct Val { + val: f64, +} + +impl Val { + fn value(&self) -> &f64 { + &self.val + } +} + + +fn exercise3() { + let x = Val{ val: 3.0 }; + let y = Val{ val: "hello".to_string()}; + println!("{}, {}", x.value(), y.value()); +} + +// Exercise 4 +// Find the maximum value in a collection +// Make it compile +// Implementing logic +// Run tests + +fn find_max(collection: &[T]) -> Option<&T> { + todo!() +} + +// Exercise 5 +// Reverse the elements in a collection +// Make it compile +// Run tests +fn reverse_collection(collection: &[T]) { + todo!() +} + + +// Exercise 6 +// Function to check if a collection contains a specific value +fn contains_value(collection: &[T], value: &T) -> bool { + todo!() +} + +// Unit tests +#[cfg(test)] +mod tests { + use super::*; + + // Test for exercise 4 + #[test] + fn test_find_max_with_numbers() { + let numbers = vec![1, 5, 3, 8, 2]; + assert_eq!(find_max(&numbers), Some(&8)); + } + + // Test for exercise 4 + #[test] + fn test_find_max_with_strings() { + let strings = vec!["apple", "banana", "cherry", "durian"]; + assert_eq!(find_max(&strings), Some(&"durian")); + } + + // Test for exercise 4 + #[test] + fn test_find_max_with_empty_collection() { + let empty: Vec = Vec::new(); + assert_eq!(find_max(&empty), None); + } + + // Test for exercise 5 + #[test] + fn test_reverse_collection_with_numbers() { + let mut numbers = vec![1, 2, 3, 4, 5]; + reverse_collection(&mut numbers); + assert_eq!(numbers, vec![5, 4, 3, 2, 1]); + } + + // Test for exercise 5 + #[test] + fn test_reverse_collection_with_strings() { + let mut strings = vec!["apple", "banana", "cherry", "durian"]; + reverse_collection(&mut strings); + assert_eq!(strings, vec!["durian", "cherry", "banana", "apple"]); + } + + // Test for exercise 5 + #[test] + fn test_reverse_collection_with_empty_collection() { + let mut empty: Vec = Vec::new(); + reverse_collection(&mut empty); + assert_eq!(empty, Vec::::new()); + } + + // Test for exercise 6 + #[test] + fn test_contains_value_with_numbers() { + let numbers = vec![1, 2, 3, 4, 5]; + assert_eq!(contains_value(&numbers, &3), true); + assert_eq!(contains_value(&numbers, &6), false); + } + + // Test for exercise 6 + #[test] + fn test_contains_value_with_strings() { + let strings = vec!["apple", "banana", "cherry", "durian"]; + assert_eq!(contains_value(&strings, &"banana"), true); + assert_eq!(contains_value(&strings, &"grape"), false); + } + + // Test for exercise 6 + #[test] + fn test_contains_value_with_empty_collection() { + let empty: Vec = Vec::new(); + assert_eq!(contains_value(&empty, &5), false); + } + +} diff --git a/exercises/traits/Cargo.toml b/exercises/traits/Cargo.toml new file mode 100644 index 00000000..460f45f0 --- /dev/null +++ b/exercises/traits/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "traits" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/exercises/traits/README.md b/exercises/traits/README.md new file mode 100644 index 00000000..f87c4b5c --- /dev/null +++ b/exercises/traits/README.md @@ -0,0 +1,10 @@ +## Complete Trait exercises +### Trait + ++ Make it compile and Complete `Traits` exercises in `exercises/traits/src/lib.rs` + ++ Run tests to check your implementation + +``` + cargo test --test traits +``` \ No newline at end of file diff --git a/exercises/traits/src/lib.rs b/exercises/traits/src/lib.rs new file mode 100644 index 00000000..154cfb0d --- /dev/null +++ b/exercises/traits/src/lib.rs @@ -0,0 +1,185 @@ +// Exercise 1 +// Fill in the two impl blocks to make the code work. +// Make it compile +// Run tests +trait Hello { + fn say_hi(&self) -> String { + String::from("hi") + } + + fn say_something(&self) -> String; +} + +//TODO +struct Student {} +impl Hello for Student { +} +//TODO +struct Teacher {} +impl Hello for Teacher { +} + + +// Exercise 2 +// Make it compile in unit test for exercise 2 +// Hint: use #[derive] for struct Point +// Run tests +struct Point { + x: i32, + y: i32, +} + + +// Exercise 3 +// Make it compile +// Implement `fn sum` with trait bound in two ways. +// Run tests +// Hint: Trait Bound +fn sum(x: T, y: T) -> T { + x + y +} + + +// Exercise 4 +// Fix errors and implement +// Hint: Static Dispatch and Dynamic Dispatch +// Run tests +trait Foo { + fn method(&self) -> String; +} + +impl Foo for u8 { + fn method(&self) -> String { format!("u8: {}", *self) } +} + +impl Foo for String { + fn method(&self) -> String { format!("string: {}", *self) } +} + +// IMPLEMENT below with generics and parameters +fn static_dispatch(x) { + todo!() +} + +// Implement below with trait objects and parameters +fn dynamic_dispatch(x) { + todo!() +} + +// Exercise 5 +// Fix errors and fill in the blanks +// Run tests +// Hint: &dyn and Box +trait Draw { + fn draw(&self) -> String; +} + +impl Draw for u8 { + fn draw(&self) -> String { + format!("u8: {}", *self) + } +} + +impl Draw for f64 { + fn draw(&self) -> String { + format!("f64: {}", *self) + } +} + +fn draw_with_box(x: Box) { + x.draw(); +} + +fn draw_with_ref(x: __) { + x.draw(); +} + +// Exercise 6 +// Fix errors and implement +// Run tests +// Hint: Associated Type + +trait Container { + type Item; + fn insert(&mut self, item: Self::Item); + fn remove(&mut self) -> Option; + fn is_empty(&self) -> bool; +} + +struct Stack { + items: Vec, +} + +//TODO implement Container for Stack + + + +#[cfg(test)] +mod tests { + use super::*; + + // Test for exercise 2 + + #[test] + fn exercise1_should_work() { + let s = Student {}; + assert_eq!(s.say_hi(), "hi"); + assert_eq!(s.say_something(), "I'm a good student"); + + let t = Teacher {}; + assert_eq!(t.say_hi(), "Hi, I'm your new teacher"); + assert_eq!(t.say_something(), "I'm not a bad teacher"); + } + + #[test] + fn exercise2_should_work() { + let point1 = Point { x: 1, y: 2 }; + let point2 = Point { x: 1, y: 2 }; + let point3 = Point { x: 3, y: 4 }; + + assert_eq!(point1, point2); + assert_ne!(point1, point3); + } + + #[test] + fn exercise3_should_work() { + assert_eq!(sum(1, 2), 3); + } + + #[test] + fn exercise4_should_work() { + let x = 5u8; + let y = "Hello".to_string(); + + static_dispatch(x); + dynamic_dispatch(&y); + } + + #[test] + fn exercise5_should_work() { + let x = 1.1f64; + let y = 8u8; + + // Draw x. + draw_with_box(__); + + // Draw y. + draw_with_ref(&y); + } + + #[test] + fn exercise6_should_work(){ + let mut stack: Stack = Stack { items: Vec::new() }; + assert!(stack.is_empty()); + stack.insert(1); + stack.insert(2); + stack.insert(3); + assert!(!stack.is_empty()); + assert_eq!(stack.remove(), Some(3)); + assert_eq!(stack.remove(), Some(2)); + assert_eq!(stack.remove(), Some(1)); + assert_eq!(stack.remove(), None); + assert!(stack.is_empty()); + } + +} diff --git a/final-project/state-machine-atm/Cargo.toml b/final-project/state-machine-atm/Cargo.toml new file mode 100644 index 00000000..91ed298a --- /dev/null +++ b/final-project/state-machine-atm/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "state-machine-atm" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/final-project/state-machine-atm/README.md b/final-project/state-machine-atm/README.md new file mode 100644 index 00000000..e07fd87c --- /dev/null +++ b/final-project/state-machine-atm/README.md @@ -0,0 +1,8 @@ +## Complete Final Project +### State Machine ATM + ++ Make it compile and add logic in `state-machine-atm` ++ Run test + ``` + cargo test --test final-project + ``` diff --git a/final-project/state-machine-atm/src/atm.rs b/final-project/state-machine-atm/src/atm.rs new file mode 100644 index 00000000..974fa01a --- /dev/null +++ b/final-project/state-machine-atm/src/atm.rs @@ -0,0 +1,377 @@ +//! The automated teller machine gives you cash after you swipe your card and enter your pin. +//! The atm may fail to give you cash if it is empty or you haven't swiped your card, or you have +//! entered the wrong pin. + +use crate::traits::{StateMachine, hash}; +/// The keys on the ATM keypad +#[derive(Hash, Debug, PartialEq, Clone, Copy)] +pub enum Key { + One, + Two, + Three, + Four, + Enter, +} + + + + +/// Something you can do to the ATM +pub enum Action { + /// Swipe your card at the ATM. The attached value is the hash of the pin + /// that should be keyed in on the keypad next. + SwipeCard(u64), + /// Press a key on the keypad + PressKey(Key), +} + +/// The various states of authentication possible with the ATM +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum Auth { + /// No session has begun yet. Waiting for the user to swipe their card + Waiting, + /// The user has swiped their card, providing the enclosed PIN hash. + /// Waiting for the user to key in their pin + Authenticating(u64), + /// The user has authenticated. Waiting for them to key in the amount + /// of cash to withdraw + Authenticated, +} + + + + +/// The ATM. When a card is swiped, the ATM learns the correct pin's hash. +/// It waits for you to key in your pin. You can press as many numeric keys as +/// you like followed by enter. If the pin is incorrect, your card is returned +/// and the ATM automatically goes back to the main menu. If your pin is correct, +/// the ATM waits for you to key in an amount of money to withdraw. Withdraws +/// are bounded only by the cash in the machine (there is no account balance). +#[derive(Debug, PartialEq, Clone)] +pub struct Atm { + /// How much money is in the ATM + cash_inside: u64, + /// The machine's authentication status. + expected_pin_hash: Auth, + /// All the keys that have been pressed since the last `Enter` + keystroke_register: Vec, +} + + +//TODO +// Implement trait Default for Auth +// return Waiting status +impl Default for Auth { + fn default() -> Self { + Auth::Waiting + } +} + + +//TODO +// Implement trait From for &str +// Convert elements in Key to &str +impl From for &str { + fn from(key: Key) -> Self { + match key { + Key::One => "1", + Key::Two => "2", + Key::Three => "3", + Key::Four => "4", + Key::Enter => "Enter", + } + } +} +impl From for String { + fn from(key: Key) -> Self { + match key { + Key::One => "1".to_string(), + Key::Two => "2".to_string(), + Key::Three => "3".to_string(), + Key::Four => "4".to_string(), + Key::Enter => "Enter".to_string(), + } + } +} +impl StateMachine for Atm { + // Notice that we are using the same type for the state as we are using for the machine this time. + type State = Self; + type Transition = Action; + // Hint + // Should use `default` method when auth status is Waiting status + // Should use `from` method to convert elements in Key to &str + // Parse &str to integer to calculate amount + // Use a hash function to verify the PIN both before and after the user presses the Enter key. + fn next_state(starting_state: &Self::State, t: &Self::Transition) -> Self::State { + match t { + Action::SwipeCard(pin_hash) => { + Self { + cash_inside: starting_state.cash_inside, + expected_pin_hash: Auth::Authenticating(*pin_hash), + keystroke_register: starting_state.keystroke_register.clone() + } + }, + Action::PressKey(key) => { + match &starting_state.expected_pin_hash { + Auth::Waiting => starting_state.clone(), + Auth::Authenticating(expected_pin_hash) => { + if let Key::Enter = key { + // let keyed_pin: String = starting_state.keystroke_register.iter().map(|k| >::into(*k)).collect(); + let keyed_pin_hash = hash(&starting_state.keystroke_register); + if keyed_pin_hash == *expected_pin_hash { + Self { + cash_inside: starting_state.cash_inside, + expected_pin_hash: Auth::Authenticated, + keystroke_register: Vec::new(), + } + } else { + Self { + cash_inside: starting_state.cash_inside, + expected_pin_hash: Auth::Waiting, + keystroke_register: Vec::new(), + } + } + } else { + let mut new_register = starting_state.keystroke_register.clone(); + new_register.push(key.clone()); + Self { + cash_inside: starting_state.cash_inside, + expected_pin_hash: starting_state.expected_pin_hash.clone(), + keystroke_register: new_register, + } + } + }, + Auth::Authenticated => { + if let Key::Enter = key { + let withdrawal: u64 = starting_state.keystroke_register.iter().map(|k| >::into(*k)).collect::().parse().unwrap_or(0); + if withdrawal <= starting_state.cash_inside { + Self { + cash_inside: starting_state.cash_inside - withdrawal, + expected_pin_hash: Auth::Waiting, + keystroke_register: Vec::new(), + } + } else { + Self { + cash_inside: starting_state.cash_inside, + expected_pin_hash: Auth::Waiting, + keystroke_register: Vec::new(), + } + } + } else { + let mut new_register = starting_state.keystroke_register.clone(); + new_register.push(key.clone()); + Self { + cash_inside: starting_state.cash_inside, + expected_pin_hash: Auth::Authenticated, + keystroke_register: new_register, + } + } + }, + } + }, + } + } +} + +#[test] +fn sm_3_simple_swipe_card() { + let start = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Waiting, + keystroke_register: Vec::new(), + }; + let end = Atm::next_state(&start, &Action::SwipeCard(1234)); + let expected = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticating(1234), + keystroke_register: Vec::new(), + }; + + assert_eq!(end, expected); +} + +#[test] +fn sm_3_swipe_card_again_part_way_through() { + let start = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticating(1234), + keystroke_register: Vec::new(), + }; + let end = Atm::next_state(&start, &Action::SwipeCard(1234)); + let expected = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticating(1234), + keystroke_register: Vec::new(), + }; + + assert_eq!(end, expected); + + let start = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticating(1234), + keystroke_register: vec![Key::One, Key::Three], + }; + let end = Atm::next_state(&start, &Action::SwipeCard(1234)); + let expected = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticating(1234), + keystroke_register: vec![Key::One, Key::Three], + }; + + assert_eq!(end, expected); +} + +#[test] +fn sm_3_press_key_before_card_swipe() { + let start = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Waiting, + keystroke_register: Vec::new(), + }; + let end = Atm::next_state(&start, &Action::PressKey(Key::One)); + let expected = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Waiting, + keystroke_register: Vec::new(), + }; + + assert_eq!(end, expected); +} + +#[test] +fn sm_3_enter_single_digit_of_pin() { + let start = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticating(1234), + keystroke_register: Vec::new(), + }; + let end = Atm::next_state(&start, &Action::PressKey(Key::One)); + let expected = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticating(1234), + keystroke_register: vec![Key::One], + }; + + assert_eq!(end, expected); + + let start = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticating(1234), + keystroke_register: vec![Key::One], + }; + let end1 = Atm::next_state(&start, &Action::PressKey(Key::Two)); + let expected1 = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticating(1234), + keystroke_register: vec![Key::One, Key::Two], + }; + + assert_eq!(end1, expected1); +} + +#[test] +fn sm_3_enter_wrong_pin() { + // Create hash of pin + let pin = vec![Key::One, Key::Two, Key::Three, Key::Four]; + let pin_hash = hash(&pin); + + let start = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticating(pin_hash), + keystroke_register: vec![Key::Three, Key::Three, Key::Three, Key::Three], + }; + let end = Atm::next_state(&start, &Action::PressKey(Key::Enter)); + let expected = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Waiting, + keystroke_register: Vec::new(), + }; + + assert_eq!(end, expected); +} + +#[test] +fn sm_3_enter_correct_pin() { + // Create hash of pin + let pin = vec![Key::One, Key::Two, Key::Three, Key::Four]; + let pin_hash = hash(&pin); + + let start = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticating(pin_hash), + keystroke_register: vec![Key::One, Key::Two, Key::Three, Key::Four], + }; + let end = Atm::next_state(&start, &Action::PressKey(Key::Enter)); + let expected = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticated, + keystroke_register: Vec::new(), + }; + + assert_eq!(end, expected); +} + +#[test] +fn sm_3_enter_single_digit_of_withdraw_amount() { + let start = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticated, + keystroke_register: Vec::new(), + }; + let end = Atm::next_state(&start, &Action::PressKey(Key::One)); + let expected = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticated, + keystroke_register: vec![Key::One], + }; + + assert_eq!(end, expected); + + let start = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticated, + keystroke_register: vec![Key::One], + }; + let end1 = Atm::next_state(&start, &Action::PressKey(Key::Four)); + let expected1 = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticated, + keystroke_register: vec![Key::One, Key::Four], + }; + + assert_eq!(end1, expected1); +} + +#[test] +fn sm_3_try_to_withdraw_too_much() { + let start = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticated, + keystroke_register: vec![Key::One, Key::Four], + }; + let end = Atm::next_state(&start, &Action::PressKey(Key::Enter)); + let expected = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Waiting, + keystroke_register: Vec::new(), + }; + + assert_eq!(end, expected); +} + +#[test] +fn sm_3_withdraw_acceptable_amount() { + let start = Atm { + cash_inside: 10, + expected_pin_hash: Auth::Authenticated, + keystroke_register: vec![Key::One], + }; + let end = Atm::next_state(&start, &Action::PressKey(Key::Enter)); + let expected = Atm { + cash_inside: 9, + expected_pin_hash: Auth::Waiting, + keystroke_register: Vec::new(), + }; + + assert_eq!(end, expected); +} \ No newline at end of file diff --git a/final-project/state-machine-atm/src/lib.rs b/final-project/state-machine-atm/src/lib.rs new file mode 100644 index 00000000..3f5a57c3 --- /dev/null +++ b/final-project/state-machine-atm/src/lib.rs @@ -0,0 +1,2 @@ +mod traits; +mod atm; diff --git a/final-project/state-machine-atm/src/traits.rs b/final-project/state-machine-atm/src/traits.rs new file mode 100644 index 00000000..adbb13ea --- /dev/null +++ b/final-project/state-machine-atm/src/traits.rs @@ -0,0 +1,44 @@ +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + + +/// A state machine - Generic over the transition type +pub trait StateMachine { + /// The states that can be occupied by this machine + type State; + + /// The transitions that can be made between states + type Transition; + + /// Calculate the resulting state when this state undergoes the given transition + fn next_state(starting_state: &Self::State, t: &Self::Transition) -> Self::State; +} + + + +// Simple helper to do some hashing. + +pub fn hash(t: &T) -> u64 { + let mut s = DefaultHasher::new(); + t.hash(&mut s); + s.finish() +} + +// Test for hash function +#[test] +fn test_hash_enum_vec() { + #[derive(Hash)] + enum KeyTest{ + One, + Two, + Three, + Four + + } + let input: Vec = vec![KeyTest::One, KeyTest::Two, KeyTest::Three, KeyTest::Four]; + + let hash1 = hash(&input); + let hash2 = hash(&input); + + assert_eq!(hash1, hash2); +}