use std::future::{Future, IntoFuture};
use std::{any, fmt};
#[cfg(feature = "uniffi")]
use celestia_types::Hash;
use futures::future::{BoxFuture, FutureExt};
use tonic::metadata::{Ascii, Binary, KeyAndValueRef, MetadataKey, MetadataMap, MetadataValue};
use crate::error::MetadataError;
use crate::Result;
mod auth;
mod bank;
mod gas_estimation;
mod node;
mod tendermint;
mod staking;
mod celestia_tx;
mod blob;
mod cosmos_tx;
pub use crate::grpc::celestia_tx::{TxStatus, TxStatusResponse};
pub use crate::grpc::cosmos_tx::{BroadcastMode, GetTxResponse};
pub use crate::grpc::gas_estimation::{GasEstimate, TxPriority};
pub use crate::grpc::node::ConfigResponse;
pub use celestia_proto::cosmos::base::abci::v1beta1::GasInfo;
#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
pub use crate::grpc::cosmos_tx::JsBroadcastMode;
#[cfg(feature = "uniffi")]
uniffi::use_remote_type!(celestia_types::Hash);
type RequestFuture<Response, Error> = BoxFuture<'static, Result<Response, Error>>;
type CallFn<Response, Error> = Box<dyn FnOnce(Context) -> RequestFuture<Response, Error>>;
#[doc(hidden)]
#[derive(Debug, Default, Clone)]
pub struct Context {
pub metadata: MetadataMap,
}
impl Context {
pub(crate) fn append_metadata(&mut self, key: &str, val: &str) -> Result<(), MetadataError> {
let value = val.parse().map_err(|_| MetadataError::Value(key.into()))?;
let key: MetadataKey<Ascii> = key.parse().map_err(|_| MetadataError::Key(key.into()))?;
self.maybe_append_ascii(key, value);
Ok(())
}
pub(crate) fn append_metadata_bin(
&mut self,
key: &str,
val: &[u8],
) -> Result<(), MetadataError> {
let key = MetadataKey::from_bytes(key.as_bytes())
.map_err(|_| MetadataError::KeyBin(key.into()))?;
let value = MetadataValue::from_bytes(val);
self.maybe_append_bin(key, value);
Ok(())
}
pub(crate) fn append_metadata_map(&mut self, metadata: &MetadataMap) {
for key_and_value in metadata.iter() {
match key_and_value {
KeyAndValueRef::Ascii(key, val) => {
self.maybe_append_ascii(key.clone(), val.clone());
}
KeyAndValueRef::Binary(key, val) => {
self.maybe_append_bin(key.clone(), val.clone());
}
}
}
}
pub(crate) fn extend(&mut self, other: &Context) {
self.append_metadata_map(&other.metadata);
}
fn maybe_append_ascii(&mut self, key: MetadataKey<Ascii>, value: MetadataValue<Ascii>) {
if !self
.metadata
.get_all(&key)
.into_iter()
.any(|val| val == value)
{
self.metadata.append(key, value);
}
}
fn maybe_append_bin(&mut self, key: MetadataKey<Binary>, value: MetadataValue<Binary>) {
if !self
.metadata
.get_all_bin(&key)
.into_iter()
.any(|val| val == value)
{
self.metadata.append_bin(key, value);
}
}
}
pub struct AsyncGrpcCall<Response, Error = crate::Error> {
call: CallFn<Response, Error>,
pub(crate) context: Context,
}
impl<Response, Error> AsyncGrpcCall<Response, Error> {
#[doc(hidden)]
pub fn new<F, Fut>(call_fn: F) -> Self
where
F: FnOnce(Context) -> Fut + 'static,
Fut: Future<Output = Result<Response, Error>> + Send + 'static,
{
Self {
call: Box::new(|context| call_fn(context).boxed()),
context: Context::default(),
}
}
#[doc(hidden)]
pub fn context(mut self, context: &Context) -> Self {
self.context.extend(context);
self
}
pub fn metadata(mut self, key: &str, val: &str) -> Result<Self, MetadataError> {
self.context.append_metadata(key, val)?;
Ok(self)
}
pub fn metadata_bin(mut self, key: &str, val: &[u8]) -> Result<Self, MetadataError> {
self.context.append_metadata_bin(key, val)?;
Ok(self)
}
pub fn metadata_map(mut self, metadata: MetadataMap) -> Self {
self.context.append_metadata_map(&metadata);
self
}
pub fn block_height(self, height: u64) -> Self {
self.metadata("x-cosmos-block-height", &height.to_string())
.expect("valid ascii metadata")
}
}
impl<Response, Error> IntoFuture for AsyncGrpcCall<Response, Error> {
type Output = Result<Response, Error>;
type IntoFuture = RequestFuture<Response, Error>;
fn into_future(self) -> Self::IntoFuture {
(self.call)(self.context)
}
}
impl<Response> fmt::Debug for AsyncGrpcCall<Response> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(&format!("AsyncGrpcCall<{}>", any::type_name::<Response>()))
.field("context", &self.context)
.finish()
}
}
pub(crate) trait FromGrpcResponse<T> {
fn try_from_response(self) -> Result<T>;
}
pub(crate) trait IntoGrpcParam<T> {
fn into_parameter(self) -> T;
}
macro_rules! make_empty_params {
($request_type:ident) => {
impl crate::grpc::IntoGrpcParam<$request_type> for () {
fn into_parameter(self) -> $request_type {
$request_type {}
}
}
};
}
pub(crate) use make_empty_params;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn context_appending_metadata() {
let mut context = Context::default();
context.append_metadata("foo", "bar").unwrap();
context.append_metadata("foo", "bar").unwrap();
assert_eq!(context.metadata.get_all("foo").into_iter().count(), 1);
context.append_metadata("foo", "bar2").unwrap();
assert_eq!(context.metadata.get_all("foo").into_iter().count(), 2);
context.append_metadata_bin("foo-bin", b"bar").unwrap();
context.append_metadata_bin("foo-bin", b"bar").unwrap();
assert_eq!(
context.metadata.get_all_bin("foo-bin").into_iter().count(),
1
);
context.append_metadata_bin("foo-bin", b"bar2").unwrap();
assert_eq!(
context.metadata.get_all_bin("foo-bin").into_iter().count(),
2
);
}
}