1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
use derive_more::Display;
use reqwest::{Client, Method, RequestBuilder};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::Value as JsonValue;

pub use error::{Error, RequestError};
pub use resources::*;

mod error;
mod resources;

pub type Response<T> = Result<T, Error>;

#[derive(Display, Debug, Clone, PartialEq, Eq)]
enum Endpoint {
	#[display(fmt = "addresses")]
	Addresses,
	#[display(fmt = "addresses/{}", "_0")]
	Address(String),
	#[display(fmt = "heartbeat")]
	Heartbeat,
	#[display(fmt = "info")]
	Info,
	#[display(fmt = "keys")]
	Keys,
	#[display(fmt = "keys/{}", "_0")]
	Key(String),
	#[display(fmt = "labels")]
	Labels,
	#[display(fmt = "labels/{}", "_0")]
	Label(String),
	#[display(fmt = "networks")]
	Networks,
	#[display(fmt = "networks/{}", "_0")]
	Network(String),
	#[display(fmt = "stats")]
	Stats,
	#[display(fmt = "upstream")]
	Upstream,
}

#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Env {
	#[serde(rename = "localhost")]
	Localhost = 1,
	#[serde(rename = "testnet")]
	Testnet = 2,
	#[serde(rename = "mainnet")]
	#[default]
	Mainnet = 3,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Blockchain {
	#[serde(rename = "bitcoin")]
	Bitcoin = 1,
	#[serde(rename = "evm")]
	Evm = 2,
}

pub struct Barreleye {
	version: String,
	url: String,
	api_key: Option<String>,
}

impl Barreleye {
	pub fn new(url: &str, api_key: Option<&str>) -> Self {
		Self {
			version: "v0".to_string(),
			url: url.trim_end_matches('/').to_string(),
			api_key: api_key.map(|v| v.to_owned()),
		}
	}

	fn is_auth_request(&self, endpoint: Endpoint) -> bool {
		endpoint != Endpoint::Info && endpoint != Endpoint::Upstream
	}

	async fn get<T: DeserializeOwned>(
		&self,
		endpoint: Endpoint,
		query: &[(&str, &str)],
	) -> Response<T> {
		self.req::<T>(self.make(Method::GET, endpoint).query(&query)).await
	}

	async fn post<T: DeserializeOwned>(
		&self,
		method: Method,
		endpoint: Endpoint,
		body: JsonValue,
	) -> Response<T> {
		self.req::<T>(self.make(method, endpoint).json(&body)).await
	}

	fn make(&self, method: Method, endpoint: Endpoint) -> RequestBuilder {
		let url = format!("{}/{}/{}", self.url, self.version, endpoint);
		let mut req = Client::new().request(method, url);

		if self.is_auth_request(endpoint) {
			req = req.bearer_auth(self.api_key.clone().unwrap());
		}

		req
	}

	async fn req<T: DeserializeOwned>(&self, req: RequestBuilder) -> Response<T> {
		let res = req.send().await.map_err(|_| Error::Unavailable)?;
		if res.status().is_success() {
			Ok(res.json::<T>().await?)
		} else {
			Err(Error::Barreleye(res.json::<RequestError>().await?))
		}
	}
}