diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index e2bcce359..1a2428e14 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -37,8 +37,12 @@ pub trait MessageBody { fn size(&self) -> BodySize; fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll>>; + + downcast_get_type_id!(); } +downcast!(MessageBody); + impl MessageBody for () { fn size(&self) -> BodySize { BodySize::Empty @@ -416,7 +420,10 @@ where S: Stream>, { pub fn new(size: u64, stream: S) -> Self { - SizedStream { size, stream: Box::pin(stream) } + SizedStream { + size, + stream: Box::pin(stream), + } } } @@ -643,4 +650,18 @@ mod tests { ); } } + + #[actix_rt::test] + async fn test_body_casting() { + let mut body = String::from("hello cast"); + let resp_body: &mut dyn MessageBody = &mut body; + let body = resp_body.downcast_ref::().unwrap(); + assert_eq!(body, "hello cast"); + let body = &mut resp_body.downcast_mut::().unwrap(); + body.push_str("!"); + let body = resp_body.downcast_ref::().unwrap(); + assert_eq!(body, "hello cast!"); + let not_body = resp_body.downcast_ref::<()>(); + assert!(not_body.is_none()); + } } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index b6637075c..c19aef2aa 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -1,5 +1,4 @@ //! Error and Result module -use std::any::TypeId; use std::cell::RefCell; use std::io::Write; use std::str::Utf8Error; @@ -60,12 +59,6 @@ impl Error { } } -/// A struct with a private constructor, for use with -/// `__private_get_type_id__`. Its single field is private, -/// ensuring that it can only be constructed from this module -#[doc(hidden)] -pub struct PrivateHelper(()); - /// Error that can be converted to `Response` pub trait ResponseError: fmt::Debug + fmt::Display { /// Response's status code @@ -89,43 +82,10 @@ pub trait ResponseError: fmt::Debug + fmt::Display { resp.set_body(Body::from(buf)) } - /// A helper method to get the type ID of the type - /// this trait is implemented on. - /// This method is unsafe to *implement*, since `downcast_ref` relies - /// on the returned `TypeId` to perform a cast. - /// - /// Unfortunately, Rust has no notion of a trait method that is - /// unsafe to implement (marking it as `unsafe` makes it unsafe - /// to *call*). As a workaround, we require this method - /// to return a private type along with the `TypeId`. This - /// private type (`PrivateHelper`) has a private constructor, - /// making it impossible for safe code to construct outside of - /// this module. This ensures that safe code cannot violate - /// type-safety by implementing this method. - #[doc(hidden)] - fn __private_get_type_id__(&self) -> (TypeId, PrivateHelper) - where - Self: 'static, - { - (TypeId::of::(), PrivateHelper(())) - } + downcast_get_type_id!(); } -impl dyn ResponseError + 'static { - /// Downcasts a response error to a specific type. - pub fn downcast_ref(&self) -> Option<&T> { - if self.__private_get_type_id__().0 == TypeId::of::() { - // Safety: external crates cannot override the default - // implementation of `__private_get_type_id__`, since - // it requires returning a private type. We can therefore - // rely on the returned `TypeId`, which ensures that this - // case is correct. - unsafe { Some(&*(self as *const dyn ResponseError as *const T)) } - } else { - None - } - } -} +downcast!(ResponseError); impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index a5ae4b447..1cda9437f 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -10,6 +10,9 @@ #[macro_use] extern crate log; +#[macro_use] +mod macros; + pub mod body; mod builder; pub mod client; diff --git a/actix-http/src/macros.rs b/actix-http/src/macros.rs new file mode 100644 index 000000000..0aaf1abec --- /dev/null +++ b/actix-http/src/macros.rs @@ -0,0 +1,95 @@ +#[macro_export] +macro_rules! downcast_get_type_id { + () => { + /// A helper method to get the type ID of the type + /// this trait is implemented on. + /// This method is unsafe to *implement*, since `downcast_ref` relies + /// on the returned `TypeId` to perform a cast. + /// + /// Unfortunately, Rust has no notion of a trait method that is + /// unsafe to implement (marking it as `unsafe` makes it unsafe + /// to *call*). As a workaround, we require this method + /// to return a private type along with the `TypeId`. This + /// private type (`PrivateHelper`) has a private constructor, + /// making it impossible for safe code to construct outside of + /// this module. This ensures that safe code cannot violate + /// type-safety by implementing this method. + #[doc(hidden)] + fn __private_get_type_id__(&self) -> (std::any::TypeId, PrivateHelper) + where + Self: 'static, + { + (std::any::TypeId::of::(), PrivateHelper(())) + } + } +} + +//Generate implementation for dyn $name +#[macro_export] +macro_rules! downcast { + ($name:ident) => { + /// A struct with a private constructor, for use with + /// `__private_get_type_id__`. Its single field is private, + /// ensuring that it can only be constructed from this module + #[doc(hidden)] + pub struct PrivateHelper(()); + + impl dyn $name + 'static { + /// Downcasts generic body to a specific type. + pub fn downcast_ref(&self) -> Option<&T> { + if self.__private_get_type_id__().0 == std::any::TypeId::of::() { + // Safety: external crates cannot override the default + // implementation of `__private_get_type_id__`, since + // it requires returning a private type. We can therefore + // rely on the returned `TypeId`, which ensures that this + // case is correct. + unsafe { Some(&*(self as *const dyn $name as *const T)) } + } else { + None + } + } + /// Downcasts a generic body to a mutable specific type. + pub fn downcast_mut(&mut self) -> Option<&mut T> { + if self.__private_get_type_id__().0 == std::any::TypeId::of::() { + // Safety: external crates cannot override the default + // implementation of `__private_get_type_id__`, since + // it requires returning a private type. We can therefore + // rely on the returned `TypeId`, which ensures that this + // case is correct. + unsafe { + Some(&mut *(self as *const dyn $name as *const T as *mut T)) + } + } else { + None + } + } + } + }; +} + +#[cfg(test)] +mod tests { + + trait MB { + downcast_get_type_id!(); + } + + downcast!(MB); + + impl MB for String {} + impl MB for () {} + + #[actix_rt::test] + async fn test_any_casting() { + let mut body = String::from("hello cast"); + let resp_body: &mut dyn MB = &mut body; + let body = resp_body.downcast_ref::().unwrap(); + assert_eq!(body, "hello cast"); + let body = &mut resp_body.downcast_mut::().unwrap(); + body.push_str("!"); + let body = resp_body.downcast_ref::().unwrap(); + assert_eq!(body, "hello cast!"); + let not_body = resp_body.downcast_ref::<()>(); + assert!(not_body.is_none()); + } +}