Skip to main content

music21_rs/
error.rs

1use std::convert::Infallible;
2
3use thiserror::Error as ThisError;
4
5/// Result type returned by fallible `music21-rs` operations.
6pub type Result<T, E = Error> = std::result::Result<T, E>;
7
8/// Error variants produced by the crate's theory helpers.
9#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
10#[non_exhaustive]
11pub enum Error {
12    /// Error associated with a generic music21-style object.
13    #[error("Music21Object error: {0}")]
14    Music21Object(String),
15    /// Error associated with chord construction or analysis.
16    #[error("Chord error: {0}")]
17    Chord(String),
18    /// Error associated with pitch construction, spelling or conversion.
19    #[error("Pitch error: {0}")]
20    Pitch(String),
21    /// Error associated with microtone construction or conversion.
22    #[error("Microtone error: {0}")]
23    Microtone(String),
24    /// Error associated with accidental parsing or conversion.
25    #[error("Accidental error: {0}")]
26    Accidental(String),
27    /// Error associated with generated chord-table lookup data.
28    #[error("ChordTables error: {0}")]
29    ChordTables(String),
30    /// Error associated with interval construction or conversion.
31    #[error("Interval error: {0}")]
32    Interval(String),
33    /// Error associated with step-name parsing or conversion.
34    #[error("StepName error: {0}")]
35    StepName(String),
36    /// Error associated with numeric pitch-class parsing.
37    #[error("PitchClass error: {0}")]
38    PitchClass(String),
39    /// Error associated with ordinal-name parsing.
40    #[error("Ordinal error: {0}")]
41    Ordinal(String),
42    /// Error associated with polyrhythm construction or timing.
43    #[error("Polyrhythm error: {0}")]
44    Polyrhythm(String),
45    /// Error associated with tuning-system parsing or lookup.
46    #[error("TuningSystem error: {0}")]
47    TuningSystem(String),
48    /// Error associated with MIDI import or export.
49    #[error("Midi error: {0}")]
50    Midi(String),
51    /// Error associated with analysis helpers.
52    #[error("Analysis error: {0}")]
53    Analysis(String),
54    /// Error associated with time-signature parsing or beat lookup.
55    #[error("Meter error: {0}")]
56    Meter(String),
57    /// Error associated with Xenakis sieve parsing or evaluation.
58    #[error("Sieve error: {0}")]
59    Sieve(String),
60    /// Error associated with duration values.
61    #[error("Duration error: {0}")]
62    Duration(String),
63    /// Error associated with keys and key signatures.
64    #[error("Key error: {0}")]
65    Key(String),
66    /// Error associated with scale degrees and realization.
67    #[error("Scale error: {0}")]
68    Scale(String),
69    /// Error associated with tempo marks.
70    #[error("Tempo error: {0}")]
71    Tempo(String),
72    /// Error associated with instruments and the names scores give them.
73    #[error("Instrument error: {0}")]
74    Instrument(String),
75    /// Error associated with figured bass and its realization.
76    #[error("Figured bass error: {0}")]
77    FiguredBass(String),
78    /// ABC text that cannot be read.
79    #[error("ABC error: {0}")]
80    Abc(String),
81    /// Humdrum that cannot be read.
82    #[error("Humdrum error: {0}")]
83    Humdrum(String),
84    /// A document that is not well-formed XML.
85    #[error("XML error: {0}")]
86    Xml(String),
87    /// MEI that cannot be read.
88    #[error("MEI error: {0}")]
89    Mei(String),
90    /// RomanText that cannot be read.
91    #[error("RomanText error: {0}")]
92    RomanText(String),
93    /// Capella's CapXML that cannot be read.
94    #[error("Capella error: {0}")]
95    Capella(String),
96    /// MuseData that cannot be read.
97    #[error("MuseData error: {0}")]
98    MuseData(String),
99    /// NoteWorthy Composer text that cannot be read.
100    #[error("NoteWorthy error: {0}")]
101    Noteworthy(String),
102    /// A MuseScore file that cannot be read, or MuseScore itself could not be
103    /// run or could not convert a file.
104    #[error("MuseScore error: {0}")]
105    MuseScore(String),
106    /// Error associated with ornaments and other expressions.
107    #[error("Expression error: {0}")]
108    Expression(String),
109    /// Error associated with clefs.
110    #[error("Clef error: {0}")]
111    Clef(String),
112    /// Error associated with tone rows and serial transformations.
113    #[error("Serial error: {0}")]
114    Serial(String),
115    /// Error associated with ties, noteheads, stems and lyrics.
116    #[error("Notation error: {0}")]
117    Notation(String),
118    /// Error associated with note volumes.
119    #[error("Volume error: {0}")]
120    Volume(String),
121    /// A stream that cannot be changed as asked: music21's
122    /// `StreamException`.
123    #[error("Stream error: {0}")]
124    Stream(String),
125
126    /// A feature that cannot be read out of a piece, or a normalized feature
127    /// whose values are all nought: music21's `FeatureException`.
128    #[error("Feature error: {0}")]
129    Feature(String),
130    /// Text that cannot be worked on as asked: an article looked for in a
131    /// language music21 knows none of.
132    #[error("Text error: {0}")]
133    Text(String),
134    /// A search that cannot be made as asked: music21's `SearchException`
135    /// and `LyricSearcherException`.
136    #[error("Search error: {0}")]
137    Search(String),
138    /// A fretboard that cannot say what it sounds: one not tuned a pitch a
139    /// string, or a note on a string it has not got.
140    #[error("Tablature error: {0}")]
141    Tablature(String),
142    /// A timespan tree that cannot answer as asked: music21's
143    /// `TreeException`.
144    #[error("Tree error: {0}")]
145    Tree(String),
146    /// A score the OMR correctors cannot work on: parts of different
147    /// lengths, where music21 runs out of measures in one of them, or a
148    /// measure with no time signature in force.
149    #[error("OMR error: {0}")]
150    Omr(String),
151    /// Variants that cannot be merged, placed or made real as asked:
152    /// music21's `VariantException`, and the errors music21 meets on the
153    /// way.
154    #[error("Variant error: {0}")]
155    Variant(String),
156    /// A score that cannot be written as MusicXML.
157    #[cfg(feature = "musicxml")]
158    #[error("MusicXML export error: {0}")]
159    MusicXml(String),
160    /// A value the caller gave that nothing musical could be read from.
161    ///
162    /// music21 keeps this apart from its own exceptions — an octave written
163    /// before a pitch name, or a name that is not a string at all, is a
164    /// `ValueError` there and not a `PitchException` — and callers catch the
165    /// two separately, so the crate keeps them apart too.
166    #[error("Value error: {0}")]
167    Value(String),
168}
169
170impl From<Infallible> for Error {
171    fn from(value: Infallible) -> Self {
172        match value {}
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179    use std::error::Error as StdError;
180
181    #[test]
182    fn test_display_music21object() {
183        let err = Error::Music21Object("error message".to_string());
184        assert_eq!(format!("{err}"), "Music21Object error: error message");
185    }
186
187    #[test]
188    fn test_display_chord() {
189        let err = Error::Chord("chord error".to_string());
190        assert_eq!(format!("{err}"), "Chord error: chord error");
191    }
192
193    #[test]
194    fn test_display_pitch() {
195        let err = Error::Pitch("pitch error".to_string());
196        assert_eq!(format!("{err}"), "Pitch error: pitch error");
197    }
198
199    #[test]
200    fn test_display_microtone() {
201        let err = Error::Microtone("microtone error".to_string());
202        assert_eq!(format!("{err}"), "Microtone error: microtone error");
203    }
204
205    #[test]
206    fn test_display_accidental() {
207        let err = Error::Accidental("accidental error".to_string());
208        assert_eq!(format!("{err}"), "Accidental error: accidental error");
209    }
210
211    #[test]
212    fn test_display_chordtables() {
213        let err = Error::ChordTables("chordtables error".to_string());
214        assert_eq!(format!("{err}"), "ChordTables error: chordtables error");
215    }
216
217    #[test]
218    fn test_display_interval() {
219        let err = Error::Interval("interval error".to_string());
220        assert_eq!(format!("{err}"), "Interval error: interval error");
221    }
222
223    #[test]
224    fn test_display_stepname() {
225        let err = Error::StepName("step name error".to_string());
226        assert_eq!(format!("{err}"), "StepName error: step name error");
227    }
228
229    #[test]
230    fn test_display_ordinal() {
231        let err = Error::Ordinal("ordinal error".to_string());
232        assert_eq!(format!("{err}"), "Ordinal error: ordinal error");
233    }
234
235    #[test]
236    fn test_display_polyrhythm() {
237        let err = Error::Polyrhythm("polyrhythm error".to_string());
238        assert_eq!(format!("{err}"), "Polyrhythm error: polyrhythm error");
239    }
240
241    #[test]
242    fn test_display_tuningsystem() {
243        let err = Error::TuningSystem("tuning system error".to_string());
244        assert_eq!(format!("{err}"), "TuningSystem error: tuning system error");
245    }
246
247    #[test]
248    fn test_source_none() {
249        let errors = [
250            Error::Music21Object("music21".to_string()),
251            Error::Chord("chord".to_string()),
252            Error::Pitch("pitch".to_string()),
253            Error::Microtone("microtone".to_string()),
254            Error::Accidental("accidental".to_string()),
255            Error::ChordTables("chordtables".to_string()),
256            Error::Interval("interval".to_string()),
257            Error::StepName("step".to_string()),
258            Error::PitchClass("pitch class".to_string()),
259            Error::Ordinal("ordinal".to_string()),
260            Error::Polyrhythm("polyrhythm".to_string()),
261            Error::TuningSystem("tuning system".to_string()),
262            Error::Midi("midi".to_string()),
263            Error::Analysis("analysis".to_string()),
264        ];
265
266        for err in errors.iter() {
267            // Ensure that source() returns None for each Error.
268            assert!(
269                err.source().is_none(),
270                "Expected None for source() in {err:?}"
271            );
272        }
273    }
274
275    #[test]
276    fn test_all_errors_display() {
277        let cases = [
278            (
279                Error::Music21Object("music21".to_string()),
280                "Music21Object error: music21",
281            ),
282            (Error::Chord("chord".to_string()), "Chord error: chord"),
283            (Error::Pitch("pitch".to_string()), "Pitch error: pitch"),
284            (
285                Error::Microtone("microtone".to_string()),
286                "Microtone error: microtone",
287            ),
288            (
289                Error::Accidental("accidental".to_string()),
290                "Accidental error: accidental",
291            ),
292            (
293                Error::ChordTables("chordtables".to_string()),
294                "ChordTables error: chordtables",
295            ),
296            (
297                Error::Interval("interval".to_string()),
298                "Interval error: interval",
299            ),
300            (Error::StepName("step".to_string()), "StepName error: step"),
301            (
302                Error::PitchClass("pitchclass".to_string()),
303                "PitchClass error: pitchclass",
304            ),
305            (
306                Error::Ordinal("ordinal".to_string()),
307                "Ordinal error: ordinal",
308            ),
309            (
310                Error::Polyrhythm("polyrhythm".to_string()),
311                "Polyrhythm error: polyrhythm",
312            ),
313            (
314                Error::TuningSystem("tuning system".to_string()),
315                "TuningSystem error: tuning system",
316            ),
317            (Error::Midi("midi".to_string()), "Midi error: midi"),
318            (
319                Error::Analysis("analysis".to_string()),
320                "Analysis error: analysis",
321            ),
322            (Error::Serial("serial".to_string()), "Serial error: serial"),
323        ];
324
325        for (err, expected) in cases.iter() {
326            assert_eq!(format!("{err}"), *expected);
327            assert!(err.source().is_none());
328        }
329    }
330}