microsandbox-core 0.2.6

`microsandbox-core` is a tool for managing lightweight sandboxes and images.
Documentation
use microsandbox_utils::MicrosandboxUtilsError;
use sqlx::migrate::MigrateError;
use std::{
    error::Error,
    fmt::{self, Display},
    path::{PathBuf, StripPrefixError},
    time::SystemTimeError,
};
use thiserror::Error;

use crate::oci::DockerRegistryResponseError;

//--------------------------------------------------------------------------------------------------
// Types
//--------------------------------------------------------------------------------------------------

/// The result of a microsandbox-related operation.
pub type MicrosandboxResult<T> = Result<T, MicrosandboxError>;

/// An error that occurred during a file system operation.
#[derive(pretty_error_debug::Debug, Error)]
pub enum MicrosandboxError {
    /// An I/O error.
    #[error("io error: {0}")]
    Io(#[from] std::io::Error),

    /// An error that can represent any error.
    #[error(transparent)]
    Custom(#[from] AnyError),

    /// An error that occurred during an OCI distribution operation.
    #[error("oci distribution error: {0}")]
    OciDistribution(#[from] anyhow::Error),

    /// An error that occurred during an HTTP request.
    #[error("http request error: {0}")]
    HttpRequest(#[from] reqwest::Error),

    /// An error that occurred during an HTTP middleware operation.
    #[error("http middleware error: {0}")]
    HttpMiddleware(#[from] reqwest_middleware::Error),

    /// An error that occurred during a database operation.
    #[error("database error: {0}")]
    Database(#[from] sqlx::Error),

    /// An error that occurred when a manifest was not found.
    #[error("manifest not found")]
    ManifestNotFound,

    /// An error that occurred when a join handle returned an error.
    #[error("join error: {0}")]
    JoinError(#[from] tokio::task::JoinError),

    /// An error that occurred when an unsupported image hash algorithm was used.
    #[error("unsupported image hash algorithm: {0}")]
    UnsupportedImageHashAlgorithm(String),

    /// An error that occurred when an image layer download failed.
    #[error("image layer download failed: {0}")]
    ImageLayerDownloadFailed(String),

    /// An error that occurred when an invalid path pair was used.
    #[error("invalid path pair: {0}")]
    InvalidPathPair(String),

    /// An error that occurred when an invalid port pair was used.
    #[error("invalid port pair: {0}")]
    InvalidPortPair(String),

    /// An error that occurred when an invalid environment variable pair was used.
    #[error("invalid environment variable pair: {0}")]
    InvalidEnvPair(String),

    /// An error that occurred when an invalid MicroVm configuration was used.
    #[error("invalid MicroVm configuration: {0}")]
    InvalidMicroVMConfig(InvalidMicroVMConfigError),

    /// An error that occurred when an invalid resource limit format was used.
    #[error("invalid resource limit format: {0}")]
    InvalidRLimitFormat(String),

    /// An error that occurred when an invalid resource limit value was used.
    #[error("invalid resource limit value: {0}")]
    InvalidRLimitValue(String),

    /// An error that occurred when an invalid resource limit resource was used.
    #[error("invalid resource limit resource: {0}")]
    InvalidRLimitResource(String),

    /// An error that occurred when a Serde JSON error occurred.
    #[error("serde json error: {0}")]
    SerdeJson(#[from] serde_json::Error),

    /// An error that occurred when a Serde YAML error occurred.
    #[error("serde yaml error: {0}")]
    SerdeYaml(#[from] serde_yaml::Error),

    /// An error that occurred when a TOML error occurred.
    #[error("toml error: {0}")]
    Toml(#[from] toml::de::Error),

    /// An error that occurred when a configuration validation error occurred.
    #[error("configuration validation error: {0}")]
    ConfigValidation(String),

    /// An error that occurred when a configuration validation error occurred.
    #[error("configuration validation errors: {0:?}")]
    ConfigValidationErrors(Vec<String>),

    /// An error that occurs when trying to access group resources for a service that has no group
    #[error("service '{0}' belongs to no group")]
    ServiceBelongsToNoGroup(String),

    /// An error that occurs when trying to access group resources for a service that belongs to a
    /// different group.
    #[error("service '{0}' belongs to wrong group: '{1}'")]
    ServiceBelongsToWrongGroup(String, String),

    /// An error that occurred when failed to get shutdown eventfd
    #[error("failed to get shutdown eventfd: {0}")]
    FailedToGetShutdownEventFd(i32),

    /// An error that occurred when failed to write to shutdown eventfd
    #[error("failed to write to shutdown eventfd: {0}")]
    FailedToShutdown(String),

    /// An error that occurred when failed to start VM
    #[error("failed to start VM: {0}")]
    FailedToStartVM(i32),

    /// An error that occurred when a path does not exist
    #[error("path does not exist: {0}")]
    PathNotFound(String),

    /// An error that occurred when a rootfs path does not exist
    #[error("rootfs path does not exist: {0}")]
    RootFsPathNotFound(String),

    /// An error that occurred when the supervisor binary was not found
    #[error("supervisor binary not found: {0}")]
    SupervisorBinaryNotFound(String),

    /// An error that occurred when failed to start VM
    #[error("failed to start VM: {0}")]
    StartVmFailed(i32),

    /// An error that occurred when waiting for a process to exit
    #[error("process wait error: {0}")]
    ProcessWaitError(String),

    /// An error that occurred running the supervisor.
    #[error("supervisor error: {0}")]
    SupervisorError(String),

    /// An error that occurred when failed to kill process
    #[error("failed to kill process: {0}")]
    ProcessKillError(String),

    /// An error that occurred when merging configurations
    #[error("configuration merge error: {0}")]
    ConfigMerge(String),

    /// An error that occurred when no more IP addresses are available for assignment
    #[error("no available IP addresses in the pool")]
    NoAvailableIPs,

    /// An error that occurred during a walkdir operation
    #[error("walkdir error: {0}")]
    WalkDir(#[from] walkdir::Error),

    /// An error that occurred when stripping a path prefix
    #[error("strip prefix error: {0}")]
    StripPrefix(#[from] StripPrefixError),

    /// An error that occurred during a nix operation
    #[error("nix error: {0}")]
    NixError(#[from] nix::Error),

    /// An error that occurred when converting system time
    #[error("system time error: {0}")]
    SystemTime(#[from] SystemTimeError),

    /// An error that occurred during layer extraction.
    /// This typically happens when the join handle for the blocking task fails.
    #[error("layer extraction error: {0}")]
    LayerExtraction(String),

    /// An error that occurred during layer handling operations like opening files or unpacking archives.
    /// Contains both the underlying IO error and the path to the layer being processed.
    #[error("layer handling error: {source}")]
    LayerHandling {
        /// The underlying IO error that occurred
        source: std::io::Error,
        /// The path to the layer being processed when the error occurred
        layer: String,
    },

    /// An error that occurred when a configuration file was not found
    #[error("configuration file not found: {0}")]
    ConfigNotFound(String),

    /// Error when a service's rootfs directory is not found
    #[error("Service rootfs not found: {0}")]
    RootfsNotFound(String),

    /// An error that occurred when parsing an image reference
    #[error("invalid image reference: {0}")]
    ImageReferenceError(String),

    /// An error that occurred when trying to remove running services
    #[error("Cannot remove running services: {0}")]
    ServiceStillRunning(String),

    /// An error that occurred when invalid command line arguments were provided
    #[error("{0}")]
    InvalidArgument(String),

    /// An error that occurred when validating paths
    #[error("path validation error: {0}")]
    PathValidation(String),

    /// An error that occurred when the microsandbox config file was not found
    #[error("microsandbox config file not found at: {0}")]
    MicrosandboxConfigNotFound(String),

    /// An error that occurred when failed to parse configuration file
    #[error("failed to parse configuration file: {0}")]
    ConfigParseError(String),

    /// An error that occurred when a log file was not found
    #[error("log not found: {0}")]
    LogNotFound(String),

    /// An error that occurred when a pager error occurred
    #[error("pager error: {0}")]
    PagerError(String),

    /// An error from microsandbox-utils
    #[error("microsandbox-utils error: {0}")]
    MicrosandboxUtilsError(#[from] MicrosandboxUtilsError),

    /// An error that occurred when a migration error occurred
    #[error("migration error: {0}")]
    MigrationError(#[from] MigrateError),

    /// An error that occurred when a Docker registry response error occurred
    #[error("docker registry response error: {0}")]
    DockerRegistryResponseError(#[from] DockerRegistryResponseError),

    /// An error that occurred when parsing an image reference selector with an invalid format
    #[error("invalid image reference    selector format: {0}")]
    InvalidReferenceSelectorFormat(String),

    /// An error that occurred when parsing an invalid digest in an image reference selector
    #[error("invalid image reference selector digest: {0}")]
    InvalidReferenceSelectorDigest(String),

    /// An error that occurred when a feature is not yet implemented
    #[error("feature not yet implemented: {0}")]
    NotImplemented(String),

    /// An error that occurred when a sandbox was not found in the configuration
    #[error("cannot find sandbox: '{0}' in '{1}'")]
    SandboxNotFoundInConfig(String, PathBuf),

    /// An error that occurs when an invalid log level is used.
    #[error("invalid log level: {0}")]
    InvalidLogLevel(u8),

    /// Empty path segment
    #[error("empty path segment")]
    EmptyPathSegment,

    /// Invalid path component (e.g. ".", "..", "/")
    #[error("invalid path component: {0}")]
    InvalidPathComponent(String),

    /// Script not found in sandbox configuration
    #[error("script '{0}' not found in sandbox configuration '{1}'")]
    ScriptNotFoundInSandbox(String, String),

    /// An error that occurred running the sandbox server.
    #[error("sandbox server error: {0}")]
    SandboxServerError(String),

    /// An error that occurred when an invalid network scope was used.
    #[error("invalid network scope: {0}")]
    InvalidNetworkScope(String),

    /// An error that occurred when a start script or exec command or shell is missing.
    #[error("missing start script or exec command or shell")]
    MissingStartOrExecOrShell,

    /// An error that occurred when trying to install a script with the same name as an existing command.
    #[error("command already exists: {0}")]
    CommandExists(String),

    /// An error that occurred when a command was not found.
    #[error("command not found: {0}")]
    CommandNotFound(String),
}

/// An error that occurred when an invalid MicroVm configuration was used.
#[derive(Debug, Error)]
pub enum InvalidMicroVMConfigError {
    /// The root path does not exist.
    #[error("root path does not exist: {0}")]
    RootPathDoesNotExist(String),

    /// A host path that should be mounted does not exist.
    #[error("host path does not exist: {0}")]
    HostPathDoesNotExist(String),

    /// The number of vCPUs is zero.
    #[error("number of vCPUs is zero")]
    NumVCPUsIsZero,

    /// The amount of memory is zero.
    #[error("amount of memory is zero")]
    MemoryIsZero,

    /// The command line contains invalid characters. Only printable ASCII characters (space through tilde) are allowed.
    #[error("command line contains invalid characters (only ASCII characters between space and tilde are allowed): {0}")]
    InvalidCommandLineString(String),

    /// An error that occurs when conflicting guest paths are detected.
    #[error("Conflicting guest paths: '{0}' and '{1}' overlap")]
    ConflictingGuestPaths(String, String),
}

/// An error that can represent any error.
#[derive(Debug)]
pub struct AnyError {
    error: anyhow::Error,
}

//--------------------------------------------------------------------------------------------------
// Methods
//--------------------------------------------------------------------------------------------------

impl MicrosandboxError {
    /// Creates a new `Err` result.
    pub fn custom(error: impl Into<anyhow::Error>) -> MicrosandboxError {
        MicrosandboxError::Custom(AnyError {
            error: error.into(),
        })
    }
}

impl AnyError {
    /// Downcasts the error to a `T`.
    pub fn downcast<T>(&self) -> Option<&T>
    where
        T: Display + fmt::Debug + Send + Sync + 'static,
    {
        self.error.downcast_ref::<T>()
    }
}

//--------------------------------------------------------------------------------------------------
// Functions
//--------------------------------------------------------------------------------------------------

/// Creates an `Ok` `MicrosandboxResult`.
#[allow(non_snake_case)]
pub fn Ok<T>(value: T) -> MicrosandboxResult<T> {
    Result::Ok(value)
}

//--------------------------------------------------------------------------------------------------
// Trait Implementations
//--------------------------------------------------------------------------------------------------

impl PartialEq for AnyError {
    fn eq(&self, other: &Self) -> bool {
        self.error.to_string() == other.error.to_string()
    }
}

impl Display for AnyError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.error)
    }
}

impl Error for AnyError {}