diff --git a/README.md b/README.md index c09e67f..b76ced8 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ let mut u = Username::new(" valid name \n\n").unwrap(); assert_eq!(u.get(), "valid name"); // now we're talking! // This also works for mutations: -assert!(matches!(u.try_mutate(|u| *u = " ".to_owned()), Err(prae::ValidationError))); +assert!(matches!(u.try_mutate(|u| *u = " ".to_owned()), Err(prae::ValidationError { .. }))) ``` Now our `Username` trims provided value automatically. diff --git a/prae/Cargo.toml b/prae/Cargo.toml index 6383260..02ac9f0 100644 --- a/prae/Cargo.toml +++ b/prae/Cargo.toml @@ -16,5 +16,6 @@ prae_macro = { version = "0.2", path = "../prae_macro" } serde = { version = "1.0", optional = true } [dev-dependencies] +assert_matches = "1.5" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/prae/src/core.rs b/prae/src/core.rs index 0debdf4..83d9e26 100644 --- a/prae/src/core.rs +++ b/prae/src/core.rs @@ -1,23 +1,32 @@ use core::hash::Hash; -use std::fmt; use std::ops::{Deref, Index}; +use std::{error::Error, fmt}; + +/// Used for [`define!`](prae_macro::define) macro with `ensure` keyword. +#[derive(Clone, Debug)] +pub struct ValidationError { + /// The name of the type where this error originated. + type_name: &'static str, + /// Stringified value that caused the error. + value: String, +} -/// Default validation error. It is used for [`define!`](prae_macro::define) macro with `ensure` -/// keyword. -#[derive(PartialEq)] -pub struct ValidationError; - -impl std::error::Error for ValidationError {} - -impl fmt::Debug for ValidationError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "provided value is not valid") +impl ValidationError { + /// Create a new error with the input value that failed. + pub fn new(type_name: &'static str, value: String) -> Self { + ValidationError { type_name, value } } } +impl Error for ValidationError {} + impl fmt::Display for ValidationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "provided value is not valid") + write!( + f, + "failed to create {} from value {}: provided value is invalid", + self.type_name, self.value, + ) } } diff --git a/prae/src/lib.rs b/prae/src/lib.rs index 602e49b..1bf3b25 100644 --- a/prae/src/lib.rs +++ b/prae/src/lib.rs @@ -38,7 +38,7 @@ //! assert_eq!(u.get(), "valid name"); // now we're talking! //! //! // This also works for mutations: -//! assert!(matches!(u.try_mutate(|u| *u = " ".to_owned()), Err(prae::ValidationError))); +//! assert!(matches!(u.try_mutate(|u| *u = " ".to_owned()), Err(prae::ValidationError { .. }))); //! ``` //! Now our `Username` trims provided value automatically. //! @@ -77,8 +77,11 @@ mod core; pub use crate::core::*; pub use prae_macro::define; +// We need this to silince the unused_crate_dependencies warning. +// See: https://github.com/rust-lang/rust/issues/57274 #[cfg(test)] mod test_deps { + use assert_matches as _; use serde as _; use serde_json as _; } diff --git a/prae/tests/adjust_ensure.rs b/prae/tests/adjust_ensure.rs index b5ab2e7..2b9591e 100644 --- a/prae/tests/adjust_ensure.rs +++ b/prae/tests/adjust_ensure.rs @@ -1,3 +1,5 @@ +use assert_matches::assert_matches; + use prae; prae::define! { @@ -8,7 +10,10 @@ prae::define! { #[test] fn construction_fails_for_invalid_data() { - assert_eq!(Username::new(" ").unwrap_err(), prae::ValidationError); + assert_matches!( + Username::new(" ").unwrap_err(), + prae::ValidationError { .. } + ) } #[test] @@ -20,10 +25,10 @@ fn construction_succeeds_for_valid_data() { #[test] fn mutation_fails_for_invalid_data() { let mut un = Username::new("user").unwrap(); - assert_eq!( + assert_matches!( un.try_mutate(|u| *u = " ".to_owned()).unwrap_err(), - prae::ValidationError - ); + prae::ValidationError { .. } + ) } #[test] @@ -38,4 +43,4 @@ fn mutation_succeeds_for_valid_data() { let mut un = Username::new("user").unwrap(); assert!(un.try_mutate(|u| *u = " new user ".to_owned()).is_ok()); assert_eq!(un.get(), "new user"); -} \ No newline at end of file +} diff --git a/prae/tests/adjust_validate.rs b/prae/tests/adjust_validate.rs index 6e40fb2..d43f18b 100644 --- a/prae/tests/adjust_validate.rs +++ b/prae/tests/adjust_validate.rs @@ -1,6 +1,8 @@ +use assert_matches::assert_matches; + use prae; -#[derive(Debug, PartialEq)] +#[derive(Debug)] pub struct UsernameError; prae::define! { @@ -17,7 +19,7 @@ prae::define! { #[test] fn construction_fails_for_invalid_data() { - assert_eq!(Username::new(" ").unwrap_err(), UsernameError {}); + assert_matches!(Username::new(" ").unwrap_err(), UsernameError {}); } #[test] @@ -29,7 +31,7 @@ fn construction_succeeds_for_valid_data() { #[test] fn mutation_fails_for_invalid_data() { let mut un = Username::new("user").unwrap(); - assert_eq!( + assert_matches!( un.try_mutate(|u| *u = " ".to_owned()).unwrap_err(), UsernameError {} ); @@ -47,4 +49,4 @@ fn mutation_succeeds_for_valid_data() { let mut un = Username::new("user").unwrap(); assert!(un.try_mutate(|u| *u = " new user ".to_owned()).is_ok()); assert_eq!(un.get(), "new user"); -} \ No newline at end of file +} diff --git a/prae/tests/ensure.rs b/prae/tests/ensure.rs index c903df1..3321f77 100644 --- a/prae/tests/ensure.rs +++ b/prae/tests/ensure.rs @@ -1,3 +1,5 @@ +use assert_matches::assert_matches; + use prae; prae::define! { @@ -7,7 +9,20 @@ prae::define! { #[test] fn construction_fails_for_invalid_data() { - assert_eq!(Username::new("").unwrap_err(), prae::ValidationError); + assert_matches!( + Username::new("").unwrap_err(), + prae::ValidationError { .. } + ) +} + +#[test] +fn error_formats_correctly() { + let error = Username::new("").unwrap_err(); + let message = format!("{}", error); + assert_eq!( + message, + "failed to create Username from value \"\": provided value is invalid" + ); } #[test] @@ -19,10 +34,10 @@ fn construction_succeeds_for_valid_data() { #[test] fn mutation_fails_for_invalid_data() { let mut un = Username::new("user").unwrap(); - assert_eq!( + assert_matches!( un.try_mutate(|u| *u = "".to_owned()).unwrap_err(), - prae::ValidationError - ); + prae::ValidationError { .. } + ) } #[test] @@ -37,4 +52,4 @@ fn mutation_succeeds_for_valid_data() { let mut un = Username::new("user").unwrap(); assert!(un.try_mutate(|u| *u = " new user ".to_owned()).is_ok()); assert_eq!(un.get(), " new user "); -} \ No newline at end of file +} diff --git a/prae/tests/non_clone.rs b/prae/tests/non_clone.rs new file mode 100644 index 0000000..beaa558 --- /dev/null +++ b/prae/tests/non_clone.rs @@ -0,0 +1,11 @@ +#[derive(Debug)] +struct User { + name: String, +} + +prae::define! { + ValidUser: User + ensure |u| !u.name.is_empty() +} + + diff --git a/prae/tests/tests.rs b/prae/tests/tests.rs index abc3fbc..6491e1c 100644 --- a/prae/tests/tests.rs +++ b/prae/tests/tests.rs @@ -1,4 +1,5 @@ mod adjust_ensure; mod adjust_validate; mod ensure; -mod validate; \ No newline at end of file +mod non_clone; +mod validate; diff --git a/prae/tests/validate.rs b/prae/tests/validate.rs index f884c4d..7df1e8d 100644 --- a/prae/tests/validate.rs +++ b/prae/tests/validate.rs @@ -1,6 +1,8 @@ +use assert_matches::assert_matches; + use prae; -#[derive(Debug, PartialEq)] +#[derive(Debug)] pub struct UsernameError; prae::define! { @@ -16,7 +18,7 @@ prae::define! { #[test] fn construction_fails_for_invalid_data() { - assert_eq!(Username::new("").unwrap_err(), UsernameError {}); + assert_matches!(Username::new("").unwrap_err(), UsernameError {}); } #[test] @@ -28,7 +30,7 @@ fn construction_succeeds_for_valid_data() { #[test] fn mutation_fails_for_invalid_data() { let mut un = Username::new("user").unwrap(); - assert_eq!( + assert_matches!( un.try_mutate(|u| *u = "".to_owned()).unwrap_err(), UsernameError {} ); @@ -46,4 +48,4 @@ fn mutation_succeeds_for_valid_data() { let mut un = Username::new("user").unwrap(); assert!(un.try_mutate(|u| *u = " new user ".to_owned()).is_ok()); assert_eq!(un.get(), " new user "); -} \ No newline at end of file +} diff --git a/prae_macro/src/lib.rs b/prae_macro/src/lib.rs index f4d588c..85e15af 100644 --- a/prae_macro/src/lib.rs +++ b/prae_macro/src/lib.rs @@ -36,7 +36,7 @@ pub fn define(input: TokenStream) -> TokenStream { GuardClosure::Ensure(EnsureClosure(closure)) => quote! { fn validate(v: &Self::Target) -> Option { let f: fn(&Self::Target) -> bool = #closure; - if f(v) { None } else { Some(prae::ValidationError) } + if f(v) { None } else { Some(prae::ValidationError::new(stringify!(#ident), format!("{:?}", v))) } } }, GuardClosure::Validate(ValidateClosure(closure, err_ty)) => quote! {