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}
47
48impl AftError {
49    /// Returns the error code string used in JSON error responses.
50    pub fn code(&self) -> &'static str {
51        match self {
52            AftError::SymbolNotFound { .. } => "symbol_not_found",
53            AftError::AmbiguousSymbol { .. } => "ambiguous_symbol",
54            AftError::ParseError { .. } => "parse_error",
55            AftError::FileNotFound { .. } => "file_not_found",
56            AftError::InvalidRequest { .. } => "invalid_request",
57            AftError::CheckpointNotFound { .. } => "checkpoint_not_found",
58            AftError::NoUndoHistory { .. } => "no_undo_history",
59            AftError::AmbiguousMatch { .. } => "ambiguous_match",
60            AftError::ScopeNotFound { .. } => "scope_not_found",
61            AftError::MemberNotFound { .. } => "member_not_found",
62        }
63    }
64
65    /// Produces a `serde_json::Value` suitable for the error portion of a response.
66    ///
67    /// Shape: `{ "code": "...", "message": "..." }`
68    pub fn to_error_json(&self) -> serde_json::Value {
69        serde_json::json!({
70            "code": self.code(),
71            "message": self.to_string(),
72        })
73    }
74}
75
76impl fmt::Display for AftError {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        match self {
79            AftError::SymbolNotFound { name, file } => {
80                write!(f, "symbol '{}' not found in {}", name, file)
81            }
82            AftError::AmbiguousSymbol { name, candidates } => {
83                write!(
84                    f,
85                    "symbol '{}' is ambiguous, candidates: [{}]",
86                    name,
87                    candidates.join(", ")
88                )
89            }
90            AftError::ParseError { message } => {
91                write!(f, "parse error: {}", message)
92            }
93            AftError::FileNotFound { path } => {
94                write!(f, "file not found: {}", path)
95            }
96            AftError::InvalidRequest { message } => {
97                write!(f, "invalid request: {}", message)
98            }
99            AftError::CheckpointNotFound { name } => {
100                write!(f, "checkpoint not found: {}", name)
101            }
102            AftError::NoUndoHistory { path } => {
103                write!(f, "no undo history for: {}", path)
104            }
105            AftError::AmbiguousMatch { pattern, count } => {
106                write!(
107                    f,
108                    "pattern '{}' matches {} occurrences, expected exactly 1",
109                    pattern, count
110                )
111            }
112            AftError::ScopeNotFound {
113                scope,
114                available,
115                file,
116            } => {
117                if available.is_empty() {
118                    write!(
119                        f,
120                        "scope '{}' not found in {} (no scopes available)",
121                        scope, file
122                    )
123                } else {
124                    write!(
125                        f,
126                        "scope '{}' not found in {}, available: [{}]",
127                        scope,
128                        file,
129                        available.join(", ")
130                    )
131                }
132            }
133            AftError::MemberNotFound {
134                member,
135                scope,
136                file,
137            } => {
138                write!(
139                    f,
140                    "member '{}' not found in scope '{}' in {}",
141                    member, scope, file
142                )
143            }
144        }
145    }
146}
147
148impl std::error::Error for AftError {}