1use serde::{Deserialize, Serialize};
8use std::collections::BTreeMap;
9
10pub const SNAPSHOTS_PATH: &str = "/internal/v1/moulin/snapshots";
11pub const SANDBOXES_PATH: &str = "/internal/v1/moulin/sandboxes";
12pub const SANDBOX_PATH: &str = "/internal/v1/moulin/sandboxes/{sandbox}";
13pub const BINDINGS_PATH: &str = "/internal/v1/moulin/runtime-bindings";
14pub const BINDING_PATH: &str = "/internal/v1/moulin/runtime-bindings/{bindingId}";
15pub const BINDING_BY_RUNTIME_PATH: &str = "/internal/v1/moulin/runtime-bindings:by-runtime";
16
17#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct SnapshotView {
20 pub id: String,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub name: Option<String>,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub created_at: Option<String>,
25}
26
27#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(rename_all = "camelCase")]
29pub struct SnapshotPage {
30 pub items: Vec<SnapshotView>,
31}
32
33#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase", deny_unknown_fields)]
35pub struct CreateSandboxRequest {
36 pub snapshot: String,
37 #[serde(default)]
38 pub env: Option<BTreeMap<String, String>>,
39}
40
41#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct SandboxView {
44 pub sandbox: String,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub snapshot: Option<String>,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub state: Option<String>,
49 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub runtime_identity_id: Option<String>,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub binding_id: Option<String>,
53}
54
55#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(rename_all = "camelCase")]
57pub struct SandboxPage {
58 pub items: Vec<SandboxView>,
59}
60
61#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase", deny_unknown_fields)]
63pub struct BindRuntimeRequest {
64 pub runtime_identity_id: String,
65 pub sandbox: String,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct RuntimeSandboxBinding {
71 pub id: String,
72 pub tenant_id: String,
73 pub project_id: String,
74 pub runtime_identity_id: String,
75 pub sandbox: String,
76 pub created_at_ms: u64,
77 pub updated_at_ms: u64,
78}
79
80#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
81#[serde(rename_all = "camelCase")]
82pub struct BindingPage {
83 pub items: Vec<RuntimeSandboxBinding>,
84}
85
86#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub struct TenantProjectScope {
89 pub tenant_id: String,
90 pub project_id: String,
91}
92
93impl TenantProjectScope {
94 pub fn new(
95 tenant_id: impl Into<String>,
96 project_id: impl Into<String>,
97 ) -> Result<Self, String> {
98 let tenant_id = tenant_id.into();
99 let project_id = project_id.into();
100 if tenant_id.trim().is_empty() || tenant_id.len() > 128 {
101 return Err("tenant_id must contain 1..=128 bytes".into());
102 }
103 if project_id.trim().is_empty() || project_id.len() > 128 {
104 return Err("project_id must contain 1..=128 bytes".into());
105 }
106 Ok(Self {
107 tenant_id,
108 project_id,
109 })
110 }
111}
112
113#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(rename_all = "camelCase")]
115pub struct LookupBindingQuery {
116 pub runtime_identity_id: String,
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn create_sandbox_requires_snapshot() {
125 let request: CreateSandboxRequest =
126 serde_json::from_str(r#"{"snapshot":"python"}"#).unwrap();
127 assert_eq!(request.snapshot, "python");
128 assert!(request.env.is_none());
129 }
130}