Skip to main content

lean_ctx/core/
error.rs

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>;