Skip to main content

lean_agent_core/
error.rs

1//! Error types for `lean-agent-core`.
2
3use camino::Utf8PathBuf;
4use thiserror::Error;
5
6/// Project-local result alias.
7pub type Result<T> = std::result::Result<T, Error>;
8
9/// Errors produced by tracing, parsing, discovery, and writing.
10#[derive(Debug, Error)]
11pub enum Error {
12    /// A filesystem path could not be represented as UTF-8.
13    #[error("path is not valid UTF-8: {path:?}")]
14    NonUtf8Path {
15        /// Original path rendered lossily.
16        path: std::path::PathBuf,
17    },
18
19    /// A requested path does not exist.
20    #[error("path does not exist: {path}")]
21    PathDoesNotExist {
22        /// Missing path.
23        path: Utf8PathBuf,
24    },
25
26    /// A requested path is not a Lean source file.
27    #[error("not a .lean file: {path}")]
28    NotLeanFile {
29        /// Invalid file path.
30        path: Utf8PathBuf,
31    },
32
33    /// A `FILE.lean:LINE` target could not be parsed.
34    #[error("invalid FILE.lean:LINE target: {spec}")]
35    InvalidLineSpec {
36        /// The raw target string as given.
37        spec: String,
38    },
39
40    /// IO failure.
41    #[error(transparent)]
42    Io(#[from] std::io::Error),
43
44    /// JSON serialization/deserialization failure.
45    #[error(transparent)]
46    Json(#[from] serde_json::Error),
47
48    /// A config file could not be read from disk.
49    #[error("reading config {path}: {source}")]
50    ConfigRead {
51        /// Config file path.
52        path: Utf8PathBuf,
53        /// Underlying IO error.
54        #[source]
55        source: std::io::Error,
56    },
57
58    /// A config file could not be parsed as TOML.
59    #[error("parsing config {path}: {source}")]
60    ConfigParse {
61        /// Config file path.
62        path: Utf8PathBuf,
63        /// Underlying TOML parse error.
64        #[source]
65        source: toml::de::Error,
66    },
67
68    /// External Lean/Lake process timed out.
69    #[error("Lean process timed out after {timeout_seconds}s for {file}")]
70    Timeout {
71        /// File being checked.
72        file: Utf8PathBuf,
73        /// Timeout in seconds.
74        timeout_seconds: u64,
75    },
76
77    /// An edit path is absolute or uses `..`, so it could escape the workspace.
78    #[error("edit path escapes the workspace root: {path}")]
79    OutsideWorkspace {
80        /// The offending relative path as given.
81        path: Utf8PathBuf,
82    },
83
84    /// A span carried a zero line number; lines are one-based.
85    #[error("span line numbers must be one-based and non-zero (file {file})")]
86    ZeroLineSpan {
87        /// File the span pointed at.
88        file: Utf8PathBuf,
89    },
90
91    /// A span's start line is greater than its end line.
92    #[error("inverted span in {file}: start_line {start_line} > end_line {end_line}")]
93    InvertedSpan {
94        /// File the span pointed at.
95        file: Utf8PathBuf,
96        /// One-based first line.
97        start_line: u32,
98        /// One-based last line.
99        end_line: u32,
100    },
101
102    /// A span falls outside the target file's line range.
103    #[error("span {start_line}..={end_line} is outside {file} ({line_count} lines)")]
104    SpanOutOfBounds {
105        /// File the span pointed at.
106        file: Utf8PathBuf,
107        /// One-based first line.
108        start_line: u32,
109        /// One-based last line.
110        end_line: u32,
111        /// Number of lines actually present.
112        line_count: usize,
113    },
114
115    /// An attempt edits more than one file without the multi-file flag.
116    #[error("multi-file edit refused ({files} files); pass the multi-file flag to allow")]
117    MultiFileEditNotAllowed {
118        /// Distinct files the attempt touched.
119        files: usize,
120    },
121
122    /// A replacement would splice a top-level command into the edited file.
123    ///
124    /// A single-span patch is a proof body; a column-zero command such as
125    /// `#eval`/`#print`/`import`/`set_option`/`macro`/`elab`/`open` is refused so
126    /// it cannot perturb the accept guards that read the compile.
127    #[error("replacement in {file} introduces a top-level command: {detail}")]
128    DisallowedReplacement {
129        /// File the span pointed at.
130        file: Utf8PathBuf,
131        /// The offending line and why it was refused.
132        detail: String,
133    },
134
135    /// Starting the external agent runner failed.
136    #[error("failed to start runner {runner}: {source}")]
137    RunnerSpawn {
138        /// Runner path that could not be started.
139        runner: Utf8PathBuf,
140        /// Underlying IO error.
141        #[source]
142        source: std::io::Error,
143    },
144
145    /// The runner broke the line-oriented request/response contract.
146    #[error("runner protocol error: {detail}")]
147    RunnerProtocol {
148        /// What went wrong with the runner exchange.
149        detail: String,
150    },
151
152    /// Copying the Lake project into an isolated workspace failed.
153    #[error("workspace copy failed for {path}: {source}")]
154    WorkspaceCopy {
155        /// Path being copied when the failure happened.
156        path: Utf8PathBuf,
157        /// Underlying IO error.
158        #[source]
159        source: std::io::Error,
160    },
161
162    /// Placeholder for code paths intentionally left as TODO.
163    #[error("not implemented yet: {feature}")]
164    Todo {
165        /// Feature name.
166        feature: &'static str,
167    },
168}