mekate_searcher_rs/
lib.rs

1#![warn(clippy::nursery, clippy::cargo)]
2
3use async_trait::async_trait;
4use reqwest::{Client, ClientBuilder, Url};
5
6mod auction;
7pub use auction::{AuctionRequst, AuctionResponse};
8
9mod bid;
10pub use bid::{BidRequst, BidResponse, Kind as BidKind};
11
12mod error;
13pub use error::AuctionError;
14
15static USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
16
17#[async_trait::async_trait]
18pub trait Builder: Send + Sync {
19    async fn bid(
20        &self,
21        chain_id: String,
22        height: i64,
23        kind: bid::Kind,
24        txs: Vec<Vec<u8>>,
25    ) -> Result<bid::BidResponse, error::Error>;
26
27    async fn auction(
28        &self,
29        chain_id: String,
30        height: i64,
31    ) -> Result<auction::AuctionResponse, error::Error>;
32}
33
34#[derive(Clone)]
35pub struct Http {
36    base_url: Url,
37    client: Client,
38}
39
40impl Http {
41    pub fn new(url: String) -> Result<Self, error::Error> {
42        let base_url = Url::parse(&url).map_err(|e| error::Error::Init(e.to_string()))?;
43        let client = ClientBuilder::new().user_agent(USER_AGENT).build()?;
44
45        Ok(Self { base_url, client })
46    }
47
48    pub fn new_client(url: String, client: Client) -> Result<Self, error::Error> {
49        let base_url = Url::parse(&url).map_err(|e| error::Error::Init(e.to_string()))?;
50
51        Ok(Self { base_url, client })
52    }
53}
54
55#[async_trait]
56impl Builder for Http {
57    async fn bid(
58        &self,
59        chain_id: String,
60        height: i64,
61        kind: bid::Kind,
62        txs: Vec<Vec<u8>>,
63    ) -> Result<bid::BidResponse, error::Error> {
64        let req = bid::BidRequst {
65            chain_id,
66            height,
67            kind,
68            txs,
69        };
70        let res = self
71            .client
72            .post(self.base_url.join("v0/bid")?)
73            .json(&req)
74            .send()
75            .await
76            .map_err(error::Error::Transport)?;
77
78        if !res.status().is_success() {
79            return Err(error::Error::Auction(res.json::<AuctionError>().await?));
80        }
81
82        Ok(res.json::<bid::BidResponse>().await?)
83    }
84
85    async fn auction(
86        &self,
87        chain_id: String,
88        height: i64,
89    ) -> Result<auction::AuctionResponse, error::Error> {
90        let req = auction::AuctionRequst { chain_id, height };
91        let res = self
92            .client
93            .get(self.base_url.join("v0/auction")?)
94            .json(&req)
95            .send()
96            .await
97            .map_err(error::Error::Transport)?;
98
99        if !res.status().is_success() {
100            return Err(error::Error::Auction(res.json::<AuctionError>().await?));
101        }
102
103        Ok(res.json::<auction::AuctionResponse>().await?)
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use wiremock::{
110        matchers::{method, path},
111        Mock,
112        MockServer,
113        ResponseTemplate,
114    };
115
116    use super::{error, Builder, Http};
117
118    #[tokio::test]
119    async fn auction_gone() -> Result<(), Box<dyn std::error::Error>> {
120        let height = 5994269;
121        let response = ResponseTemplate::new(410)
122            .set_body_bytes(include_bytes!("../fixtures/auction_gone.json").to_vec());
123
124        let server = MockServer::start().await;
125        Mock::given(method("GET"))
126            .and(path("v0/auction"))
127            .respond_with(response)
128            .mount(&server)
129            .await;
130
131        let http = Http::new(server.uri())?;
132        let p = http.auction("osmosis-1".to_string(), height).await;
133
134        assert!(matches!(
135            p,
136            Err(error::Error::Auction(error::AuctionError {
137                status_code: 410,
138                ..
139            }))
140        ));
141
142        Ok(())
143    }
144}