Skip to main content

aft/
error.rs

1use std::fmt;
2
3/// Core error type for the aft binary.
4///
5/// Each variant maps to a structured error response with a `code` string
6/// and a human-readable `message`.
7#[derive(Debug)]
8pub enum AftError {
9    SymbolNotFound {
10        name: String,
11        file: String,
12    },
13    AmbiguousSymbol {
14        name: String,
15        candidates: Vec<String>,
16    },
17    ParseError {
18        message: String,
19    },
20    FileNotFound {
21        path: String,
22    },
23    InvalidRequest {
24        message: String,
25    },
26    CheckpointNotFound {
27        name: String,
28    },
29    NoUndoHistory {
30        path: String,
31    },
32    AmbiguousMatch {
33        pattern: String,
34        count: usize,
35    },
36    ScopeNotFound {
37        scope: String,
38        available: Vec<String>,
39        file: String,
40    },
41    MemberNotFound {
42        member: String,
43        scope: String,
44        file: String,
45    },
46    IoError {
47        path: String,
48        message: String,
49    },
50}
51
52impl AftError {
53    /// Returns the error code string used in JSON error responses.
54    pub fn code(&self) -> &'static str {
55        match self {
56            AftError::SymbolNotFound { .. } => "symbol_not_found",
57            AftError::AmbiguousSymbol { .. } => "ambiguous_symbol",
58            AftError::ParseError { .. } => "parse_error",
59            AftError::FileNotFound { .. } => "file_not_found",
60            AftError::InvalidRequest { .. } => "invalid_request",
61            AftError::CheckpointNotFound { .. } => "checkpoint_not_found",
62            AftError::NoUndoHistory { .. } => "no_undo_history",
63            AftError::AmbiguousMatch { .. } => "ambiguous_match",
64            AftError::ScopeNotFound { .. } => "scope_not_found",
65            AftError::MemberNotFound { .. } => "member_not_found",
66            AftError::IoError { .. } => "io_error",
67        }
68    }
69
70    /// Produces a `serde_json::Value` suitable for the error portion of a response.
71    ///
72    /// Shape: `{ "code": "...", "message": "..." }`
73    pub fn to_error_json(&self) -> serde_json::Value {
74        serde_json::json!({
75            "code": self.code(),
76            "message": self.to_string(),
77        })
78    }
79}
80
81impl fmt::Display for AftError {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self {
84            AftError::SymbolNotFound { name, file } => {
85                write!(f, "symbol '{}' not found in {}", name, file)
86            }
87            AftError::AmbiguousSymbol { name, candidates } => {
88                write!(
89                    f,
90                    "symbol '{}' is ambiguous, candidates: [{}]",
91                    name,
92                    candidates.join(", ")
93                )
94            }
95            AftError::ParseError { message } => {
96                write!(f, "parse error: {}", message)
97            }
98            AftError::FileNotFound { path } => {
99                write!(f, "file not found: {}", path)
100            }
101            AftError::InvalidRequest { message } => {
102                write!(f, "invalid request: {}", message)
103            }
104            AftError::CheckpointNotFound { name } => {
105                write!(f, "checkpoint not found: {}", name)
106            }
107            AftError::NoUndoHistory { path } => {
108                write!(f, "no undo history for: {}", path)
109            }
110            AftError::AmbiguousMatch { pattern, count } => {
111                write!(
112                    f,
113                    "pattern '{}' matches {} occurrences, expected exactly 1",
114                    pattern, count
115                )
116            }
117            AftError::ScopeNotFound {
118                scope,
119                available,
120                file,
121            } => {
122                if available.is_empty() {
123                    write!(
124                        f,
125                        "scope '{}' not found in {} (no scopes available)",
126                        scope, file
127                    )
128                } else {
129                    write!(
130                        f,
131                        "scope '{}' not found in {}, available: [{}]",
132                        scope,
133                        file,
134                        available.join(", ")
135                    )
136                }
137            }
138            AftError::MemberNotFound {
139                member,
140                scope,
141                file,
142            } => {
143                write!(
144                    f,
145                    "member '{}' not found in scope '{}' in {}",
146                    member, scope, file
147                )
148            }
149            AftError::IoError { path, message } => {
150                write!(f, "I/O error on {}: {}", path, message)
151            }
152        }
153    }
154}
155
156impl std::error::Error for AftError {}