1use crate::error::GrpcError;
4use crate::fake::FakeGrpc;
5use crate::transport::GrpcTransport;
6use bytes::Bytes;
7use serde::{de::DeserializeOwned, Serialize};
8use sova_core::extend::BoxFuture;
9use std::sync::Arc;
10
11struct HttpTransport {
12 client: reqwest::Client,
13}
14
15impl GrpcTransport for HttpTransport {
16 fn call(&self, base: &str, method: &str, body: Bytes) -> BoxFuture<Result<Bytes, GrpcError>> {
17 let client = self.client.clone();
18 let url = format!(
19 "{}/{}",
20 base.trim_end_matches('/'),
21 method.trim_start_matches('/')
22 );
23 Box::pin(async move {
24 let res = client
25 .post(url)
26 .header(http::header::CONTENT_TYPE, "application/json")
27 .header("connect-protocol-version", "1")
28 .body(body)
29 .send()
30 .await
31 .map_err(|e| GrpcError::Transport(e.to_string()))?;
32 let status = res.status().as_u16();
33 let bytes = res
34 .bytes()
35 .await
36 .map_err(|e| GrpcError::Transport(e.to_string()))?;
37 if !(200..300).contains(&status) {
38 let body_str = String::from_utf8_lossy(&bytes).into_owned();
39 if let Some(parsed) = crate::error_envelope::parse_connect_error(&body_str) {
40 return Err(GrpcError::Rpc {
41 code: parsed.code,
42 message: parsed.message,
43 });
44 }
45 return Err(GrpcError::Http {
46 status,
47 body: body_str,
48 });
49 }
50 Ok(bytes)
51 })
52 }
53}
54
55#[derive(Clone)]
57pub struct GrpcClient {
58 base: String,
59 transport: Arc<dyn GrpcTransport>,
60 fake: Option<FakeGrpc>,
61}
62
63impl GrpcClient {
64 pub fn base(&self) -> &str {
65 &self.base
66 }
67
68 pub fn fake(&self) -> Option<&FakeGrpc> {
69 self.fake.as_ref()
70 }
71
72 pub(crate) fn http(base: impl Into<String>) -> Self {
73 Self {
74 base: base.into(),
75 transport: Arc::new(HttpTransport {
76 client: reqwest::Client::new(),
77 }),
78 fake: None,
79 }
80 }
81
82 pub(crate) fn with_fake(base: impl Into<String>, fake: FakeGrpc) -> Self {
83 Self {
84 base: base.into(),
85 transport: Arc::new(fake.clone()),
86 fake: Some(fake),
87 }
88 }
89
90 pub async fn call<Req, Res>(&self, method: &str, req: &Req) -> Result<Res, GrpcError>
91 where
92 Req: Serialize,
93 Res: DeserializeOwned,
94 {
95 let body =
96 Bytes::from(serde_json::to_vec(req).map_err(|e| GrpcError::Decode(e.to_string()))?);
97 let bytes = self.transport.call(&self.base, method, body).await?;
98 serde_json::from_slice(&bytes).map_err(|e| GrpcError::Decode(e.to_string()))
99 }
100
101 pub async fn call_raw(&self, method: &str, body: Bytes) -> Result<Bytes, GrpcError> {
102 let started = std::time::Instant::now();
103 let bytes_in = body.len() as u64;
104 let result = self.transport.call(&self.base, method, body).await;
105 crate::trace::emit_client(
106 method,
107 &self.base,
108 started.elapsed().as_secs_f64() * 1000.0,
109 &result,
110 bytes_in,
111 );
112 result
113 }
114}