0

I'm trying to mock some structs by making them implement traits but I encounter an error when I define a trait as return type for a method:

the trait RequestBuilderTrait cannot be made into an object.

consider moving send_form to another trait. consider moving send_body to another trait. consider moving method to another trait. consider moving post to another trait. consider moving get to another trait. consider moving put to another trait. consider moving delete to another trait. consider moving query to another trait. consider moving headers to another trait. consider moving header to another trait. consider moving basic_auth to another trait. consider moving bearer_auth to another trait.

for a trait to be "object safe" it needs to allow building a vtable to allow the call to be >resolvable dynamically.

After some research I found that traits use a vtable where all of their methods signature are stated so those methods need to be sized to make trait objects safe. So I think the problem is that some of my methods have generic parameters and use Self keyword as return type. I thought that if I was using a Box type and simple reference it would solve the problem but this is not the case, I have the feeling there is no solution for this situation. Do you think there is any solution ? I know that I could use where Self: Sized for methods which return Self but it makes that method unusable later.

The error appears on the method which have Box<dyn RequestBuilderTrait> as return type

My code :

#[async_trait]
pub trait HttpClientTrait: Send + Sync {
    fn builder(&self) -> Box<dyn RequestBuilderTrait>;
    fn get_client(&self) -> &Client<HttpsConnector<HttpConnector>>;
}

#[derive(Debug, Clone)]
pub struct HttpClient {
    http_client: Client<HttpsConnector<HttpConnector>>,
}
impl HttpClient {
    pub fn new() -> Self {
        HttpClient {
            http_client: Client::builder().build::<_, Body>(HttpsConnector::with_native_roots()),
        }
    }
}

impl HttpClientTrait for HttpClient{
    fn builder(&self) -> Box<dyn RequestBuilderTrait> {
        RequestBuilder::new(&self.http_client)
    }

    fn get_client(&self) -> &Client<HttpsConnector<HttpConnector>> {
        &self.http_client
    }
}

#[async_trait]
pub trait RequestBuilderTrait {
    async fn send_and_get_hyper_response_future(mut self) -> Result<ResponseFuture, RequestError>;
    async fn send(mut self) -> Result<RequestResponse, RequestError>;
    async fn send_form<T: Serialize + Send + Sync>(mut self, form: &T) -> Result<RequestResponse, RequestError> ;
    async fn send_body<T: Serialize + Send + Sync>(mut self, object: &T) -> Result<RequestResponse, RequestError>;
    async fn send_multipart(mut self, multipart: MultipartForm) -> Result<RequestResponse, RequestError>;
    fn method( self, url: &str, method: Method) -> Box<Self>;
    fn post( self, url: &str) -> Box<Self>;
    fn get( self, url: &str) -> Box<Self>;
    fn put( self, url: &str) -> Box<Self>;
    fn delete( self, url: &str) -> Box<Self>;
    fn query<P: Serialize>( self, parameters: &P) -> Result<Box<Self>, ResponseError>;
    fn headers<K: Clone + IntoHeaderName>( self, headers: &[(K, &str)]) -> Result<Box<Self>, ResponseError>;
    fn header<K: IntoHeaderName>(self, key: K, val: &str) -> Result<Box<Self>, ResponseError>;
    fn basic_auth( self, username: &str, password: &str) -> Result<Box<Self>, ResponseError>;
    fn bearer_auth( self, token: &str) -> Result<Box<Self>, ResponseError>;
}

pub struct RequestBuilder<'a> {
    client: &'a Client<HttpsConnector<HttpConnector>, Body>,
    builder: Option<Builder>,
    request: Option<Request<Body>>,
}

#[async_trait]
impl<'a> RequestBuilderTrait for RequestBuilder<'a>{
...
}
  • Yes, trait objects cannot contain generic functions, as described in the error message. Is there a reason you need trait objects instead of just normal generics? This feels like an XY problem – cameron1024 Feb 09 '22 at 14:11
  • Yes I need trait objects to mock HttpClient and RequestBuilder structs and then be able to unit test more easily methods which use those two structs – David SABO Feb 09 '22 at 15:07
  • 1
    What part of that requires trait objects (i.e. `Box`) instead of plain generics? For example, if you have a function that does some HTTP requests, why can't it look something like: `fn load_data(client: T)` rather than `fn load_data(client: Box)`? Both of those allow you to use a mocked http client in tests – cameron1024 Feb 09 '22 at 15:27
  • You need _traits_ for mocking, but you don't necessarily need trait _objects._ Can't you make the methods you want to test generic? – Jmb Feb 09 '22 at 15:27
  • Thank you for your replies guys. I tried what you suggested and it could have worked if I didnt had the method `fn builder(&self) -> Box` in the `HttpClientTrait ` because if I use `T` as return type instead I can't initialize and return structs which implement the `RequestBuilderTrait`. Am I missing something ? – David SABO Feb 10 '22 at 09:41
  • IIUC, you can use an [associated type](https://doc.rust-lang.org/stable/book/ch19-03-advanced-traits.html?highlight=associated%20type#specifying-placeholder-types-in-trait-definitions-with-associated-types): `pub trait HttpClientTrait: Send + Sync { type RequestBuilder: RequestBuilderTrait; fn builder(&self) -> Self::RequestBuilder; }` allowing you to mock the `RequestBuilder` alongside the `HttpClient`. – Jmb Feb 10 '22 at 10:45
  • Didnt know about this feature, thank you. – David SABO Feb 10 '22 at 13:38

0 Answers0