Skip to main content

lean_ctx_fs/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2use std::path::PathBuf;
3use thiserror::Error;
4
5#[derive(Error, Debug)]
6pub enum LeanCtxError {
7    #[error("configuration error: {0}")]
8    Config(String),
9
10    #[error("I/O error: {0}")]
11    Io(#[from] std::io::Error),
12
13    #[error("parse error: {0}")]
14    Parse(#[from] serde_json::Error),
15
16    #[error("lock error: {0}")]
17    Lock(String),
18
19    #[error("network error: {0}")]
20    Network(String),
21
22    #[error("plugin error: {0}")]
23    Plugin(String),
24
25    #[error("internal error: {0}")]
26    Internal(String),
27}
28
29#[derive(Error, Debug)]
30pub enum ConfigError {
31    #[error("Cannot determine config path")]
32    MissingPath,
33
34    #[error("Unknown config key: {key}")]
35    UnknownKey { key: String },
36
37    #[error("Cannot read config: {source}")]
38    Read {
39        #[source]
40        source: std::io::Error,
41    },
42
43    #[error("Config parse error: {source}")]
44    ParseToml {
45        #[source]
46        source: toml::de::Error,
47    },
48
49    #[error("Invalid value for '{key}': {message}")]
50    InvalidValue { key: String, message: String },
51
52    #[error("Error saving config: {source}")]
53    Save {
54        #[source]
55        source: Box<LeanCtxError>,
56    },
57
58    #[error("Expected bool (true/false), got: {value}")]
59    ExpectedBool { value: String },
60
61    #[error("Expected integer, got: {value}")]
62    ExpectedInteger { value: String },
63
64    #[error("Expected unsigned integer, got: {value}")]
65    ExpectedUnsignedInteger { value: String },
66
67    #[error("Expected number, got: {value}")]
68    ExpectedNumber { value: String },
69
70    #[error("Invalid value '{value}'. Allowed: {allowed}")]
71    InvalidEnumValue { value: String, allowed: String },
72
73    #[error("Cannot set table '{value}' via CLI. Edit config.toml directly.")]
74    CannotSetTable { value: String },
75
76    #[error(
77        "Cannot set '{key}': '{part}' already holds a non-table value in config.toml. Fix or remove that key first."
78    )]
79    NonTableParent { key: String, part: String },
80
81    #[error("{0}")]
82    Message(String),
83}
84
85#[derive(Error, Debug)]
86pub enum DispatchError {
87    #[error("path resolution failed: {message}")]
88    PathResolution { message: String },
89
90    #[error("{message}")]
91    Tool { message: String },
92}
93
94#[derive(Error, Debug)]
95pub enum ShellError {
96    #[error("{message}")]
97    Blocked { message: String },
98}
99
100impl From<String> for ShellError {
101    fn from(message: String) -> Self {
102        Self::Blocked { message }
103    }
104}
105
106impl From<&str> for ShellError {
107    fn from(message: &str) -> Self {
108        Self::Blocked {
109            message: message.to_string(),
110        }
111    }
112}
113
114impl ShellError {
115    pub fn message(&self) -> &str {
116        match self {
117            Self::Blocked { message } => message,
118        }
119    }
120
121    pub fn contains(&self, needle: &str) -> bool {
122        self.message().contains(needle)
123    }
124
125    pub fn lines(&self) -> std::str::Lines<'_> {
126        self.message().lines()
127    }
128}
129
130#[derive(Error, Debug)]
131pub enum PathJailError {
132    #[error("protected runtime context access denied: {reason}")]
133    ProtectedContext { reason: String },
134
135    #[error("path contains null byte")]
136    NullByte,
137
138    #[error("path does not exist and has no existing ancestor: {path}")]
139    NoExistingAncestor { path: PathBuf },
140
141    #[error("path escapes project root: {path} (root: {root}){hint}")]
142    EscapesRoot {
143        path: PathBuf,
144        root: PathBuf,
145        hint: String,
146    },
147
148    #[error("post-canonicalize jail escape detected: {path} resolves to {resolved}")]
149    PostCanonicalizeEscape { path: PathBuf, resolved: PathBuf },
150
151    #[error("symlink not allowed in jailed path: {path}")]
152    Symlink { path: PathBuf },
153
154    /// Outside every root of the launcher-bound Workspace (#2120).
155    #[error(
156        "path is outside the Workspace '{workspace}' (rev {revision}): {path} — attached roots: {roots}"
157    )]
158    OutsideWorkspace {
159        path: PathBuf,
160        workspace: String,
161        revision: u64,
162        roots: String,
163    },
164
165    /// A Workspace is configured but unusable: every path is denied (#2120).
166    #[error("{reason}")]
167    WorkspaceUnavailable { reason: String },
168}
169
170impl From<String> for ConfigError {
171    fn from(message: String) -> Self {
172        ConfigError::Message(message)
173    }
174}
175
176impl From<&str> for ConfigError {
177    fn from(message: &str) -> Self {
178        ConfigError::Message(message.to_string())
179    }
180}
181
182impl From<toml::de::Error> for LeanCtxError {
183    fn from(e: toml::de::Error) -> Self {
184        LeanCtxError::Config(e.to_string())
185    }
186}
187
188pub type Result<T> = std::result::Result<T, LeanCtxError>;