connectrpc 0.1.0

Connect RPC implementation for Rust.
Documentation
#[cfg(feature = "reqwest")]
pub mod reqwest;

#[cfg(feature = "reqwest")]
pub use reqwest::ReqwestClient;

use crate::Result;
use crate::codec::Codec;
use crate::connect::{DecodeMessage, EncodeMessage};
use crate::error::Error;
use crate::request::{self, UnaryRequest};
use crate::response::UnaryResponse;
use bytes::Bytes;
use http::Uri;

#[cfg(feature = "async")]
pub trait AsyncUnaryClient<I, O>: Send + Sync
where
    I: Send + Sync,
    O: Send + Sync,
{
    fn call_unary(
        &self,
        path: &str,
        req: UnaryRequest<I>,
    ) -> impl Future<Output = Result<UnaryResponse<O>>>;

    fn call_unary_get(
        &self,
        path: &str,
        req: UnaryRequest<I>,
    ) -> impl Future<Output = Result<UnaryResponse<O>>>;
}

#[cfg(feature = "sync")]
pub trait SyncUnaryClient<I, O>: Send + Sync
where
    I: Send + Sync,
    O: Send + Sync,
{
    fn call_unary(&self, path: &str, req: UnaryRequest<I>) -> Result<UnaryResponse<O>>;

    fn call_unary_get(&self, path: &str, req: UnaryRequest<I>) -> Result<UnaryResponse<O>>;
}

#[derive(Clone)]
pub struct CommonClient {
    builder: request::Builder,
    message_codec: Codec,
}

impl CommonClient {
    pub fn new(uri: Uri, message_codec: Codec) -> Result<Self> {
        let http::uri::Parts {
            scheme,
            authority,
            path_and_query,
            ..
        } = uri.into_parts();

        let path = path_and_query
            .as_ref()
            .map(|pq| pq.path().trim_end_matches('/').to_string())
            .unwrap_or_default();

        let query = path_and_query
            .as_ref()
            .and_then(|pq| pq.query())
            .map(|q| q.to_string());

        let mut builder = request::Builder::new()
            .scheme(scheme.ok_or_else(|| {
                Error::invalid_request("URI scheme is required, e.g. http or https")
            })?)
            .authority(
                authority
                    .ok_or_else(|| {
                        Error::invalid_request("URI authority is required, e.g. example.com")
                    })?
                    .as_str(),
            )?
            .path_prefix(path);

        if let Some(q) = query {
            builder = builder.query(q);
        }

        Ok(Self {
            builder,
            message_codec,
        })
    }

    pub fn unary_request<Req>(
        &self,
        path: &str,
        req: UnaryRequest<Req>,
    ) -> Result<http::Request<Vec<u8>>>
    where
        Req: EncodeMessage,
    {
        let crate::request::Parts { metadata, body } = req.into_parts();
        let body = self.message_codec.encode(&body);
        self.builder
            .clone()
            .rpc_path(path)?
            .message_codec(self.message_codec)
            .append_metadata(metadata)
            .unary(body)
    }

    pub fn unary_get_request<Req>(
        &self,
        path: &str,
        req: UnaryRequest<Req>,
    ) -> Result<http::Request<()>>
    where
        Req: EncodeMessage,
    {
        let crate::request::Parts { metadata, body } = req.into_parts();
        let body = self.message_codec.encode(&body);
        self.builder
            .clone()
            .rpc_path(path)?
            .message_codec(self.message_codec)
            .append_metadata(metadata)
            .unary_get(body)
    }

    pub async fn unary_response<Res>(
        &self,
        resp: http::Response<Bytes>,
    ) -> Result<UnaryResponse<Res>>
    where
        Res: DecodeMessage,
    {
        let (parts, body) = resp.into_parts();
        let body = self.message_codec.decode(&body)?;
        Ok(UnaryResponse::from_parts(crate::response::Parts {
            status: parts.status,
            metadata: parts.headers,
            message: body,
        }))
    }
}