diff --git a/Cargo.lock b/Cargo.lock index 77aeba0..49d41d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -507,6 +507,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "der" version = "0.7.10" @@ -1038,6 +1073,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -1548,6 +1589,28 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.101" @@ -2154,6 +2217,12 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -2544,6 +2613,21 @@ dependencies = [ "serde_derive", "serde_json", "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" +dependencies = [ + "darling", + "once_cell", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 128758f..78c7536 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,7 @@ sqlx = { version = "0.8", optional = true, default-features = false, features = "migrate", ] } redis = { version = "0.32", optional = true, default-features = false } -validator = { version = "0.20", optional = true } +validator = { version = "0.20", optional = true, features = ["derive"] } config = { version = "0.15", optional = true } utoipa = { version = "5.3", optional = true } tokio = { version = "1", optional = true, features = ["time"] } diff --git a/src/convert/sqlx.rs b/src/convert/sqlx.rs index 4bdf0e4..f79e9c2 100644 --- a/src/convert/sqlx.rs +++ b/src/convert/sqlx.rs @@ -61,3 +61,24 @@ impl From for AppError { AppError::database(Some(err.to_string())) } } + +#[cfg(all(test, feature = "sqlx"))] +mod tests { + use std::io; + + use super::*; + use crate::AppErrorKind; + + #[test] + fn row_not_found_maps_to_not_found() { + let err: AppError = SqlxError::RowNotFound.into(); + assert!(matches!(err.kind, AppErrorKind::NotFound)); + } + + #[test] + fn other_error_maps_to_database() { + let io_err = io::Error::new(io::ErrorKind::Other, "boom"); + let err: AppError = SqlxError::Io(io_err).into(); + assert!(matches!(err.kind, AppErrorKind::Database)); + } +} diff --git a/src/convert/tokio.rs b/src/convert/tokio.rs index a5199c7..8592095 100644 --- a/src/convert/tokio.rs +++ b/src/convert/tokio.rs @@ -49,3 +49,21 @@ impl From for AppError { AppError::timeout("Operation timed out") } } + +#[cfg(all(test, feature = "tokio"))] +mod tests { + use tokio::time::{Duration, sleep, timeout}; + + use super::*; + use crate::AppErrorKind; + + #[tokio::test] + async fn elapsed_maps_to_timeout() { + let fut = sleep(Duration::from_millis(20)); + let err = timeout(Duration::from_millis(1), fut) + .await + .expect_err("expect timeout"); + let app_err: AppError = err.into(); + assert!(matches!(app_err.kind, AppErrorKind::Timeout)); + } +} diff --git a/src/convert/validator.rs b/src/convert/validator.rs index a25091b..0022f0e 100644 --- a/src/convert/validator.rs +++ b/src/convert/validator.rs @@ -56,3 +56,26 @@ impl From for AppError { AppError::validation(err.to_string()) } } + +#[cfg(all(test, feature = "validator"))] +mod tests { + use validator::Validate; + + use super::*; + use crate::AppErrorKind; + + #[derive(Validate)] + struct Payload { + #[validate(range(min = 1))] + val: i32 + } + + #[test] + fn validation_errors_map_to_validation_kind() { + let bad = Payload { + val: 0 + }; + let err: AppError = bad.validate().unwrap_err().into(); + assert!(matches!(err.kind, AppErrorKind::Validation)); + } +}