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}