sprites 0.1.0

Official Rust SDK for Sprites - stateful sandbox environments from Fly.io
Documentation
//! Type definitions for the Sprites API
//!
//! These types mirror the Go SDK at github.com/superfly/sprites-go

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

/// Sprite status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SpriteStatus {
    /// Sprite is hibernating (no compute cost)
    #[default]
    Cold,
    /// Sprite is waking up
    Warm,
    /// Sprite is running (billable)
    Running,
    /// Sprite is stopped
    Stopped,
}

/// URL authentication settings
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum UrlAuth {
    /// Requires sprite token
    #[default]
    Sprite,
    /// Publicly accessible
    Public,
}

/// URL settings for a sprite
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct UrlSettings {
    /// Authentication type
    pub auth: UrlAuth,
}

/// Sprite configuration
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SpriteConfig {
    /// RAM allocation in MB
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ram_mb: Option<u32>,

    /// Number of CPUs
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cpus: Option<u32>,

    /// Primary region
    #[serde(skip_serializing_if = "Option::is_none")]
    pub region: Option<String>,

    /// Storage size in GB
    #[serde(skip_serializing_if = "Option::is_none")]
    pub storage_gb: Option<u32>,
}

/// Full sprite information returned by the API
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpriteInfo {
    /// Unique sprite ID
    pub id: String,

    /// Sprite name (unique within organization)
    pub name: String,

    /// Organization name
    #[serde(default)]
    pub organization_name: String,

    /// Current status
    #[serde(default)]
    pub status: SpriteStatus,

    /// Configuration
    #[serde(default)]
    pub config: SpriteConfig,

    /// Environment variables
    #[serde(default)]
    pub environment: std::collections::HashMap<String, String>,

    /// Creation timestamp
    #[serde(default)]
    pub created_at: Option<DateTime<Utc>>,

    /// Last update timestamp
    #[serde(default)]
    pub updated_at: Option<DateTime<Utc>>,

    /// Public URL
    #[serde(default)]
    pub url: Option<String>,

    /// URL settings
    #[serde(default)]
    pub url_settings: Option<UrlSettings>,

    /// Bucket name for storage
    #[serde(default)]
    pub bucket_name: Option<String>,

    /// Primary region
    #[serde(default)]
    pub primary_region: Option<String>,
}

/// Request to create a new sprite
#[derive(Debug, Clone, Serialize)]
pub struct CreateSpriteRequest {
    /// Sprite name (required)
    pub name: String,

    /// Configuration (optional)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub config: Option<SpriteConfig>,

    /// URL settings (optional)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url_settings: Option<UrlSettings>,
}

/// Response from creating a sprite
#[derive(Debug, Clone, Deserialize)]
pub struct CreateSpriteResponse {
    /// Created sprite info (validated but not used since we already have the name)
    #[serde(flatten)]
    #[allow(dead_code)]
    pub sprite: SpriteInfo,
}

/// Response from listing sprites
#[derive(Debug, Clone, Deserialize)]
pub struct ListSpritesResponse {
    /// List of sprites
    pub sprites: Vec<SpriteInfo>,

    /// Token for pagination
    #[serde(default)]
    pub next_continuation_token: Option<String>,

    /// Whether there are more results
    #[serde(default)]
    pub has_more: bool,
}

/// Options for listing sprites
#[derive(Debug, Clone, Default)]
pub struct ListOptions {
    /// Maximum number of results
    pub max_results: Option<u32>,

    /// Continuation token for pagination
    pub continuation_token: Option<String>,

    /// Filter by name prefix
    pub prefix: Option<String>,
}

/// Checkpoint information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Checkpoint {
    /// Checkpoint ID (e.g., "v1", "v2")
    pub id: String,

    /// Optional comment
    #[serde(default)]
    pub comment: Option<String>,

    /// Creation timestamp (API returns as create_time)
    #[serde(default, alias = "create_time")]
    pub created_at: Option<DateTime<Utc>>,

    /// Parent checkpoint ID
    #[serde(default)]
    pub source_id: Option<String>,

    /// Path to checkpoint data
    #[serde(default)]
    pub path: Option<String>,
}

/// Response from listing checkpoints
#[derive(Debug, Clone, Deserialize)]
pub struct ListCheckpointsResponse {
    /// List of checkpoints
    pub checkpoints: Vec<Checkpoint>,
}

/// Options for listing checkpoints
#[derive(Debug, Clone, Default)]
pub struct ListCheckpointsOptions {
    /// Include auto-generated checkpoints (default: true)
    pub include_auto: bool,

    /// Filter by checkpoint history/lineage (e.g., "v5" to get history of v5)
    pub history_filter: Option<String>,
}

/// Network policy rule
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkPolicyRule {
    /// Domain pattern (e.g., "*.github.com")
    pub domain: String,

    /// Action to take
    pub action: PolicyAction,
}

/// Network policy action
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PolicyAction {
    /// Allow traffic
    Allow,
    /// Deny traffic
    Deny,
}

/// Network policy configuration
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NetworkPolicy {
    /// List of rules
    pub rules: Vec<NetworkPolicyRule>,

    /// Optional preset bundles to include
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub include: Vec<String>,
}

/// Execution options for commands
#[derive(Debug, Clone, Default)]
pub struct ExecOptions {
    /// Enable TTY mode
    pub tty: bool,

    /// Enable stdin
    pub stdin: bool,

    /// Terminal columns
    pub cols: Option<u16>,

    /// Terminal rows
    pub rows: Option<u16>,

    /// Working directory
    pub dir: Option<String>,

    /// Environment variables
    pub env: std::collections::HashMap<String, String>,

    /// Timeout after disconnect (seconds)
    pub max_run_after_disconnect: Option<u32>,
}

/// Active execution session
#[derive(Debug, Clone, Deserialize)]
pub struct Session {
    /// Session ID
    pub id: String,

    /// Command being executed
    pub cmd: String,

    /// Creation timestamp
    pub created_at: DateTime<Utc>,

    /// Whether the session is active
    pub active: bool,

    /// Working directory
    #[serde(default)]
    pub cwd: Option<String>,
}

/// Command output
#[derive(Debug, Clone)]
pub struct Output {
    /// Exit status code
    pub status: i32,

    /// Standard output
    pub stdout: Vec<u8>,

    /// Standard error
    pub stderr: Vec<u8>,
}

impl Output {
    /// Check if the command succeeded (exit code 0)
    pub fn success(&self) -> bool {
        self.status == 0
    }

    /// Get stdout as a string (lossy UTF-8 conversion)
    pub fn stdout_str(&self) -> String {
        String::from_utf8_lossy(&self.stdout).to_string()
    }

    /// Get stderr as a string (lossy UTF-8 conversion)
    pub fn stderr_str(&self) -> String {
        String::from_utf8_lossy(&self.stderr).to_string()
    }
}

/// Exit status from a command
#[derive(Debug, Clone, Copy)]
pub struct ExitStatus {
    /// Exit code
    pub code: i32,
}

impl ExitStatus {
    /// Create a new exit status
    pub fn new(code: i32) -> Self {
        Self { code }
    }

    /// Check if the command succeeded
    pub fn success(&self) -> bool {
        self.code == 0
    }

    /// Get the exit code
    pub fn code(&self) -> i32 {
        self.code
    }
}

/// Streaming message types for checkpoints and exec
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum StreamMessage {
    /// Informational message
    Info { data: String },

    /// Operation completed
    Complete { data: String },

    /// Error occurred
    Error { message: String },
}