use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SailboxStatus {
Running,
Paused,
Sleeping,
Failed,
Terminated,
Other(String),
}
impl Default for SailboxStatus {
fn default() -> Self {
SailboxStatus::Other(String::new())
}
}
impl SailboxStatus {
pub fn as_str(&self) -> &str {
match self {
SailboxStatus::Running => "running",
SailboxStatus::Paused => "paused",
SailboxStatus::Sleeping => "sleeping",
SailboxStatus::Failed => "failed",
SailboxStatus::Terminated => "terminated",
SailboxStatus::Other(s) => s,
}
}
}
impl From<&str> for SailboxStatus {
fn from(s: &str) -> SailboxStatus {
match s {
"running" => SailboxStatus::Running,
"paused" => SailboxStatus::Paused,
"sleeping" => SailboxStatus::Sleeping,
"failed" => SailboxStatus::Failed,
"terminated" => SailboxStatus::Terminated,
other => SailboxStatus::Other(other.to_string()),
}
}
}
impl std::fmt::Display for SailboxStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl Serialize for SailboxStatus {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for SailboxStatus {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<SailboxStatus, D::Error> {
Ok(SailboxStatus::from(
String::deserialize(deserializer)?.as_str(),
))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SailboxStatusFilter {
Running,
Paused,
Sleeping,
Failed,
Terminated,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SailboxListOrder {
#[default]
Activity,
CreatedAtDesc,
}
impl SailboxListOrder {
pub fn as_str(&self) -> &'static str {
match self {
SailboxListOrder::Activity => "activity",
SailboxListOrder::CreatedAtDesc => "created_at_desc",
}
}
pub fn parse(raw: &str) -> Option<SailboxListOrder> {
match raw.trim() {
"activity" => Some(SailboxListOrder::Activity),
"created_at_desc" => Some(SailboxListOrder::CreatedAtDesc),
_ => None,
}
}
}
impl SailboxStatusFilter {
pub fn as_str(&self) -> &'static str {
match self {
SailboxStatusFilter::Running => "running",
SailboxStatusFilter::Paused => "paused",
SailboxStatusFilter::Sleeping => "sleeping",
SailboxStatusFilter::Failed => "failed",
SailboxStatusFilter::Terminated => "terminated",
}
}
pub fn parse(s: &str) -> Option<SailboxStatusFilter> {
match s {
"running" => Some(SailboxStatusFilter::Running),
"paused" => Some(SailboxStatusFilter::Paused),
"sleeping" => Some(SailboxStatusFilter::Sleeping),
"failed" => Some(SailboxStatusFilter::Failed),
"terminated" => Some(SailboxStatusFilter::Terminated),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ListenerProtocol {
Tcp,
Http,
Other(String),
}
impl ListenerProtocol {
pub fn as_str(&self) -> &str {
match self {
ListenerProtocol::Tcp => "tcp",
ListenerProtocol::Http => "http",
ListenerProtocol::Other(s) => s,
}
}
}
impl From<&str> for ListenerProtocol {
fn from(s: &str) -> ListenerProtocol {
match s {
"tcp" => ListenerProtocol::Tcp,
"http" => ListenerProtocol::Http,
other => ListenerProtocol::Other(other.to_string()),
}
}
}
impl std::fmt::Display for ListenerProtocol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl Serialize for ListenerProtocol {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for ListenerProtocol {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<ListenerProtocol, D::Error> {
Ok(ListenerProtocol::from(
String::deserialize(deserializer)?.as_str(),
))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ListenerRouteStatus {
Unspecified,
Pending,
Active,
Restoring,
Unavailable,
Other(String),
}
impl ListenerRouteStatus {
pub fn as_str(&self) -> &str {
match self {
ListenerRouteStatus::Unspecified => "unspecified",
ListenerRouteStatus::Pending => "pending",
ListenerRouteStatus::Active => "active",
ListenerRouteStatus::Restoring => "restoring",
ListenerRouteStatus::Unavailable => "unavailable",
ListenerRouteStatus::Other(s) => s,
}
}
}
impl From<&str> for ListenerRouteStatus {
fn from(s: &str) -> ListenerRouteStatus {
match s {
"LISTENER_ROUTE_STATUS_UNSPECIFIED" | "unspecified" => ListenerRouteStatus::Unspecified,
"LISTENER_ROUTE_STATUS_PENDING" | "pending" => ListenerRouteStatus::Pending,
"LISTENER_ROUTE_STATUS_ACTIVE" | "active" => ListenerRouteStatus::Active,
"LISTENER_ROUTE_STATUS_RESTORING" | "restoring" => ListenerRouteStatus::Restoring,
"LISTENER_ROUTE_STATUS_UNAVAILABLE" | "unavailable" => ListenerRouteStatus::Unavailable,
other => ListenerRouteStatus::Other(other.to_string()),
}
}
}
impl std::fmt::Display for ListenerRouteStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl Serialize for ListenerRouteStatus {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for ListenerRouteStatus {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<ListenerRouteStatus, D::Error> {
Ok(ListenerRouteStatus::from(
String::deserialize(deserializer)?.as_str(),
))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IngressProtocol {
Tcp,
Http,
}
impl IngressProtocol {
pub fn as_str(&self) -> &'static str {
match self {
IngressProtocol::Tcp => "tcp",
IngressProtocol::Http => "http",
}
}
pub fn parse(s: &str) -> Option<IngressProtocol> {
match s {
"tcp" => Some(IngressProtocol::Tcp),
"http" => Some(IngressProtocol::Http),
_ => None,
}
}
}
impl std::fmt::Display for IngressProtocol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl Serialize for IngressProtocol {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
#[derive(Debug, Clone, Default)]
pub struct CheckpointOptions {
pub name: Option<String>,
pub ttl: Option<std::time::Duration>,
}
fn normalized_architecture<'de, D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<String, D::Error> {
let raw = String::deserialize(deserializer)?;
Ok(match raw.as_str() {
"x86_64" | "amd64" => "amd64".to_string(),
"aarch64" | "arm64" => "arm64".to_string(),
_ => raw,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SailboxSize {
Small,
Medium,
}
impl SailboxSize {
pub fn as_str(&self) -> &'static str {
match self {
SailboxSize::Small => "s",
SailboxSize::Medium => "m",
}
}
pub fn parse(s: &str) -> Option<SailboxSize> {
match s {
"s" => Some(SailboxSize::Small),
"m" => Some(SailboxSize::Medium),
_ => None,
}
}
pub fn allowed() -> &'static [&'static str] {
&["s", "m"]
}
}
impl std::fmt::Display for SailboxSize {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl Serialize for SailboxSize {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct SailboxHandle {
pub sailbox_id: String,
pub name: String,
pub status: SailboxStatus,
pub worker_address: String,
pub exec_endpoint: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SailboxInfo {
pub sailbox_id: String,
pub app_id: String,
pub app_name: String,
pub image_id: String,
pub name: String,
pub status: SailboxStatus,
pub memory_mib: i64,
pub vcpu_count: i64,
pub state_disk_size_gib: i64,
pub cpu_requested_vcpu: i64,
pub cpu_used_vcpu: f64,
pub memory_requested_bytes: i64,
pub memory_used_bytes: i64,
pub disk_requested_bytes: i64,
pub disk_used_bytes: i64,
#[serde(deserialize_with = "normalized_architecture", default)]
pub architecture: String,
#[serde(default)]
pub guest_schema_version: Option<i64>,
#[serde(default)]
pub error_message: Option<String>,
pub checkpoint_generation: i64,
#[serde(with = "time::serde::rfc3339::option", default)]
pub started_at: Option<OffsetDateTime>,
#[serde(with = "time::serde::rfc3339::option", default)]
pub last_checkpointed_at: Option<OffsetDateTime>,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
#[serde(with = "time::serde::rfc3339")]
pub updated_at: OffsetDateTime,
}
#[derive(Debug, Clone, Serialize)]
pub struct SailboxPage {
pub items: Vec<SailboxInfo>,
pub limit: i64,
pub offset: i64,
pub total: i64,
pub has_more: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct SailboxCheckpoint {
pub checkpoint_id: String,
pub sailbox_id: String,
pub checkpoint_generation: i64,
#[serde(with = "time::serde::rfc3339::option")]
pub expires_at: Option<OffsetDateTime>,
pub status: SailboxStatus,
}
#[derive(Debug, Clone)]
pub struct ListSailboxesQuery {
pub app_id: Option<String>,
pub status: Option<SailboxStatusFilter>,
pub search: Option<String>,
pub max_guest_schema_version: Option<i64>,
pub order: SailboxListOrder,
pub limit: i64,
pub offset: i64,
}
#[doc(hidden)]
pub const DEFAULT_LIST_LIMIT: i64 = 50;
impl Default for ListSailboxesQuery {
fn default() -> ListSailboxesQuery {
ListSailboxesQuery {
app_id: None,
status: None,
search: None,
max_guest_schema_version: None,
order: SailboxListOrder::Activity,
limit: DEFAULT_LIST_LIMIT,
offset: 0,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct IngressPort {
pub guest_port: u32,
pub protocol: IngressProtocol,
pub allowlist: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct VolumeMount {
pub volume_id: String,
pub mount_path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VolumeInfo {
pub volume_id: String,
pub name: String,
pub backend: String,
pub status: String,
#[serde(with = "time::serde::rfc3339::option", default)]
pub created_at: Option<OffsetDateTime>,
#[serde(with = "time::serde::rfc3339::option", default)]
pub updated_at: Option<OffsetDateTime>,
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct AddListenerWire {
pub(crate) guest_port: u32,
pub(crate) protocol: ListenerProtocol,
#[serde(default)]
pub(crate) public_url: String,
#[serde(default, rename = "tcp_public_host")]
pub(crate) public_host: String,
#[serde(default, rename = "tcp_public_port")]
pub(crate) public_port: u32,
}
impl From<AddListenerWire> for crate::worker::Listener {
fn from(wire: AddListenerWire) -> crate::worker::Listener {
crate::worker::Listener {
guest_port: wire.guest_port,
protocol: wire.protocol,
route_status: ListenerRouteStatus::Unspecified,
public_url: wire.public_url,
public_host: wire.public_host,
public_port: wire.public_port,
}
}
}
#[derive(Debug, Clone)]
pub struct IssuedUserCert {
pub certificate: String,
pub key_id: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ListenerEndpoint {
Http {
url: String,
},
Tcp {
host: String,
port: u32,
},
}
impl std::fmt::Display for ListenerEndpoint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ListenerEndpoint::Http { url } => f.write_str(url),
ListenerEndpoint::Tcp { host, port } => write!(f, "{host}:{port}"),
}
}
}
#[derive(Debug, Clone)]
pub struct CreateSailboxRequest {
pub app_id: String,
pub name: String,
pub ingress_ports: Vec<IngressPort>,
pub volume_mounts: Vec<VolumeMount>,
pub image: crate::image::ImageSpec,
pub size: Option<SailboxSize>,
pub memory_gib: Option<u32>,
pub disk_gib: Option<u32>,
pub autosleep_timeout_minutes: Option<i64>,
pub ssh: bool,
}
impl Default for CreateSailboxRequest {
fn default() -> CreateSailboxRequest {
CreateSailboxRequest {
app_id: String::new(),
name: String::new(),
ingress_ports: Vec::new(),
volume_mounts: Vec::new(),
image: crate::image::ImageSpec {
base: Some(crate::image::BaseImage::Debian),
..crate::image::ImageSpec::default()
},
size: None,
memory_gib: None,
disk_gib: None,
autosleep_timeout_minutes: None,
ssh: false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn list_query_default_uses_a_valid_nonzero_limit() {
let query = ListSailboxesQuery::default();
assert_eq!(query.limit, DEFAULT_LIST_LIMIT);
assert!(query.limit > 0);
assert_eq!(query.offset, 0);
}
}