bs_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
71/// `tonic::client::Grpc<T>` requires a codec to convert between the
72/// in-memory representation (usually protobuf structs generated from
73/// IDL) to and from the serialized payload for the call, and the
74/// inverse direction for responses. Since the `GenericClient` already
75/// takes encoded `Vec<u8>` there is no work for us to do.
76#[derive(Default)]
77pub struct VecCodec {}
78
79impl Codec for VecCodec {
80    type Encode = Vec<u8>;
81    type Decode = bytes::Bytes;
82    type Encoder = VecEncoder;
83    type Decoder = VecDecoder;
84
85    fn encoder(&mut self) -> Self::Encoder {
86        ENCODER
87    }
88
89    fn decoder(&mut self) -> Self::Decoder {
90        DECODER
91    }
92}
93
94use tonic::codec::{Codec, Decoder, Encoder};
95
96#[derive(Debug, Clone, Default)]
97pub struct VecEncoder;
98
99impl Encoder for VecEncoder {
100    type Item = Vec<u8>;
101    type Error = tonic::Status;
102
103    fn encode(
104        &mut self,
105        item: Self::Item,
106        buf: &mut tonic::codec::EncodeBuf<'_>,
107    ) -> Result<(), Self::Error> {
108        buf.put(item.as_slice());
109        Ok(())
110    }
111}
112
113#[derive(Debug, Clone, Default)]
114pub struct VecDecoder;
115
116impl Decoder for VecDecoder {
117    type Item = bytes::Bytes;
118    type Error = tonic::Status;
119    fn decode(
120        &mut self,
121        buf: &mut tonic::codec::DecodeBuf<'_>,
122    ) -> Result<Option<Self::Item>, Self::Error> {
123        let buf = buf.copy_to_bytes(buf.remaining());
124        Ok(Some(buf))
125    }
126}