Improved validation interface.

Implemented new validation interface for `Move`.
Added more tests to `Move`.
This commit is contained in:
2020-07-14 19:41:23 +01:00
parent 36c429372e
commit b08c6431cf
3 changed files with 118 additions and 57 deletions

View File

@@ -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<Self::Error, Self::Warning> {
// 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,
}

View File

@@ -5,44 +5,53 @@
// Imports
use super::*;
use crate::Validatable;
#[test]
fn bytes() {
// Valid moves with no warnings
#[rustfmt::skip]
let valid_moves: &[(Move, <Move as Bytes>::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, <Move as Bytes>::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 = <Move as 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 = <Move as Bytes>::ByteArray::default();
Move::to_bytes(mov, &mut mov_bytes).expect("Unable to convert move to bytes");
assert_eq!(&mov_bytes, bytes);
}
}

View File

@@ -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<Self::Error, Self::Warning>;
}
/// 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<Error, Warning> {
/// All warnings
warnings: Vec<Warning>,
/// Errors type
type Errors;
/// All errors
errors: Vec<Error>,
}
/// 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<Error, Warning> Default for Validation<Error, Warning> {
fn default() -> Self {
Self {
warnings: vec![],
errors: vec![],
}
}
}
impl<Error, Warning> Validation<Error, Warning> {
/// Creates an empty validation
#[must_use]
pub fn new() -> Self {
Self::default()
}
/// Returns any warnings
fn warnings(&self) -> Option<Self::Warnings>;
/// Emits a warning
pub fn emit_warning(&mut self, warning: Warning) {
self.warnings.push(warning);
}
/// Returns any errors
fn errors(&self) -> Option<Self::Errors>;
/// 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()
}
}