1use 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 #[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 #[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>;