Skip to main content

ane/commands/chord_engine/
errors.rs

1use std::fmt;
2use std::path::Path;
3
4use crate::data::chord_types::{Component, Scope};
5
6fn filename(buffer_name: &str) -> &str {
7    Path::new(buffer_name)
8        .file_name()
9        .and_then(|f| f.to_str())
10        .unwrap_or(buffer_name)
11}
12
13#[derive(Debug)]
14pub enum ChordError {
15    ParseError {
16        input: String,
17        position: usize,
18        message: String,
19        suggestion: Option<String>,
20    },
21    ResolveError {
22        buffer_name: String,
23        message: String,
24        available_symbols: Vec<String>,
25    },
26    PatchError {
27        buffer_name: String,
28        message: String,
29    },
30    LspRequired {
31        scope: Scope,
32        lsp_state: String,
33    },
34    InvalidCombination {
35        scope: Scope,
36        component: Component,
37        valid_components: Vec<Component>,
38    },
39}
40
41impl fmt::Display for ChordError {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        match self {
44            ChordError::ParseError {
45                input,
46                position,
47                message,
48                suggestion,
49            } => {
50                write!(f, "chord parse error: {message}\n  input: {input}")?;
51                if *position > 0 {
52                    write!(f, "\n  position: {position}")?;
53                }
54                if let Some(sug) = suggestion {
55                    write!(f, "\n  did you mean: {sug}?")?;
56                }
57                Ok(())
58            }
59            ChordError::ResolveError {
60                buffer_name,
61                message,
62                available_symbols,
63            } => {
64                let name = filename(buffer_name);
65                write!(f, "chord resolve error in {name}: {message}")?;
66                if !available_symbols.is_empty() {
67                    write!(f, "\n  available symbols: {}", available_symbols.join(", "))?;
68                }
69                Ok(())
70            }
71            ChordError::PatchError {
72                buffer_name,
73                message,
74            } => {
75                let name = filename(buffer_name);
76                write!(f, "chord patch error in {name}: {message}")
77            }
78            ChordError::LspRequired { scope, lsp_state } => {
79                write!(
80                    f,
81                    "chord requires LSP for {scope} scope (LSP status: {lsp_state})"
82                )
83            }
84            ChordError::InvalidCombination {
85                scope,
86                component,
87                valid_components,
88            } => {
89                let valid: Vec<String> = valid_components.iter().map(|c| format!("{c}")).collect();
90                write!(
91                    f,
92                    "invalid component '{component}' for scope '{scope}'\n  {scope} scope supports components: {}",
93                    valid.join(", ")
94                )
95            }
96        }
97    }
98}
99
100impl std::error::Error for ChordError {}
101
102impl ChordError {
103    pub fn parse(input: &str, position: usize, message: impl Into<String>) -> Self {
104        Self::ParseError {
105            input: input.to_string(),
106            position,
107            message: message.into(),
108            suggestion: None,
109        }
110    }
111
112    pub fn parse_with_suggestion(
113        input: &str,
114        position: usize,
115        message: impl Into<String>,
116        suggestion: impl Into<String>,
117    ) -> Self {
118        Self::ParseError {
119            input: input.to_string(),
120            position,
121            message: message.into(),
122            suggestion: Some(suggestion.into()),
123        }
124    }
125
126    pub fn resolve(buffer_name: &str, message: impl Into<String>) -> Self {
127        Self::ResolveError {
128            buffer_name: buffer_name.to_string(),
129            message: message.into(),
130            available_symbols: Vec::new(),
131        }
132    }
133
134    pub fn resolve_with_symbols(
135        buffer_name: &str,
136        message: impl Into<String>,
137        symbols: Vec<String>,
138    ) -> Self {
139        Self::ResolveError {
140            buffer_name: buffer_name.to_string(),
141            message: message.into(),
142            available_symbols: symbols,
143        }
144    }
145
146    pub fn patch(buffer_name: &str, message: impl Into<String>) -> Self {
147        Self::PatchError {
148            buffer_name: buffer_name.to_string(),
149            message: message.into(),
150        }
151    }
152
153    pub fn invalid_combination(scope: Scope, component: Component) -> Self {
154        let valid = valid_components_for_scope(scope);
155        Self::InvalidCombination {
156            scope,
157            component,
158            valid_components: valid,
159        }
160    }
161}
162
163fn valid_components_for_scope(scope: Scope) -> Vec<Component> {
164    let all = [
165        Component::Beginning,
166        Component::Contents,
167        Component::End,
168        Component::Value,
169        Component::Parameters,
170        Component::Arguments,
171        Component::Name,
172        Component::Self_,
173    ];
174    all.iter()
175        .copied()
176        .filter(|c| crate::data::chord_types::is_valid_combination(scope, *c))
177        .collect()
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn resolve_with_symbols_renders_candidate_list() {
186        let err = ChordError::resolve_with_symbols(
187            "/tmp/foo.rs",
188            "symbol 'fn_x' not found",
189            vec!["fn_one".to_string(), "fn_two".to_string()],
190        );
191        let msg = err.to_string();
192        assert!(msg.contains("symbol 'fn_x' not found"), "got: {msg}");
193        assert!(
194            msg.contains("available symbols: fn_one, fn_two"),
195            "got: {msg}"
196        );
197    }
198
199    #[test]
200    fn resolve_with_no_symbols_omits_candidate_line() {
201        let err = ChordError::resolve("/tmp/foo.rs", "boom");
202        let msg = err.to_string();
203        assert_eq!(msg, "chord resolve error in foo.rs: boom");
204    }
205}