1#[cfg(feature = "rust")]
2use bon::Builder;
3#[cfg(feature = "node")]
4use napi_derive::napi;
5#[cfg(feature = "python")]
6use pyo3::{pyclass, pymethods};
7#[cfg(feature = "python")]
8use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
9use serde::{Deserialize, Deserializer, Serialize};
10
11fn deserialize_as_json_string<'de, D>(deserializer: D) -> Result<String, D::Error>
12where
13 D: Deserializer<'de>,
14{
15 let value = serde_json::Value::deserialize(deserializer)?;
16 serde_json::to_string(&value).map_err(serde::de::Error::custom)
17}
18
19#[cfg_attr(feature = "node", napi(string_enum))]
23#[cfg_attr(not(feature = "node"), derive(Clone))]
24#[derive(Debug, Serialize, Deserialize)]
25#[serde(rename_all = "snake_case")]
26pub enum StreamRegion {
27 UsaEast,
28 EuropeCentral,
29 AsiaEast,
30}
31
32#[cfg_attr(feature = "node", napi(string_enum))]
34#[cfg_attr(not(feature = "node"), derive(Clone))]
35#[derive(Debug, Serialize, Deserialize)]
36#[serde(rename_all = "snake_case")]
37pub enum StreamDataset {
38 Block,
39 BlockWithReceipts,
40 Transactions,
41 Logs,
42 Receipts,
43 TraceBlocks,
44 DebugTraces,
45 BlockWithReceiptsDebugTrace,
46 BlockWithReceiptsTraceBlock,
47 BlobSidecars,
48 ProgramsWithLogs,
49 Ledger,
50 Events,
51 Orders,
52 Trades,
53 BookUpdates,
54 Twap,
55 WriterActions,
56}
57
58#[cfg_attr(feature = "node", napi(string_enum))]
60#[cfg_attr(not(feature = "node"), derive(Clone))]
61#[derive(Debug, Serialize, Deserialize)]
62#[serde(rename_all = "snake_case")]
63pub enum StreamDestination {
64 Webhook,
65 S3,
66 Azure,
67 Postgres,
68 Kafka,
69}
70
71#[cfg_attr(feature = "node", napi(string_enum))]
73#[cfg_attr(not(feature = "node"), derive(Clone))]
74#[derive(Debug, Serialize, Deserialize)]
75#[serde(rename_all = "snake_case")]
76pub enum FilterLanguage {
77 Javascript,
78 Go,
79 Wasm,
80}
81
82#[cfg_attr(feature = "node", napi(string_enum))]
84#[cfg_attr(not(feature = "node"), derive(Clone))]
85#[derive(Debug, Serialize, Deserialize)]
86#[serde(rename_all = "snake_case")]
87pub enum StreamMetadataLocation {
88 Body,
89 Header,
90 None,
91}
92
93#[cfg_attr(feature = "node", napi(string_enum))]
95#[cfg_attr(not(feature = "node"), derive(Clone))]
96#[derive(Debug, Serialize, Deserialize)]
97#[serde(rename_all = "snake_case")]
98pub enum ProductType {
99 Stream,
100 Webhook,
101}
102
103#[cfg_attr(feature = "node", napi(string_enum))]
105#[cfg_attr(not(feature = "node"), derive(Clone))]
106#[derive(Debug, Serialize, Deserialize)]
107#[serde(rename_all = "snake_case")]
108pub enum StreamStatus {
109 Active,
110 Paused,
111 Terminated,
112 Completed,
113 Blocked,
114}
115
116#[cfg_attr(feature = "python", gen_stub_pyclass)]
123#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
124#[cfg_attr(feature = "node", napi(object))]
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct WebhookAttributes {
127 pub url: String,
129 pub max_retry: i32,
131 pub retry_interval_sec: i32,
133 pub post_timeout_sec: i32,
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub security_token: Option<String>,
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub compression: Option<String>,
141}
142
143#[cfg(feature = "python")]
144#[gen_stub_pymethods]
145#[pymethods]
146impl WebhookAttributes {
147 #[new]
148 #[pyo3(signature = (url, max_retry, retry_interval_sec, post_timeout_sec, compression=None, security_token=None))]
149 pub fn new(
150 url: String,
151 max_retry: i32,
152 retry_interval_sec: i32,
153 post_timeout_sec: i32,
154 compression: Option<String>,
155 security_token: Option<String>,
156 ) -> Self {
157 Self {
158 url,
159 max_retry,
160 retry_interval_sec,
161 post_timeout_sec,
162 security_token,
163 compression,
164 }
165 }
166}
167
168#[cfg_attr(feature = "python", gen_stub_pyclass)]
170#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
171#[cfg_attr(feature = "node", napi(object))]
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct S3Attributes {
174 pub endpoint: String,
176 pub access_key: String,
178 pub secret_key: String,
180 pub bucket: String,
182 pub object_prefix: String,
184 pub compression: String,
186 pub file_type: String,
188 pub max_retry: i32,
190 pub retry_interval_sec: i32,
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub use_ssl: Option<bool>,
195}
196
197#[cfg(feature = "python")]
198#[gen_stub_pymethods]
199#[pymethods]
200impl S3Attributes {
201 #[new]
202 #[allow(clippy::too_many_arguments)]
203 #[pyo3(signature = (endpoint, access_key, secret_key, bucket, object_prefix, compression, file_type, max_retry, retry_interval_sec, use_ssl=None))]
204 pub fn new(
205 endpoint: String,
206 access_key: String,
207 secret_key: String,
208 bucket: String,
209 object_prefix: String,
210 compression: String,
211 file_type: String,
212 max_retry: i32,
213 retry_interval_sec: i32,
214 use_ssl: Option<bool>,
215 ) -> Self {
216 Self {
217 endpoint,
218 access_key,
219 secret_key,
220 bucket,
221 object_prefix,
222 compression,
223 file_type,
224 max_retry,
225 retry_interval_sec,
226 use_ssl,
227 }
228 }
229}
230
231#[cfg_attr(feature = "python", gen_stub_pyclass)]
233#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
234#[cfg_attr(feature = "node", napi(object))]
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct AzureAttributes {
237 pub storage_account: String,
239 pub sas_token: String,
241 pub container: String,
243 pub compression: String,
245 pub file_type: String,
247 pub max_retry: i32,
249 pub retry_interval_sec: i32,
251 #[serde(skip_serializing_if = "Option::is_none")]
253 pub blob_prefix: Option<String>,
254}
255
256#[cfg(feature = "python")]
257#[gen_stub_pymethods]
258#[pymethods]
259impl AzureAttributes {
260 #[new]
261 #[allow(clippy::too_many_arguments)]
262 #[pyo3(signature = (storage_account, sas_token, container, compression, file_type, max_retry, retry_interval_sec, blob_prefix=None))]
263 pub fn new(
264 storage_account: String,
265 sas_token: String,
266 container: String,
267 compression: String,
268 file_type: String,
269 max_retry: i32,
270 retry_interval_sec: i32,
271 blob_prefix: Option<String>,
272 ) -> Self {
273 Self {
274 storage_account,
275 sas_token,
276 container,
277 compression,
278 file_type,
279 max_retry,
280 retry_interval_sec,
281 blob_prefix,
282 }
283 }
284}
285
286#[cfg_attr(feature = "python", gen_stub_pyclass)]
288#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
289#[cfg_attr(feature = "node", napi(object))]
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct PostgresAttributes {
292 pub host: String,
294 pub port: i32,
296 pub database: String,
298 pub username: String,
300 pub password: String,
302 pub table_name: String,
304 pub sslmode: String,
306 pub max_retry: i32,
308 pub retry_interval_sec: i32,
310}
311
312#[cfg(feature = "python")]
313#[gen_stub_pymethods]
314#[pymethods]
315impl PostgresAttributes {
316 #[new]
317 #[allow(clippy::too_many_arguments)]
318 pub fn new(
319 host: String,
320 port: i32,
321 database: String,
322 username: String,
323 password: String,
324 table_name: String,
325 sslmode: String,
326 max_retry: i32,
327 retry_interval_sec: i32,
328 ) -> Self {
329 Self {
330 host,
331 port,
332 database,
333 username,
334 password,
335 table_name,
336 sslmode,
337 max_retry,
338 retry_interval_sec,
339 }
340 }
341}
342
343#[cfg_attr(feature = "python", gen_stub_pyclass)]
345#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
346#[cfg_attr(feature = "node", napi(object))]
347#[derive(Debug, Clone, Serialize, Deserialize)]
348pub struct KafkaAttributes {
349 pub bootstrap_servers: String,
351 pub topic_name: String,
353 pub compression_type: String,
355 pub batch_size: i32,
357 pub linger_ms: i32,
359 pub max_message_bytes: i32,
361 pub timeout_sec: i32,
363 pub max_retry: i32,
365 pub retry_interval_sec: i32,
367 #[serde(skip_serializing_if = "Option::is_none")]
369 pub username: Option<String>,
370 #[serde(skip_serializing_if = "Option::is_none")]
372 pub password: Option<String>,
373 #[serde(skip_serializing_if = "Option::is_none")]
375 pub protocol: Option<String>,
376 #[serde(skip_serializing_if = "Option::is_none")]
378 pub mechanisms: Option<String>,
379}
380
381#[cfg(feature = "python")]
382#[gen_stub_pymethods]
383#[pymethods]
384impl KafkaAttributes {
385 #[new]
386 #[pyo3(signature = (bootstrap_servers, topic_name, compression_type, batch_size, linger_ms, max_message_bytes, timeout_sec, max_retry, retry_interval_sec, username=None, password=None, protocol=None, mechanisms=None))]
387 #[allow(clippy::too_many_arguments)]
388 pub fn new(
389 bootstrap_servers: String,
390 topic_name: String,
391 compression_type: String,
392 batch_size: i32,
393 linger_ms: i32,
394 max_message_bytes: i32,
395 timeout_sec: i32,
396 max_retry: i32,
397 retry_interval_sec: i32,
398 username: Option<String>,
399 password: Option<String>,
400 protocol: Option<String>,
401 mechanisms: Option<String>,
402 ) -> Self {
403 Self {
404 bootstrap_servers,
405 topic_name,
406 compression_type,
407 batch_size,
408 linger_ms,
409 max_message_bytes,
410 timeout_sec,
411 max_retry,
412 retry_interval_sec,
413 username,
414 password,
415 protocol,
416 mechanisms,
417 }
418 }
419}
420
421#[cfg_attr(feature = "python", gen_stub_pyclass)]
426#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
427#[cfg_attr(feature = "node", napi(object))]
428#[derive(Debug, Clone, Serialize, Deserialize)]
429pub struct AddressBookConfig {
430 pub address_book_id: String,
432 #[serde(skip_serializing_if = "Option::is_none")]
434 pub objects_filter_path: Option<String>,
435 pub elements_filter_paths: Vec<String>,
437}
438
439#[cfg(feature = "python")]
440#[gen_stub_pymethods]
441#[pymethods]
442impl AddressBookConfig {
443 #[new]
444 #[pyo3(signature = (address_book_id, elements_filter_paths, objects_filter_path=None))]
445 pub fn new(
446 address_book_id: String,
447 elements_filter_paths: Vec<String>,
448 objects_filter_path: Option<String>,
449 ) -> Self {
450 Self {
451 address_book_id,
452 objects_filter_path,
453 elements_filter_paths,
454 }
455 }
456}
457
458#[derive(Debug, Clone, Serialize, Deserialize)]
468#[serde(
469 tag = "destination",
470 content = "destination_attributes",
471 rename_all = "snake_case"
472)]
473pub enum DestinationAttributes {
474 Webhook(WebhookAttributes),
476 S3(S3Attributes),
478 Azure(AzureAttributes),
480 Postgres(PostgresAttributes),
482 Kafka(KafkaAttributes),
484}
485
486impl DestinationAttributes {
487 pub fn tag(&self) -> StreamDestination {
488 match self {
489 Self::Webhook(_) => StreamDestination::Webhook,
490 Self::S3(_) => StreamDestination::S3,
491 Self::Azure(_) => StreamDestination::Azure,
492 Self::Postgres(_) => StreamDestination::Postgres,
493 Self::Kafka(_) => StreamDestination::Kafka,
494 }
495 }
496}
497
498#[cfg_attr(feature = "rust", derive(Builder))]
502#[derive(Debug, Clone, Serialize, Deserialize)]
503pub struct CreateStreamParams {
504 pub name: String,
506 pub region: StreamRegion,
508 pub network: String,
510 pub dataset: StreamDataset,
512 pub start_range: i64,
514 pub end_range: i64,
516 #[serde(flatten)]
519 pub destination_attributes: DestinationAttributes,
520 #[serde(skip_serializing_if = "Option::is_none")]
522 pub plan: Option<String>,
523 #[serde(skip_serializing_if = "Option::is_none")]
525 pub threshold_fetch_buffer: Option<i64>,
526 pub dataset_batch_size: i64,
528 #[serde(skip_serializing_if = "Option::is_none")]
530 pub max_batch_size: Option<i64>,
531 #[serde(skip_serializing_if = "Option::is_none")]
533 pub max_buffer_range_size: Option<i64>,
534 #[serde(skip_serializing_if = "Option::is_none")]
536 pub max_buffer_processing_workers: Option<i64>,
537 #[serde(skip_serializing_if = "Option::is_none")]
539 pub keep_distance_from_tip: Option<i64>,
540 #[serde(skip_serializing_if = "Option::is_none")]
542 pub filter_function: Option<String>,
543 #[serde(skip_serializing_if = "Option::is_none")]
545 pub filter_language: Option<FilterLanguage>,
546 #[serde(skip_serializing_if = "Option::is_none")]
548 pub address_book_config: Option<AddressBookConfig>,
549 #[serde(skip_serializing_if = "Option::is_none")]
551 pub include_stream_metadata: Option<StreamMetadataLocation>,
552 #[serde(skip_serializing_if = "Option::is_none")]
554 pub product_type: Option<ProductType>,
555 #[serde(skip_serializing_if = "Option::is_none")]
557 pub status: Option<StreamStatus>,
558 #[serde(skip_serializing_if = "Option::is_none")]
560 pub notification_email: Option<String>,
561 #[serde(skip_serializing_if = "Option::is_none")]
563 pub charge_min_cap: Option<i32>,
564 #[serde(skip_serializing_if = "Option::is_none")]
566 pub fix_block_reorgs: Option<i32>,
567 pub elastic_batch_enabled: bool,
569 #[serde(skip_serializing_if = "Option::is_none")]
572 pub extra_destinations: Option<Vec<DestinationAttributes>>,
573}
574
575#[derive(Debug, Clone, Serialize, Deserialize)]
579pub struct Stream {
580 pub id: String,
582 pub name: String,
584 pub status: String,
586 pub created_at: String,
588 pub updated_at: String,
590 pub sequence: i64,
592 pub network: String,
594 pub dataset: String,
596 pub region: String,
598 pub start_range: i64,
600 pub end_range: i64,
602 #[serde(skip_serializing_if = "Option::is_none")]
604 pub plan: Option<String>,
605 #[serde(skip_serializing_if = "Option::is_none")]
607 pub threshold_fetch_buffer: Option<i64>,
608 #[serde(skip_serializing_if = "Option::is_none")]
610 pub dataset_batch_size: Option<i64>,
611 #[serde(skip_serializing_if = "Option::is_none")]
613 pub max_batch_size: Option<i64>,
614 #[serde(skip_serializing_if = "Option::is_none")]
616 pub max_buffer_range_size: Option<i64>,
617 #[serde(skip_serializing_if = "Option::is_none")]
619 pub max_buffer_processing_workers: Option<i64>,
620 #[serde(skip_serializing_if = "Option::is_none")]
622 pub keep_distance_from_tip: Option<i64>,
623 #[serde(skip_serializing_if = "Option::is_none")]
625 pub filter_function: Option<String>,
626 #[serde(skip_serializing_if = "Option::is_none")]
628 pub filter_language: Option<String>,
629 #[serde(skip_serializing_if = "Option::is_none")]
631 pub include_stream_metadata: Option<String>,
632 #[serde(skip_serializing_if = "Option::is_none")]
634 pub product_type: Option<String>,
635 #[serde(skip_serializing_if = "Option::is_none")]
637 pub notification_email: Option<String>,
638 #[serde(skip_serializing_if = "Option::is_none")]
640 pub fix_block_reorgs: Option<i32>,
641 #[serde(skip_serializing_if = "Option::is_none")]
643 pub current_hash: Option<String>,
644 #[serde(flatten, default, skip_serializing_if = "Option::is_none")]
647 pub destination_attributes: Option<DestinationAttributes>,
648 #[serde(skip_serializing_if = "Option::is_none")]
650 pub elastic_batch_enabled: Option<bool>,
651 #[serde(skip_serializing_if = "Option::is_none")]
653 pub qn_account_id: Option<String>,
654 #[serde(skip_serializing_if = "Option::is_none")]
656 pub charge_min_cap: Option<i32>,
657 #[serde(skip_serializing_if = "Option::is_none")]
659 pub memo: Option<String>,
660 #[serde(skip_serializing_if = "Option::is_none")]
662 pub address_book_config: Option<AddressBookConfig>,
663 #[serde(default, skip_serializing_if = "Option::is_none")]
665 pub extra_destinations: Option<Vec<DestinationAttributes>>,
666}
667
668#[cfg_attr(feature = "python", gen_stub_pyclass)]
672#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
673#[cfg_attr(feature = "node", napi(object))]
674#[derive(Debug, Clone, Serialize, Deserialize)]
675pub struct PageInfo {
676 pub limit: i64,
678 pub offset: i64,
680 pub total: i64,
682}
683
684#[derive(Debug, Clone, Serialize, Deserialize)]
686pub struct ListStreamsResponse {
687 pub data: Vec<Stream>,
689 #[serde(rename = "pageInfo")]
691 pub page_info: PageInfo,
692}
693
694#[cfg_attr(feature = "node", napi(object))]
696#[cfg_attr(not(feature = "node"), derive(Clone))]
697#[derive(Debug, Default, Serialize, Deserialize)]
698pub struct ListStreamsParams {
699 #[serde(skip_serializing_if = "Option::is_none")]
701 pub stream_type: Option<String>,
702 #[serde(skip_serializing_if = "Option::is_none")]
704 pub offset: Option<i64>,
705 #[serde(skip_serializing_if = "Option::is_none")]
707 pub limit: Option<i64>,
708 #[serde(skip_serializing_if = "Option::is_none")]
710 pub order_by: Option<String>,
711 #[serde(skip_serializing_if = "Option::is_none")]
713 pub order_direction: Option<String>,
714}
715
716#[derive(Debug, Default, Clone, Serialize, Deserialize)]
719pub struct UpdateStreamParams {
720 #[serde(skip_serializing_if = "Option::is_none")]
722 pub name: Option<String>,
723 #[serde(skip_serializing_if = "Option::is_none")]
725 pub region: Option<StreamRegion>,
726 #[serde(skip_serializing_if = "Option::is_none")]
728 pub network: Option<String>,
729 #[serde(skip_serializing_if = "Option::is_none")]
731 pub dataset: Option<StreamDataset>,
732 #[serde(skip_serializing_if = "Option::is_none")]
734 pub start_range: Option<i64>,
735 #[serde(skip_serializing_if = "Option::is_none")]
737 pub end_range: Option<i64>,
738 #[serde(flatten, skip_serializing_if = "Option::is_none")]
741 pub destination_attributes: Option<DestinationAttributes>,
742 #[serde(skip_serializing_if = "Option::is_none")]
744 pub plan: Option<String>,
745 #[serde(skip_serializing_if = "Option::is_none")]
747 pub threshold_fetch_buffer: Option<i64>,
748 #[serde(skip_serializing_if = "Option::is_none")]
750 pub dataset_batch_size: Option<i64>,
751 #[serde(skip_serializing_if = "Option::is_none")]
753 pub max_batch_size: Option<i64>,
754 #[serde(skip_serializing_if = "Option::is_none")]
756 pub max_buffer_range_size: Option<i64>,
757 #[serde(skip_serializing_if = "Option::is_none")]
759 pub max_buffer_processing_workers: Option<i64>,
760 #[serde(skip_serializing_if = "Option::is_none")]
762 pub keep_distance_from_tip: Option<i64>,
763 #[serde(skip_serializing_if = "Option::is_none")]
765 pub filter_function: Option<String>,
766 #[serde(skip_serializing_if = "Option::is_none")]
768 pub filter_language: Option<FilterLanguage>,
769 #[serde(skip_serializing_if = "Option::is_none")]
771 pub address_book_config: Option<AddressBookConfig>,
772 #[serde(skip_serializing_if = "Option::is_none")]
774 pub include_stream_metadata: Option<StreamMetadataLocation>,
775 #[serde(skip_serializing_if = "Option::is_none")]
777 pub notification_email: Option<String>,
778 #[serde(skip_serializing_if = "Option::is_none")]
780 pub charge_min_cap: Option<i32>,
781 #[serde(skip_serializing_if = "Option::is_none")]
783 pub fix_block_reorgs: Option<i32>,
784 #[serde(skip_serializing_if = "Option::is_none")]
786 pub elastic_batch_enabled: Option<bool>,
787 #[serde(skip_serializing_if = "Option::is_none")]
789 pub status: Option<StreamStatus>,
790 #[serde(skip_serializing_if = "Option::is_none")]
792 pub memo: Option<String>,
793 #[serde(skip_serializing_if = "Option::is_none")]
795 pub extra_destinations: Option<Vec<DestinationAttributes>>,
796}
797
798#[cfg_attr(feature = "node", napi(object))]
800#[cfg_attr(not(feature = "node"), derive(Clone))]
801#[derive(Debug, Serialize, Deserialize)]
802pub struct TestFilterParams {
803 pub network: String,
805 pub dataset: StreamDataset,
807 pub block: String,
809 #[serde(skip_serializing_if = "Option::is_none")]
811 pub filter_function: Option<String>,
812 #[serde(skip_serializing_if = "Option::is_none")]
814 pub filter_language: Option<FilterLanguage>,
815 #[serde(skip_serializing_if = "Option::is_none")]
817 pub address_book_config: Option<AddressBookConfig>,
818}
819
820#[cfg_attr(feature = "python", gen_stub_pyclass)]
822#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
823#[cfg_attr(feature = "node", napi(object))]
824#[derive(Debug, Clone, Serialize, Deserialize)]
825pub struct TestFilterResponse {
826 #[serde(deserialize_with = "deserialize_as_json_string")]
828 pub result: String,
829 pub logs: Vec<String>,
831}
832
833#[cfg_attr(feature = "python", gen_stub_pyclass)]
835#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
836#[cfg_attr(feature = "node", napi(object))]
837#[derive(Debug, Clone, Serialize, Deserialize)]
838pub struct EnabledCountResponse {
839 pub total: i64,
841}
842
843#[cfg(test)]
844#[allow(clippy::unwrap_used)]
845mod destination_attributes_tests {
846 use super::*;
847
848 #[test]
849 fn webhook_roundtrip() {
850 let attrs = DestinationAttributes::Webhook(WebhookAttributes {
851 url: "https://x.example/hook".to_string(),
852 max_retry: 3,
853 retry_interval_sec: 5,
854 post_timeout_sec: 10,
855 compression: Some("none".to_string()),
856 security_token: None,
857 });
858 let json = serde_json::to_string(&attrs).unwrap();
859 assert!(json.contains(r#""destination":"webhook""#));
860 assert!(json.contains(r#""url":"https://x.example/hook""#));
861 let parsed: DestinationAttributes = serde_json::from_str(&json).unwrap();
862 assert!(matches!(parsed, DestinationAttributes::Webhook(_)));
863 assert!(matches!(parsed.tag(), StreamDestination::Webhook));
864 }
865
866 #[test]
867 fn s3_roundtrip() {
868 let attrs = DestinationAttributes::S3(S3Attributes {
869 endpoint: "s3.amazonaws.com".to_string(),
870 access_key: "AK".to_string(),
871 secret_key: "SK".to_string(),
872 bucket: "b".to_string(),
873 object_prefix: "p".to_string(),
874 compression: "none".to_string(),
875 file_type: "json".to_string(),
876 max_retry: 3,
877 retry_interval_sec: 5,
878 use_ssl: Some(true),
879 });
880 let json = serde_json::to_string(&attrs).unwrap();
881 assert!(json.contains(r#""destination":"s3""#));
882 let parsed: DestinationAttributes = serde_json::from_str(&json).unwrap();
883 assert!(matches!(parsed, DestinationAttributes::S3(_)));
884 }
885
886 #[test]
887 fn azure_roundtrip() {
888 let attrs = DestinationAttributes::Azure(AzureAttributes {
889 storage_account: "acct".to_string(),
890 sas_token: "tok".to_string(),
891 container: "c".to_string(),
892 compression: "none".to_string(),
893 file_type: "json".to_string(),
894 max_retry: 3,
895 retry_interval_sec: 5,
896 blob_prefix: None,
897 });
898 let json = serde_json::to_string(&attrs).unwrap();
899 assert!(json.contains(r#""destination":"azure""#));
900 let parsed: DestinationAttributes = serde_json::from_str(&json).unwrap();
901 assert!(matches!(parsed, DestinationAttributes::Azure(_)));
902 }
903
904 #[test]
905 fn postgres_roundtrip() {
906 let attrs = DestinationAttributes::Postgres(PostgresAttributes {
907 host: "h".to_string(),
908 port: 5432,
909 database: "db".to_string(),
910 username: "u".to_string(),
911 password: "p".to_string(),
912 table_name: "t".to_string(),
913 sslmode: "disable".to_string(),
914 max_retry: 3,
915 retry_interval_sec: 5,
916 });
917 let json = serde_json::to_string(&attrs).unwrap();
918 assert!(json.contains(r#""destination":"postgres""#));
919 let parsed: DestinationAttributes = serde_json::from_str(&json).unwrap();
920 assert!(matches!(parsed, DestinationAttributes::Postgres(_)));
921 }
922
923 #[test]
924 fn kafka_roundtrip() {
925 let attrs = DestinationAttributes::Kafka(KafkaAttributes {
926 bootstrap_servers: "host:9092".to_string(),
927 topic_name: "t".to_string(),
928 compression_type: "gzip".to_string(),
929 batch_size: 100,
930 linger_ms: 10,
931 max_message_bytes: 1024,
932 timeout_sec: 30,
933 max_retry: 3,
934 retry_interval_sec: 5,
935 username: None,
936 password: None,
937 protocol: None,
938 mechanisms: None,
939 });
940 let json = serde_json::to_string(&attrs).unwrap();
941 assert!(json.contains(r#""destination":"kafka""#));
942 let parsed: DestinationAttributes = serde_json::from_str(&json).unwrap();
943 assert!(matches!(parsed, DestinationAttributes::Kafka(_)));
944 }
945}