microsandbox_server/
payload.rs

1//! Request and response payload definitions for the microsandbox server.
2//!
3//! This module defines the data structures for:
4//! - API request payloads for sandbox operations
5//! - API response payloads for operation results
6//! - Error response structures and types
7//! - Status message formatting
8//!
9//! The module implements:
10//! - Request/response serialization and deserialization
11//! - Structured error responses with type categorization
12//! - Success message formatting for sandbox operations
13//! - Detailed error information handling
14
15use serde::{Deserialize, Serialize};
16use serde_json::Value;
17
18//--------------------------------------------------------------------------------------------------
19// Constants
20//--------------------------------------------------------------------------------------------------
21
22/// JSON-RPC version - always "2.0"
23pub const JSONRPC_VERSION: &str = "2.0";
24
25//--------------------------------------------------------------------------------------------------
26// Types: JSON-RPC Payloads
27//--------------------------------------------------------------------------------------------------
28
29/// JSON-RPC request structure
30#[derive(Debug, Deserialize, Serialize)]
31pub struct JsonRpcRequest {
32    /// JSON-RPC version, must be "2.0"
33    pub jsonrpc: String,
34
35    /// Method name
36    pub method: String,
37
38    /// Optional parameters for the method
39    #[serde(default)]
40    pub params: Value,
41
42    /// Request ID (optional for notifications)
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub id: Option<Value>,
45}
46
47/// JSON-RPC notification structure (no id field, no response expected)
48#[derive(Debug, Deserialize, Serialize)]
49pub struct JsonRpcNotification {
50    /// JSON-RPC version, must be "2.0"
51    pub jsonrpc: String,
52
53    /// Method name
54    pub method: String,
55
56    /// Optional parameters for the method
57    #[serde(default)]
58    pub params: Value,
59}
60
61/// JSON-RPC response structure
62#[derive(Debug, Deserialize, Serialize)]
63pub struct JsonRpcResponse {
64    /// JSON-RPC version, always "2.0"
65    pub jsonrpc: String,
66
67    /// Result of the method execution (if successful)
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub result: Option<Value>,
70
71    /// Error details (if failed)
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub error: Option<JsonRpcError>,
74
75    /// Response ID (same as request ID, optional for notifications)
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub id: Option<Value>,
78}
79
80/// JSON-RPC error structure
81#[derive(Debug, Deserialize, Serialize)]
82pub struct JsonRpcError {
83    /// Error code
84    pub code: i32,
85
86    /// Error message
87    pub message: String,
88
89    /// Optional error data
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub data: Option<Value>,
92}
93
94/// JSON-RPC response or notification result
95/// This enum allows handlers to return either a response (for regular requests)
96/// or no response (for notifications) while maintaining type safety
97#[derive(Debug, Serialize)]
98#[serde(untagged)]
99pub enum JsonRpcResponseOrNotification {
100    /// A regular JSON-RPC response
101    Response(JsonRpcResponse),
102
103    /// A processed notification (no response should be sent)
104    Notification(ProcessedNotification),
105}
106
107/// Represents a processed JSON-RPC notification (no response expected)
108#[derive(Debug, Serialize)]
109pub struct ProcessedNotification {
110    /// Indicates this was a notification that was processed
111    #[serde(skip)]
112    pub processed: bool,
113}
114
115//--------------------------------------------------------------------------------------------------
116// Types: Server Operations
117//--------------------------------------------------------------------------------------------------
118
119/// Request payload for starting a sandbox
120#[derive(Debug, Deserialize)]
121pub struct SandboxStartParams {
122    /// Sandbox name
123    pub sandbox: String,
124
125    /// Optional namespace
126    pub namespace: String,
127
128    /// Optional sandbox configuration
129    pub config: Option<SandboxConfig>,
130}
131
132/// Request payload for stopping a sandbox
133#[derive(Debug, Deserialize)]
134pub struct SandboxStopParams {
135    /// Sandbox name
136    pub sandbox: String,
137
138    /// Optional namespace
139    pub namespace: String,
140}
141
142/// Request payload for getting sandbox metrics
143#[derive(Debug, Deserialize)]
144pub struct SandboxMetricsGetParams {
145    /// Optional sandbox name - if not provided, all sandboxes in the namespace will be included
146    pub sandbox: Option<String>,
147
148    /// Namespace - use "*" to get metrics from all namespaces
149    pub namespace: String,
150}
151
152/// Configuration for a sandbox
153/// Similar to microsandbox-core's Sandbox but with optional fields for update operations
154#[derive(Debug, Deserialize)]
155pub struct SandboxConfig {
156    /// The image to use (optional for updates)
157    pub image: Option<String>,
158
159    /// The amount of memory in MiB to use
160    pub memory: Option<u32>,
161
162    /// The number of vCPUs to use
163    pub cpus: Option<u8>,
164
165    /// The volumes to mount
166    #[serde(default)]
167    pub volumes: Vec<String>,
168
169    /// The ports to expose
170    #[serde(default)]
171    pub ports: Vec<String>,
172
173    /// The environment variables to use
174    #[serde(default)]
175    pub envs: Vec<String>,
176
177    /// The sandboxes to depend on
178    #[serde(default)]
179    pub depends_on: Vec<String>,
180
181    /// The working directory to use
182    pub workdir: Option<String>,
183
184    /// The shell to use (optional for updates)
185    pub shell: Option<String>,
186
187    /// The scripts that can be run
188    #[serde(default)]
189    pub scripts: std::collections::HashMap<String, String>,
190
191    /// The exec command to run
192    pub exec: Option<String>,
193    // SECURITY: Needs networking namespacing to be implemented
194    // /// The network scope for the sandbox
195    // pub scope: Option<String>,
196}
197
198//--------------------------------------------------------------------------------------------------
199// Types: Portal-mirrored RPC Payloads
200//--------------------------------------------------------------------------------------------------
201
202/// Request parameters for executing code in a REPL environment
203#[derive(Debug, Deserialize, Serialize)]
204pub struct SandboxReplRunParams {
205    /// Code to be executed
206    pub code: String,
207
208    /// Programming language to use for execution
209    pub language: String,
210}
211
212/// Request parameters for retrieving output from a previous REPL execution
213#[derive(Debug, Deserialize, Serialize)]
214pub struct SandboxReplGetOutputParams {
215    /// Unique identifier for the execution
216    pub execution_id: String,
217}
218
219/// Request parameters for executing a shell command
220#[derive(Debug, Deserialize, Serialize)]
221pub struct SandboxCommandRunParams {
222    /// Command to execute
223    pub command: String,
224
225    /// Optional arguments for the command
226    #[serde(default)]
227    pub args: Vec<String>,
228}
229
230/// Request parameters for retrieving output from a previous command execution
231#[derive(Debug, Deserialize, Serialize)]
232pub struct SandboxCommandGetOutputParams {
233    /// Unique identifier for the command execution
234    pub execution_id: String,
235}
236
237//--------------------------------------------------------------------------------------------------
238// Methods
239//--------------------------------------------------------------------------------------------------
240
241impl JsonRpcRequest {
242    /// Create a new JSON-RPC request
243    pub fn new(method: String, params: Value, id: Value) -> Self {
244        Self {
245            jsonrpc: JSONRPC_VERSION.to_string(),
246            method,
247            params,
248            id: Some(id),
249        }
250    }
251
252    /// Create a new JSON-RPC notification (no response expected)
253    pub fn new_notification(method: String, params: Value) -> Self {
254        Self {
255            jsonrpc: JSONRPC_VERSION.to_string(),
256            method,
257            params,
258            id: None,
259        }
260    }
261
262    /// Check if this is a notification (no id field)
263    pub fn is_notification(&self) -> bool {
264        self.id.is_none()
265    }
266}
267
268impl ProcessedNotification {
269    /// Create a new processed notification marker
270    pub fn processed() -> Self {
271        Self { processed: true }
272    }
273}
274
275impl JsonRpcResponseOrNotification {
276    /// Create a successful response
277    pub fn success(result: Value, id: Option<Value>) -> Self {
278        Self::Response(JsonRpcResponse::success(result, id))
279    }
280
281    /// Create an error response
282    pub fn error(error: JsonRpcError, id: Option<Value>) -> Self {
283        Self::Response(JsonRpcResponse::error(error, id))
284    }
285
286    /// Create a response from a JsonRpcResponse
287    pub fn response(response: JsonRpcResponse) -> Self {
288        Self::Response(response)
289    }
290
291    /// Create a notification result (no response)
292    pub fn notification(notification: ProcessedNotification) -> Self {
293        Self::Notification(notification)
294    }
295
296    /// Create a no-response result for notifications (deprecated - use notification() instead)
297    pub fn no_response() -> Self {
298        Self::Notification(ProcessedNotification::processed())
299    }
300}
301
302impl JsonRpcResponse {
303    /// Create a new successful JSON-RPC response
304    pub fn success(result: Value, id: Option<Value>) -> Self {
305        Self {
306            jsonrpc: JSONRPC_VERSION.to_string(),
307            result: Some(result),
308            error: None,
309            id,
310        }
311    }
312
313    /// Create a new error JSON-RPC response
314    pub fn error(error: JsonRpcError, id: Option<Value>) -> Self {
315        Self {
316            jsonrpc: JSONRPC_VERSION.to_string(),
317            result: None,
318            error: Some(error),
319            id,
320        }
321    }
322}
323
324//--------------------------------------------------------------------------------------------------
325// Types: Responses
326//--------------------------------------------------------------------------------------------------
327
328/// Response type for regular message responses
329#[derive(Debug, Serialize)]
330pub struct RegularMessageResponse {
331    /// Message indicating the status of the sandbox operation
332    pub message: String,
333}
334
335/// System status response
336#[derive(Debug, Serialize)]
337pub struct SystemStatusResponse {}
338
339/// Sandbox status response
340#[derive(Debug, Serialize)]
341pub struct SandboxStatusResponse {
342    /// List of sandbox statuses
343    pub sandboxes: Vec<SandboxStatus>,
344}
345
346/// Sandbox configuration response
347#[derive(Debug, Serialize)]
348pub struct SandboxConfigResponse {}
349
350/// Status of an individual sandbox
351#[derive(Debug, Serialize)]
352pub struct SandboxStatus {
353    /// Namespace the sandbox belongs to
354    pub namespace: String,
355
356    /// The name of the sandbox
357    pub name: String,
358
359    /// Whether the sandbox is running
360    pub running: bool,
361
362    /// CPU usage percentage
363    pub cpu_usage: Option<f32>,
364
365    /// Memory usage in MiB
366    pub memory_usage: Option<u64>,
367
368    /// Disk usage of the RW layer in bytes
369    pub disk_usage: Option<u64>,
370}
371
372//--------------------------------------------------------------------------------------------------
373// Trait Implementations
374//--------------------------------------------------------------------------------------------------
375
376impl axum::response::IntoResponse for JsonRpcResponseOrNotification {
377    fn into_response(self) -> axum::response::Response {
378        match self {
379            JsonRpcResponseOrNotification::Response(response) => {
380                (axum::http::StatusCode::OK, axum::Json(response)).into_response()
381            }
382            JsonRpcResponseOrNotification::Notification(_notification) => {
383                // For JSON-RPC notifications, send HTTP 200 with empty body
384                // This satisfies the HTTP protocol requirement while sending no JSON-RPC response
385                axum::http::StatusCode::OK.into_response()
386            }
387        }
388    }
389}