From b08c6431cf4a136d9cbccf3596ddaba53514ca47 Mon Sep 17 00:00:00 2001 From: Filipe Rodrigues Date: Tue, 14 Jul 2020 19:41:23 +0100 Subject: [PATCH] Improved validation interface. Implemented new validation interface for `Move`. Added more tests to `Move`. --- src/game/card/property/moves.rs | 34 +++++++++--- src/game/card/property/moves/test.rs | 61 ++++++++++++--------- src/game/validation.rs | 80 +++++++++++++++++++--------- 3 files changed, 118 insertions(+), 57 deletions(-) diff --git a/src/game/card/property/moves.rs b/src/game/card/property/moves.rs index 3f105b0..30316be 100644 --- a/src/game/card/property/moves.rs +++ b/src/game/card/property/moves.rs @@ -10,7 +10,7 @@ use crate::game::{ array_split, array_split_mut, null_ascii_string::{self, NullAsciiString}, }, - Bytes, + Bytes, Validatable, Validation, }; use byteorder::{ByteOrder, LittleEndian}; @@ -44,7 +44,6 @@ pub enum ToBytesError { Name(#[source] null_ascii_string::WriteError), } -// Bytes impl Bytes for Move { type ByteArray = [u8; 0x1c]; type FromError = FromBytesError; @@ -84,26 +83,47 @@ impl Bytes for Move { // And return Ok Ok(()) } +} - /* - fn validate(&self) -> Validation { +impl Validatable for Move { + type Error = ValidationError; + type Warning = ValidationWarning; + + fn validate(&self) -> Validation { // Create the initial validation let mut validation = Validation::new(); // If our name is longer or equal to `0x16` bytes, emit error if self.name.len() >= 0x16 { - validation.add_error("Name must be at most 21 characters."); + validation.emit_error(ValidationError::NameTooLong); } // If the power isn't a multiple of 10, warn, as we don't know how the game handles // powers that aren't multiples of 10. // TODO: Verify if the game can handle non-multiple of 10 powers. if self.power % 10 != 0 { - validation.add_warning("Powers that are not a multiple of 10 are not fully supported."); + validation.emit_warning(ValidationWarning::PowerMultiple10); } // And return the validation validation } - */ +} + +/// All warnings for [`Move`] validation +#[derive(PartialEq, Eq, Clone, Debug)] +#[derive(derive_more::Display)] +pub enum ValidationWarning { + /// Power is not a multiple of 10 + #[display(fmt = "Power is not a multiple of 10.")] + PowerMultiple10, +} + +/// All errors for [`Move`] validation +#[derive(PartialEq, Eq, Clone, Debug)] +#[derive(derive_more::Display)] +pub enum ValidationError { + /// Name length + #[display(fmt = "Name is too long. Must be at most 21 characters")] + NameTooLong, } diff --git a/src/game/card/property/moves/test.rs b/src/game/card/property/moves/test.rs index 1d60868..875d56d 100644 --- a/src/game/card/property/moves/test.rs +++ b/src/game/card/property/moves/test.rs @@ -5,44 +5,53 @@ // Imports use super::*; +use crate::Validatable; #[test] fn bytes() { // Valid moves with no warnings - #[rustfmt::skip] - let valid_moves: &[(Move, ::ByteArray)] = &[( - Move { - name: ascii::AsciiString::from_ascii("Digimon").expect("Unable to convert string to ascii"), - power: LittleEndian::read_u16(&[1, 2]), - unknown: LittleEndian::read_u32(&[1, 2, 3, 4]), - }, - [ - // Power - 1, 2, - - // Unknown, - 1, 2, 3, 4, - - // Name - b'D', b'i', b'g', b'i', b'm', b'o', b'n', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', - b'\0', b'\0', - ], - )]; + let valid_moves: &[(Move, ::ByteArray)] = &[ + ( + Move { + name: ascii::AsciiString::from_ascii("Digimon").expect("Unable to convert string to ascii"), + power: LittleEndian::read_u16(&[4, 1]), + unknown: LittleEndian::read_u32(&[1, 2, 3, 4]), + }, + [ + 4, 1, 1, 2, 3, 4, b'D', b'i', b'g', b'i', b'm', b'o', b'n', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + ), + ( + Move { + name: ascii::AsciiString::from_ascii("123456789012345678901").expect("Unable to convert string to ascii"), + power: 0, + unknown: 0, + }, + [ + 0, 0, 0, 0, 0, 0, b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', + b'0', b'1', 0, + ], + ), + ]; + + for (mov, bytes) in valid_moves { + // Print the move and move bytes + println!("Move: {:?}", mov); + println!("Bytes: {:?}", bytes); - for (mov, move_bytes) in valid_moves { // Check that we can create the move from bytes - assert_eq!(&Move::from_bytes(move_bytes).expect("Unable to convert move from bytes"), mov); + assert_eq!(&Move::from_bytes(bytes).expect("Unable to convert move from bytes"), mov); // Make sure the validation succeeds - /* let validation = mov.validate(); + println!("Errors: {:?}", validation.errors()); + println!("Warnings: {:?}", validation.warnings()); assert!(validation.successful()); assert!(validation.warnings().is_empty()); - */ // Then serialize it to bytes and make sure it's equal - let mut bytes = ::ByteArray::default(); - Move::to_bytes(mov, &mut bytes).expect("Unable to convert move to bytes"); - assert_eq!(&bytes, move_bytes); + let mut mov_bytes = ::ByteArray::default(); + Move::to_bytes(mov, &mut mov_bytes).expect("Unable to convert move to bytes"); + assert_eq!(&mov_bytes, bytes); } } diff --git a/src/game/validation.rs b/src/game/validation.rs index 3bedd87..a566efc 100644 --- a/src/game/validation.rs +++ b/src/game/validation.rs @@ -13,37 +13,69 @@ /// 2. It provides warnings alongside the errors. These are also provided via `log::warn`, but /// these cannot be sent to the user easily. pub trait Validatable { - /// Validation type - type Output: Validation; + /// Error type for this validation + type Error; + + /// Warning type for this validation + type Warning; /// Validates this structure - fn validate(&self) -> Self::Output; + fn validate(&self) -> Validation; } -/// A validation type. -/// -/// This is the output of structures which may be validated. -/// It is a trait to offer more flexibility to each structure to report -/// errors and warnings in it's preferred manner. -pub trait Validation: Clone { - /// Warnings type - type Warnings; +/// A validation +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct Validation { + /// All warnings + warnings: Vec, - /// Errors type - type Errors; + /// All errors + errors: Vec, +} - /// If this validation was successful. - /// - /// A successful validation is one that, although may emit warnings, did not emit - /// any errors. Conversely, this also indicates that calling [`to_bytes`] will _not_ - /// produce a `Err` value. - fn successful(&self) -> bool { - self.errors().is_none() +impl Default for Validation { + fn default() -> Self { + Self { + warnings: vec![], + errors: vec![], + } + } +} + +impl Validation { + /// Creates an empty validation + #[must_use] + pub fn new() -> Self { + Self::default() } - /// Returns any warnings - fn warnings(&self) -> Option; + /// Emits a warning + pub fn emit_warning(&mut self, warning: Warning) { + self.warnings.push(warning); + } - /// Returns any errors - fn errors(&self) -> Option; + /// Emits an error + pub fn emit_error(&mut self, error: Error) { + self.errors.push(error); + } + + /// Returns all warnings + #[must_use] + pub fn warnings(&self) -> &[Warning] { + &self.warnings + } + + /// Returns all errors + #[must_use] + pub fn errors(&self) -> &[Error] { + &self.errors + } + + /// Returns if this validation was successful + /// + /// A validation is considered successful if no errors occurred. + #[must_use] + pub fn successful(&self) -> bool { + self.errors.is_empty() + } }