diff --git a/.travis.yml b/.travis.yml index 47bfc526..3569d047 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: "rust" cache: "cargo" rust: - - 1.39.0 + - 1.40.0 - stable - beta - nightly diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f5c6451..87e1fe9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # [unreleased] +Breaking changes: + +* Instead of one `Error` type, there is now two: `FromHttpError` and `IntoHttpError` + * In constrast to the old `Error` types, these can be pattern matched and generally allow getting + much more context for why things failed +* Out Minimum Supported Rust Version is now 1.40.0 + # 0.12.1 Improvements: diff --git a/Cargo.toml b/Cargo.toml index 84bae69a..fdcd7ebe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ edition = "2018" http = "0.2.0" percent-encoding = { version = "2.1.0", optional = true } ruma-api-macros = { version = "=0.9.1", path = "ruma-api-macros", optional = true } -ruma-identifiers = "0.14.0" +ruma-identifiers = { version = "0.14.0", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } serde_json = "1.0.42" serde_urlencoded = "0.6.1" @@ -27,7 +27,13 @@ ruma-events = "0.15.1" [features] default = ["with-ruma-api-macros"] -with-ruma-api-macros = ["percent-encoding", "ruma-api-macros", "serde", "url"] +with-ruma-api-macros = [ + "percent-encoding", + "ruma-api-macros", + "ruma-identifiers", + "serde", + "url", +] [workspace] members = [ diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs index 80eb34c1..29180673 100644 --- a/ruma-api-macros/src/api.rs +++ b/ruma-api-macros/src/api.rs @@ -369,7 +369,7 @@ impl ToTokens for Api { #request_type impl std::convert::TryFrom>> for #request_try_from_type { - type Error = ruma_api::Error; + type Error = ruma_api::FromHttpError; #[allow(unused_variables)] fn try_from(request: ruma_api::exports::http::Request>) -> Result { @@ -388,7 +388,7 @@ impl ToTokens for Api { } impl std::convert::TryFrom for ruma_api::exports::http::Request> { - type Error = ruma_api::Error; + type Error = ruma_api::IntoHttpError; #[allow(unused_mut, unused_variables)] fn try_from(request: Request) -> Result { @@ -419,7 +419,7 @@ impl ToTokens for Api { #response_type impl std::convert::TryFrom for ruma_api::exports::http::Response> { - type Error = ruma_api::Error; + type Error = ruma_api::IntoHttpError; #[allow(unused_variables)] fn try_from(response: Response) -> Result { @@ -433,7 +433,7 @@ impl ToTokens for Api { } impl std::convert::TryFrom>> for #response_try_from_type { - type Error = ruma_api::Error; + type Error = ruma_api::FromHttpError; #[allow(unused_variables)] fn try_from( @@ -449,7 +449,7 @@ impl ToTokens for Api { #response_init_fields }) } else { - Err(http_response.status().clone().into()) + Err(ruma_api::FromHttpError::Http(http_response)) } } } diff --git a/src/lib.rs b/src/lib.rs index 25cdaae9..6a85e6ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,11 +17,9 @@ use std::{ convert::{TryFrom, TryInto}, error::Error as StdError, fmt::{Display, Formatter, Result as FmtResult}, - io, }; -use http::{self, Method, StatusCode}; -use ruma_identifiers; +use http::{self, Method}; use serde_json; use serde_urlencoded; @@ -240,111 +238,89 @@ pub trait Outgoing { /// A Matrix API endpoint. /// /// The type implementing this trait contains any data needed to make a request to the endpoint. -pub trait Endpoint: Outgoing + TryInto>, Error = Error> +pub trait Endpoint: Outgoing + TryInto>, Error = IntoHttpError> where - ::Incoming: TryFrom>, Error = Error>, - ::Incoming: TryFrom>, Error = Error>, + ::Incoming: TryFrom>, Error = FromHttpError>, + ::Incoming: TryFrom>, Error = FromHttpError>, { /// Data returned in a successful response from the endpoint. - type Response: Outgoing + TryInto>, Error = Error>; + type Response: Outgoing + TryInto>, Error = IntoHttpError>; /// Metadata about the endpoint. const METADATA: Metadata; } -/// An error when converting an `Endpoint` request or response to the corresponding type from the -/// `http` crate. +/// An error when converting an `http` request or response to the corresponding #[derive(Debug)] -pub struct Error(pub(crate) InnerError); +#[non_exhaustive] +pub enum FromHttpError { + /// The server returned a non-success status + Http(http::Response>), + /// JSON deserialization failed + Json(serde_json::Error), + /// Deserialization of query parameters failed + Query(serde_urlencoded::de::Error), +} -impl Display for Error { +/// An error when converting a request or response to the corresponding http type. +#[derive(Debug)] +#[non_exhaustive] +pub enum IntoHttpError { + /// JSON serialization failed + Json(serde_json::Error), + /// Serialization of query parameters failed + Query(serde_urlencoded::ser::Error), +} + +impl From for FromHttpError { + fn from(err: serde_json::Error) -> Self { + Self::Json(err) + } +} + +impl From for FromHttpError { + fn from(err: serde_urlencoded::de::Error) -> Self { + Self::Query(err) + } +} + +impl Display for FromHttpError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - let message = match self.0 { - InnerError::Http(_) => "An error converting to or from `http` types occurred.".into(), - InnerError::Io(_) => "An I/O error occurred.".into(), - InnerError::SerdeJson(_) => "A JSON error occurred.".into(), - InnerError::SerdeUrlEncodedDe(_) => { - "A URL encoding deserialization error occurred.".into() - } - InnerError::SerdeUrlEncodedSer(_) => { - "A URL encoding serialization error occurred.".into() - } - InnerError::RumaIdentifiers(_) => "A ruma-identifiers error occurred.".into(), - InnerError::StatusCode(code) => format!("A HTTP {} error occurred.", code), - }; - - write!(f, "{}", message) + match self { + Self::Http(res) => match res.status().canonical_reason() { + Some(reason) => write!(f, "HTTP status {} {}", res.status().as_str(), reason), + None => write!(f, "HTTP status {}", res.status().as_str()), + }, + Self::Json(err) => write!(f, "JSON deserialization failed: {}", err), + Self::Query(err) => write!(f, "Query parameter deserialization failed: {}", err), + } } } -impl StdError for Error {} +impl StdError for FromHttpError {} -/// Internal representation of errors. -#[derive(Debug)] -pub(crate) enum InnerError { - /// An HTTP error. - Http(http::Error), - - /// A I/O error. - Io(io::Error), - - /// A Serde JSON error. - SerdeJson(serde_json::Error), - - /// A Serde URL decoding error. - SerdeUrlEncodedDe(serde_urlencoded::de::Error), - - /// A Serde URL encoding error. - SerdeUrlEncodedSer(serde_urlencoded::ser::Error), - - /// A Ruma Identitifiers error. - RumaIdentifiers(ruma_identifiers::Error), - - /// An HTTP status code indicating error. - StatusCode(StatusCode), -} - -impl From for Error { - fn from(error: http::Error) -> Self { - Self(InnerError::Http(error)) +impl From for IntoHttpError { + fn from(err: serde_json::Error) -> Self { + Self::Json(err) } } -impl From for Error { - fn from(error: io::Error) -> Self { - Self(InnerError::Io(error)) +impl From for IntoHttpError { + fn from(err: serde_urlencoded::ser::Error) -> Self { + Self::Query(err) } } -impl From for Error { - fn from(error: serde_json::Error) -> Self { - Self(InnerError::SerdeJson(error)) +impl Display for IntoHttpError { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self { + Self::Json(err) => write!(f, "JSON serialization failed: {}", err), + Self::Query(err) => write!(f, "Query parameter serialization failed: {}", err), + } } } -impl From for Error { - fn from(error: serde_urlencoded::de::Error) -> Self { - Self(InnerError::SerdeUrlEncodedDe(error)) - } -} - -impl From for Error { - fn from(error: serde_urlencoded::ser::Error) -> Self { - Self(InnerError::SerdeUrlEncodedSer(error)) - } -} - -impl From for Error { - fn from(error: ruma_identifiers::Error) -> Self { - Self(InnerError::RumaIdentifiers(error)) - } -} - -impl From for Error { - fn from(error: StatusCode) -> Self { - Self(InnerError::StatusCode(error)) - } -} +impl StdError for IntoHttpError {} /// Metadata about an API endpoint. #[derive(Clone, Debug)] @@ -381,7 +357,7 @@ mod tests { use serde::{de::IntoDeserializer, Deserialize, Serialize}; use serde_json; - use crate::{Endpoint, Error, Metadata, Outgoing}; + use crate::{Endpoint, FromHttpError, IntoHttpError, Metadata, Outgoing}; /// A request to create a new room alias. #[derive(Debug)] @@ -408,7 +384,7 @@ mod tests { } impl TryFrom for http::Request> { - type Error = Error; + type Error = IntoHttpError; fn try_from(request: Request) -> Result>, Self::Error> { let metadata = Request::METADATA; @@ -423,14 +399,15 @@ mod tests { let http_request = http::Request::builder() .method(metadata.method) .uri(path) - .body(serde_json::to_vec(&request_body).map_err(Error::from)?)?; + .body(serde_json::to_vec(&request_body)?) + .expect("http request building to succeed"); Ok(http_request) } } impl TryFrom>> for Request { - type Error = Error; + type Error = FromHttpError; fn try_from(request: http::Request>) -> Result { let request_body: RequestBody = @@ -462,19 +439,19 @@ mod tests { } impl TryFrom>> for Response { - type Error = Error; + type Error = FromHttpError; fn try_from(http_response: http::Response>) -> Result { if http_response.status().is_success() { Ok(Response) } else { - Err(http_response.status().into()) + Err(FromHttpError::Http(http_response)) } } } impl TryFrom for http::Response> { - type Error = Error; + type Error = IntoHttpError; fn try_from(_: Response) -> Result>, Self::Error> { let response = http::Response::builder()