1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use utoipa::ToSchema;
6
7fn deserialize_optional_json_value<'de, D>(
8 deserializer: D,
9) -> Result<Option<Option<serde_json::Value>>, D::Error>
10where
11 D: serde::Deserializer<'de>,
12{
13 Option::<serde_json::Value>::deserialize(deserializer).map(Some)
14}
15
16fn default_true() -> bool {
17 true
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
21#[serde(rename_all = "snake_case")]
22pub enum ApiMissedTickPolicy {
23 Skip,
24 Burst,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
28pub struct ApiScheduleDef {
29 pub interval_secs: Option<u64>,
30 pub missed_tick_policy: Option<ApiMissedTickPolicy>,
31 pub max_requests_per_minute: Option<u32>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
35#[serde(rename_all = "snake_case")]
36pub enum ApiDiffMode {
37 Db,
38 Memory,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
42pub struct ApiDeltaDef {
43 pub diff_mode: Option<ApiDiffMode>,
44 pub fail_safe_threshold: Option<f64>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
48pub struct ApiRetryDef {
49 pub max_retries: Option<u32>,
50 pub retry_base_delay_secs: Option<u64>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
54#[serde(rename_all = "snake_case")]
55pub enum ApiPipeRecipeType {
56 PostgresMetadata,
57 PostgresSnapshot,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
61pub struct ApiPipeRecipeDef {
62 #[serde(rename = "type")]
63 pub recipe_type: ApiPipeRecipeType,
64 pub prefix: String,
65 #[serde(default)]
66 pub entity_type_id: Option<String>,
67 #[serde(default)]
68 pub schema_id: Option<String>,
69 #[serde(default)]
70 pub schemas: Option<Vec<String>>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
74#[serde(rename_all = "lowercase")]
75pub enum ApiLinkStrategy {
76 Exact,
77 Normalized,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
81pub struct ApiLinkDef {
82 pub name: String,
83 pub left_field: String,
84 pub right_field: String,
85 pub strategy: Option<ApiLinkStrategy>,
86 pub target_origin: String,
87 pub target_query: String,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
91#[serde(tag = "type", rename_all = "snake_case")]
92pub enum ApiAuthConfig {
93 Bearer { token: String },
94 Header { name: String, value: String },
95 Basic { username: String, password: String },
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
99pub struct ApiEmptyOriginConfig {}
100
101#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
102pub struct ApiTrinoExtraCredentials {
103 pub username: String,
104 pub password: String,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
108#[serde(tag = "type", rename_all = "snake_case")]
109pub enum ApiTrinoAuth {
110 Bearer { token: String },
111 Basic { username: String, password: String },
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
115pub struct ApiTrinoOriginConfig {
116 pub user: Option<String>,
117 pub catalog: Option<String>,
118 pub schema: Option<String>,
119 pub timeout_secs: Option<u64>,
120 pub auth: Option<ApiTrinoAuth>,
121 pub extra_credentials: Option<ApiTrinoExtraCredentials>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
125pub struct ApiClickHouseOriginConfig {
126 pub user: Option<String>,
127 pub password: Option<String>,
128 pub database: Option<String>,
129 pub timeout_secs: Option<u64>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
133#[serde(tag = "type", rename_all = "snake_case")]
134pub enum ApiHttpPaginationConfig {
135 Offset {
136 page_size: usize,
137 limit_param: Option<String>,
138 offset_param: Option<String>,
139 },
140 Cursor {
141 page_size: usize,
142 cursor_param: Option<String>,
143 cursor_path: String,
144 },
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
148pub struct ApiHttpOriginConfig {
149 pub headers: Option<std::collections::HashMap<String, String>>,
150 pub auth: Option<ApiAuthConfig>,
151 pub pagination: Option<ApiHttpPaginationConfig>,
152 pub response_path: Option<String>,
153 pub timeout_secs: Option<u64>,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
157pub struct ApiGraphqlPagination {
158 pub cursor_variable: Option<String>,
159 pub has_next_path: Option<String>,
160 pub end_cursor_path: Option<String>,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
164pub struct ApiGraphqlOriginConfig {
165 pub headers: Option<std::collections::HashMap<String, String>>,
166 pub auth: Option<ApiAuthConfig>,
167 pub response_path: Option<String>,
168 pub timeout_secs: Option<u64>,
169 pub pagination: Option<ApiGraphqlPagination>,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
173pub struct ApiMcpOriginConfig {
174 pub args: Option<Vec<String>>,
175 pub key_field: Option<String>,
176 pub response_path: Option<String>,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
180pub struct ApiKafkaOriginConfig {
181 pub brokers: String,
182 pub topic: String,
183 pub group_id: String,
184 pub auto_offset_reset: Option<String>,
185 pub auth: Option<ApiKafkaAuth>,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
189pub struct ApiSurrealDbOriginConfig {
190 pub url: String,
191 pub namespace: String,
192 pub database: String,
193 pub username: Option<String>,
194 pub password: Option<String>,
195 pub live: Option<bool>,
196 pub table: Option<String>,
197 pub key_column: Option<String>,
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
201#[serde(untagged)]
202pub enum ApiOriginConfig {
203 Empty(ApiEmptyOriginConfig),
204 Trino(ApiTrinoOriginConfig),
205 ClickHouse(ApiClickHouseOriginConfig),
206 Http(ApiHttpOriginConfig),
207 Graphql(ApiGraphqlOriginConfig),
208 Mcp(ApiMcpOriginConfig),
209 Kafka(ApiKafkaOriginConfig),
210 SurrealDb(ApiSurrealDbOriginConfig),
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
214pub struct ApiKafkaAuth {
215 pub security_protocol: Option<String>,
216 pub sasl_mechanism: Option<String>,
217 pub sasl_username: Option<String>,
218 pub sasl_password: Option<String>,
219 pub sasl_kerberos_keytab: Option<String>,
220 pub sasl_kerberos_principal: Option<String>,
221 pub ssl_ca_location: Option<String>,
222 pub ssl_certificate_location: Option<String>,
223 pub ssl_key_location: Option<String>,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
227#[serde(rename_all = "snake_case")]
228pub enum ApiKafkaKeyFormat {
229 String,
230 JsonObject,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
234#[serde(rename_all = "snake_case")]
235pub enum ApiKafkaValueFormat {
236 Envelope,
237 Compact,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
241#[serde(rename_all = "snake_case")]
242pub enum ApiSurrealDbSinkMode {
243 Envelope,
244 Document,
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
248#[serde(rename_all = "snake_case")]
249pub enum ApiFilterOp {
250 Eq,
251 Ne,
252 Gt,
253 Gte,
254 Lt,
255 Lte,
256 Contains,
257 Exists,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
261#[serde(tag = "type", rename_all = "snake_case")]
262pub enum ApiTransformStep {
263 Rename {
264 from: String,
265 to: String,
266 },
267 Set {
268 field: String,
269 value: serde_json::Value,
270 },
271 Upper {
272 field: String,
273 },
274 Lower {
275 field: String,
276 },
277 Remove {
278 field: String,
279 },
280 Copy {
281 from: String,
282 to: String,
283 },
284 Default {
285 field: String,
286 value: serde_json::Value,
287 },
288 Filter {
289 field: String,
290 op: ApiFilterOp,
291 #[serde(default)]
292 value: Option<serde_json::Value>,
293 },
294 MapValue {
295 field: String,
296 mapping: std::collections::HashMap<String, serde_json::Value>,
297 },
298 Truncate {
299 field: String,
300 max_len: usize,
301 },
302 Nest {
303 fields: Vec<String>,
304 into: String,
305 },
306 Flatten {
307 field: String,
308 },
309 Hash {
310 field: String,
311 },
312 Coalesce {
313 fields: Vec<String>,
314 into: String,
315 },
316 SchemaFilter {
317 field: String,
318 #[serde(default)]
319 allow: Option<Vec<String>>,
320 #[serde(default)]
321 deny: Option<Vec<String>>,
322 },
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
326pub struct ApiStdoutSinkConfig {
327 pub pretty: Option<bool>,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
331pub struct ApiKafkaSinkConfig {
332 pub brokers: String,
333 pub topic: String,
334 pub auth: Option<ApiKafkaAuth>,
335 pub key_format: Option<ApiKafkaKeyFormat>,
336 pub key_field: Option<String>,
337 pub value_format: Option<ApiKafkaValueFormat>,
338 pub created_change_type: Option<String>,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
342pub struct ApiSurrealDbSinkConfig {
343 pub url: String,
344 pub namespace: String,
345 pub database: String,
346 pub table: String,
347 pub username: Option<String>,
348 pub password: Option<String>,
349 pub mode: Option<ApiSurrealDbSinkMode>,
350 pub key_field: Option<String>,
351}
352
353#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
354pub struct ApiMcpSinkConfig {
355 pub dsn: String,
356 pub args: Option<Vec<String>>,
357 pub tool_name: String,
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
361pub struct ApiMysqlSinkConfig {
362 pub dsn: String,
363 pub table: String,
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
367pub struct ApiPostgresSinkConfig {
368 pub dsn: String,
369 pub table: String,
370 pub schema: Option<String>,
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
374pub struct ApiClickHouseSinkConfig {
375 pub url: String,
376 pub table: String,
377 pub database: Option<String>,
378 pub user: Option<String>,
379 pub password: Option<String>,
380 pub timeout_secs: Option<u64>,
381}
382
383#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
384pub struct ApiHttpSinkConfig {
385 pub url: String,
386 pub method: Option<String>,
387 pub headers: Option<std::collections::HashMap<String, String>>,
388 pub auth: Option<ApiAuthConfig>,
389 pub timeout_secs: Option<u64>,
390 pub retry_count: Option<u32>,
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
394#[serde(untagged)]
395pub enum ApiSinkConfig {
396 Stdout(ApiStdoutSinkConfig),
397 Kafka(ApiKafkaSinkConfig),
398 SurrealDb(ApiSurrealDbSinkConfig),
399 Mcp(ApiMcpSinkConfig),
400 Mysql(ApiMysqlSinkConfig),
401 Postgres(ApiPostgresSinkConfig),
402 ClickHouse(ApiClickHouseSinkConfig),
403 Http(ApiHttpSinkConfig),
404}
405
406#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
407pub struct HealthResponse {
408 pub status: String,
409 pub version: String,
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
413pub struct CycleInfo {
414 pub cycle_id: u64,
415 pub source: String,
416 pub query: String,
417 pub status: String,
418 pub started_at: DateTime<Utc>,
419 pub finished_at: Option<DateTime<Utc>>,
420 pub rows_created: u64,
421 pub rows_updated: u64,
422 pub rows_deleted: u64,
423 pub duration_ms: Option<u64>,
424 pub error: Option<String>,
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
428pub struct SinkInfo {
429 pub name: String,
430 pub sink_type: String,
431 #[serde(skip_serializing_if = "Option::is_none")]
432 #[schema(value_type = Option<ApiSinkConfig>)]
433 pub config: Option<serde_json::Value>,
434}
435
436#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
437pub struct ErrorResponse {
438 pub error: String,
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
442pub struct SinkListResponse {
443 pub sinks: Vec<SinkInfo>,
444}
445
446#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
449pub struct CreateSinkRequest {
450 pub name: String,
451 pub sink_type: String,
452 #[serde(default)]
453 #[schema(value_type = ApiSinkConfig)]
454 pub config: serde_json::Value,
455}
456
457#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
458pub struct UpdateSinkRequest {
459 #[serde(default, skip_serializing_if = "Option::is_none")]
460 pub sink_type: Option<String>,
461 #[serde(default, skip_serializing_if = "Option::is_none")]
462 #[schema(value_type = Option<ApiSinkConfig>)]
463 pub config: Option<serde_json::Value>,
464 #[serde(default, skip_serializing_if = "Option::is_none")]
465 pub enabled: Option<bool>,
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
469pub struct MutationResponse {
470 pub ok: bool,
471 pub message: String,
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
475pub struct PipeRunQueryResult {
476 pub query_id: String,
477 pub created: usize,
478 pub updated: usize,
479 pub deleted: usize,
480}
481
482#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
483pub struct PipeRunResponse {
484 pub ok: bool,
485 pub message: String,
486 pub results: Vec<PipeRunQueryResult>,
487}
488
489#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
490pub struct HistoryResponse {
491 pub cycles: Vec<CycleInfo>,
492}
493
494#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
495pub struct StatusResponse {
496 pub running: bool,
497 pub paused: bool,
498}
499
500#[derive(Debug, Clone, Copy, Serialize, Deserialize, ToSchema)]
501#[serde(rename_all = "lowercase")]
502pub enum ExportConfigFormat {
503 Toml,
504 Json,
505}
506
507#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
508pub struct ExportConfigQuery {
509 #[serde(default, skip_serializing_if = "Option::is_none")]
510 pub format: Option<ExportConfigFormat>,
511}
512
513#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
514pub struct ExportConfigResponse {
515 pub format: ExportConfigFormat,
516 pub content: String,
517}
518
519#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
520pub struct ImportConfigRequest {
521 pub format: ExportConfigFormat,
522 pub content: String,
523}
524
525#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
526pub struct ImportConfigResponse {
527 pub ok: bool,
528 pub message: String,
529 pub warnings: Vec<String>,
530}
531
532#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
535pub struct PipeListResponse {
536 pub pipes: Vec<PipeInfo>,
537}
538
539#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
540pub struct PipePresetListResponse {
541 pub presets: Vec<PipePresetInfo>,
542}
543
544#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
545pub struct PipeInfo {
546 pub name: String,
547 pub origin_connector: String,
548 pub origin_dsn: String,
549 pub targets: Vec<String>,
550 pub interval_secs: u64,
551 pub query_count: usize,
552 #[serde(skip_serializing_if = "Option::is_none")]
553 #[schema(value_type = Option<ApiPipeRecipeDef>)]
554 pub recipe: Option<serde_json::Value>,
555 pub enabled: bool,
556}
557
558#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
559pub struct PipePresetSpecInput {
560 pub origin_connector: String,
561 pub origin_dsn: String,
562 #[serde(default, skip_serializing_if = "Option::is_none")]
563 pub origin_credential: Option<String>,
564 #[serde(default, skip_serializing_if = "Option::is_none")]
565 pub trino_url: Option<String>,
566 #[serde(default)]
567 #[schema(value_type = ApiOriginConfig)]
568 pub origin_config: serde_json::Value,
569 #[serde(default)]
570 pub parameters: Vec<PipePresetParameterInput>,
571 #[serde(default)]
572 pub targets: Vec<String>,
573 #[serde(default)]
574 pub queries: Vec<PipeQueryInput>,
575 #[serde(default)]
576 #[schema(value_type = ApiScheduleDef)]
577 pub schedule: serde_json::Value,
578 #[serde(default)]
579 #[schema(value_type = ApiDeltaDef)]
580 pub delta: serde_json::Value,
581 #[serde(default)]
582 #[schema(value_type = ApiRetryDef)]
583 pub retry: serde_json::Value,
584 #[serde(default)]
585 #[schema(value_type = Option<ApiPipeRecipeDef>)]
586 pub recipe: Option<serde_json::Value>,
587 #[serde(default)]
588 #[schema(value_type = Vec<ApiTransformStep>)]
589 pub filters: Vec<serde_json::Value>,
590 #[serde(default)]
591 #[schema(value_type = Vec<ApiTransformStep>)]
592 pub transforms: Vec<serde_json::Value>,
593 #[serde(default)]
594 #[schema(value_type = Vec<ApiLinkDef>)]
595 pub links: Vec<serde_json::Value>,
596}
597
598#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
599pub struct PipePresetParameterInput {
600 pub name: String,
601 #[serde(default)]
602 pub label: Option<String>,
603 #[serde(default)]
604 pub description: Option<String>,
605 #[serde(default)]
606 pub default: Option<String>,
607 #[serde(default = "default_true")]
608 pub required: bool,
609 #[serde(default)]
610 pub secret: bool,
611}
612
613#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
614pub struct PipePresetInfo {
615 pub name: String,
616 #[serde(skip_serializing_if = "Option::is_none")]
617 pub description: Option<String>,
618 #[schema(value_type = PipePresetSpecInput)]
619 pub spec: serde_json::Value,
620}
621
622#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
623pub struct PipeQueryInput {
624 pub id: String,
625 pub sql: String,
626 pub key_column: String,
627 #[serde(default, skip_serializing_if = "Option::is_none")]
628 pub sinks: Option<Vec<String>>,
629 #[serde(default, skip_serializing_if = "Option::is_none")]
630 pub transform: Option<String>,
631}
632
633#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
634pub struct CreatePipeRequest {
635 pub name: String,
636 pub origin_connector: String,
637 pub origin_dsn: String,
638 #[serde(default, skip_serializing_if = "Option::is_none")]
639 pub origin_credential: Option<String>,
640 #[serde(default, skip_serializing_if = "Option::is_none")]
641 pub trino_url: Option<String>,
642 #[serde(default)]
643 #[schema(value_type = ApiOriginConfig)]
644 pub origin_config: serde_json::Value,
645 #[serde(default)]
646 pub targets: Vec<String>,
647 #[serde(default)]
648 #[schema(value_type = ApiScheduleDef)]
649 pub schedule: serde_json::Value,
650 #[serde(default)]
651 #[schema(value_type = ApiDeltaDef)]
652 pub delta: serde_json::Value,
653 #[serde(default)]
654 #[schema(value_type = ApiRetryDef)]
655 pub retry: serde_json::Value,
656 #[serde(default)]
657 #[schema(value_type = Option<ApiPipeRecipeDef>)]
658 pub recipe: Option<serde_json::Value>,
659 #[serde(default)]
660 #[schema(value_type = Vec<ApiTransformStep>)]
661 pub filters: Vec<serde_json::Value>,
662 #[serde(default)]
663 #[schema(value_type = Vec<ApiTransformStep>)]
664 pub transforms: Vec<serde_json::Value>,
665 #[serde(default)]
666 #[schema(value_type = Vec<ApiLinkDef>)]
667 pub links: Vec<serde_json::Value>,
668 #[serde(default)]
669 pub queries: Vec<PipeQueryInput>,
670}
671
672#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
673pub struct UpdatePipeRequest {
674 #[serde(default, skip_serializing_if = "Option::is_none")]
675 pub origin_connector: Option<String>,
676 #[serde(default, skip_serializing_if = "Option::is_none")]
677 pub origin_dsn: Option<String>,
678 #[serde(default, skip_serializing_if = "Option::is_none")]
679 pub origin_credential: Option<String>,
680 #[serde(default, skip_serializing_if = "Option::is_none")]
681 pub trino_url: Option<String>,
682 #[serde(default, skip_serializing_if = "Option::is_none")]
683 #[schema(value_type = Option<ApiOriginConfig>)]
684 pub origin_config: Option<serde_json::Value>,
685 #[serde(default, skip_serializing_if = "Option::is_none")]
686 pub targets: Option<Vec<String>>,
687 #[serde(default, skip_serializing_if = "Option::is_none")]
688 #[schema(value_type = Option<ApiScheduleDef>)]
689 pub schedule: Option<serde_json::Value>,
690 #[serde(default, skip_serializing_if = "Option::is_none")]
691 #[schema(value_type = Option<ApiDeltaDef>)]
692 pub delta: Option<serde_json::Value>,
693 #[serde(default, skip_serializing_if = "Option::is_none")]
694 #[schema(value_type = Option<ApiRetryDef>)]
695 pub retry: Option<serde_json::Value>,
696 #[serde(
697 default,
698 deserialize_with = "deserialize_optional_json_value",
699 skip_serializing_if = "Option::is_none"
700 )]
701 #[schema(value_type = Option<ApiPipeRecipeDef>)]
702 pub recipe: Option<Option<serde_json::Value>>,
703 #[serde(default, skip_serializing_if = "Option::is_none")]
704 #[schema(value_type = Option<Vec<ApiTransformStep>>)]
705 pub filters: Option<Vec<serde_json::Value>>,
706 #[serde(default, skip_serializing_if = "Option::is_none")]
707 #[schema(value_type = Option<Vec<ApiTransformStep>>)]
708 pub transforms: Option<Vec<serde_json::Value>>,
709 #[serde(default, skip_serializing_if = "Option::is_none")]
710 #[schema(value_type = Option<Vec<ApiLinkDef>>)]
711 pub links: Option<Vec<serde_json::Value>>,
712 #[serde(default, skip_serializing_if = "Option::is_none")]
713 pub queries: Option<Vec<PipeQueryInput>>,
714 #[serde(default, skip_serializing_if = "Option::is_none")]
715 pub enabled: Option<bool>,
716}
717
718#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
719pub struct CreatePipePresetRequest {
720 pub name: String,
721 #[serde(default, skip_serializing_if = "Option::is_none")]
722 pub description: Option<String>,
723 pub spec: PipePresetSpecInput,
724}
725
726#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
727pub struct UpdatePipePresetRequest {
728 #[serde(default, skip_serializing_if = "Option::is_none")]
729 pub description: Option<String>,
730 #[serde(default, skip_serializing_if = "Option::is_none")]
731 pub spec: Option<PipePresetSpecInput>,
732}
733
734#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
737pub struct CredentialListResponse {
738 pub credentials: Vec<CredentialInfo>,
739}
740
741#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
742pub struct CredentialInfo {
743 pub name: String,
744 pub credential_type: String,
745 pub created_at: String,
746}
747
748#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
749pub struct CreateCredentialRequest {
750 pub name: String,
751 pub credential_type: String,
752 pub secret: String,
753}
754
755#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
756pub struct UpdateCredentialRequest {
757 #[serde(default, skip_serializing_if = "Option::is_none")]
758 pub secret: Option<String>,
759 #[serde(default, skip_serializing_if = "Option::is_none")]
760 pub credential_type: Option<String>,
761}
762
763#[cfg(test)]
764mod tests {
765 use super::{HealthResponse, UpdatePipeRequest};
766
767 #[test]
768 fn update_pipe_request_distinguishes_null_recipe_from_omitted() {
769 let cleared: UpdatePipeRequest = serde_json::from_value(serde_json::json!({
770 "recipe": null
771 }))
772 .expect("request with null recipe should deserialize");
773 assert!(matches!(cleared.recipe, Some(None)));
774
775 let set: UpdatePipeRequest = serde_json::from_value(serde_json::json!({
776 "recipe": {"type": "postgres_snapshot", "prefix": "demo"}
777 }))
778 .expect("request with recipe object should deserialize");
779 assert!(matches!(set.recipe, Some(Some(_))));
780
781 let omitted: UpdatePipeRequest = serde_json::from_value(serde_json::json!({}))
782 .expect("empty request should deserialize");
783 assert!(omitted.recipe.is_none());
784 }
785
786 #[test]
787 fn update_pipe_request_omits_unset_fields_when_serializing() {
788 let request = UpdatePipeRequest {
789 origin_connector: None,
790 origin_dsn: None,
791 origin_credential: None,
792 trino_url: None,
793 origin_config: None,
794 targets: None,
795 schedule: None,
796 delta: None,
797 retry: None,
798 recipe: Some(None),
799 filters: None,
800 transforms: None,
801 links: None,
802 queries: None,
803 enabled: Some(true),
804 };
805
806 let json = serde_json::to_value(&request).expect("request should serialize");
807 assert_eq!(json, serde_json::json!({ "recipe": null, "enabled": true }));
808 }
809
810 #[test]
811 fn health_response_round_trips_as_owned_strings() {
812 let response = HealthResponse {
813 status: "ok".into(),
814 version: "0.6.1".into(),
815 };
816
817 let json = serde_json::to_value(&response).expect("response should serialize");
818 let round_trip: HealthResponse =
819 serde_json::from_value(json).expect("response should deserialize");
820 assert_eq!(round_trip.status, "ok");
821 assert_eq!(round_trip.version, "0.6.1");
822 }
823}