Unverified Commit e1cf72e5 authored by John DiSanti's avatar John DiSanti Committed by GitHub
Browse files

Make the `Debug` impl on `TypeErasedBox` useful (#2665)

## Motivation and Context
This PR ports @rcoh's addition of `Debug` in `ConfigBag` to the
`TypeErasedBox`.

----

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._
parent 23d0f5ec
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -156,8 +156,8 @@ class ResponseDeserializerGenerator(
            """
            pub(crate) fn $fnName<O, E>(result: Result<O, E>) -> Result<#{Output}, #{Error}>
            where
                O: Send + Sync + 'static,
                E: Send + Sync + 'static,
                O: std::fmt::Debug + Send + Sync + 'static,
                E: std::fmt::Debug + Send + Sync + 'static,
            {
                result.map(|output| #{TypedBox}::new(output).erase())
                    .map_err(|error| #{TypedBox}::new(error).erase())
+6 −7
Original line number Diff line number Diff line
@@ -7,9 +7,8 @@ use crate::client::identity::{Identity, IdentityResolver, IdentityResolvers};
use crate::client::orchestrator::{BoxError, HttpRequest};
use crate::config_bag::ConfigBag;
use crate::type_erasure::{TypeErasedBox, TypedBox};
use std::any::Any;
use std::borrow::Cow;
use std::fmt::Debug;
use std::fmt;
use std::sync::Arc;

#[cfg(feature = "http-auth")]
@@ -39,16 +38,16 @@ impl AuthSchemeId {
pub struct AuthOptionResolverParams(TypeErasedBox);

impl AuthOptionResolverParams {
    pub fn new<T: Any + Send + Sync + 'static>(params: T) -> Self {
    pub fn new<T: fmt::Debug + Send + Sync + 'static>(params: T) -> Self {
        Self(TypedBox::new(params).erase())
    }

    pub fn get<T: 'static>(&self) -> Option<&T> {
    pub fn get<T: fmt::Debug + Send + Sync + 'static>(&self) -> Option<&T> {
        self.0.downcast_ref()
    }
}

pub trait AuthOptionResolver: Send + Sync + Debug {
pub trait AuthOptionResolver: Send + Sync + fmt::Debug {
    fn resolve_auth_options<'a>(
        &'a self,
        params: &AuthOptionResolverParams,
@@ -87,7 +86,7 @@ impl HttpAuthSchemes {
    }
}

pub trait HttpAuthScheme: Send + Sync + Debug {
pub trait HttpAuthScheme: Send + Sync + fmt::Debug {
    fn scheme_id(&self) -> AuthSchemeId;

    fn identity_resolver<'a>(
@@ -98,7 +97,7 @@ pub trait HttpAuthScheme: Send + Sync + Debug {
    fn request_signer(&self) -> &dyn HttpRequestSigner;
}

pub trait HttpRequestSigner: Send + Sync + Debug {
pub trait HttpRequestSigner: Send + Sync + fmt::Debug {
    /// Return a signed version of the given request using the given identity.
    ///
    /// If the provided identity is incompatible with this signer, an error must be returned.
+8 −9
Original line number Diff line number Diff line
@@ -14,8 +14,7 @@ use aws_smithy_async::future::now_or_later::NowOrLater;
use aws_smithy_async::rt::sleep::AsyncSleep;
use aws_smithy_http::body::SdkBody;
use aws_smithy_http::endpoint::EndpointPrefix;
use std::any::Any;
use std::fmt::Debug;
use std::fmt;
use std::future::Future as StdFuture;
use std::pin::Pin;
use std::sync::Arc;
@@ -27,15 +26,15 @@ pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
pub type BoxFuture<T> = Pin<Box<dyn StdFuture<Output = Result<T, BoxError>>>>;
pub type Future<T> = NowOrLater<Result<T, BoxError>, BoxFuture<T>>;

pub trait TraceProbe: Send + Sync + Debug {
pub trait TraceProbe: Send + Sync + fmt::Debug {
    fn dispatch_events(&self);
}

pub trait RequestSerializer: Send + Sync + Debug {
pub trait RequestSerializer: Send + Sync + fmt::Debug {
    fn serialize_input(&self, input: Input) -> Result<HttpRequest, BoxError>;
}

pub trait ResponseDeserializer: Send + Sync + Debug {
pub trait ResponseDeserializer: Send + Sync + fmt::Debug {
    fn deserialize_streaming(&self, response: &mut HttpResponse) -> Option<OutputOrError> {
        let _ = response;
        None
@@ -44,7 +43,7 @@ pub trait ResponseDeserializer: Send + Sync + Debug {
    fn deserialize_nonstreaming(&self, response: &HttpResponse) -> OutputOrError;
}

pub trait Connection: Send + Sync + Debug {
pub trait Connection: Send + Sync + fmt::Debug {
    fn call(&self, request: HttpRequest) -> BoxFuture<HttpResponse>;
}

@@ -58,16 +57,16 @@ impl Connection for Box<dyn Connection> {
pub struct EndpointResolverParams(TypeErasedBox);

impl EndpointResolverParams {
    pub fn new<T: Any + Send + Sync + 'static>(params: T) -> Self {
    pub fn new<T: fmt::Debug + Send + Sync + 'static>(params: T) -> Self {
        Self(TypedBox::new(params).erase())
    }

    pub fn get<T: 'static>(&self) -> Option<&T> {
    pub fn get<T: fmt::Debug + Send + Sync + 'static>(&self) -> Option<&T> {
        self.0.downcast_ref()
    }
}

pub trait EndpointResolver: Send + Sync + Debug {
pub trait EndpointResolver: Send + Sync + fmt::Debug {
    fn resolve_and_apply_endpoint(
        &self,
        params: &EndpointResolverParams,
+8 −45
Original line number Diff line number Diff line
@@ -12,10 +12,9 @@
mod typeid_map;

use crate::config_bag::typeid_map::TypeIdMap;

use std::any::{type_name, Any, TypeId};
use crate::type_erasure::TypeErasedBox;
use std::any::{type_name, TypeId};
use std::borrow::Cow;

use std::fmt::{Debug, Formatter};
use std::iter::Rev;
use std::marker::PhantomData;
@@ -77,45 +76,9 @@ impl<T: Default> Default for Value<T> {
    }
}

struct DebugErased {
    field: Box<dyn Any + Send + Sync>,
    #[allow(dead_code)]
    type_name: &'static str,
    #[allow(clippy::type_complexity)]
    debug: Box<dyn Fn(&DebugErased, &mut Formatter<'_>) -> std::fmt::Result + Send + Sync>,
}

impl Debug for DebugErased {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        (self.debug)(self, f)
    }
}

impl DebugErased {
    fn new<T: Send + Sync + Debug + 'static>(value: T) -> Self {
        let debug = |value: &DebugErased, f: &mut Formatter<'_>| {
            Debug::fmt(value.as_ref::<T>().expect("typechecked"), f)
        };
        let name = type_name::<T>();
        Self {
            field: Box::new(value),
            type_name: name,
            debug: Box::new(debug),
        }
    }

    fn as_ref<T: Send + Sync + 'static>(&self) -> Option<&T> {
        self.field.downcast_ref()
    }

    fn as_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
        self.field.downcast_mut()
    }
}

pub struct Layer {
    name: Cow<'static, str>,
    props: TypeIdMap<DebugErased>,
    props: TypeIdMap<TypeErasedBox>,
}

/// Trait defining how types can be stored and loaded from the config bag
@@ -211,20 +174,20 @@ impl Debug for Layer {
impl Layer {
    pub fn put<T: Store>(&mut self, value: T::StoredType) -> &mut Self {
        self.props
            .insert(TypeId::of::<T>(), DebugErased::new(value));
            .insert(TypeId::of::<T>(), TypeErasedBox::new(value));
        self
    }

    pub fn get<T: Send + Sync + Store + 'static>(&self) -> Option<&T::StoredType> {
        self.props
            .get(&TypeId::of::<T>())
            .map(|t| t.as_ref().expect("typechecked"))
            .map(|t| t.downcast_ref().expect("typechecked"))
    }

    pub fn get_mut<T: Send + Sync + Store + 'static>(&mut self) -> Option<&mut T::StoredType> {
        self.props
            .get_mut(&TypeId::of::<T>())
            .map(|t| t.as_mut().expect("typechecked"))
            .map(|t| t.downcast_mut().expect("typechecked"))
    }

    pub fn get_mut_or_default<T: Send + Sync + Store + 'static>(&mut self) -> &mut T::StoredType
@@ -233,8 +196,8 @@ impl Layer {
    {
        self.props
            .entry(TypeId::of::<T>())
            .or_insert_with(|| DebugErased::new(T::StoredType::default()))
            .as_mut()
            .or_insert_with(|| TypeErasedBox::new(T::StoredType::default()))
            .downcast_mut()
            .expect("typechecked")
    }

+58 −44
Original line number Diff line number Diff line
@@ -3,7 +3,8 @@
 * SPDX-License-Identifier: Apache-2.0
 */

use std::any::Any;
use std::any::{type_name, Any};
use std::fmt;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};

@@ -18,7 +19,6 @@ use std::ops::{Deref, DerefMut};
/// and to avoid the monomorphization that brings with it. This `TypedBox` will primarily be useful
/// for operation-specific or service-specific interceptors that need to operate on the actual
/// input/output/error types.
#[derive(Debug)]
pub struct TypedBox<T> {
    inner: TypeErasedBox,
    _phantom: PhantomData<T>,
@@ -26,12 +26,12 @@ pub struct TypedBox<T> {

impl<T> TypedBox<T>
where
    T: Send + Sync + 'static,
    T: fmt::Debug + Send + Sync + 'static,
{
    // Creates a new `TypedBox`.
    pub fn new(inner: T) -> Self {
        Self {
            inner: TypeErasedBox::new(Box::new(inner) as _),
            inner: TypeErasedBox::new(inner),
            _phantom: Default::default(),
        }
    }
@@ -62,7 +62,17 @@ where
    }
}

impl<T: 'static> Deref for TypedBox<T> {
impl<T> fmt::Debug for TypedBox<T>
where
    T: Send + Sync + 'static,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("TypedBox:")?;
        (self.inner.debug)(&self.inner, f)
    }
}

impl<T: fmt::Debug + Send + Sync + 'static> Deref for TypedBox<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
@@ -70,67 +80,66 @@ impl<T: 'static> Deref for TypedBox<T> {
    }
}

impl<T: 'static> DerefMut for TypedBox<T> {
impl<T: fmt::Debug + Send + Sync + 'static> DerefMut for TypedBox<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.inner.downcast_mut().expect("type checked")
    }
}

#[derive(Debug)]
pub struct TypedRef<'a, T> {
    inner: &'a TypeErasedBox,
    _phantom: PhantomData<T>,
}

impl<'a, T: 'static> TypedRef<'a, T> {
    pub fn assume_from(type_erased: &'a TypeErasedBox) -> Option<TypedRef<'a, T>> {
        if type_erased.downcast_ref::<T>().is_some() {
            Some(TypedRef {
                inner: type_erased,
                _phantom: Default::default(),
            })
        } else {
            None
        }
    }
/// A new-type around `Box<dyn Debug + Send + Sync>`
pub struct TypeErasedBox {
    field: Box<dyn Any + Send + Sync>,
    #[allow(dead_code)]
    type_name: &'static str,
    #[allow(clippy::type_complexity)]
    debug: Box<dyn Fn(&TypeErasedBox, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync>,
}

impl<'a, T: 'static> Deref for TypedRef<'a, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        self.inner.downcast_ref().expect("type checked")
impl fmt::Debug for TypeErasedBox {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("TypeErasedBox:")?;
        (self.debug)(self, f)
    }
}

/// A new-type around `Box<dyn Any + Send + Sync>`
#[derive(Debug)]
pub struct TypeErasedBox {
    inner: Box<dyn Any + Send + Sync>,
}

impl TypeErasedBox {
    // Creates a new `TypeErasedBox`.
    pub fn new(inner: Box<dyn Any + Send + Sync>) -> Self {
        Self { inner }
    pub fn new<T: Send + Sync + fmt::Debug + 'static>(value: T) -> Self {
        let debug = |value: &TypeErasedBox, f: &mut fmt::Formatter<'_>| {
            fmt::Debug::fmt(value.downcast_ref::<T>().expect("typechecked"), f)
        };
        let name = type_name::<T>();
        Self {
            field: Box::new(value),
            type_name: name,
            debug: Box::new(debug),
        }
    }

    // Downcast into a `Box<T>`, or return `Self` if it is not a `T`.
    pub fn downcast<T: 'static>(self) -> Result<Box<T>, Self> {
        match self.inner.downcast() {
    pub fn downcast<T: fmt::Debug + Send + Sync + 'static>(self) -> Result<Box<T>, Self> {
        let TypeErasedBox {
            field,
            type_name,
            debug,
        } = self;
        match field.downcast() {
            Ok(t) => Ok(t),
            Err(s) => Err(Self { inner: s }),
            Err(s) => Err(Self {
                field: s,
                type_name,
                debug,
            }),
        }
    }

    /// Downcast as a `&T`, or return `None` if it is not a `T`.
    pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
        self.inner.downcast_ref()
    pub fn downcast_ref<T: fmt::Debug + Send + Sync + 'static>(&self) -> Option<&T> {
        self.field.downcast_ref()
    }

    /// Downcast as a `&mut T`, or return `None` if it is not a `T`.
    pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
        self.inner.downcast_mut()
    pub fn downcast_mut<T: fmt::Debug + Send + Sync + 'static>(&mut self) -> Option<&mut T> {
        self.field.downcast_mut()
    }
}

@@ -148,6 +157,9 @@ mod tests {
        let foo = TypedBox::new(Foo("1"));
        let bar = TypedBox::new(Bar(2));

        assert_eq!("TypedBox:Foo(\"1\")", format!("{foo:?}"));
        assert_eq!("TypedBox:Bar(2)", format!("{bar:?}"));

        let mut foo_erased = foo.erase();
        foo_erased
            .downcast_mut::<Foo>()
@@ -155,6 +167,8 @@ mod tests {
            .0 = "3";

        let bar_erased = bar.erase();
        assert_eq!("TypeErasedBox:Foo(\"3\")", format!("{foo_erased:?}"));
        assert_eq!("TypeErasedBox:Bar(2)", format!("{bar_erased:?}"));

        let bar_erased = TypedBox::<Foo>::assume_from(bar_erased).expect_err("it's not a Foo");
        let mut bar = TypedBox::<Bar>::assume_from(bar_erased).expect("it's a Bar");