From aabaafc91a75c7b920a21ba6a35cd58a56eb25b1 Mon Sep 17 00:00:00 2001 From: Filipe Rodrigues Date: Sun, 16 May 2021 08:31:25 +0100 Subject: [PATCH] Added new validation interface in `dcb-bytes`. --- dcb-bytes/Cargo.toml | 3 ++ dcb-bytes/src/lib.rs | 2 + dcb-bytes/src/validate.rs | 47 +++++++++++++++++ dcb/src/card/property/moves.rs | 25 ++------- dcb/src/card/property/moves/test.rs | 8 ++- dcb/src/lib.rs | 2 - dcb/src/validation.rs | 82 ----------------------------- 7 files changed, 60 insertions(+), 109 deletions(-) create mode 100644 dcb-bytes/src/validate.rs delete mode 100644 dcb/src/validation.rs diff --git a/dcb-bytes/Cargo.toml b/dcb-bytes/Cargo.toml index 4b1359b..44096f5 100644 --- a/dcb-bytes/Cargo.toml +++ b/dcb-bytes/Cargo.toml @@ -9,3 +9,6 @@ edition = "2018" # Bytes byteorder = "1.4.2" arrayref = "0.3.6" + +# Util +either = "1.6.1" \ No newline at end of file diff --git a/dcb-bytes/src/lib.rs b/dcb-bytes/src/lib.rs index bec7cef..c542d60 100644 --- a/dcb-bytes/src/lib.rs +++ b/dcb-bytes/src/lib.rs @@ -62,9 +62,11 @@ pub mod byteorder_ext; pub mod bytes; pub mod derive; +pub mod validate; // Exports pub use byteorder_ext::ByteOrderExt; pub use bytes::{ByteArray, Bytes}; +pub use validate::{Validate, ValidateVisitor}; #[doc(hidden)] pub use ::{arrayref, byteorder}; diff --git a/dcb-bytes/src/validate.rs b/dcb-bytes/src/validate.rs new file mode 100644 index 0000000..1ba1644 --- /dev/null +++ b/dcb-bytes/src/validate.rs @@ -0,0 +1,47 @@ +//! Validation interface + +// Imports +use either::Either; + +/// Structures that are validatable before being written to bytes. +/// +/// This works in tandem with the [`Bytes`](crate::Bytes) interface to allow +/// applications which take user input to validate input before serializing it. +/// +/// Although this information exists by calling [`Bytes::to_bytes`](crate::Bytes::to_bytes), +/// this interface provides two main advantages: +/// +/// 1. It is faster than serializing the data, as it doesn't need to write the raw bytes and +/// can focus on simply parsing possible errors. +/// 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 Validate<'a> { + /// Error type for this validation + type Error: 'a; + + /// Warning type for this validation + type Warning: 'a; + + /// Validates this type with the visitor `visitor` + fn validate>(&'a self, visitor: V); +} + +/// A validate visitor, used to collect errors and warnings +pub trait ValidateVisitor<'a, T: ?Sized + Validate<'a>> { + /// Visits a warning + fn visit_warning(&mut self, warning: T::Warning); + + /// Visits an error + fn visit_error(&mut self, error: T::Error); +} + +/// A closure taking a `Either` is a visitor +impl<'a, T: ?Sized + Validate<'a>, F: FnMut(Either)> ValidateVisitor<'a, T> for F { + fn visit_warning(&mut self, warning: T::Warning) { + self(Either::Left(warning)); + } + + fn visit_error(&mut self, error: T::Error) { + self(Either::Right(error)); + } +} diff --git a/dcb/src/card/property/moves.rs b/dcb/src/card/property/moves.rs index 34f7f3f..2c64c36 100644 --- a/dcb/src/card/property/moves.rs +++ b/dcb/src/card/property/moves.rs @@ -5,9 +5,8 @@ mod test; // Imports -use crate::{Validatable, Validation}; use byteorder::{ByteOrder, LittleEndian}; -use dcb_bytes::Bytes; +use dcb_bytes::{Bytes, Validate, ValidateVisitor}; use dcb_util::{ array_split, array_split_mut, null_ascii_string::{self, NullAsciiString}, @@ -77,23 +76,17 @@ impl Bytes for Move { } } -impl Validatable for Move { - type Error = ValidationError; +impl<'a> Validate<'a> for Move { + type Error = !; type Warning = ValidationWarning; - fn validate(&self) -> Validation { - // Create the initial validation - let mut validation = Validation::new(); - + fn validate>(&'a self, mut visitor: V) { // 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.emit_warning(ValidationWarning::PowerMultiple10); + visitor.visit_warning(ValidationWarning::PowerMultiple10); } - - // And return the validation - validation } } @@ -104,11 +97,3 @@ pub enum ValidationWarning { #[error("Power is not a multiple of 10.")] PowerMultiple10, } - -/// All errors for [`Move`] validation -#[derive(PartialEq, Eq, Clone, Debug, thiserror::Error)] -pub enum ValidationError { - /// Name length - #[error("Name is too long. Must be at most 21 characters")] - NameTooLong, -} diff --git a/dcb/src/card/property/moves/test.rs b/dcb/src/card/property/moves/test.rs index 4f137b1..9e90d07 100644 --- a/dcb/src/card/property/moves/test.rs +++ b/dcb/src/card/property/moves/test.rs @@ -2,7 +2,6 @@ // Imports use super::*; -use crate::Validatable; use std::convert::TryFrom; #[test] @@ -47,10 +46,9 @@ fn valid_bytes() { mov ); - // Make sure the validation succeeds - let validation = mov.validate(); - assert!(validation.successful()); - assert!(validation.warnings().is_empty()); + // Make sure the validation succeeds without warnings or errors + let mut successful = true; + mov.validate(|_| successful = false); // Then serialize it to bytes and make sure it's equal let mut mov_bytes = ::ByteArray::default(); diff --git a/dcb/src/lib.rs b/dcb/src/lib.rs index f3ac0c2..42f7ed8 100644 --- a/dcb/src/lib.rs +++ b/dcb/src/lib.rs @@ -69,9 +69,7 @@ // Modules pub mod card; pub mod deck; -pub mod validation; // Exports pub use card::{Digimon, Digivolve, Item, Table as CardTable}; pub use deck::{Deck, Table as DeckTable}; -pub use validation::{Validatable, Validation}; diff --git a/dcb/src/validation.rs b/dcb/src/validation.rs deleted file mode 100644 index 08a66e8..0000000 --- a/dcb/src/validation.rs +++ /dev/null @@ -1,82 +0,0 @@ -//! Error and warning validation for structures - -/// Structures that are validatable to be written to bytes. -/// -/// This works in tandem with the [`Bytes`](dcb_bytes::Bytes) interface to allow -/// applications which take user input to validate input before serializing it. -/// -/// Although this information exists by calling [`Bytes::to_bytes`](dcb_bytes::Bytes::to_bytes), -/// this interface provides two main advantages: -/// -/// 1. It is faster than serializing the data, as it doesn't need to write the raw bytes and -/// can focus on simply parsing possible errors. -/// 2. It provides warnings alongside the errors. These are also provided via `log::warn`, but -/// these cannot be sent to the user easily. -// TODO: Move to `dcb-bytes`. -pub trait Validatable { - /// Error type for this validation - type Error; - - /// Warning type for this validation - type Warning; - - /// Validates this structure - fn validate(&self) -> Validation; -} - -/// A validation -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct Validation { - /// All warnings - warnings: Vec, - - /// All errors - errors: Vec, -} - -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() - } - - /// Emits a warning - pub fn emit_warning(&mut self, warning: Warning) { - self.warnings.push(warning); - } - - /// 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() - } -}