Added new validation interface in dcb-bytes.

This commit is contained in:
Filipe Rodrigues 2021-05-16 08:31:25 +01:00
parent f98d8ae290
commit aabaafc91a
7 changed files with 60 additions and 109 deletions

View File

@ -9,3 +9,6 @@ edition = "2018"
# Bytes
byteorder = "1.4.2"
arrayref = "0.3.6"
# Util
either = "1.6.1"

View File

@ -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};

47
dcb-bytes/src/validate.rs Normal file
View File

@ -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<V: ValidateVisitor<'a, Self>>(&'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<Warning, Error>` is a visitor
impl<'a, T: ?Sized + Validate<'a>, F: FnMut(Either<T::Warning, T::Error>)> 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));
}
}

View File

@ -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<Self::Error, Self::Warning> {
// Create the initial validation
let mut validation = Validation::new();
fn validate<V: ValidateVisitor<'a, Self>>(&'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,
}

View File

@ -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 = <Move as Bytes>::ByteArray::default();

View File

@ -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};

View File

@ -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<Self::Error, Self::Warning>;
}
/// A validation
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Validation<Error, Warning> {
/// All warnings
warnings: Vec<Warning>,
/// All errors
errors: Vec<Error>,
}
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()
}
/// 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()
}
}