1use serde::{Deserialize, Serialize};
10use time::OffsetDateTime;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum SailboxStatus {
18 Running,
20 Paused,
22 Sleeping,
24 Failed,
26 Terminated,
28 Other(String),
30}
31
32impl Default for SailboxStatus {
33 fn default() -> Self {
34 SailboxStatus::Other(String::new())
35 }
36}
37
38impl SailboxStatus {
39 pub fn as_str(&self) -> &str {
41 match self {
42 SailboxStatus::Running => "running",
43 SailboxStatus::Paused => "paused",
44 SailboxStatus::Sleeping => "sleeping",
45 SailboxStatus::Failed => "failed",
46 SailboxStatus::Terminated => "terminated",
47 SailboxStatus::Other(s) => s,
48 }
49 }
50}
51
52impl From<&str> for SailboxStatus {
53 fn from(s: &str) -> SailboxStatus {
54 match s {
55 "running" => SailboxStatus::Running,
56 "paused" => SailboxStatus::Paused,
57 "sleeping" => SailboxStatus::Sleeping,
58 "failed" => SailboxStatus::Failed,
59 "terminated" => SailboxStatus::Terminated,
60 other => SailboxStatus::Other(other.to_string()),
61 }
62 }
63}
64
65impl std::fmt::Display for SailboxStatus {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 f.write_str(self.as_str())
68 }
69}
70
71impl Serialize for SailboxStatus {
72 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
73 serializer.serialize_str(self.as_str())
74 }
75}
76
77impl<'de> Deserialize<'de> for SailboxStatus {
78 fn deserialize<D: serde::Deserializer<'de>>(
79 deserializer: D,
80 ) -> Result<SailboxStatus, D::Error> {
81 Ok(SailboxStatus::from(
82 String::deserialize(deserializer)?.as_str(),
83 ))
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum SailboxStatusFilter {
92 Running,
94 Paused,
96 Sleeping,
98 Failed,
100 Terminated,
102}
103
104impl SailboxStatusFilter {
105 pub fn as_str(&self) -> &'static str {
107 match self {
108 SailboxStatusFilter::Running => "running",
109 SailboxStatusFilter::Paused => "paused",
110 SailboxStatusFilter::Sleeping => "sleeping",
111 SailboxStatusFilter::Failed => "failed",
112 SailboxStatusFilter::Terminated => "terminated",
113 }
114 }
115
116 pub fn parse(s: &str) -> Option<SailboxStatusFilter> {
118 match s {
119 "running" => Some(SailboxStatusFilter::Running),
120 "paused" => Some(SailboxStatusFilter::Paused),
121 "sleeping" => Some(SailboxStatusFilter::Sleeping),
122 "failed" => Some(SailboxStatusFilter::Failed),
123 "terminated" => Some(SailboxStatusFilter::Terminated),
124 _ => None,
125 }
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq)]
132pub enum ListenerProtocol {
133 Tcp,
135 Http,
137 Other(String),
139}
140
141impl ListenerProtocol {
142 pub fn as_str(&self) -> &str {
144 match self {
145 ListenerProtocol::Tcp => "tcp",
146 ListenerProtocol::Http => "http",
147 ListenerProtocol::Other(s) => s,
148 }
149 }
150}
151
152impl From<&str> for ListenerProtocol {
153 fn from(s: &str) -> ListenerProtocol {
154 match s {
155 "tcp" => ListenerProtocol::Tcp,
156 "http" => ListenerProtocol::Http,
157 other => ListenerProtocol::Other(other.to_string()),
158 }
159 }
160}
161
162impl std::fmt::Display for ListenerProtocol {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 f.write_str(self.as_str())
165 }
166}
167
168impl Serialize for ListenerProtocol {
169 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
170 serializer.serialize_str(self.as_str())
171 }
172}
173
174impl<'de> Deserialize<'de> for ListenerProtocol {
175 fn deserialize<D: serde::Deserializer<'de>>(
176 deserializer: D,
177 ) -> Result<ListenerProtocol, D::Error> {
178 Ok(ListenerProtocol::from(
179 String::deserialize(deserializer)?.as_str(),
180 ))
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
187pub enum ListenerRouteStatus {
188 Unspecified,
190 Pending,
192 Active,
194 Restoring,
196 Unavailable,
198 Other(String),
200}
201
202impl ListenerRouteStatus {
203 pub fn as_str(&self) -> &str {
207 match self {
208 ListenerRouteStatus::Unspecified => "unspecified",
209 ListenerRouteStatus::Pending => "pending",
210 ListenerRouteStatus::Active => "active",
211 ListenerRouteStatus::Restoring => "restoring",
212 ListenerRouteStatus::Unavailable => "unavailable",
213 ListenerRouteStatus::Other(s) => s,
214 }
215 }
216}
217
218impl From<&str> for ListenerRouteStatus {
219 fn from(s: &str) -> ListenerRouteStatus {
222 match s {
223 "LISTENER_ROUTE_STATUS_UNSPECIFIED" | "unspecified" => ListenerRouteStatus::Unspecified,
224 "LISTENER_ROUTE_STATUS_PENDING" | "pending" => ListenerRouteStatus::Pending,
225 "LISTENER_ROUTE_STATUS_ACTIVE" | "active" => ListenerRouteStatus::Active,
226 "LISTENER_ROUTE_STATUS_RESTORING" | "restoring" => ListenerRouteStatus::Restoring,
227 "LISTENER_ROUTE_STATUS_UNAVAILABLE" | "unavailable" => ListenerRouteStatus::Unavailable,
228 other => ListenerRouteStatus::Other(other.to_string()),
229 }
230 }
231}
232
233impl std::fmt::Display for ListenerRouteStatus {
234 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235 f.write_str(self.as_str())
236 }
237}
238
239impl Serialize for ListenerRouteStatus {
240 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
241 serializer.serialize_str(self.as_str())
242 }
243}
244
245impl<'de> Deserialize<'de> for ListenerRouteStatus {
246 fn deserialize<D: serde::Deserializer<'de>>(
247 deserializer: D,
248 ) -> Result<ListenerRouteStatus, D::Error> {
249 Ok(ListenerRouteStatus::from(
250 String::deserialize(deserializer)?.as_str(),
251 ))
252 }
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq)]
259pub enum IngressProtocol {
260 Tcp,
262 Http,
264}
265
266impl IngressProtocol {
267 pub fn as_str(&self) -> &'static str {
269 match self {
270 IngressProtocol::Tcp => "tcp",
271 IngressProtocol::Http => "http",
272 }
273 }
274
275 pub fn parse(s: &str) -> Option<IngressProtocol> {
278 match s {
279 "tcp" => Some(IngressProtocol::Tcp),
280 "http" => Some(IngressProtocol::Http),
281 _ => None,
282 }
283 }
284}
285
286impl std::fmt::Display for IngressProtocol {
287 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288 f.write_str(self.as_str())
289 }
290}
291
292impl Serialize for IngressProtocol {
293 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
294 serializer.serialize_str(self.as_str())
295 }
296}
297
298#[derive(Debug, Clone, Default)]
300pub struct CheckpointOptions {
301 pub name: Option<String>,
303 pub ttl: Option<std::time::Duration>,
305}
306
307fn normalized_architecture<'de, D: serde::Deserializer<'de>>(
310 deserializer: D,
311) -> Result<String, D::Error> {
312 let raw = String::deserialize(deserializer)?;
313 Ok(match raw.as_str() {
314 "x86_64" | "amd64" => "amd64".to_string(),
315 "aarch64" | "arm64" => "arm64".to_string(),
316 _ => raw,
317 })
318}
319
320#[derive(Debug, Clone, Copy, PartialEq, Eq)]
326pub enum SailboxSize {
327 Small,
330 Medium,
332}
333
334impl SailboxSize {
335 pub fn as_str(&self) -> &'static str {
337 match self {
338 SailboxSize::Small => "s",
339 SailboxSize::Medium => "m",
340 }
341 }
342
343 pub fn parse(s: &str) -> Option<SailboxSize> {
345 match s {
346 "s" => Some(SailboxSize::Small),
347 "m" => Some(SailboxSize::Medium),
348 _ => None,
349 }
350 }
351
352 pub fn allowed() -> &'static [&'static str] {
354 &["s", "m"]
355 }
356}
357
358impl std::fmt::Display for SailboxSize {
359 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
360 f.write_str(self.as_str())
361 }
362}
363
364impl Serialize for SailboxSize {
365 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
366 serializer.serialize_str(self.as_str())
367 }
368}
369
370#[derive(Debug, Clone, Default, Serialize)]
373pub struct SailboxHandle {
374 pub sailbox_id: String,
376 pub name: String,
378 pub status: SailboxStatus,
380 pub worker_address: String,
382 pub exec_endpoint: String,
384}
385
386#[derive(Debug, Clone, Serialize, Deserialize)]
389pub struct SailboxInfo {
390 pub sailbox_id: String,
392 pub app_id: String,
394 pub app_name: String,
396 pub image_id: String,
398 pub name: String,
400 pub status: SailboxStatus,
402 pub memory_mib: i64,
404 pub vcpu_count: i64,
406 pub state_disk_size_gib: i64,
408 pub cpu_requested_vcpu: i64,
410 pub cpu_used_vcpu: f64,
412 pub memory_requested_bytes: i64,
414 pub memory_used_bytes: i64,
416 pub disk_requested_bytes: i64,
418 pub disk_used_bytes: i64,
420 #[serde(deserialize_with = "normalized_architecture", default)]
423 pub architecture: String,
424 #[serde(default)]
426 pub guest_schema_version: Option<i64>,
427 #[serde(default)]
429 pub error_message: Option<String>,
430 pub checkpoint_generation: i64,
432 #[serde(with = "time::serde::rfc3339::option", default)]
434 pub started_at: Option<OffsetDateTime>,
435 #[serde(with = "time::serde::rfc3339::option", default)]
437 pub last_checkpointed_at: Option<OffsetDateTime>,
438 #[serde(with = "time::serde::rfc3339")]
440 pub created_at: OffsetDateTime,
441 #[serde(with = "time::serde::rfc3339")]
443 pub updated_at: OffsetDateTime,
444}
445
446#[derive(Debug, Clone, Serialize)]
448pub struct SailboxPage {
449 pub items: Vec<SailboxInfo>,
451 pub limit: i64,
453 pub offset: i64,
455 pub total: i64,
457 pub has_more: bool,
459}
460
461#[derive(Debug, Clone, Serialize)]
465pub struct SailboxCheckpoint {
466 pub checkpoint_id: String,
468 pub sailbox_id: String,
470 pub checkpoint_generation: i64,
472 #[serde(with = "time::serde::rfc3339::option")]
476 pub expires_at: Option<OffsetDateTime>,
477 pub status: SailboxStatus,
479}
480
481#[derive(Debug, Clone)]
483pub struct ListSailboxesQuery {
484 pub app_id: Option<String>,
486 pub status: Option<SailboxStatusFilter>,
488 pub search: Option<String>,
490 pub max_guest_schema_version: Option<i64>,
492 pub limit: i64,
494 pub offset: i64,
496}
497
498pub const DEFAULT_LIST_LIMIT: i64 = 50;
501
502impl Default for ListSailboxesQuery {
503 fn default() -> ListSailboxesQuery {
504 ListSailboxesQuery {
505 app_id: None,
506 status: None,
507 search: None,
508 max_guest_schema_version: None,
509 limit: DEFAULT_LIST_LIMIT,
510 offset: 0,
511 }
512 }
513}
514
515#[derive(Debug, Clone, Serialize)]
517pub struct IngressPort {
518 pub guest_port: u32,
520 pub protocol: IngressProtocol,
522 pub allowlist: Vec<String>,
526}
527
528#[derive(Debug, Clone, Serialize)]
530pub struct VolumeMount {
531 pub volume_id: String,
533 pub mount_path: String,
535}
536
537#[derive(Debug, Clone, Serialize, Deserialize)]
540pub struct VolumeInfo {
541 pub volume_id: String,
543 pub name: String,
545 pub backend: String,
547 pub status: String,
549 #[serde(with = "time::serde::rfc3339::option", default)]
551 pub created_at: Option<OffsetDateTime>,
552 #[serde(with = "time::serde::rfc3339::option", default)]
554 pub updated_at: Option<OffsetDateTime>,
555}
556
557#[derive(Debug, Clone, Deserialize)]
562pub(crate) struct AddListenerWire {
563 pub(crate) guest_port: u32,
564 pub(crate) protocol: ListenerProtocol,
565 #[serde(default)]
566 pub(crate) public_url: String,
567 #[serde(default, rename = "tcp_public_host")]
568 pub(crate) public_host: String,
569 #[serde(default, rename = "tcp_public_port")]
570 pub(crate) public_port: u32,
571}
572
573impl From<AddListenerWire> for crate::worker::Listener {
574 fn from(wire: AddListenerWire) -> crate::worker::Listener {
575 crate::worker::Listener {
576 guest_port: wire.guest_port,
577 protocol: wire.protocol,
578 route_status: ListenerRouteStatus::Unspecified,
581 public_url: wire.public_url,
582 public_host: wire.public_host,
583 public_port: wire.public_port,
584 }
585 }
586}
587
588#[derive(Debug, Clone)]
591pub struct IssuedUserCert {
592 pub certificate: String,
594 pub key_id: String,
596}
597
598#[derive(Debug, Clone, PartialEq, Eq)]
601pub enum ListenerEndpoint {
602 Http {
604 url: String,
606 },
607 Tcp {
609 host: String,
611 port: u32,
613 },
614}
615
616impl std::fmt::Display for ListenerEndpoint {
617 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
618 match self {
619 ListenerEndpoint::Http { url } => f.write_str(url),
620 ListenerEndpoint::Tcp { host, port } => write!(f, "{host}:{port}"),
621 }
622 }
623}
624
625#[derive(Debug, Clone)]
629pub struct CreateSailboxRequest {
630 pub app_id: String,
632 pub name: String,
634 pub ingress_ports: Vec<IngressPort>,
636 pub volume_mounts: Vec<VolumeMount>,
638 pub image: crate::image::ImageSpec,
641 pub size: Option<SailboxSize>,
643 pub memory_gib: Option<u32>,
646 pub disk_gib: Option<u32>,
649 pub autosleep_timeout_minutes: Option<i64>,
651 pub ssh: bool,
655}
656
657impl Default for CreateSailboxRequest {
658 fn default() -> CreateSailboxRequest {
661 CreateSailboxRequest {
662 app_id: String::new(),
663 name: String::new(),
664 ingress_ports: Vec::new(),
665 volume_mounts: Vec::new(),
666 image: crate::image::ImageSpec {
667 base: Some(crate::image::BaseImage::Debian),
668 ..crate::image::ImageSpec::default()
669 },
670 size: None,
671 memory_gib: None,
672 disk_gib: None,
673 autosleep_timeout_minutes: None,
674 ssh: false,
675 }
676 }
677}
678
679#[cfg(test)]
680mod tests {
681 use super::*;
682
683 #[test]
684 fn list_query_default_uses_a_valid_nonzero_limit() {
685 let query = ListSailboxesQuery::default();
688 assert_eq!(query.limit, DEFAULT_LIST_LIMIT);
689 assert!(query.limit > 0);
690 assert_eq!(query.offset, 0);
691 }
692}