1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema, utoipa::IntoParams)]
4pub struct MigrationPlanQuery {
5 pub from_version: Option<u64>,
6 pub to_version: Option<u64>,
7 pub is_edge: Option<bool>,
8 pub expand_contract: Option<bool>,
9}
10
11impl MigrationPlanQuery {
12 pub fn require_from_version(&self) -> Result<u64, String> {
13 self.from_version
14 .ok_or_else(|| "from_version required".to_string())
15 }
16 pub fn require_to_version(&self) -> Result<u64, String> {
17 self.to_version
18 .ok_or_else(|| "to_version required".to_string())
19 }
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
23pub struct MigrationExecuteRequest {
24 pub plan_json: String,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
28pub struct MigrationRollbackRequest {
29 pub plan_json: String,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
33pub struct MigrationPlanResponse {
34 pub plan: serde_json::Value,
35 pub plan_json: String,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
39pub struct MigrationExecuteResponse {
40 pub success: bool,
41 pub steps_completed: usize,
42 pub rows_migrated: u64,
43 pub errors: Vec<String>,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub preview: Option<bool>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
49pub struct MigrationStatusResponse {
50 pub space: Option<String>,
51 pub label: Option<String>,
52 pub is_edge: Option<bool>,
53 pub latest_applied_version: Option<u64>,
54 pub applied_versions: Vec<u64>,
55 pub history_count: usize,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
59pub struct MigrationHistoryResponse {
60 pub space: String,
61 pub label: String,
62 pub is_edge: bool,
63 pub applied_versions: Vec<u64>,
64 pub history: Vec<serde_json::Value>,
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn test_dto_serde_roundtrip() {
73 let req = MigrationExecuteRequest {
74 plan_json: "{}".to_string(),
75 };
76 let json = serde_json::to_string(&req).unwrap();
77 let decoded: MigrationExecuteRequest = serde_json::from_str(&json).unwrap();
78 assert_eq!(decoded.plan_json, "{}");
79
80 let query = MigrationPlanQuery {
81 from_version: Some(1),
82 to_version: Some(2),
83 is_edge: Some(false),
84 expand_contract: Some(true),
85 };
86 let json = serde_json::to_string(&query).unwrap();
87 let decoded: MigrationPlanQuery = serde_json::from_str(&json).unwrap();
88 assert_eq!(decoded.from_version, Some(1));
89 }
90
91 #[test]
92 fn test_status_response() {
93 let resp = MigrationStatusResponse {
94 space: Some("s".into()),
95 label: Some("l".into()),
96 is_edge: Some(false),
97 latest_applied_version: Some(5),
98 applied_versions: vec![1, 2, 5],
99 history_count: 3,
100 };
101 let json = serde_json::to_string(&resp).unwrap();
102 let decoded: MigrationStatusResponse = serde_json::from_str(&json).unwrap();
103 assert_eq!(decoded.latest_applied_version, Some(5));
104 }
105}