1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use utoipa::ToSchema;
4
5fn deserialize_optional_json_value<'de, D>(
6 deserializer: D,
7) -> Result<Option<Option<serde_json::Value>>, D::Error>
8where
9 D: serde::Deserializer<'de>,
10{
11 Option::<serde_json::Value>::deserialize(deserializer).map(Some)
12}
13
14fn default_true() -> bool {
15 true
16}
17
18#[derive(Debug, Serialize, ToSchema)]
19pub struct HealthResponse {
20 pub status: &'static str,
21 pub version: &'static str,
22}
23
24#[derive(Debug, Clone, Serialize, ToSchema)]
25pub struct CycleInfo {
26 pub cycle_id: u64,
27 pub source: String,
28 pub query: String,
29 pub status: String,
30 pub started_at: DateTime<Utc>,
31 pub finished_at: Option<DateTime<Utc>>,
32 pub rows_created: u64,
33 pub rows_updated: u64,
34 pub rows_deleted: u64,
35 pub duration_ms: Option<u64>,
36 pub error: Option<String>,
37}
38
39#[derive(Debug, Serialize, ToSchema)]
40pub struct SinkInfo {
41 pub name: String,
42 pub sink_type: String,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub config: Option<serde_json::Value>,
45}
46
47#[derive(Debug, Serialize, ToSchema)]
48pub struct ErrorResponse {
49 pub error: String,
50}
51
52#[derive(Debug, Serialize, ToSchema)]
53pub struct SinkListResponse {
54 pub sinks: Vec<SinkInfo>,
55}
56
57#[derive(Debug, Deserialize, ToSchema)]
60pub struct CreateSinkRequest {
61 pub name: String,
62 pub sink_type: String,
63 #[serde(default)]
64 pub config: serde_json::Value,
65}
66
67#[derive(Debug, Deserialize, ToSchema)]
68pub struct UpdateSinkRequest {
69 pub sink_type: Option<String>,
70 pub config: Option<serde_json::Value>,
71 pub enabled: Option<bool>,
72}
73
74#[derive(Debug, Serialize, ToSchema)]
75pub struct MutationResponse {
76 pub ok: bool,
77 pub message: String,
78}
79
80#[derive(Debug, Serialize, ToSchema)]
81pub struct HistoryResponse {
82 pub cycles: Vec<CycleInfo>,
83}
84
85#[derive(Debug, Serialize, ToSchema)]
86pub struct StatusResponse {
87 pub running: bool,
88 pub paused: bool,
89}
90
91#[derive(Debug, Clone, Copy, Serialize, Deserialize, ToSchema)]
92#[serde(rename_all = "lowercase")]
93pub enum ExportConfigFormat {
94 Toml,
95 Json,
96}
97
98#[derive(Debug, Deserialize, ToSchema)]
99pub struct ExportConfigQuery {
100 pub format: Option<ExportConfigFormat>,
101}
102
103#[derive(Debug, Serialize, ToSchema)]
104pub struct ExportConfigResponse {
105 pub format: ExportConfigFormat,
106 pub content: String,
107}
108
109#[derive(Debug, Deserialize, ToSchema)]
110pub struct ImportConfigRequest {
111 pub format: ExportConfigFormat,
112 pub content: String,
113}
114
115#[derive(Debug, Serialize, ToSchema)]
116pub struct ImportConfigResponse {
117 pub ok: bool,
118 pub message: String,
119 pub warnings: Vec<String>,
120}
121
122#[derive(Debug, Serialize, ToSchema)]
125pub struct PipeListResponse {
126 pub pipes: Vec<PipeInfo>,
127}
128
129#[derive(Debug, Serialize, ToSchema)]
130pub struct PipePresetListResponse {
131 pub presets: Vec<PipePresetInfo>,
132}
133
134#[derive(Debug, Serialize, ToSchema)]
135pub struct PipeInfo {
136 pub name: String,
137 pub origin_connector: String,
138 pub origin_dsn: String,
139 pub targets: Vec<String>,
140 pub interval_secs: u64,
141 pub query_count: usize,
142 #[serde(skip_serializing_if = "Option::is_none")]
143 pub recipe: Option<serde_json::Value>,
144 pub enabled: bool,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
148pub struct PipePresetSpecInput {
149 pub origin_connector: String,
150 pub origin_dsn: String,
151 #[serde(default)]
152 pub origin_credential: Option<String>,
153 #[serde(default)]
154 pub trino_url: Option<String>,
155 #[serde(default)]
156 pub origin_config: serde_json::Value,
157 #[serde(default)]
158 pub parameters: Vec<PipePresetParameterInput>,
159 #[serde(default)]
160 pub targets: Vec<String>,
161 #[serde(default)]
162 pub queries: Vec<PipeQueryInput>,
163 #[serde(default)]
164 pub schedule: serde_json::Value,
165 #[serde(default)]
166 pub delta: serde_json::Value,
167 #[serde(default)]
168 pub retry: serde_json::Value,
169 #[serde(default)]
170 pub recipe: Option<serde_json::Value>,
171 #[serde(default)]
172 pub filters: Vec<serde_json::Value>,
173 #[serde(default)]
174 pub transforms: Vec<serde_json::Value>,
175 #[serde(default)]
176 pub links: Vec<serde_json::Value>,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
180pub struct PipePresetParameterInput {
181 pub name: String,
182 #[serde(default)]
183 pub label: Option<String>,
184 #[serde(default)]
185 pub description: Option<String>,
186 #[serde(default)]
187 pub default: Option<String>,
188 #[serde(default = "default_true")]
189 pub required: bool,
190 #[serde(default)]
191 pub secret: bool,
192}
193
194#[derive(Debug, Clone, Serialize, ToSchema)]
195pub struct PipePresetInfo {
196 pub name: String,
197 #[serde(skip_serializing_if = "Option::is_none")]
198 pub description: Option<String>,
199 pub spec: serde_json::Value,
200}
201
202#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
203pub struct PipeQueryInput {
204 pub id: String,
205 pub sql: String,
206 pub key_column: String,
207 #[serde(default)]
208 pub sinks: Option<Vec<String>>,
209 #[serde(default)]
210 pub transform: Option<String>,
211}
212
213#[derive(Debug, Deserialize, ToSchema)]
214pub struct CreatePipeRequest {
215 pub name: String,
216 pub origin_connector: String,
217 pub origin_dsn: String,
218 #[serde(default)]
219 pub origin_credential: Option<String>,
220 #[serde(default)]
221 pub trino_url: Option<String>,
222 #[serde(default)]
223 pub origin_config: serde_json::Value,
224 #[serde(default)]
225 pub targets: Vec<String>,
226 #[serde(default)]
227 pub schedule: serde_json::Value,
228 #[serde(default)]
229 pub delta: serde_json::Value,
230 #[serde(default)]
231 pub retry: serde_json::Value,
232 #[serde(default)]
233 pub recipe: Option<serde_json::Value>,
234 #[serde(default)]
235 pub filters: Vec<serde_json::Value>,
236 #[serde(default)]
237 pub transforms: Vec<serde_json::Value>,
238 #[serde(default)]
239 pub links: Vec<serde_json::Value>,
240 #[serde(default)]
241 pub queries: Vec<PipeQueryInput>,
242}
243
244#[derive(Debug, Deserialize, ToSchema)]
245pub struct UpdatePipeRequest {
246 pub origin_connector: Option<String>,
247 pub origin_dsn: Option<String>,
248 pub origin_credential: Option<String>,
249 pub trino_url: Option<String>,
250 pub origin_config: Option<serde_json::Value>,
251 pub targets: Option<Vec<String>>,
252 pub schedule: Option<serde_json::Value>,
253 pub delta: Option<serde_json::Value>,
254 pub retry: Option<serde_json::Value>,
255 #[serde(default, deserialize_with = "deserialize_optional_json_value")]
256 pub recipe: Option<Option<serde_json::Value>>,
257 pub filters: Option<Vec<serde_json::Value>>,
258 pub transforms: Option<Vec<serde_json::Value>>,
259 pub links: Option<Vec<serde_json::Value>>,
260 pub queries: Option<Vec<PipeQueryInput>>,
261 pub enabled: Option<bool>,
262}
263
264#[derive(Debug, Deserialize, ToSchema)]
265pub struct CreatePipePresetRequest {
266 pub name: String,
267 #[serde(default)]
268 pub description: Option<String>,
269 pub spec: PipePresetSpecInput,
270}
271
272#[derive(Debug, Deserialize, ToSchema)]
273pub struct UpdatePipePresetRequest {
274 #[serde(default)]
275 pub description: Option<String>,
276 pub spec: Option<PipePresetSpecInput>,
277}
278
279#[derive(Debug, Serialize, ToSchema)]
282pub struct CredentialListResponse {
283 pub credentials: Vec<CredentialInfo>,
284}
285
286#[derive(Debug, Serialize, ToSchema)]
287pub struct CredentialInfo {
288 pub name: String,
289 pub credential_type: String,
290 pub created_at: String,
291}
292
293#[derive(Debug, Deserialize, ToSchema)]
294pub struct CreateCredentialRequest {
295 pub name: String,
296 pub credential_type: String,
297 pub secret: String,
298}
299
300#[derive(Debug, Deserialize, ToSchema)]
301pub struct UpdateCredentialRequest {
302 pub secret: Option<String>,
303 pub credential_type: Option<String>,
304}
305
306#[cfg(test)]
307mod tests {
308 use super::UpdatePipeRequest;
309
310 #[test]
311 fn update_pipe_request_distinguishes_null_recipe_from_omitted() {
312 let cleared: UpdatePipeRequest = serde_json::from_value(serde_json::json!({
313 "recipe": null
314 }))
315 .expect("request with null recipe should deserialize");
316 assert!(matches!(cleared.recipe, Some(None)));
317
318 let set: UpdatePipeRequest = serde_json::from_value(serde_json::json!({
319 "recipe": {"type": "postgres_snapshot", "prefix": "demo"}
320 }))
321 .expect("request with recipe object should deserialize");
322 assert!(matches!(set.recipe, Some(Some(_))));
323
324 let omitted: UpdatePipeRequest = serde_json::from_value(serde_json::json!({}))
325 .expect("empty request should deserialize");
326 assert!(omitted.recipe.is_none());
327 }
328}