use std::time::Duration;
use reqwest::Client as HttpClient;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::error::ClientError;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct FaucetResponse {
pub address: String,
pub usdc: u64,
pub mtf: u64,
pub status: String,
}
#[derive(Serialize)]
struct FaucetRequest<'a> {
address: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
amount: Option<u64>,
}
pub async fn request_faucet(
faucet_base_url: &str,
address: &str,
amount: Option<u64>,
) -> Result<FaucetResponse, ClientError> {
if !faucet_base_url.starts_with("http://") && !faucet_base_url.starts_with("https://") {
return Err(ClientError::Builder(format!(
"faucet_base_url must start with http(s)://, got `{faucet_base_url}`"
)));
}
let base = faucet_base_url.trim_end_matches('/');
let url = format!("{base}/faucet");
let http = HttpClient::builder()
.user_agent(concat!("metaflux-client/", env!("CARGO_PKG_VERSION")))
.timeout(Duration::from_secs(30))
.build()
.map_err(|e| ClientError::Builder(e.to_string()))?;
let body = FaucetRequest { address, amount };
let resp = http.post(&url).json(&body).send().await?;
let status = resp.status();
let bytes = resp.bytes().await?;
if !status.is_success() {
if let Ok(env) = serde_json::from_slice::<Value>(&bytes) {
if let Some(msg) = env.get("error").and_then(Value::as_str) {
return Err(ClientError::ProtocolError {
code: status.as_u16(),
msg: msg.into(),
});
}
}
return Err(ClientError::ProtocolError {
code: status.as_u16(),
msg: String::from_utf8_lossy(&bytes).into_owned(),
});
}
serde_json::from_slice(&bytes).map_err(ClientError::from)
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn rejects_non_http_url() {
let err = request_faucet("ftp://faucet", "0x00", None)
.await
.unwrap_err();
assert!(matches!(err, ClientError::Builder(_)));
}
}