mekate_searcher_rs/
lib.rs1#![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}