gl_client/node/
generic.rs

1//! An implementation of a Grpc Client that does not perform protobuf
2//! encoding/decoding. It takes already encoded protobuf messages as
3//! `Vec<u8>`, along with the URI and returns the unparsed results to
4//! the caller, or a `tonic::Status` in case of failure. This is
5//! rather useful when creating bindings, in that only the
6//! `GenericClient` and its `call` method need to be mapped through
7//! the language boundary, making for a slim interface. This is in
8//! contrast to the fat generated interface in which each
9//! `tonic::Service` and method on that service is spelled out, and
10//! would make for a very wide interface to be mapped.
11
12use bytes::{Buf, BufMut, Bytes};
13use http_body::Body;
14use log::trace;
15use std::str::FromStr;
16use tonic::codegen::StdError;
17
18const CODEC: VecCodec = VecCodec {};
19const DECODER: VecDecoder = VecDecoder {};
20const ENCODER: VecEncoder = VecEncoder {};
21
22/// A GRPC client that can call and return pre-encoded messages. Used
23/// by the language bindings to keep the interface between languages
24/// small: the client language is used to encode the protobuf
25/// payloads, and on the Rust side we just expose the `call` method.
26#[derive(Debug, Clone)]
27pub struct GenericClient<T> {
28    inner: tonic::client::Grpc<T>,
29}
30
31impl<T> GenericClient<T>
32where
33    T: tonic::client::GrpcService<tonic::body::BoxBody>,
34    T::ResponseBody: http_body::Body<Data = bytes::Bytes> + Send + 'static,
35    T::Error: Into<StdError>,
36    T::ResponseBody: Body<Data = Bytes> + Send + 'static,
37    <T::ResponseBody as Body>::Error: Into<StdError> + Send,
38{
39    pub fn new(inner: T) -> Self {
40        let inner = tonic::client::Grpc::new(inner);
41        Self { inner }
42    }
43
44    pub async fn call(
45        &mut self,
46        path: &str,
47        payload: Vec<u8>,
48    ) -> Result<tonic::Response<bytes::Bytes>, tonic::Status> {
49        trace!(
50            "Generic call to {} with {}bytes of payload",
51            path,
52            payload.len()
53        );
54
55        self.inner.ready().await.map_err(|e| {
56            tonic::Status::new(
57                tonic::Code::Unknown,
58                format!("Service was not ready: {}", e.into()),
59            )
60        })?;
61
62        let path = http::uri::PathAndQuery::from_str(path).unwrap();
63        self.inner
64            .unary(tonic::Request::new(payload), path, CODEC)
65            .await
66    }
67
68    // TODO Add a `streaming_call` for methods that return a stream to the client
69
70    pub fn max_decoding_message_size(mut self, limit: usize) -> Self
71    where
72        T: tonic::client::GrpcService<tonic::body::BoxBody>,
73    {
74        self.inner = self.inner.max_decoding_message_size(limit);
75        self
76    }
77}
78
79/// `tonic::client::Grpc<T>` requires a codec to convert between the
80/// in-memory representation (usually protobuf structs generated from
81/// IDL) to and from the serialized payload for the call, and the
82/// inverse direction for responses. Since the `GenericClient` already
83/// takes encoded `Vec<u8>` there is no work for us to do.
84#[derive(Default)]
85pub struct VecCodec {}
86
87impl Codec for VecCodec {
88    type Encode = Vec<u8>;
89    type Decode = bytes::Bytes;
90    type Encoder = VecEncoder;
91    type Decoder = VecDecoder;
92
93    fn encoder(&mut self) -> Self::Encoder {
94        ENCODER
95    }
96
97    fn decoder(&mut self) -> Self::Decoder {
98        DECODER
99    }
100}
101
102use tonic::codec::{Codec, Decoder, Encoder};
103
104#[derive(Debug, Clone, Default)]
105pub struct VecEncoder;
106
107impl Encoder for VecEncoder {
108    type Item = Vec<u8>;
109    type Error = tonic::Status;
110
111    fn encode(
112        &mut self,
113        item: Self::Item,
114        buf: &mut tonic::codec::EncodeBuf<'_>,
115    ) -> Result<(), Self::Error> {
116        buf.put(item.as_slice());
117        Ok(())
118    }
119}
120
121#[derive(Debug, Clone, Default)]
122pub struct VecDecoder;
123
124impl Decoder for VecDecoder {
125    type Item = bytes::Bytes;
126    type Error = tonic::Status;
127    fn decode(
128        &mut self,
129        buf: &mut tonic::codec::DecodeBuf<'_>,
130    ) -> Result<Option<Self::Item>, Self::Error> {
131        let buf = buf.copy_to_bytes(buf.remaining());
132        Ok(Some(buf))
133    }
134}