Skip to main content

rlmesh_sandbox/
error.rs

1//! Public error type for the rlmesh-sandbox crate.
2//!
3//! The crate uses [`anyhow`] internally for ergonomic context propagation but
4//! exposes this typed [`SandboxError`] enum on its public API so consumers can
5//! discriminate failure classes (e.g. "docker unavailable" vs "unpinned HF
6//! source" vs "invalid options") without matching on `Display` text.
7
8use thiserror::Error;
9
10/// Errors returned by the public rlmesh-sandbox API.
11///
12/// This enum is `#[non_exhaustive]`: new variants may be added in future
13/// releases, so downstream `match` expressions must include a wildcard arm.
14#[derive(Debug, Error)]
15#[non_exhaustive]
16pub enum SandboxError {
17    /// An environment source reference could not be parsed.
18    #[error("invalid sandbox source: {message}")]
19    InvalidSource {
20        /// Human-readable description of the parse failure.
21        message: String,
22    },
23
24    /// A sandbox option (base image, packages, imports, num_envs,
25    /// vectorization mode, rlmesh package spec, ...) was invalid.
26    #[error("invalid sandbox option: {message}")]
27    InvalidOption {
28        /// Human-readable description of the invalid option.
29        message: String,
30    },
31
32    /// An `hf://` source was rejected by the trust/pinning policy (remote code
33    /// not trusted, or revision not pinned to a full git SHA).
34    #[error("hugging face source policy violation: {message}")]
35    HuggingFacePolicy {
36        /// Human-readable description of the policy violation.
37        message: String,
38    },
39
40    /// Resolving an `hf://` revision (via `git ls-remote`) or materializing the
41    /// source tree failed.
42    #[error("failed to resolve hugging face source: {message}")]
43    SourceResolution {
44        /// Human-readable description of the resolution failure.
45        message: String,
46    },
47
48    /// Selecting or reading a local RLMesh wheel failed.
49    #[error("rlmesh wheel resolution failed: {message}")]
50    Wheel {
51        /// Human-readable description of the wheel resolution failure.
52        message: String,
53    },
54
55    /// Building the sandbox image failed (e.g. `docker build` returned an
56    /// error, or the build context could not be assembled).
57    #[error("sandbox image build failed: {message}")]
58    ImageBuild {
59        /// Human-readable description of the build failure.
60        message: String,
61    },
62
63    /// Starting the container or waiting for it to become ready failed.
64    #[error("sandbox container failed to start: {message}")]
65    ContainerStartup {
66        /// Human-readable description of the startup failure, including any
67        /// captured container state and logs.
68        message: String,
69    },
70
71    /// Invoking the `docker` CLI failed (e.g. docker is not installed or the
72    /// daemon is unavailable), or a docker subcommand returned an error.
73    #[error("docker command failed: {message}")]
74    Docker {
75        /// Human-readable description of the docker failure.
76        message: String,
77    },
78}
79
80impl SandboxError {
81    pub(crate) fn invalid_source(err: impl std::fmt::Display) -> Self {
82        Self::InvalidSource {
83            message: err.to_string(),
84        }
85    }
86
87    pub(crate) fn invalid_option(err: impl std::fmt::Display) -> Self {
88        Self::InvalidOption {
89            message: err.to_string(),
90        }
91    }
92
93    pub(crate) fn huggingface_policy(err: impl std::fmt::Display) -> Self {
94        Self::HuggingFacePolicy {
95            message: err.to_string(),
96        }
97    }
98
99    pub(crate) fn source_resolution(err: impl std::fmt::Display) -> Self {
100        Self::SourceResolution {
101            message: err.to_string(),
102        }
103    }
104
105    pub(crate) fn wheel(err: impl std::fmt::Display) -> Self {
106        Self::Wheel {
107            message: err.to_string(),
108        }
109    }
110
111    pub(crate) fn container_startup(err: impl std::fmt::Display) -> Self {
112        Self::ContainerStartup {
113            message: err.to_string(),
114        }
115    }
116
117    /// Classify an internal docker-backend error. If the failure was an
118    /// inability to spawn the `docker` CLI (e.g. it is not installed or the
119    /// daemon socket is unreachable), it is reported as [`SandboxError::Docker`]
120    /// regardless of which operation triggered it; otherwise the supplied
121    /// operation-specific variant is used.
122    pub(crate) fn from_docker_op(
123        err: anyhow::Error,
124        operation: impl FnOnce(String) -> Self,
125    ) -> Self {
126        if let Some(io_err) = err
127            .chain()
128            .find_map(|cause| cause.downcast_ref::<std::io::Error>())
129            && io_err.kind() == std::io::ErrorKind::NotFound
130        {
131            return Self::Docker {
132                message: format!("docker CLI not found: {err:#}"),
133            };
134        }
135        operation(format!("{err:#}"))
136    }
137}