1use anyhow::Result;
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4use url::Url;
5
6use crate::ColumnMapping;
7
8#[derive(Debug, Clone, Deserialize, Serialize)]
10pub struct ClientConfig {
11 #[serde(default)]
13 pub url: String,
14 #[serde(default)]
16 pub api_token: String,
17 #[serde(default = "ClientConfig::default_http_req_timeout_millis")]
19 pub http_req_timeout_millis: u64,
20 #[serde(default = "ClientConfig::default_max_num_retries")]
22 pub max_num_retries: usize,
23 #[serde(default = "ClientConfig::default_retry_backoff_ms")]
25 pub retry_backoff_ms: u64,
26 #[serde(default = "ClientConfig::default_retry_base_ms")]
28 pub retry_base_ms: u64,
29 #[serde(default = "ClientConfig::default_retry_ceiling_ms")]
31 pub retry_ceiling_ms: u64,
32 #[serde(default)]
34 pub serialization_format: SerializationFormat,
35 #[serde(default = "ClientConfig::default_proactive_rate_limit_sleep")]
40 pub proactive_rate_limit_sleep: bool,
41}
42
43impl Default for ClientConfig {
44 fn default() -> Self {
45 Self {
46 url: String::default(),
47 api_token: String::default(),
48 http_req_timeout_millis: Self::default_http_req_timeout_millis(),
49 max_num_retries: Self::default_max_num_retries(),
50 retry_backoff_ms: Self::default_retry_backoff_ms(),
51 retry_base_ms: Self::default_retry_base_ms(),
52 retry_ceiling_ms: Self::default_retry_ceiling_ms(),
53 serialization_format: SerializationFormat::default(),
54 proactive_rate_limit_sleep: Self::default_proactive_rate_limit_sleep(),
55 }
56 }
57}
58
59impl ClientConfig {
60 pub const fn default_http_req_timeout_millis() -> u64 {
62 30_000
63 }
64
65 pub const fn default_max_num_retries() -> usize {
67 12
68 }
69
70 pub const fn default_retry_backoff_ms() -> u64 {
72 500
73 }
74
75 pub const fn default_retry_base_ms() -> u64 {
77 200
78 }
79
80 pub const fn default_retry_ceiling_ms() -> u64 {
82 5_000
83 }
84
85 pub const fn default_proactive_rate_limit_sleep() -> bool {
87 true
88 }
89 pub fn validate(&self) -> Result<()> {
91 if self.url.is_empty() {
92 anyhow::bail!("url is required");
93 }
94
95 if Url::parse(&self.url).is_err() {
97 anyhow::bail!("url is malformed");
98 }
99
100 if self.api_token.is_empty() {
101 anyhow::bail!("api_token is required - get one from https://envio.dev/app/api-tokens");
102 }
103 if uuid::Uuid::parse_str(self.api_token.as_str()).is_err() {
105 anyhow::bail!("api_token is malformed - make sure its a token from https://envio.dev/app/api-tokens");
106 }
107
108 if self.http_req_timeout_millis == 0 {
109 anyhow::bail!("http_req_timeout_millis must be greater than 0");
110 }
111
112 Ok(())
113 }
114}
115
116#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
118pub enum SerializationFormat {
119 Json,
121 CapnProto {
123 should_cache_queries: bool,
125 },
126}
127
128impl Default for SerializationFormat {
129 fn default() -> Self {
130 Self::CapnProto {
131 should_cache_queries: true,
132 }
133 }
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
138pub struct StreamConfig {
139 pub column_mapping: Option<ColumnMapping>,
142 pub event_signature: Option<String>,
144 #[serde(default)]
146 pub hex_output: HexOutput,
147 #[serde(default = "StreamConfig::default_batch_size")]
149 pub batch_size: u64,
150 #[serde(default = "StreamConfig::default_max_batch_size")]
152 pub max_batch_size: u64,
153 #[serde(default = "StreamConfig::default_min_batch_size")]
155 pub min_batch_size: u64,
156 #[serde(default = "StreamConfig::default_concurrency")]
158 pub concurrency: usize,
159 #[serde(default)]
161 pub max_num_blocks: Option<usize>,
162 #[serde(default)]
164 pub max_num_transactions: Option<usize>,
165 #[serde(default)]
167 pub max_num_logs: Option<usize>,
168 #[serde(default)]
170 pub max_num_traces: Option<usize>,
171 #[serde(default = "StreamConfig::default_response_bytes_ceiling")]
173 pub response_bytes_ceiling: u64,
174 #[serde(default = "StreamConfig::default_response_bytes_floor")]
176 pub response_bytes_floor: u64,
177 #[serde(default = "StreamConfig::default_reverse")]
179 pub reverse: bool,
180}
181
182#[derive(Default, Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
184pub enum HexOutput {
185 #[default]
187 NoEncode,
188 Prefixed,
190 NonPrefixed,
192}
193
194impl Default for StreamConfig {
195 fn default() -> Self {
196 Self {
197 column_mapping: None,
198 event_signature: None,
199 hex_output: HexOutput::default(),
200 batch_size: Self::default_batch_size(),
201 max_batch_size: Self::default_max_batch_size(),
202 min_batch_size: Self::default_min_batch_size(),
203 concurrency: Self::default_concurrency(),
204 max_num_blocks: None,
205 max_num_transactions: None,
206 max_num_logs: None,
207 max_num_traces: None,
208 response_bytes_ceiling: Self::default_response_bytes_ceiling(),
209 response_bytes_floor: Self::default_response_bytes_floor(),
210 reverse: Self::default_reverse(),
211 }
212 }
213}
214
215impl StreamConfig {
216 pub const fn default_concurrency() -> usize {
218 10
219 }
220
221 pub const fn default_batch_size() -> u64 {
223 1000
224 }
225
226 pub const fn default_max_batch_size() -> u64 {
228 200_000
229 }
230
231 pub const fn default_min_batch_size() -> u64 {
233 200
234 }
235
236 pub const fn default_response_bytes_ceiling() -> u64 {
238 500_000
239 }
240
241 pub const fn default_response_bytes_floor() -> u64 {
243 250_000
244 }
245
246 pub const fn default_reverse() -> bool {
248 false
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_validate() {
258 let valid_cfg = ClientConfig {
259 url: "https://hypersync.xyz".into(),
260 api_token: "00000000-0000-0000-0000-000000000000".to_string(),
261 ..Default::default()
262 };
263
264 assert!(valid_cfg.validate().is_ok(), "valid config");
265
266 let cfg = ClientConfig {
267 url: "https://hypersync.xyz".to_string(),
268 api_token: "not a uuid".to_string(),
269 ..Default::default()
270 };
271
272 assert!(cfg.validate().is_err(), "invalid uuid");
273
274 let cfg = ClientConfig {
275 url: "https://hypersync.xyz".to_string(),
276 ..Default::default()
277 };
278
279 assert!(cfg.validate().is_err(), "missing api token");
280
281 let cfg = ClientConfig {
282 api_token: "00000000-0000-0000-0000-000000000000".to_string(),
283 ..Default::default()
284 };
285
286 assert!(cfg.validate().is_err(), "missing url");
287 let cfg = ClientConfig {
288 http_req_timeout_millis: 0,
289 ..valid_cfg
290 };
291 assert!(
292 cfg.validate().is_err(),
293 "http_req_timeout_millis must be greater than 0"
294 );
295 }
296
297 #[test]
298 fn test_stream_config_defaults() {
299 let default_config = StreamConfig::default();
300
301 assert_eq!(default_config.concurrency, 10);
303 assert_eq!(default_config.batch_size, 1000);
304 assert_eq!(default_config.max_batch_size, 200_000);
305 assert_eq!(default_config.min_batch_size, 200);
306 assert_eq!(default_config.response_bytes_ceiling, 500_000);
307 assert_eq!(default_config.response_bytes_floor, 250_000);
308 assert!(!default_config.reverse);
309 assert_eq!(default_config.hex_output, HexOutput::NoEncode);
310 assert!(default_config.column_mapping.is_none());
311 assert!(default_config.event_signature.is_none());
312 assert!(default_config.max_num_blocks.is_none());
313 assert!(default_config.max_num_transactions.is_none());
314 assert!(default_config.max_num_logs.is_none());
315 assert!(default_config.max_num_traces.is_none());
316 }
317
318 #[test]
319 fn test_stream_config_serde() {
320 let default_config = StreamConfig::default();
322 let json = serde_json::to_string(&default_config).unwrap();
323 let deserialized: StreamConfig = serde_json::from_str(&json).unwrap();
324
325 assert_eq!(deserialized.concurrency, default_config.concurrency);
327 assert_eq!(deserialized.batch_size, default_config.batch_size);
328 assert_eq!(deserialized.reverse, default_config.reverse);
329
330 let partial_json = r#"{"reverse": true, "batch_size": 500}"#;
332 let partial_config: StreamConfig = serde_json::from_str(partial_json).unwrap();
333
334 assert!(partial_config.reverse);
335 assert_eq!(partial_config.batch_size, 500);
336 assert_eq!(partial_config.concurrency, 10); assert_eq!(partial_config.max_batch_size, 200_000); }
339}