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
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
106pub enum SailboxListOrder {
107 #[default]
109 Activity,
110 CreatedAtDesc,
112}
113
114impl SailboxListOrder {
115 pub fn as_str(&self) -> &'static str {
117 match self {
118 SailboxListOrder::Activity => "activity",
119 SailboxListOrder::CreatedAtDesc => "created_at_desc",
120 }
121 }
122
123 pub fn parse(raw: &str) -> Option<SailboxListOrder> {
125 match raw.trim() {
126 "activity" => Some(SailboxListOrder::Activity),
127 "created_at_desc" => Some(SailboxListOrder::CreatedAtDesc),
128 _ => None,
129 }
130 }
131}
132
133impl SailboxStatusFilter {
134 pub fn as_str(&self) -> &'static str {
136 match self {
137 SailboxStatusFilter::Running => "running",
138 SailboxStatusFilter::Paused => "paused",
139 SailboxStatusFilter::Sleeping => "sleeping",
140 SailboxStatusFilter::Failed => "failed",
141 SailboxStatusFilter::Terminated => "terminated",
142 }
143 }
144
145 pub fn parse(s: &str) -> Option<SailboxStatusFilter> {
147 match s {
148 "running" => Some(SailboxStatusFilter::Running),
149 "paused" => Some(SailboxStatusFilter::Paused),
150 "sleeping" => Some(SailboxStatusFilter::Sleeping),
151 "failed" => Some(SailboxStatusFilter::Failed),
152 "terminated" => Some(SailboxStatusFilter::Terminated),
153 _ => None,
154 }
155 }
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
161pub enum ListenerProtocol {
162 Tcp,
164 Http,
166 Other(String),
168}
169
170impl ListenerProtocol {
171 pub fn as_str(&self) -> &str {
173 match self {
174 ListenerProtocol::Tcp => "tcp",
175 ListenerProtocol::Http => "http",
176 ListenerProtocol::Other(s) => s,
177 }
178 }
179}
180
181impl From<&str> for ListenerProtocol {
182 fn from(s: &str) -> ListenerProtocol {
183 match s {
184 "tcp" => ListenerProtocol::Tcp,
185 "http" => ListenerProtocol::Http,
186 other => ListenerProtocol::Other(other.to_string()),
187 }
188 }
189}
190
191impl std::fmt::Display for ListenerProtocol {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 f.write_str(self.as_str())
194 }
195}
196
197impl Serialize for ListenerProtocol {
198 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
199 serializer.serialize_str(self.as_str())
200 }
201}
202
203impl<'de> Deserialize<'de> for ListenerProtocol {
204 fn deserialize<D: serde::Deserializer<'de>>(
205 deserializer: D,
206 ) -> Result<ListenerProtocol, D::Error> {
207 Ok(ListenerProtocol::from(
208 String::deserialize(deserializer)?.as_str(),
209 ))
210 }
211}
212
213#[derive(Debug, Clone, PartialEq, Eq)]
216pub enum ListenerRouteStatus {
217 Unspecified,
219 Pending,
221 Active,
223 Restoring,
225 Unavailable,
227 Other(String),
229}
230
231impl ListenerRouteStatus {
232 pub fn as_str(&self) -> &str {
236 match self {
237 ListenerRouteStatus::Unspecified => "unspecified",
238 ListenerRouteStatus::Pending => "pending",
239 ListenerRouteStatus::Active => "active",
240 ListenerRouteStatus::Restoring => "restoring",
241 ListenerRouteStatus::Unavailable => "unavailable",
242 ListenerRouteStatus::Other(s) => s,
243 }
244 }
245}
246
247impl From<&str> for ListenerRouteStatus {
248 fn from(s: &str) -> ListenerRouteStatus {
251 match s {
252 "LISTENER_ROUTE_STATUS_UNSPECIFIED" | "unspecified" => ListenerRouteStatus::Unspecified,
253 "LISTENER_ROUTE_STATUS_PENDING" | "pending" => ListenerRouteStatus::Pending,
254 "LISTENER_ROUTE_STATUS_ACTIVE" | "active" => ListenerRouteStatus::Active,
255 "LISTENER_ROUTE_STATUS_RESTORING" | "restoring" => ListenerRouteStatus::Restoring,
256 "LISTENER_ROUTE_STATUS_UNAVAILABLE" | "unavailable" => ListenerRouteStatus::Unavailable,
257 other => ListenerRouteStatus::Other(other.to_string()),
258 }
259 }
260}
261
262impl std::fmt::Display for ListenerRouteStatus {
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264 f.write_str(self.as_str())
265 }
266}
267
268impl Serialize for ListenerRouteStatus {
269 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
270 serializer.serialize_str(self.as_str())
271 }
272}
273
274impl<'de> Deserialize<'de> for ListenerRouteStatus {
275 fn deserialize<D: serde::Deserializer<'de>>(
276 deserializer: D,
277 ) -> Result<ListenerRouteStatus, D::Error> {
278 Ok(ListenerRouteStatus::from(
279 String::deserialize(deserializer)?.as_str(),
280 ))
281 }
282}
283
284#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288pub enum IngressProtocol {
289 Tcp,
291 Http,
293}
294
295impl IngressProtocol {
296 pub fn as_str(&self) -> &'static str {
298 match self {
299 IngressProtocol::Tcp => "tcp",
300 IngressProtocol::Http => "http",
301 }
302 }
303
304 pub fn parse(s: &str) -> Option<IngressProtocol> {
307 match s {
308 "tcp" => Some(IngressProtocol::Tcp),
309 "http" => Some(IngressProtocol::Http),
310 _ => None,
311 }
312 }
313}
314
315impl std::fmt::Display for IngressProtocol {
316 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317 f.write_str(self.as_str())
318 }
319}
320
321impl Serialize for IngressProtocol {
322 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
323 serializer.serialize_str(self.as_str())
324 }
325}
326
327#[derive(Debug, Clone, Default)]
329pub struct CheckpointOptions {
330 pub name: Option<String>,
332 pub ttl: Option<std::time::Duration>,
334}
335
336fn normalized_architecture<'de, D: serde::Deserializer<'de>>(
339 deserializer: D,
340) -> Result<String, D::Error> {
341 let raw = String::deserialize(deserializer)?;
342 Ok(match raw.as_str() {
343 "x86_64" | "amd64" => "amd64".to_string(),
344 "aarch64" | "arm64" => "arm64".to_string(),
345 _ => raw,
346 })
347}
348
349#[derive(Debug, Clone, Copy, PartialEq, Eq)]
354pub enum SailboxSize {
355 Small,
358 Medium,
360}
361
362impl SailboxSize {
363 pub fn as_str(&self) -> &'static str {
365 match self {
366 SailboxSize::Small => "s",
367 SailboxSize::Medium => "m",
368 }
369 }
370
371 pub fn parse(s: &str) -> Option<SailboxSize> {
373 match s {
374 "s" => Some(SailboxSize::Small),
375 "m" => Some(SailboxSize::Medium),
376 _ => None,
377 }
378 }
379
380 pub fn allowed() -> &'static [&'static str] {
382 &["s", "m"]
383 }
384}
385
386impl std::fmt::Display for SailboxSize {
387 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
388 f.write_str(self.as_str())
389 }
390}
391
392impl Serialize for SailboxSize {
393 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
394 serializer.serialize_str(self.as_str())
395 }
396}
397
398#[derive(Debug, Clone, Default, Serialize)]
401pub struct SailboxHandle {
402 pub sailbox_id: String,
404 pub name: String,
406 pub status: SailboxStatus,
408 pub worker_address: String,
410 pub exec_endpoint: String,
412}
413
414#[derive(Debug, Clone, Serialize, Deserialize)]
417pub struct SailboxInfo {
418 pub sailbox_id: String,
420 pub app_id: String,
422 pub app_name: String,
424 pub image_id: String,
426 pub name: String,
428 pub status: SailboxStatus,
430 pub memory_mib: i64,
432 pub vcpu_count: i64,
434 pub state_disk_size_gib: i64,
436 pub cpu_requested_vcpu: i64,
438 pub cpu_used_vcpu: f64,
440 pub memory_requested_bytes: i64,
442 pub memory_used_bytes: i64,
444 pub disk_requested_bytes: i64,
446 pub disk_used_bytes: i64,
448 #[serde(deserialize_with = "normalized_architecture", default)]
451 pub architecture: String,
452 #[serde(default)]
454 pub guest_schema_version: Option<i64>,
455 #[serde(default)]
457 pub error_message: Option<String>,
458 pub checkpoint_generation: i64,
460 #[serde(with = "time::serde::rfc3339::option", default)]
462 pub started_at: Option<OffsetDateTime>,
463 #[serde(with = "time::serde::rfc3339::option", default)]
465 pub last_checkpointed_at: Option<OffsetDateTime>,
466 #[serde(with = "time::serde::rfc3339")]
468 pub created_at: OffsetDateTime,
469 #[serde(with = "time::serde::rfc3339")]
471 pub updated_at: OffsetDateTime,
472}
473
474#[derive(Debug, Clone, Serialize)]
476pub struct SailboxPage {
477 pub items: Vec<SailboxInfo>,
479 pub limit: i64,
481 pub offset: i64,
483 pub total: i64,
485 pub has_more: bool,
487}
488
489#[derive(Debug, Clone, Default)]
491pub struct SailboxSpendQuery {
492 pub app_id: Option<String>,
494 pub sailbox_id: Option<String>,
496 pub from: Option<OffsetDateTime>,
499 pub to: Option<OffsetDateTime>,
502}
503
504#[derive(Debug, Clone, Serialize, Deserialize)]
506pub struct SailboxSpendResponse {
507 #[serde(with = "time::serde::rfc3339")]
509 pub start_at: OffsetDateTime,
510 #[serde(with = "time::serde::rfc3339")]
512 pub end_at: OffsetDateTime,
513 pub finalized_cost_usd_nanos: i64,
515 pub estimated_active_cost_usd_nanos: i64,
517 pub estimated_total_cost_usd_nanos: i64,
519 pub duration_seconds: i64,
521 pub vcpu_seconds: f64,
523 pub memory_gib_seconds: f64,
525 pub state_disk_gib_seconds: f64,
527 pub pricing_configured: bool,
529 pub rates: SailboxSpendRates,
531 pub sailboxes: Vec<SailboxSpendItem>,
533}
534
535#[derive(Debug, Clone, Default, Serialize, Deserialize)]
537pub struct SailboxSpendRates {
538 pub vcpu_second_usd_nanos: i64,
540 pub memory_gib_second_usd_nanos: i64,
542 pub state_disk_gib_second_usd_nanos: i64,
544 pub creation_usd_nanos: i64,
546 #[serde(default)]
548 pub amd64_state_disk_gib_second_usd_nanos: i64,
549}
550
551#[derive(Debug, Clone, Serialize, Deserialize)]
553pub struct SailboxSpendItem {
554 pub sailbox_id: String,
556 pub app_id: String,
558 pub finalized_cost_usd_nanos: i64,
560 pub estimated_active_cost_usd_nanos: i64,
562 pub estimated_total_cost_usd_nanos: i64,
564 pub duration_seconds: i64,
566 pub vcpu_seconds: f64,
568 pub memory_gib_seconds: f64,
570 pub state_disk_gib_seconds: f64,
572 pub active: bool,
574}
575
576#[derive(Debug, Clone)]
578pub struct SailboxMetricsQuery {
579 pub range: String,
581}
582
583impl Default for SailboxMetricsQuery {
584 fn default() -> SailboxMetricsQuery {
585 SailboxMetricsQuery {
586 range: "24h".to_string(),
587 }
588 }
589}
590
591#[derive(Debug, Clone, Serialize, Deserialize)]
593pub struct SailboxMetricsResponse {
594 pub range: String,
596 pub data: Vec<SailboxMetricPoint>,
598}
599
600#[derive(Debug, Clone, Serialize, Deserialize)]
602pub struct SailboxMetricPoint {
603 #[serde(with = "time::serde::rfc3339")]
605 pub timestamp: OffsetDateTime,
606 pub cpu_used_vcpu: f64,
608 pub cpu_requested_vcpu: i64,
610 pub memory_used_bytes: i64,
612 pub memory_requested_bytes: i64,
614 pub disk_used_bytes: i64,
616 pub disk_requested_bytes: i64,
618}
619
620#[derive(Debug, Clone, Serialize)]
624pub struct SailboxCheckpoint {
625 pub checkpoint_id: String,
627 pub sailbox_id: String,
629 pub checkpoint_generation: i64,
631 #[serde(with = "time::serde::rfc3339::option")]
635 pub expires_at: Option<OffsetDateTime>,
636 pub status: SailboxStatus,
638}
639
640#[derive(Debug, Clone)]
642pub struct ListSailboxesQuery {
643 pub app_id: Option<String>,
645 pub status: Option<SailboxStatusFilter>,
647 pub search: Option<String>,
649 pub max_guest_schema_version: Option<i64>,
651 pub order: SailboxListOrder,
653 pub limit: i64,
655 pub offset: i64,
657}
658
659#[doc(hidden)]
662pub const DEFAULT_LIST_LIMIT: i64 = 50;
663
664impl Default for ListSailboxesQuery {
665 fn default() -> ListSailboxesQuery {
666 ListSailboxesQuery {
667 app_id: None,
668 status: None,
669 search: None,
670 max_guest_schema_version: None,
671 order: SailboxListOrder::Activity,
672 limit: DEFAULT_LIST_LIMIT,
673 offset: 0,
674 }
675 }
676}
677
678#[derive(Debug, Clone, Serialize)]
680pub struct IngressPort {
681 pub guest_port: u32,
683 pub protocol: IngressProtocol,
685 pub allowlist: Vec<String>,
689}
690
691#[derive(Debug, Clone, Serialize)]
693pub struct VolumeMount {
694 pub volume_id: String,
696 pub mount_path: String,
698}
699
700#[derive(Debug, Clone, Serialize, Deserialize)]
703pub struct VolumeInfo {
704 pub volume_id: String,
706 pub name: String,
708 pub backend: String,
710 pub status: String,
712 #[serde(with = "time::serde::rfc3339::option", default)]
714 pub created_at: Option<OffsetDateTime>,
715 #[serde(with = "time::serde::rfc3339::option", default)]
717 pub updated_at: Option<OffsetDateTime>,
718}
719
720#[derive(Debug, Clone, Deserialize)]
725pub(crate) struct AddListenerWire {
726 pub(crate) guest_port: u32,
727 pub(crate) protocol: ListenerProtocol,
728 #[serde(default)]
729 pub(crate) public_url: String,
730 #[serde(default, rename = "tcp_public_host")]
731 pub(crate) public_host: String,
732 #[serde(default, rename = "tcp_public_port")]
733 pub(crate) public_port: u32,
734}
735
736impl From<AddListenerWire> for crate::worker::Listener {
737 fn from(wire: AddListenerWire) -> crate::worker::Listener {
738 crate::worker::Listener {
739 guest_port: wire.guest_port,
740 protocol: wire.protocol,
741 route_status: ListenerRouteStatus::Unspecified,
744 public_url: wire.public_url,
745 public_host: wire.public_host,
746 public_port: wire.public_port,
747 }
748 }
749}
750
751#[derive(Debug, Clone)]
754pub struct IssuedUserCert {
755 pub certificate: String,
757 pub key_id: String,
759}
760
761#[derive(Debug, Clone, PartialEq, Eq)]
764pub enum ListenerEndpoint {
765 Http {
767 url: String,
769 },
770 Tcp {
772 host: String,
774 port: u32,
776 },
777}
778
779impl std::fmt::Display for ListenerEndpoint {
780 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
781 match self {
782 ListenerEndpoint::Http { url } => f.write_str(url),
783 ListenerEndpoint::Tcp { host, port } => write!(f, "{host}:{port}"),
784 }
785 }
786}
787
788#[derive(Debug, Clone)]
791pub struct CreateSailboxRequest {
792 pub app_id: String,
794 pub name: String,
796 pub ingress_ports: Vec<IngressPort>,
798 pub volume_mounts: Vec<VolumeMount>,
800 pub image: crate::image::ImageSpec,
803 pub size: Option<SailboxSize>,
805 pub memory_gib: Option<u32>,
808 pub disk_gib: Option<u32>,
811 pub autosleep_timeout_minutes: Option<i64>,
814 pub ssh: bool,
818}
819
820impl Default for CreateSailboxRequest {
821 fn default() -> CreateSailboxRequest {
824 CreateSailboxRequest {
825 app_id: String::new(),
826 name: String::new(),
827 ingress_ports: Vec::new(),
828 volume_mounts: Vec::new(),
829 image: crate::image::ImageSpec {
830 base: Some(crate::image::BaseImage::Debian),
831 ..crate::image::ImageSpec::default()
832 },
833 size: None,
834 memory_gib: None,
835 disk_gib: None,
836 autosleep_timeout_minutes: None,
837 ssh: false,
838 }
839 }
840}
841
842#[cfg(test)]
843mod tests {
844 use super::*;
845
846 #[test]
847 fn list_query_default_uses_a_valid_nonzero_limit() {
848 let query = ListSailboxesQuery::default();
851 assert_eq!(query.limit, DEFAULT_LIST_LIMIT);
852 assert!(query.limit > 0);
853 assert_eq!(query.offset, 0);
854 }
855}