From 1b44d6a02f67e4a9d6e4f0891751cda8bfb5d2ee Mon Sep 17 00:00:00 2001 From: Yu Li Date: Mon, 4 Nov 2024 21:48:57 +0800 Subject: [PATCH] chore(volo-http): implement `source` for `Error`s Signed-off-by: Yu Li --- Cargo.lock | 2 +- volo-http/Cargo.toml | 48 ++++++++++++++++------------- volo-http/src/body.rs | 10 +++++- volo-http/src/error/client.rs | 30 ++++++++++++++++-- volo-http/src/error/server.rs | 13 ++++++++ volo-http/src/server/param.rs | 9 +++++- volo-http/src/server/route/utils.rs | 10 +++++- volo-http/src/server/utils/ws.rs | 12 ++++++-- 8 files changed, 103 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 190d1fdf..dde4c0ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3868,7 +3868,7 @@ dependencies = [ [[package]] name = "volo-http" -version = "0.2.14" +version = "0.3.0-rc.1" dependencies = [ "ahash", "async-broadcast", diff --git a/volo-http/Cargo.toml b/volo-http/Cargo.toml index 2be1b485..59291eac 100644 --- a/volo-http/Cargo.toml +++ b/volo-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "volo-http" -version = "0.2.14" +version = "0.3.0-rc.1" edition.workspace = true homepage.workspace = true repository.workspace = true @@ -15,8 +15,6 @@ keywords = ["async", "rpc", "http"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - - [badges] maintenance = { status = "actively-developed" } @@ -61,12 +59,23 @@ tracing.workspace = true url.workspace = true # =====optional===== -multer = { workspace = true, optional = true } # server optional matchit = { workspace = true, optional = true } -# protocol optional +# serde and form, query, json +serde = { workspace = true, optional = true } +serde_urlencoded = { workspace = true, optional = true } +sonic-rs = { workspace = true, optional = true } + +# cookie support +cookie = { workspace = true, optional = true, features = ["percent-encode"] } +cookie_store = { workspace = true, optional = true } + +# multipart optional +multer = { workspace = true, optional = true } + +# websocket optional tungstenite = { workspace = true, optional = true } tokio-tungstenite = { workspace = true, optional = true } @@ -74,15 +83,6 @@ tokio-tungstenite = { workspace = true, optional = true } tokio-rustls = { workspace = true, optional = true } tokio-native-tls = { workspace = true, optional = true } -# cookie support -cookie = { workspace = true, optional = true, features = ["percent-encode"] } -cookie_store = { workspace = true, optional = true } - -# serde and form, query, json -serde = { workspace = true, optional = true } -serde_urlencoded = { workspace = true, optional = true } -sonic-rs = { workspace = true, optional = true } - [dev-dependencies] async-stream.workspace = true libc.workspace = true @@ -96,11 +96,22 @@ default = [] default_client = ["client", "json"] default_server = ["server", "query", "form", "json", "multipart"] -full = ["client", "server", "rustls", "cookie", "query", "form", "json", "multipart", "tls", "ws"] +full = [ + "client", "server", # core + "query", "form", "json", # serde + "tls", # https + "cookie", "multipart", "ws", +] client = ["hyper/client", "hyper/http1"] # client core server = ["hyper/server", "hyper/http1", "dep:matchit"] # server core +__serde = ["dep:serde"] # a private feature for enabling `serde` by `serde_xxx` +query = ["__serde", "dep:serde_urlencoded"] +form = ["__serde", "dep:serde_urlencoded"] +json = ["__serde", "dep:sonic-rs"] + +cookie = ["dep:cookie", "dep:cookie_store"] multipart = ["dep:multer"] ws = ["dep:tungstenite", "dep:tokio-tungstenite"] @@ -110,13 +121,6 @@ rustls = ["__tls", "dep:tokio-rustls", "volo/rustls"] native-tls = ["__tls", "dep:tokio-native-tls", "volo/native-tls"] native-tls-vendored = ["native-tls", "volo/native-tls-vendored"] -cookie = ["dep:cookie", "dep:cookie_store"] - -__serde = ["dep:serde"] # a private feature for enabling `serde` by `serde_xxx` -query = ["__serde", "dep:serde_urlencoded"] -form = ["__serde", "dep:serde_urlencoded"] -json = ["__serde", "dep:sonic-rs"] - [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/volo-http/src/body.rs b/volo-http/src/body.rs index d32a5c61..8bfd39ee 100644 --- a/volo-http/src/body.rs +++ b/volo-http/src/body.rs @@ -278,7 +278,15 @@ impl fmt::Display for BodyConvertError { } } -impl Error for BodyConvertError {} +impl Error for BodyConvertError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + #[cfg(feature = "json")] + Self::JsonDeserializeError(e) => Some(e), + _ => None, + } + } +} impl From<()> for Body { fn from(_: ()) -> Self { diff --git a/volo-http/src/error/client.rs b/volo-http/src/error/client.rs index b24ff507..f3241e40 100644 --- a/volo-http/src/error/client.rs +++ b/volo-http/src/error/client.rs @@ -79,7 +79,11 @@ impl fmt::Display for ClientError { } } -impl Error for ClientError {} +impl Error for ClientError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(self.source.as_ref()?.as_ref()) + } +} /// Error kind of [`ClientError`] #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -174,7 +178,7 @@ macro_rules! simple_error { paste! { #[doc = $kind " error \"" $msg "\""] $(#[$attr])* - #[derive(Debug)] + #[derive(Debug, PartialEq, Eq)] pub struct $name; $(#[$attr])* @@ -200,3 +204,25 @@ simple_error!(Builder => BadScheme => "bad scheme"); simple_error!(Builder => BadHostName => "bad host name"); simple_error!(Request => Timeout => "request timeout"); simple_error!(LoadBalance => NoAvailableEndpoint => "no available endpoint"); + +#[cfg(test)] +mod client_error_tests { + use std::error::Error; + + use crate::error::client::{ + bad_host_name, bad_scheme, no_address, no_available_endpoint, timeout, BadHostName, + BadScheme, NoAddress, NoAvailableEndpoint, Timeout, + }; + + #[test] + fn types_downcast() { + assert!(no_address().source().unwrap().is::()); + assert!(bad_scheme().source().unwrap().is::()); + assert!(bad_host_name().source().unwrap().is::()); + assert!(timeout().source().unwrap().is::()); + assert!(no_available_endpoint() + .source() + .unwrap() + .is::()); + } +} diff --git a/volo-http/src/error/server.rs b/volo-http/src/error/server.rs index d517f3ce..2579f223 100644 --- a/volo-http/src/error/server.rs +++ b/volo-http/src/error/server.rs @@ -38,6 +38,19 @@ impl fmt::Display for ExtractBodyError { } } +impl Error for ExtractBodyError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::Generic(e) => Some(e), + Self::String(e) => Some(e), + #[cfg(feature = "json")] + Self::Json(e) => Some(e), + #[cfg(feature = "form")] + Self::Form(e) => Some(e), + } + } +} + impl IntoResponse for ExtractBodyError { fn into_response(self) -> ServerResponse { let status = match self { diff --git a/volo-http/src/server/param.rs b/volo-http/src/server/param.rs index 61656461..54521675 100644 --- a/volo-http/src/server/param.rs +++ b/volo-http/src/server/param.rs @@ -282,7 +282,14 @@ impl fmt::Display for PathParamsRejection { } } -impl Error for PathParamsRejection {} +impl Error for PathParamsRejection { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::LengthMismatch => None, + Self::ParseError(e) => Some(e.as_ref()), + } + } +} impl IntoResponse for PathParamsRejection { fn into_response(self) -> ServerResponse { diff --git a/volo-http/src/server/route/utils.rs b/volo-http/src/server/route/utils.rs index 6e8ae318..42fb6f0d 100644 --- a/volo-http/src/server/route/utils.rs +++ b/volo-http/src/server/route/utils.rs @@ -91,7 +91,15 @@ impl fmt::Display for MatcherError { } } -impl Error for MatcherError {} +impl Error for MatcherError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::UriConflict(_) => None, + Self::RouterInsertError(e) => Some(e), + Self::RouterMatchError(e) => Some(e), + } + } +} pub(super) struct StripPrefixLayer; diff --git a/volo-http/src/server/utils/ws.rs b/volo-http/src/server/utils/ws.rs index a80fa7bf..157d2191 100644 --- a/volo-http/src/server/utils/ws.rs +++ b/volo-http/src/server/utils/ws.rs @@ -39,6 +39,7 @@ use std::{ borrow::Cow, + error::Error, fmt, future::Future, ops::{Deref, DerefMut}, @@ -467,7 +468,13 @@ impl fmt::Display for WebSocketError { } } -impl std::error::Error for WebSocketError {} +impl Error for WebSocketError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::Upgrade(e) => Some(e), + } + } +} /// What to do when a connection upgrade fails. /// @@ -498,7 +505,6 @@ impl OnFailedUpgrade for DefaultOnFailedUpgrade { /// [`Error`]s while extracting [`WebSocketUpgrade`]. /// -/// [`Error`]: std::error::Error /// [`WebSocketUpgrade`]: crate::server::utils::ws::WebSocketUpgrade #[derive(Debug)] pub enum WebSocketUpgradeRejectionError { @@ -530,7 +536,7 @@ impl WebSocketUpgradeRejectionError { } } -impl std::error::Error for WebSocketUpgradeRejectionError {} +impl Error for WebSocketUpgradeRejectionError {} impl fmt::Display for WebSocketUpgradeRejectionError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {