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