Skip to main content

zlayer_types/api/
services.rs

1//! Service endpoint DTOs.
2
3use serde::{Deserialize, Serialize};
4
5use utoipa::IntoParams;
6
7/// Service summary
8#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
9pub struct ServiceSummary {
10    /// Service name
11    pub name: String,
12    /// Deployment name
13    pub deployment: String,
14    /// Service status
15    pub status: String,
16    /// Current replica count
17    pub replicas: u32,
18    /// Desired replica count
19    pub desired_replicas: u32,
20    /// Service endpoints
21    pub endpoints: Vec<ServiceEndpoint>,
22}
23
24/// Service details
25#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
26pub struct ServiceDetails {
27    /// Service name
28    pub name: String,
29    /// Deployment name
30    pub deployment: String,
31    /// Service status
32    pub status: String,
33    /// Current replica count
34    pub replicas: u32,
35    /// Desired replica count
36    pub desired_replicas: u32,
37    /// Service endpoints
38    pub endpoints: Vec<ServiceEndpoint>,
39    /// Service metrics
40    pub metrics: ServiceMetrics,
41}
42
43/// Service endpoint
44#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
45pub struct ServiceEndpoint {
46    /// Endpoint name
47    pub name: String,
48    /// Protocol
49    pub protocol: String,
50    /// Port
51    pub port: u16,
52    /// URL (if public)
53    pub url: Option<String>,
54}
55
56/// Service metrics
57#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
58pub struct ServiceMetrics {
59    /// CPU usage percentage
60    pub cpu_percent: f64,
61    /// Memory usage percentage
62    pub memory_percent: f64,
63    /// Requests per second
64    pub rps: Option<f64>,
65}
66
67/// Scale request
68#[derive(Debug, Deserialize, utoipa::ToSchema)]
69pub struct ScaleRequest {
70    /// Target replica count
71    pub replicas: u32,
72}
73
74/// Log query parameters
75#[derive(Debug, Deserialize, IntoParams)]
76pub struct LogQuery {
77    /// Number of lines to return
78    #[serde(default = "default_lines")]
79    pub lines: u32,
80    /// Follow logs (streaming)
81    #[serde(default)]
82    pub follow: bool,
83    /// Filter by container/instance
84    pub instance: Option<String>,
85}
86
87fn default_lines() -> u32 {
88    100
89}
90
91/// Container summary for API responses
92#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
93pub struct ContainerSummary {
94    /// Container identifier (service-rep-N)
95    pub id: String,
96    /// Service name
97    pub service: String,
98    /// Replica number
99    pub replica: u32,
100    /// Image reference the container was created from (canonical form, e.g.
101    /// `docker.io/library/nginx:1.29-alpine`).
102    ///
103    /// `#[serde(default)]` keeps older payloads (which lacked this field)
104    /// deserializable.
105    #[serde(default)]
106    pub image: String,
107    /// Container state
108    pub state: String,
109    /// Process ID (if running)
110    pub pid: Option<u32>,
111    /// Overlay IP (if assigned)
112    pub overlay_ip: Option<String>,
113    /// Raft node ID of the daemon that owns this container (if known).
114    ///
115    /// Each daemon tags containers it knows about with its own local node ID.
116    /// `None` when the API is running in read-only mode (no local Raft node).
117    #[serde(default, skip_serializing_if = "Option::is_none")]
118    pub node_id: Option<String>,
119}
120
121/// Exec request body
122#[derive(Debug, Deserialize, utoipa::ToSchema)]
123pub struct ExecRequest {
124    /// Command and arguments to execute
125    pub command: Vec<String>,
126    /// Optional replica number to target
127    #[serde(default)]
128    pub replica: Option<u32>,
129}
130
131/// Exec response body
132#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
133pub struct ExecResponse {
134    /// Exit code from the command
135    pub exit_code: i32,
136    /// Standard output
137    pub stdout: String,
138    /// Standard error
139    pub stderr: String,
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn test_service_summary_serialize() {
148        let summary = ServiceSummary {
149            name: "api".to_string(),
150            deployment: "my-app".to_string(),
151            status: "running".to_string(),
152            replicas: 3,
153            desired_replicas: 3,
154            endpoints: vec![ServiceEndpoint {
155                name: "http".to_string(),
156                protocol: "http".to_string(),
157                port: 8080,
158                url: None,
159            }],
160        };
161        let json = serde_json::to_string(&summary).unwrap();
162        assert!(json.contains("api"));
163        assert!(json.contains("my-app"));
164    }
165
166    #[test]
167    fn test_scale_request_deserialize() {
168        let json = r#"{"replicas": 5}"#;
169        let request: ScaleRequest = serde_json::from_str(json).unwrap();
170        assert_eq!(request.replicas, 5);
171    }
172
173    #[test]
174    fn test_log_query_defaults() {
175        let query: LogQuery = serde_json::from_str("{}").unwrap();
176        assert_eq!(query.lines, 100);
177        assert!(!query.follow);
178        assert!(query.instance.is_none());
179    }
180}