Attribute Macro conjure_client

Source
#[conjure_client]
Expand description

Creates a Conjure client type implementing the annotated trait.

For a trait named MyService, the macro will create a type named MyServiceClient which implements the Conjure Service/AsyncService and MyService traits.

The attribute has several parameters:

  • name - The value of the service field in the Endpoint extension. Defaults to the trait’s name.
  • version - The value of the version field in the Endpoint extension. Defaults to Some(env!("CARGO_PKG_VERSION")).
  • local - For async clients, causes the generated struct to use the LocalAsyncClient APIs that don’t have a Send bound.

§Parameters

The trait can optionally be declared generic over the request body and response writer types by using the #[request_writer] and #[response_body] annotations on the type parameters.

§Endpoints

Each method corresponds to a separate HTTP endpoint, and is expected to take &self and return Result<T, Error>. Each must be annotated with #[endpoint], which has several parameters:

  • method - The HTTP method (e.g. GET). Required.
  • path - The HTTP path template. Path parameters should be identified by {name} and must make up an entire path component. Required.
  • name - The value of the name field in the Endpoint extension. Defaults to the method’s name.
  • accept - A type implementing DeserializeResponse which will be used to create the return value. Defaults to returning ().

Each method argument must have an annotation describing the type of parameter. One of:

  • #[path] - A path parameter.

    Parameters:

    • name - The name of the path template parameter. Defaults to the argument name.
    • encoder - A type implementing EncodeParam which will be used to encode the value into a string. Defaults to DisplayParamEncoder.
  • #[query] - A query parameter.

    Parameters:

    • name - The string used as the key in the encoded URI. Required.
    • encoder - A type implementing EncodeParam which will be used to encode the value into a string. Defaults to DisplayParamEncoder.
  • #[auth] - A BearerToken used to authenticate the request. A method may only have at most one auth parameter.

    Parameters:

    • cookie_name - The name of the cookie used if the token is to be passed via a Cookie header. If unset, it will be passed via an Authorization header instead.
  • #[header] - A header.

    Parameters:

    • name - The header name. Required.
    • encoder - A type implementing EncodeHeader which will be used to encode the value into a header. Defaults to DisplayHeaderEncoder.
  • #[body] - The request body. A method may only have at most one body parameter.

    Parameters:

    • serializer - A type implementing SerializeRequest which will be used to serialize the value into a body. Defaults to ConjureRequestSerializer.

§Async

Both blocking and async clients are supported. For technical reasons, async method definitions will be rewritten by the macro to require the returned future be Send unless the local flag is set in the attribute.

§Examples

use conjure_error::Error;
use conjure_http::{conjure_client, endpoint};
use conjure_http::client::{
    AsyncClient, AsyncService, Client, ConjureResponseDeserializer, DeserializeResponse,
    DisplaySeqEncoder, RequestBody, SerializeRequest, Service, WriteBody,
};
use conjure_object::BearerToken;
use http::Response;
use http::header::HeaderValue;
use std::io::Write;

#[conjure_client]
trait MyService {
    #[endpoint(method = GET, path = "/yaks/{yak_id}", accept = ConjureResponseDeserializer)]
    fn get_yak(&self, #[auth] auth: &BearerToken, #[path] yak_id: i32) -> Result<String, Error>;

    #[endpoint(method = POST, path = "/yaks")]
    fn create_yak(
        &self,
        #[auth] auth_token: &BearerToken,
        #[query(name = "parentName", encoder = DisplaySeqEncoder)] parent_id: Option<&str>,
        #[body] yak: &str,
    ) -> Result<(), Error>;
}

fn do_work(client: impl Client, auth: &BearerToken) -> Result<(), Error> {
    let client = MyServiceClient::new(client);
    client.create_yak(auth, None, "my cool yak")?;

    Ok(())
}

#[conjure_client]
trait MyServiceAsync {
    #[endpoint(method = GET, path = "/yaks/{yak_id}", accept = ConjureResponseDeserializer)]
    async fn get_yak(
        &self,
        #[auth] auth: &BearerToken,
        #[path] yak_id: i32,
    ) -> Result<String, Error>;

    #[endpoint(method = POST, path = "/yaks")]
    async fn create_yak(
        &self,
        #[auth] auth_token: &BearerToken,
        #[query(name = "parentName", encoder = DisplaySeqEncoder)] parent_id: Option<&str>,
        #[body] yak: &str,
    ) -> Result<(), Error>;
}

async fn do_work_async<C>(client: C, auth: &BearerToken) -> Result<(), Error>
where
    C: AsyncClient + Sync + Send,
    C::ResponseBody: 'static + Send,
{
    let client = MyServiceAsyncClient::new(client);
    client.create_yak(auth, None, "my cool yak").await?;

    Ok(())
}

#[conjure_client]
trait MyStreamingService<#[response_body] I, #[request_writer] O>
where
    O: Write,
{
    #[endpoint(method = POST, path = "/streamData")]
    fn upload_stream(
        &self,
        #[body(serializer = StreamingRequestSerializer)] body: StreamingRequest,
    ) -> Result<(), Error>;

    #[endpoint(method = GET, path = "/streamData", accept = StreamingResponseDeserializer)]
    fn download_stream(&self) -> Result<I, Error>;
}

struct StreamingRequest;

impl<W> WriteBody<W> for StreamingRequest
where
    W: Write,
{
    fn write_body(&mut self, w: &mut W) -> Result<(), Error> {
        // ...
        Ok(())
    }

    fn reset(&mut self) -> bool {
        true
    }
}

enum StreamingRequestSerializer {}

impl<W> SerializeRequest<'static, StreamingRequest, W> for StreamingRequestSerializer
where
    W: Write,
{
    fn content_type(_: &StreamingRequest) -> HeaderValue {
        HeaderValue::from_static("text/plain")
    }

    fn serialize(value: StreamingRequest) -> Result<RequestBody<'static, W>, Error> {
        Ok(RequestBody::Streaming(Box::new(value)))
    }
}

enum StreamingResponseDeserializer {}

impl<R> DeserializeResponse<R, R> for StreamingResponseDeserializer {
    fn accept() -> Option<HeaderValue> {
        None
    }

    fn deserialize(response: Response<R>) -> Result<R, Error> {
        Ok(response.into_body())
    }
}