ane/commands/chord_engine/
errors.rs1use 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}