1use std::fmt;
2
3#[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 ProjectTooLarge {
51 count: usize,
52 max: usize,
53 },
54}
55
56impl AftError {
57 pub fn code(&self) -> &'static str {
59 match self {
60 AftError::SymbolNotFound { .. } => "symbol_not_found",
61 AftError::AmbiguousSymbol { .. } => "ambiguous_symbol",
62 AftError::ParseError { .. } => "parse_error",
63 AftError::FileNotFound { .. } => "file_not_found",
64 AftError::InvalidRequest { .. } => "invalid_request",
65 AftError::CheckpointNotFound { .. } => "checkpoint_not_found",
66 AftError::NoUndoHistory { .. } => "no_undo_history",
67 AftError::AmbiguousMatch { .. } => "ambiguous_match",
68 AftError::ScopeNotFound { .. } => "scope_not_found",
69 AftError::MemberNotFound { .. } => "member_not_found",
70 AftError::IoError { .. } => "io_error",
71 AftError::ProjectTooLarge { .. } => "project_too_large",
72 }
73 }
74
75 pub fn to_error_json(&self) -> serde_json::Value {
79 serde_json::json!({
80 "code": self.code(),
81 "message": self.to_string(),
82 })
83 }
84}
85
86impl fmt::Display for AftError {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 match self {
89 AftError::SymbolNotFound { name, file } => {
90 write!(f, "symbol '{}' not found in {}", name, file)
91 }
92 AftError::AmbiguousSymbol { name, candidates } => {
93 write!(
94 f,
95 "symbol '{}' is ambiguous, candidates: [{}]",
96 name,
97 candidates.join(", ")
98 )
99 }
100 AftError::ParseError { message } => {
101 write!(f, "parse error: {}", message)
102 }
103 AftError::FileNotFound { path } => {
104 write!(f, "file not found: {}", path)
105 }
106 AftError::InvalidRequest { message } => {
107 write!(f, "invalid request: {}", message)
108 }
109 AftError::CheckpointNotFound { name } => {
110 write!(f, "checkpoint not found: {}", name)
111 }
112 AftError::NoUndoHistory { path } => {
113 write!(f, "no undo history for: {}", path)
114 }
115 AftError::AmbiguousMatch { pattern, count } => {
116 write!(
117 f,
118 "pattern '{}' matches {} occurrences, expected exactly 1",
119 pattern, count
120 )
121 }
122 AftError::ScopeNotFound {
123 scope,
124 available,
125 file,
126 } => {
127 if available.is_empty() {
128 write!(
129 f,
130 "scope '{}' not found in {} (no scopes available)",
131 scope, file
132 )
133 } else {
134 write!(
135 f,
136 "scope '{}' not found in {}, available: [{}]",
137 scope,
138 file,
139 available.join(", ")
140 )
141 }
142 }
143 AftError::MemberNotFound {
144 member,
145 scope,
146 file,
147 } => {
148 write!(
149 f,
150 "member '{}' not found in scope '{}' in {}",
151 member, scope, file
152 )
153 }
154 AftError::IoError { path, message } => {
155 write!(f, "I/O error on {}: {}", path, message)
156 }
157 AftError::ProjectTooLarge { count, max } => {
158 write!(
159 f,
160 "project has {count} source files, exceeding max_callgraph_files={max}. Call-graph operations (callers, trace_to, trace_data, impact) are disabled for this root. Open a specific subdirectory or raise max_callgraph_files in config."
161 )
162 }
163 }
164 }
165}
166
167impl std::error::Error for AftError {}