tonin_client/
platform_client.rs1use serde::{Deserialize, Serialize};
8use std::process::Command;
9
10#[derive(Debug, Clone)]
12pub struct PlatformClientConfig {
13 pub tonin_bin: String,
15 pub workspace: String,
17}
18
19impl Default for PlatformClientConfig {
20 fn default() -> Self {
21 Self {
22 tonin_bin: "tonin".to_string(),
23 workspace: ".".to_string(),
24 }
25 }
26}
27
28pub struct PlatformClient {
33 config: PlatformClientConfig,
34}
35
36impl PlatformClient {
37 pub fn new() -> Self {
39 Self {
40 config: PlatformClientConfig::default(),
41 }
42 }
43
44 pub fn with_config(config: PlatformClientConfig) -> Self {
46 Self { config }
47 }
48
49 pub fn deploy_service(
62 &self,
63 env: &str,
64 service: &str,
65 ) -> Result<DeployResponse, Box<dyn std::error::Error>> {
66 let output = Command::new(&self.config.tonin_bin)
67 .args([
68 "platform",
69 "deploy",
70 "--env",
71 env,
72 "--service",
73 service,
74 "--json",
75 "--workspace",
76 &self.config.workspace,
77 ])
78 .output()?;
79
80 if !output.status.success() {
81 let stderr = String::from_utf8_lossy(&output.stderr);
82 return Err(format!("tonin platform deploy failed: {}", stderr).into());
83 }
84
85 let response: DeployResponse = serde_json::from_slice(&output.stdout)?;
86 Ok(response)
87 }
88
89 pub fn get_status(
100 &self,
101 env: &str,
102 ) -> Result<Vec<ServiceStatusResponse>, Box<dyn std::error::Error>> {
103 let output = Command::new(&self.config.tonin_bin)
104 .args([
105 "platform",
106 "status",
107 "--env",
108 env,
109 "--json",
110 "--workspace",
111 &self.config.workspace,
112 ])
113 .output()?;
114
115 if !output.status.success() {
116 let stderr = String::from_utf8_lossy(&output.stderr);
117 return Err(format!("tonin platform status failed: {}", stderr).into());
118 }
119
120 let response: Vec<ServiceStatusResponse> = serde_json::from_slice(&output.stdout)?;
121 Ok(response)
122 }
123
124 pub fn rollback_service(
136 &self,
137 service: &str,
138 env: &str,
139 ) -> Result<(), Box<dyn std::error::Error>> {
140 let output = Command::new(&self.config.tonin_bin)
141 .args([
142 "platform",
143 "rollback",
144 "--service",
145 service,
146 "--env",
147 env,
148 "--workspace",
149 &self.config.workspace,
150 ])
151 .output()?;
152
153 if !output.status.success() {
154 let stderr = String::from_utf8_lossy(&output.stderr);
155 return Err(format!("tonin platform rollback failed: {}", stderr).into());
156 }
157
158 Ok(())
159 }
160}
161
162impl Default for PlatformClient {
163 fn default() -> Self {
164 Self::new()
165 }
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct DeployResponse {
171 pub service: String,
172 pub env: String,
173 pub digest: String,
174 pub status: String,
175 pub timestamp: u64,
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub message: Option<String>,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct ServiceStatusResponse {
183 pub service: String,
184 pub env: String,
185 pub running_version: Option<String>,
186 pub desired_version: Option<String>,
187 pub digest: Option<String>,
188 pub health: String,
189 pub timestamp: u64,
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn platform_client_config_default() {
198 let config = PlatformClientConfig::default();
199 assert_eq!(config.tonin_bin, "tonin");
200 assert_eq!(config.workspace, ".");
201 }
202
203 #[test]
204 fn platform_client_new() {
205 let client = PlatformClient::new();
206 assert_eq!(client.config.tonin_bin, "tonin");
207 }
208
209 #[test]
210 fn deploy_response_serialize() {
211 let response = DeployResponse {
212 service: "users-service".to_string(),
213 env: "staging".to_string(),
214 digest: "sha256:abc123".to_string(),
215 status: "success".to_string(),
216 timestamp: 1719792000,
217 message: None,
218 };
219
220 let json = serde_json::to_string(&response).expect("serialize failed");
221 assert!(json.contains("users-service"));
222
223 let deserialized: DeployResponse = serde_json::from_str(&json).expect("deserialize failed");
224 assert_eq!(deserialized.status, "success");
225 }
226
227 #[test]
228 fn service_status_response_serialize() {
229 let status = ServiceStatusResponse {
230 service: "orders-service".to_string(),
231 env: "prod".to_string(),
232 running_version: Some("1.2.3".to_string()),
233 desired_version: Some("1.2.3".to_string()),
234 digest: Some("sha256:def456".to_string()),
235 health: "healthy".to_string(),
236 timestamp: 1719792000,
237 };
238
239 let json = serde_json::to_string(&status).expect("serialize failed");
240 let deserialized: ServiceStatusResponse =
241 serde_json::from_str(&json).expect("deserialize failed");
242 assert_eq!(deserialized.health, "healthy");
243 }
244}