1use crate::error::{self, Error, Result};
4use crate::executions::ExecutionsApi;
5use crate::matviews::MatviewsApi;
6use crate::pipelines::PipelinesApi;
7use crate::queries::QueriesApi;
8use crate::tables::TablesApi;
9use crate::usage::UsageApi;
10use reqwest::header::{HeaderMap, HeaderValue};
11use secrecy::{ExposeSecret, SecretString};
12use std::fmt;
13use std::time::Duration;
14use yldfi_common::http::HttpClientConfig;
15
16const BASE_URL: &str = "https://api.dune.com/api";
17
18#[derive(Clone)]
20pub struct Config {
21 pub api_key: SecretString,
23 pub base_url: String,
25 pub http: HttpClientConfig,
27}
28
29impl fmt::Debug for Config {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 f.debug_struct("Config")
32 .field("api_key", &"[REDACTED]")
33 .field("base_url", &self.base_url)
34 .field("http", &self.http)
35 .finish()
36 }
37}
38
39impl Config {
40 pub fn new(api_key: impl Into<String>) -> Self {
42 Self {
43 api_key: SecretString::from(api_key.into()),
44 base_url: BASE_URL.to_string(),
45 http: HttpClientConfig::default(),
46 }
47 }
48
49 #[must_use]
51 pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
52 self.base_url = url.into();
53 self
54 }
55
56 #[must_use]
58 pub fn with_timeout(mut self, timeout: Duration) -> Self {
59 self.http.timeout = timeout;
60 self
61 }
62
63 #[must_use]
65 pub fn with_proxy(mut self, proxy: impl Into<String>) -> Self {
66 self.http.proxy = Some(proxy.into());
67 self
68 }
69
70 #[must_use]
72 pub fn with_optional_proxy(mut self, proxy: Option<String>) -> Self {
73 self.http.proxy = proxy;
74 self
75 }
76}
77
78#[derive(Clone)]
80pub struct Client {
81 http: reqwest::Client,
82 base_url: String,
83}
84
85impl Client {
86 pub fn new(api_key: &str) -> Result<Self> {
88 Self::with_config(Config::new(api_key))
89 }
90
91 pub fn with_base_url(api_key: &str, base_url: &str) -> Result<Self> {
93 Self::with_config(Config::new(api_key).with_base_url(base_url))
94 }
95
96 pub fn with_config(config: Config) -> Result<Self> {
98 let mut headers = HeaderMap::new();
99 headers.insert(
100 "X-Dune-API-Key",
101 HeaderValue::from_str(config.api_key.expose_secret())
102 .map_err(|_| error::invalid_api_key())?,
103 );
104
105 let mut builder = reqwest::Client::builder()
107 .timeout(config.http.timeout)
108 .user_agent(&config.http.user_agent)
109 .default_headers(headers);
110
111 if let Some(ref proxy_url) = config.http.proxy {
112 let proxy = reqwest::Proxy::all(proxy_url).map_err(|e| Error::Api {
113 status: 0,
114 message: format!("Invalid proxy URL: {}", e),
115 })?;
116 builder = builder.proxy(proxy);
117 }
118
119 let http = builder.build()?;
120
121 Ok(Self {
122 http,
123 base_url: config.base_url,
124 })
125 }
126
127 pub fn from_env() -> Result<Self> {
131 let api_key = std::env::var("DUNE_API_KEY").map_err(|_| error::invalid_api_key())?;
132 Self::new(&api_key)
133 }
134
135 pub(crate) fn http(&self) -> &reqwest::Client {
137 &self.http
138 }
139
140 pub(crate) fn base_url(&self) -> &str {
142 &self.base_url
143 }
144
145 pub fn queries(&self) -> QueriesApi<'_> {
147 QueriesApi::new(self)
148 }
149
150 pub fn executions(&self) -> ExecutionsApi<'_> {
152 ExecutionsApi::new(self)
153 }
154
155 pub fn tables(&self) -> TablesApi<'_> {
157 TablesApi::new(self)
158 }
159
160 pub fn matviews(&self) -> MatviewsApi<'_> {
162 MatviewsApi::new(self)
163 }
164
165 pub fn pipelines(&self) -> PipelinesApi<'_> {
167 PipelinesApi::new(self)
168 }
169
170 pub fn usage(&self) -> UsageApi<'_> {
172 UsageApi::new(self)
173 }
174}