1use std::future::{Future, IntoFuture};
4use std::{any, fmt};
5
6#[cfg(feature = "uniffi")]
7use celestia_types::Hash;
8use futures::future::{BoxFuture, FutureExt};
9use tonic::metadata::{Ascii, Binary, KeyAndValueRef, MetadataKey, MetadataMap, MetadataValue};
10
11use crate::Result;
12use crate::error::MetadataError;
13
14mod auth;
16mod bank;
18mod gas_estimation;
20mod node;
22mod tendermint;
24mod staking;
26mod celestia_tx;
28mod blob;
30mod cosmos_tx;
32
33pub use crate::grpc::celestia_tx::{TxStatus, TxStatusResponse};
34pub use crate::grpc::cosmos_tx::{BroadcastMode, GetTxResponse};
35pub use crate::grpc::gas_estimation::{GasEstimate, TxPriority};
36pub use crate::grpc::node::ConfigResponse;
37pub use celestia_proto::cosmos::base::abci::v1beta1::GasInfo;
38
39#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
40pub use crate::grpc::cosmos_tx::JsBroadcastMode;
41
42#[cfg(feature = "uniffi")]
43uniffi::use_remote_type!(celestia_types::Hash);
44
45type RequestFuture<Response, Error> = BoxFuture<'static, Result<Response, Error>>;
46type CallFn<Response, Error> = Box<dyn FnOnce(Context) -> RequestFuture<Response, Error>>;
47
48#[doc(hidden)]
53#[derive(Debug, Default, Clone)]
54pub struct Context {
55 pub metadata: MetadataMap,
57}
58
59impl Context {
60 pub(crate) fn append_metadata(&mut self, key: &str, val: &str) -> Result<(), MetadataError> {
62 let value = val.parse().map_err(|_| MetadataError::Value(key.into()))?;
63 let key: MetadataKey<Ascii> = key.parse().map_err(|_| MetadataError::Key(key.into()))?;
64
65 self.maybe_append_ascii(key, value);
66
67 Ok(())
68 }
69
70 pub(crate) fn append_metadata_bin(
74 &mut self,
75 key: &str,
76 val: &[u8],
77 ) -> Result<(), MetadataError> {
78 let key = MetadataKey::from_bytes(key.as_bytes())
79 .map_err(|_| MetadataError::KeyBin(key.into()))?;
80 let value = MetadataValue::from_bytes(val);
81
82 self.maybe_append_bin(key, value);
83
84 Ok(())
85 }
86
87 pub(crate) fn append_metadata_map(&mut self, metadata: &MetadataMap) {
89 for key_and_value in metadata.iter() {
90 match key_and_value {
91 KeyAndValueRef::Ascii(key, val) => {
92 self.maybe_append_ascii(key.clone(), val.clone());
93 }
94 KeyAndValueRef::Binary(key, val) => {
95 self.maybe_append_bin(key.clone(), val.clone());
96 }
97 }
98 }
99 }
100
101 pub(crate) fn extend(&mut self, other: &Context) {
103 self.append_metadata_map(&other.metadata);
104 }
105
106 fn maybe_append_ascii(&mut self, key: MetadataKey<Ascii>, value: MetadataValue<Ascii>) {
107 if !self
108 .metadata
109 .get_all(&key)
110 .into_iter()
111 .any(|val| val == value)
112 {
113 self.metadata.append(key, value);
114 }
115 }
116
117 fn maybe_append_bin(&mut self, key: MetadataKey<Binary>, value: MetadataValue<Binary>) {
118 if !self
119 .metadata
120 .get_all_bin(&key)
121 .into_iter()
122 .any(|val| val == value)
123 {
124 self.metadata.append_bin(key, value);
125 }
126 }
127}
128
129pub struct AsyncGrpcCall<Response, Error = crate::Error> {
147 call: CallFn<Response, Error>,
148 pub(crate) context: Context,
149}
150
151impl<Response, Error> AsyncGrpcCall<Response, Error> {
152 #[doc(hidden)]
157 pub fn new<F, Fut>(call_fn: F) -> Self
158 where
159 F: FnOnce(Context) -> Fut + 'static,
160 Fut: Future<Output = Result<Response, Error>> + Send + 'static,
161 {
162 Self {
163 call: Box::new(|context| call_fn(context).boxed()),
164 context: Context::default(),
165 }
166 }
167
168 #[doc(hidden)]
173 pub fn context(mut self, context: &Context) -> Self {
174 self.context.extend(context);
175 self
176 }
177
178 pub fn metadata(mut self, key: &str, val: &str) -> Result<Self, MetadataError> {
180 self.context.append_metadata(key, val)?;
181 Ok(self)
182 }
183
184 pub fn metadata_bin(mut self, key: &str, val: &[u8]) -> Result<Self, MetadataError> {
188 self.context.append_metadata_bin(key, val)?;
189 Ok(self)
190 }
191
192 pub fn metadata_map(mut self, metadata: MetadataMap) -> Self {
194 self.context.append_metadata_map(&metadata);
195 self
196 }
197
198 pub fn block_height(self, height: u64) -> Self {
202 self.metadata("x-cosmos-block-height", &height.to_string())
203 .expect("valid ascii metadata")
204 }
205}
206
207impl<Response, Error> IntoFuture for AsyncGrpcCall<Response, Error> {
208 type Output = Result<Response, Error>;
209 type IntoFuture = RequestFuture<Response, Error>;
210
211 fn into_future(self) -> Self::IntoFuture {
212 (self.call)(self.context)
213 }
214}
215
216impl<Response> fmt::Debug for AsyncGrpcCall<Response> {
217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218 f.debug_struct(&format!("AsyncGrpcCall<{}>", any::type_name::<Response>()))
219 .field("context", &self.context)
220 .finish()
221 }
222}
223
224pub(crate) trait FromGrpcResponse<T> {
225 fn try_from_response(self) -> Result<T>;
226}
227
228pub(crate) trait IntoGrpcParam<T> {
229 fn into_parameter(self) -> T;
230}
231
232macro_rules! make_empty_params {
233 ($request_type:ident) => {
234 impl crate::grpc::IntoGrpcParam<$request_type> for () {
235 fn into_parameter(self) -> $request_type {
236 $request_type {}
237 }
238 }
239 };
240}
241
242pub(crate) use make_empty_params;
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn context_appending_metadata() {
250 let mut context = Context::default();
251
252 context.append_metadata("foo", "bar").unwrap();
254 context.append_metadata("foo", "bar").unwrap();
255
256 assert_eq!(context.metadata.get_all("foo").into_iter().count(), 1);
257
258 context.append_metadata("foo", "bar2").unwrap();
259
260 assert_eq!(context.metadata.get_all("foo").into_iter().count(), 2);
261
262 context.append_metadata_bin("foo-bin", b"bar").unwrap();
264 context.append_metadata_bin("foo-bin", b"bar").unwrap();
265
266 assert_eq!(
267 context.metadata.get_all_bin("foo-bin").into_iter().count(),
268 1
269 );
270
271 context.append_metadata_bin("foo-bin", b"bar2").unwrap();
272
273 assert_eq!(
274 context.metadata.get_all_bin("foo-bin").into_iter().count(),
275 2
276 );
277 }
278}