database_replicator/remote/
models.rs

1// ABOUTME: Data structures for remote job specifications and responses
2// ABOUTME: These are serialized to JSON for API communication
3
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct JobSpec {
9    #[serde(rename = "schema_version")]
10    pub version: String,
11    pub command: String, // "init" or "sync"
12    pub source_url: String,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub target_url: Option<String>,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub target_project_id: Option<String>,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub target_branch_id: Option<String>,
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub target_databases: Option<Vec<String>>,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub seren_api_key: Option<String>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub filter: Option<FilterSpec>,
25    pub options: HashMap<String, serde_json::Value>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct FilterSpec {
30    pub include_databases: Option<Vec<String>>,
31    pub exclude_databases: Option<Vec<String>>,
32    pub include_tables: Option<Vec<String>>,
33    pub exclude_tables: Option<Vec<String>>,
34}
35
36#[derive(Debug, Clone, Deserialize)]
37pub struct JobResponse {
38    pub job_id: String,
39    pub status: String,
40}
41
42#[derive(Debug, Clone, Deserialize)]
43pub struct JobStatus {
44    pub job_id: String,
45    pub status: String, // "provisioning", "running", "completed", "failed"
46    pub created_at: Option<String>,
47    pub started_at: Option<String>,
48    pub completed_at: Option<String>,
49    pub progress: Option<ProgressInfo>,
50    pub error: Option<String>,
51}
52
53#[derive(Debug, Clone, Deserialize)]
54pub struct ProgressInfo {
55    pub current_database: Option<String>,
56    pub databases_completed: usize,
57    pub databases_total: usize,
58    pub message: Option<String>,
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn test_job_spec_serialization() {
67        let mut options = HashMap::new();
68        options.insert("drop_existing".to_string(), serde_json::Value::Bool(true));
69
70        // Test with all fields populated
71        let job_spec = JobSpec {
72            version: "1.0".to_string(),
73            command: "init".to_string(),
74            source_url: "postgresql://source".to_string(),
75            target_url: Some("postgresql://target".to_string()),
76            target_project_id: Some("proj123".to_string()),
77            target_branch_id: Some("brnch456".to_string()),
78            target_databases: Some(vec!["db1".to_string()]),
79            seren_api_key: Some("seren_key".to_string()),
80            filter: Some(FilterSpec {
81                include_databases: Some(vec!["db1".to_string()]),
82                exclude_databases: None,
83                include_tables: None,
84                exclude_tables: None,
85            }),
86            options: options.clone(),
87        };
88
89        let parsed: serde_json::Value = serde_json::to_value(&job_spec).unwrap();
90        assert_eq!(parsed["target_url"], "postgresql://target");
91        assert_eq!(parsed["target_project_id"], "proj123");
92        assert_eq!(parsed["target_branch_id"], "brnch456");
93        assert_eq!(parsed["target_databases"], serde_json::json!(["db1"]));
94        assert_eq!(parsed["seren_api_key"], "seren_key");
95        assert_eq!(
96            parsed["filter"],
97            serde_json::json!({"include_databases": ["db1"], "exclude_databases": null, "include_tables": null, "exclude_tables": null})
98        );
99        assert_eq!(parsed["schema_version"], "1.0");
100
101        // Test with optional fields as None
102        let job_spec_none = JobSpec {
103            version: "1.0".to_string(),
104            command: "init".to_string(),
105            source_url: "postgresql://source".to_string(),
106            target_url: Some("postgresql://target".to_string()),
107            target_project_id: None,
108            target_branch_id: None,
109            target_databases: None,
110            seren_api_key: None,
111            filter: None,
112            options,
113        };
114
115        let parsed_none: serde_json::Value = serde_json::to_value(&job_spec_none).unwrap();
116        assert_eq!(parsed_none["target_url"], "postgresql://target");
117        assert!(parsed_none.get("target_project_id").is_none());
118        assert!(parsed_none.get("target_branch_id").is_none());
119        assert!(parsed_none.get("target_databases").is_none());
120        assert!(parsed_none.get("seren_api_key").is_none());
121        assert!(parsed_none.get("filter").is_none());
122    }
123}