monument/
error.rs

1//! Error types for the different ways that Monument can fail.
2
3use std::{
4    fmt::{Display, Formatter},
5    ops::RangeInclusive,
6};
7
8use bellframe::{PlaceNot, Stage};
9
10#[allow(unused_imports)] // Only used for doc comments
11use crate::parameters::{Call, Method, MusicType};
12use crate::{parameters::OptionalRangeInclusive, utils::TotalLength};
13
14/// Alias for `Result<T, monument::Error>`.
15pub type Result<T> = std::result::Result<T, Error>;
16
17/// The different ways that Monument can fail.
18#[derive(Debug)]
19pub enum Error {
20    /* QUERY BUILD ERRORS */
21    /// The given `title` couldn't be found in the Central Council library.  Each `suggestions` is
22    /// paired with its 'edit distance' (i.e. a metric of how different it is from the requested
23    /// `title`)
24    MethodNotFound {
25        title: String,
26        suggestions: Vec<(String, usize)>,
27    },
28    /// Some method's place notation failed to parse
29    MethodPnParse {
30        name: String,
31        place_notation_string: String,
32        error: bellframe::place_not::PnBlockParseError,
33    },
34    /// Some course mask couldn't be parsed.
35    CustomCourseMaskParse {
36        method_title: String,
37        mask_str: String,
38        error: bellframe::mask::ParseError,
39    },
40
41    /* QUERY VERIFICATION ERRORS */
42    /// Different start/end rows were specified in a multi-part
43    DifferentStartEndRowInMultipart,
44    /// Some [`Call`] refers to a label that doesn't exist
45    UndefinedLabel { call_name: String, label: String },
46    /// No methods were defined
47    NoMethods,
48    /// Two [`Method`]s use the same shorthand
49    DuplicateShorthand {
50        shorthand: String,
51        title1: String,
52        title2: String,
53    },
54    /// Some [`Call`] doesn't have enough calling positions to cover the [`Stage`]
55    WrongCallingPositionsLength {
56        call_name: String,
57        calling_position_len: usize,
58        stage: Stage,
59    },
60    /// Two [`Call`]s have the same lead location and name
61    DuplicateCall {
62        symbol: String,
63        label: String,
64        pn1: PlaceNot,
65        pn2: PlaceNot,
66    },
67
68    /* GRAPH BUILD ERRORS */
69    /// The given maximum graph size limit was reached
70    SizeLimit(usize),
71    /// The same chunk of ringing could start at two different strokes, and some
72    /// [`MusicType`] relies on the strokes always being the same
73    InconsistentStroke,
74
75    /* LENGTH PROVING ERRORS */
76    /// The requested length range isn't achievable
77    UnachievableLength {
78        requested_range: RangeInclusive<TotalLength>,
79        next_shorter_len: Option<usize>,
80        next_longer_len: Option<usize>,
81    },
82    /// Some method range isn't achievable
83    UnachievableMethodCount {
84        method_name: String,
85        requested_range: OptionalRangeInclusive,
86        next_shorter_len: Option<usize>,
87        next_longer_len: Option<usize>,
88    },
89    /// The total of the minimum method counts is longer than the composition
90    TooMuchMethodCount {
91        min_total_method_count: usize,
92        max_length: usize,
93    },
94    TooLittleMethodCount {
95        max_total_method_count: usize,
96        min_length: usize,
97    },
98}
99
100impl Display for Error {
101    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
102        match self {
103            /* QUERY BUILD ERRORS */
104            Error::MethodNotFound { title, suggestions } => write!(
105                f,
106                "No method called {:?} in the Central Council library.  Do you mean {:?}?",
107                title, suggestions[0]
108            ),
109            Error::MethodPnParse {
110                name,
111                place_notation_string: _,
112                error,
113            } => write!(f, "Error parsing place notation for {:?}: {}", name, error),
114            Error::CustomCourseMaskParse {
115                method_title,
116                mask_str,
117                error,
118            } => write!(
119                f,
120                "Error parsing course mask {} for method {:?}: {}",
121                mask_str, method_title, error
122            ),
123
124            /* QUERY VERIFICATION ERRORS */
125            Error::DifferentStartEndRowInMultipart => {
126                write!(f, "Start/end rows must be the same for multipart comps")
127            }
128            Error::NoMethods => write!(f, "Can't have a composition with no methods"),
129            Error::WrongCallingPositionsLength {
130                call_name,
131                calling_position_len,
132                stage,
133            } => write!(
134                f,
135                "Call {:?} only specifies {} calling positions, but the stage has {} bells",
136                call_name,
137                calling_position_len,
138                stage.num_bells()
139            ),
140            Error::DuplicateShorthand {
141                shorthand,
142                title1,
143                title2,
144            } => write!(
145                f,
146                "Methods {:?} and {:?} share a shorthand ({})",
147                title1, title2, shorthand
148            ),
149            Error::UndefinedLabel { call_name, label } => write!(
150                f,
151                "Call {:?} refers to a label {:?}, which doesn't exist",
152                call_name, label
153            ), // TODO: Suggest one that does exist
154            Error::DuplicateCall {
155                symbol,
156                label,
157                pn1,
158                pn2,
159            } => write!(
160                f,
161                "Call symbol {:?} (at {:?}) is used for both {} and {}",
162                symbol, label, pn1, pn2
163            ),
164
165            /* GRAPH BUILD ERRORS */
166            Error::SizeLimit(limit) => write!(
167                f,
168                "Graph size limit of {} chunks reached.  You can set it \
169higher with `--graph-size-limit <n>`.",
170                limit
171            ),
172            Error::InconsistentStroke => write!(
173                f,
174                "The same chunk of ringing can be at multiple strokes, probably \
175because you're using a method with odd-length leads"
176            ),
177
178            /* LENGTH PROVING ERRORS */
179            Error::UnachievableLength {
180                requested_range,
181                next_shorter_len,
182                next_longer_len,
183            } => {
184                write!(f, "No compositions can fit the required length range (")?;
185                write_range(
186                    f,
187                    "length",
188                    Some(*requested_range.start()),
189                    Some(*requested_range.end()),
190                )?;
191                write!(f, ").  ")?;
192                // Describe the nearest composition length(s)
193                match (next_shorter_len, next_longer_len) {
194                    (Some(l1), Some(l2)) => write!(f, "The nearest lengths are {l1} and {l2}."),
195                    (Some(l), None) | (None, Some(l)) => write!(f, "The nearest length is {l}."),
196                    // TODO: Give this its own error?
197                    (None, None) => write!(f, "No compositions are possible."),
198                }
199            }
200            Error::UnachievableMethodCount {
201                method_name,
202                requested_range,
203                next_shorter_len,
204                next_longer_len,
205            } => {
206                assert_ne!((requested_range.min, requested_range.max), (None, None));
207                write!(
208                    f,
209                    "No method counts for {:?} satisfy the requested range (",
210                    method_name,
211                )?;
212                write_range(f, "count", requested_range.min, requested_range.max)?;
213                write!(f, ").  ")?;
214                // Describe the nearest method counts
215                match (next_shorter_len, next_longer_len) {
216                    (Some(l1), Some(l2)) => write!(f, "The nearest counts are {l1} and {l2}."),
217                    (Some(l), None) | (None, Some(l)) => write!(f, "The nearest count is {l}."),
218                    (None, None) => unreachable!(), // Method count of 0 is always possible
219                }
220            }
221            Error::TooMuchMethodCount {
222                min_total_method_count,
223                max_length,
224            } => {
225                write!(f, "Too much method counts; the method counts need at least")?;
226                write!(
227                    f,
228                    " {min_total_method_count} rows, but at most {max_length} rows are available."
229                )
230            }
231            Error::TooLittleMethodCount {
232                max_total_method_count,
233                min_length,
234            } => {
235                write!(
236                    f,
237                    "Not enough method counts; the composition needs at least {min_length} rows"
238                )?;
239                write!(
240                    f,
241                    " but the methods can make at most {max_total_method_count}."
242                )
243            }
244        }
245    }
246}
247
248/// Prettily format a (possibly open) inclusive range as an inequality (e.g. `300 <= count <= 500`)
249fn write_range<T: Ord + Display>(
250    f: &mut impl std::fmt::Write,
251    name: &str,
252    min: Option<T>,
253    max: Option<T>,
254) -> std::fmt::Result {
255    match (min, max) {
256        // Write e.g. `224 <= count <= 224` as `count == 224`
257        (Some(min), Some(max)) if min == max => write!(f, "{name} == {min}")?,
258        // Otherwise write everything as an inequality
259        (min, max) => {
260            if let Some(min) = min {
261                write!(f, "{min} <= ")?;
262            }
263            write!(f, "{name}")?;
264            if let Some(max) = max {
265                write!(f, " <= {max}")?;
266            }
267        }
268    }
269    Ok(())
270}
271
272impl std::error::Error for Error {}