Skip to main content

crv/
client.rs

1//! HTTP client for the Curve API
2
3use crate::error::{Error, Result};
4use reqwest::Client as HttpClient;
5use serde::de::DeserializeOwned;
6use std::time::Duration;
7use yldfi_common::http::HttpClientConfig;
8
9const DEFAULT_BASE_URL: &str = "https://api.curve.finance/v1";
10
11/// Configuration for the Curve API client
12#[derive(Debug, Clone)]
13pub struct Config {
14    /// Base URL for the API
15    pub base_url: String,
16    /// HTTP client configuration (timeout, proxy, user-agent)
17    pub http: HttpClientConfig,
18}
19
20impl Default for Config {
21    fn default() -> Self {
22        Self {
23            base_url: DEFAULT_BASE_URL.to_string(),
24            http: HttpClientConfig::default(),
25        }
26    }
27}
28
29impl Config {
30    /// Create a new config with default settings
31    #[must_use]
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    /// Set a custom base URL
37    #[must_use]
38    pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
39        self.base_url = url.into();
40        self
41    }
42
43    /// Set a custom timeout
44    #[must_use]
45    pub fn with_timeout(mut self, timeout: Duration) -> Self {
46        self.http.timeout = timeout;
47        self
48    }
49
50    /// Set a proxy URL
51    #[must_use]
52    pub fn with_proxy(mut self, proxy: impl Into<String>) -> Self {
53        self.http.proxy = Some(proxy.into());
54        self
55    }
56
57    /// Set optional proxy URL
58    #[must_use]
59    pub fn with_optional_proxy(mut self, proxy: Option<String>) -> Self {
60        self.http.proxy = proxy;
61        self
62    }
63}
64
65/// Client for the Curve Finance API
66#[derive(Debug, Clone)]
67pub struct Client {
68    http: HttpClient,
69    base_url: String,
70}
71
72impl Client {
73    /// Create a new client with default configuration
74    pub fn new() -> Result<Self> {
75        Self::with_config(Config::default())
76    }
77
78    /// Create a new client with custom configuration
79    pub fn with_config(config: Config) -> Result<Self> {
80        let http = yldfi_common::build_client(&config.http)?;
81
82        Ok(Self {
83            http,
84            base_url: config.base_url,
85        })
86    }
87
88    /// Make a GET request to the API
89    pub(crate) async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T> {
90        let url = format!("{}{}", self.base_url, path);
91        let response = self.http.get(&url).send().await?;
92
93        let status = response.status().as_u16();
94
95        // Handle rate limiting (429)
96        if status == 429 {
97            let retry_after = response
98                .headers()
99                .get("retry-after")
100                .and_then(|v| v.to_str().ok())
101                .and_then(|v| v.parse().ok());
102            return Err(Error::rate_limited(retry_after));
103        }
104
105        if !response.status().is_success() {
106            let message = response.text().await.unwrap_or_default();
107            return Err(Error::api(status, message));
108        }
109
110        let data = response.json().await?;
111        Ok(data)
112    }
113}