Skip to main content

music21/
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, or a MIDI value out of
49    /// range.
50    #[error("Midi error: {0}")]
51    Midi(Box<FormatError>),
52    /// Error associated with analysis helpers.
53    #[error("Analysis error: {0}")]
54    Analysis(String),
55    /// Error associated with time-signature parsing or beat lookup.
56    #[error("Meter error: {0}")]
57    Meter(String),
58    /// Error associated with Xenakis sieve parsing or evaluation.
59    #[error("Sieve error: {0}")]
60    Sieve(String),
61    /// Error associated with a score's metadata: a date relevance none of
62    /// music21's date classes takes, or a name that is not a metadata name.
63    #[error("Metadata error: {0}")]
64    Metadata(String),
65    /// Error associated with duration values.
66    #[error("Duration error: {0}")]
67    Duration(String),
68    /// Error associated with keys and key signatures.
69    #[error("Key error: {0}")]
70    Key(String),
71    /// Error associated with scale degrees and realization.
72    #[error("Scale error: {0}")]
73    Scale(String),
74    /// Error associated with tempo marks.
75    #[error("Tempo error: {0}")]
76    Tempo(String),
77    /// Error associated with instruments and the names scores give them.
78    #[error("Instrument error: {0}")]
79    Instrument(String),
80    /// Error associated with figured bass and its realization.
81    #[error("Figured bass error: {0}")]
82    FiguredBass(String),
83    /// ABC text that cannot be read.
84    #[error("ABC error: {0}")]
85    Abc(Box<FormatError>),
86    /// Humdrum that cannot be read.
87    #[error("Humdrum error: {0}")]
88    Humdrum(Box<FormatError>),
89    /// A document that is not well-formed XML.
90    #[error("XML error: {0}")]
91    Xml(Box<FormatError>),
92    /// MEI that cannot be read.
93    #[error("MEI error: {0}")]
94    Mei(Box<FormatError>),
95    /// RomanText that cannot be read.
96    #[error("RomanText error: {0}")]
97    RomanText(Box<FormatError>),
98    /// Capella's CapXML that cannot be read.
99    #[error("Capella error: {0}")]
100    Capella(Box<FormatError>),
101    /// MuseData that cannot be read.
102    #[error("MuseData error: {0}")]
103    MuseData(Box<FormatError>),
104    /// NoteWorthy Composer text that cannot be read.
105    #[error("NoteWorthy error: {0}")]
106    Noteworthy(Box<FormatError>),
107    /// A MuseScore file that cannot be read, or MuseScore itself could not be
108    /// run or could not convert a file.
109    #[error("MuseScore error: {0}")]
110    MuseScore(Box<FormatError>),
111    /// Error associated with ornaments and other expressions.
112    #[error("Expression error: {0}")]
113    Expression(String),
114    /// Error associated with clefs.
115    #[error("Clef error: {0}")]
116    Clef(String),
117    /// Error associated with tone rows and serial transformations.
118    #[error("Serial error: {0}")]
119    Serial(String),
120    /// Error associated with ties, noteheads, stems and lyrics.
121    #[error("Notation error: {0}")]
122    Notation(String),
123    /// Error associated with note volumes.
124    #[error("Volume error: {0}")]
125    Volume(String),
126    /// A stream that cannot be changed as asked: music21's
127    /// `StreamException`.
128    #[error("Stream error: {0}")]
129    Stream(String),
130
131    /// A feature that cannot be read out of a piece, or a normalized feature
132    /// whose values are all nought: music21's `FeatureException`.
133    #[error("Feature error: {0}")]
134    Feature(String),
135    /// Text that cannot be worked on as asked: an article looked for in a
136    /// language music21 knows none of.
137    #[error("Text error: {0}")]
138    Text(String),
139    /// A search that cannot be made as asked: music21's `SearchException`
140    /// and `LyricSearcherException`.
141    #[error("Search error: {0}")]
142    Search(String),
143    /// A fretboard that cannot say what it sounds: one not tuned a pitch a
144    /// string, or a note on a string it has not got.
145    #[error("Tablature error: {0}")]
146    Tablature(String),
147    /// A timespan tree that cannot answer as asked: music21's
148    /// `TreeException`.
149    #[error("Tree error: {0}")]
150    Tree(String),
151    /// A score the OMR correctors cannot work on: parts of different
152    /// lengths, where music21 runs out of measures in one of them, or a
153    /// measure with no time signature in force.
154    #[error("OMR error: {0}")]
155    Omr(String),
156    /// Variants that cannot be merged, placed or made real as asked:
157    /// music21's `VariantException`, and the errors music21 meets on the
158    /// way.
159    #[error("Variant error: {0}")]
160    Variant(String),
161    /// A MusicXML document that cannot be read, or a score that cannot be
162    /// written as one.
163    #[cfg(feature = "musicxml")]
164    #[error("MusicXML export error: {0}")]
165    MusicXml(Box<FormatError>),
166    /// A value the caller gave that nothing musical could be read from.
167    ///
168    /// music21 keeps this apart from its own exceptions — an octave written
169    /// before a pitch name, or a name that is not a string at all, is a
170    /// `ValueError` there and not a `PitchException` — and callers catch the
171    /// two separately, so the crate keeps them apart too.
172    #[error("Value error: {0}")]
173    Value(String),
174    /// Another error, placed where a reader met it: a pitch an ABC note
175    /// cannot spell, at the tune's line and measure. It reads as the error
176    /// itself; [`Error::unplaced`] hands that error back to match on.
177    #[error("{0}")]
178    Placed(Box<PlacedError>),
179    /// A preference that cannot be set or read as asked, or a settings
180    /// file that cannot be read: music21's `EnvironmentException`.
181    #[error("Environment error: {0}")]
182    Environment(String),
183}
184
185/// An error that is no format's own, and where in its document a reader
186/// met it.
187///
188/// ```
189/// use music21::error::{Error, Position};
190///
191/// let error = music21::tinynotation::from_tiny_notation("4/4 c4 e99999999999999999999");
192/// let error = error.unwrap_err();
193/// assert!(error.to_string().starts_with("Duration error: Cannot parse token"));
194/// assert!(matches!(error.unplaced(), Error::Duration(_)));
195/// assert_eq!(error.position(), Some(&Position::line(1).with_column(8)));
196/// ```
197#[derive(Clone, Debug, Eq, PartialEq)]
198#[non_exhaustive]
199pub struct PlacedError {
200    /// What went wrong.
201    pub error: Error,
202    /// Where.
203    pub position: Position,
204}
205
206impl std::fmt::Display for PlacedError {
207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208        self.error.fmt(f)
209    }
210}
211
212/// A document of one format that cannot be read or written: what kind of
213/// trouble it is, where the reader stood, and the reader's own words.
214///
215/// Its `Display` is the message alone; the [`Error`] variant holding it
216/// names the format, and holds it boxed, so that the crate's every
217/// `Result` is no wider for it.
218///
219/// ```
220/// use music21::error::{FormatErrorKind, Position};
221///
222/// let error = music21::mei::from_mei("<mei><music").unwrap_err();
223/// let read = error.format_error().unwrap();
224/// assert_eq!(read.kind, FormatErrorKind::Malformed);
225/// assert_eq!(error.position(), Some(&Position::line(1).with_column(12)));
226/// ```
227#[derive(Clone, Debug, Eq, PartialEq)]
228#[non_exhaustive]
229pub struct FormatError {
230    /// What kind of trouble it is.
231    pub kind: FormatErrorKind,
232    /// Where in the document the reader was, where it knows.
233    pub position: Option<Position>,
234    /// What the reader or writer says went wrong.
235    pub message: String,
236}
237
238impl FormatError {
239    /// An error of `kind` saying `message`, at no position yet.
240    pub fn new(kind: FormatErrorKind, message: impl Into<String>) -> Self {
241        Self {
242            kind,
243            position: None,
244            message: message.into(),
245        }
246    }
247
248    /// The same error, at `position`.
249    #[must_use]
250    pub fn with_position(mut self, position: Position) -> Self {
251        self.position = Some(position);
252        self
253    }
254}
255
256impl std::fmt::Display for FormatError {
257    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258        f.write_str(&self.message)
259    }
260}
261
262/// What kind of trouble a [`FormatError`] is.
263#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
264#[non_exhaustive]
265pub enum FormatErrorKind {
266    /// The document breaks its format's syntax: XML that is not
267    /// well-formed, a token or a number that cannot be read, a file that
268    /// ends early.
269    Malformed,
270    /// The document reads, but says something that cannot be: a value out
271    /// of range, a reference to nothing, two things that contradict.
272    Invalid,
273    /// Something the reader or the writer does not handle, or that the
274    /// format has no way to say.
275    Unsupported,
276    /// Past a limit the crate sets on what it reads, so that a hostile
277    /// file cannot exhaust memory or time: elements nested past
278    /// `MAX_DEPTH`, more than `MAX_MEASURES` measures, more than sixteen
279    /// dots.
280    TooLarge,
281    /// What was asked for is not in the document: a tune's reference
282    /// number, a part.
283    NotFound,
284    /// A program the crate runs could not be run, or failed: MuseScore.
285    External,
286}
287
288/// Where in a document a reader was when it stopped: whichever of these
289/// the reader knows. Lines and columns count from one, a column in
290/// characters.
291#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
292#[non_exhaustive]
293pub struct Position {
294    /// The line of the document.
295    pub line: Option<usize>,
296    /// The column of that line.
297    pub column: Option<usize>,
298    /// The measure, by the number the document gives it: MusicXML's are
299    /// text, `"12"` or `"X1"`.
300    pub measure: Option<String>,
301    /// The part, by the id the document gives it.
302    pub part: Option<String>,
303    /// A MIDI file's track, counted from nought.
304    pub track: Option<usize>,
305    /// A MIDI track's tick, from its start.
306    pub tick: Option<u64>,
307}
308
309impl Position {
310    /// A line of the document.
311    pub fn line(line: usize) -> Self {
312        Self {
313            line: Some(line),
314            ..Self::default()
315        }
316    }
317
318    /// A measure, by the number the document gives it.
319    pub fn measure(number: impl Into<String>) -> Self {
320        Self {
321            measure: Some(number.into()),
322            ..Self::default()
323        }
324    }
325
326    /// A MIDI file's track, counted from nought.
327    pub fn track(index: usize) -> Self {
328        Self {
329            track: Some(index),
330            ..Self::default()
331        }
332    }
333
334    /// The line and column, counted from one in characters, of the byte
335    /// `at` in `text`.
336    pub(crate) fn in_text(text: &str, at: usize) -> Self {
337        let before = &text[..at];
338        let line = before.matches('\n').count() + 1;
339        let line_start = before.rfind('\n').map_or(0, |newline| newline + 1);
340        let column = before[line_start..].chars().count() + 1;
341        Self::line(line).with_column(column)
342    }
343
344    /// The same position, at a column of its line.
345    #[must_use]
346    pub fn with_column(mut self, column: usize) -> Self {
347        self.column = Some(column);
348        self
349    }
350
351    /// The same position, in a part.
352    #[must_use]
353    pub fn in_part(mut self, part: impl Into<String>) -> Self {
354        self.part = Some(part.into());
355        self
356    }
357
358    /// The same position, at a tick of its track.
359    #[must_use]
360    pub fn at_tick(mut self, tick: u64) -> Self {
361        self.tick = Some(tick);
362        self
363    }
364
365    /// Fills what this position does not say from `outer`, which a reader
366    /// knew further out: a track around a tick.
367    fn fill_from(&mut self, outer: Position) {
368        self.line = self.line.or(outer.line);
369        self.column = self.column.or(outer.column);
370        self.measure = self.measure.take().or(outer.measure);
371        self.part = self.part.take().or(outer.part);
372        self.track = self.track.or(outer.track);
373        self.tick = self.tick.or(outer.tick);
374    }
375}
376
377impl std::fmt::Display for Position {
378    /// `line 3, column 7`, `part P1, measure 12`, `track 2, tick 960`.
379    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380        let mut said = Vec::new();
381        if let Some(part) = &self.part {
382            said.push(format!("part {part}"));
383        }
384        if let Some(measure) = &self.measure {
385            said.push(format!("measure {measure}"));
386        }
387        if let Some(track) = self.track {
388            said.push(format!("track {track}"));
389        }
390        if let Some(tick) = self.tick {
391            said.push(format!("tick {tick}"));
392        }
393        if let Some(line) = self.line {
394            said.push(format!("line {line}"));
395        }
396        if let Some(column) = self.column {
397            said.push(format!("column {column}"));
398        }
399        f.write_str(&said.join(", "))
400    }
401}
402
403/// The crate's constructor of each format's error, of kind
404/// [`FormatErrorKind::Invalid`] until a reader says otherwise.
405macro_rules! format_constructors {
406    ($($(#[$attribute:meta])* $name:ident => $variant:ident,)*) => {
407        impl Error {
408            $(
409                $(#[$attribute])*
410                pub(crate) fn $name(message: impl Into<String>) -> Self {
411                    Self::$variant(Box::new(FormatError::new(FormatErrorKind::Invalid, message)))
412                }
413            )*
414        }
415    };
416}
417
418format_constructors! {
419    midi => Midi,
420    abc => Abc,
421    humdrum => Humdrum,
422    xml => Xml,
423    mei => Mei,
424    roman_text => RomanText,
425    capella => Capella,
426    muse_data => MuseData,
427    noteworthy => Noteworthy,
428    muse_score => MuseScore,
429    #[cfg(feature = "musicxml")]
430    music_xml => MusicXml,
431}
432
433impl Error {
434    /// The error itself, without the place a reader gave it: what to match
435    /// on to ask which kind of error it is.
436    pub fn unplaced(&self) -> &Error {
437        match self {
438            Self::Placed(placed) => &placed.error,
439            _ => self,
440        }
441    }
442
443    /// The document error this is, where it is a format's: which kind of
444    /// trouble, where, and the reader's words.
445    pub fn format_error(&self) -> Option<&FormatError> {
446        match self.unplaced() {
447            Self::Midi(error)
448            | Self::Abc(error)
449            | Self::Humdrum(error)
450            | Self::Xml(error)
451            | Self::Mei(error)
452            | Self::RomanText(error)
453            | Self::Capella(error)
454            | Self::MuseData(error)
455            | Self::Noteworthy(error)
456            | Self::MuseScore(error) => Some(error.as_ref()),
457            #[cfg(feature = "musicxml")]
458            Self::MusicXml(error) => Some(error.as_ref()),
459            _ => None,
460        }
461    }
462
463    fn format_error_mut(&mut self) -> Option<&mut FormatError> {
464        match self {
465            Self::Midi(error)
466            | Self::Abc(error)
467            | Self::Humdrum(error)
468            | Self::Xml(error)
469            | Self::Mei(error)
470            | Self::RomanText(error)
471            | Self::Capella(error)
472            | Self::MuseData(error)
473            | Self::Noteworthy(error)
474            | Self::MuseScore(error) => Some(error.as_mut()),
475            #[cfg(feature = "musicxml")]
476            Self::MusicXml(error) => Some(error.as_mut()),
477            _ => None,
478        }
479    }
480
481    /// Where in its document the error was met, where the reader knows.
482    pub fn position(&self) -> Option<&Position> {
483        if let Self::Placed(placed) = self {
484            return Some(&placed.position);
485        }
486        self.format_error()?.position.as_ref()
487    }
488
489    /// Where this error says it is, to change.
490    fn position_mut(&mut self) -> Option<&mut Position> {
491        if let Self::Placed(placed) = self {
492            return Some(&mut placed.position);
493        }
494        self.format_error_mut()?.position.as_mut()
495    }
496
497    /// The same error, of `kind` where it is a format's.
498    #[must_use]
499    pub(crate) fn of_kind(mut self, kind: FormatErrorKind) -> Self {
500        if let Some(error) = self.format_error_mut() {
501            error.kind = kind;
502        }
503        self
504    }
505
506    /// The same error, its line counted `before` lines further down: a
507    /// reader that read one piece of a document places what went wrong in
508    /// the whole of it.
509    #[must_use]
510    pub(crate) fn lines_down(mut self, before: usize) -> Self {
511        let line = self
512            .position_mut()
513            .and_then(|position| position.line.as_mut());
514        if let Some(line) = line {
515            *line += before;
516        }
517        self
518    }
519
520    /// The same error, placed at what `position` says where it does not
521    /// already say it: a reader's loop over measures or tracks places what
522    /// went wrong inside one. An error that is no format's own is held in
523    /// [`Error::Placed`].
524    #[must_use]
525    pub(crate) fn placed(mut self, position: impl FnOnce() -> Position) -> Self {
526        if let Some(known) = self.position_mut() {
527            known.fill_from(position());
528            return self;
529        }
530        if let Some(error) = self.format_error_mut() {
531            error.position = Some(position());
532            return self;
533        }
534        Self::Placed(Box::new(PlacedError {
535            error: self,
536            position: position(),
537        }))
538    }
539}
540
541impl From<Infallible> for Error {
542    fn from(value: Infallible) -> Self {
543        match value {}
544    }
545}
546
547#[cfg(test)]
548mod tests {
549    use super::*;
550    use std::error::Error as StdError;
551
552    #[test]
553    fn test_display_music21object() {
554        let err = Error::Music21Object("error message".to_string());
555        assert_eq!(format!("{err}"), "Music21Object error: error message");
556    }
557
558    #[test]
559    fn test_display_chord() {
560        let err = Error::Chord("chord error".to_string());
561        assert_eq!(format!("{err}"), "Chord error: chord error");
562    }
563
564    #[test]
565    fn test_display_pitch() {
566        let err = Error::Pitch("pitch error".to_string());
567        assert_eq!(format!("{err}"), "Pitch error: pitch error");
568    }
569
570    #[test]
571    fn test_display_microtone() {
572        let err = Error::Microtone("microtone error".to_string());
573        assert_eq!(format!("{err}"), "Microtone error: microtone error");
574    }
575
576    #[test]
577    fn test_display_accidental() {
578        let err = Error::Accidental("accidental error".to_string());
579        assert_eq!(format!("{err}"), "Accidental error: accidental error");
580    }
581
582    #[test]
583    fn test_display_chordtables() {
584        let err = Error::ChordTables("chordtables error".to_string());
585        assert_eq!(format!("{err}"), "ChordTables error: chordtables error");
586    }
587
588    #[test]
589    fn test_display_interval() {
590        let err = Error::Interval("interval error".to_string());
591        assert_eq!(format!("{err}"), "Interval error: interval error");
592    }
593
594    #[test]
595    fn test_display_stepname() {
596        let err = Error::StepName("step name error".to_string());
597        assert_eq!(format!("{err}"), "StepName error: step name error");
598    }
599
600    #[test]
601    fn test_display_ordinal() {
602        let err = Error::Ordinal("ordinal error".to_string());
603        assert_eq!(format!("{err}"), "Ordinal error: ordinal error");
604    }
605
606    #[test]
607    fn test_display_polyrhythm() {
608        let err = Error::Polyrhythm("polyrhythm error".to_string());
609        assert_eq!(format!("{err}"), "Polyrhythm error: polyrhythm error");
610    }
611
612    #[test]
613    fn test_display_tuningsystem() {
614        let err = Error::TuningSystem("tuning system error".to_string());
615        assert_eq!(format!("{err}"), "TuningSystem error: tuning system error");
616    }
617
618    #[test]
619    fn a_format_error_shows_as_its_message_under_its_format() {
620        let error = Error::abc("cannot read the meter \"x\"").placed(|| Position::line(3));
621        assert_eq!(error.to_string(), "ABC error: cannot read the meter \"x\"");
622        assert_eq!(error.position(), Some(&Position::line(3)));
623        assert_eq!(error.format_error().unwrap().kind, FormatErrorKind::Invalid);
624        assert!(Error::Pitch("pitch".to_string()).format_error().is_none());
625    }
626
627    #[test]
628    fn a_position_said_inside_keeps_what_it_says() {
629        let error = Error::midi("a tempo of no time to the quarter")
630            .placed(|| Position::default().at_tick(96))
631            .placed(|| Position::track(2).at_tick(0))
632            .of_kind(FormatErrorKind::Malformed);
633        let position = error.position().unwrap();
634        assert_eq!(position, &Position::track(2).at_tick(96));
635        assert_eq!(position.to_string(), "track 2, tick 96");
636        assert_eq!(
637            Position::measure("12").in_part("P1").to_string(),
638            "part P1, measure 12"
639        );
640        assert_eq!(
641            Position::line(4).with_column(2).to_string(),
642            "line 4, column 2"
643        );
644        let shifted = Error::humdrum("x")
645            .placed(|| Position::line(2))
646            .lines_down(3);
647        assert_eq!(shifted.position(), Some(&Position::line(5)));
648    }
649
650    #[test]
651    fn an_error_of_no_format_is_placed_around_itself() {
652        let error = Error::Pitch("cannot spell it".to_string())
653            .placed(|| Position::line(4))
654            .placed(|| Position::measure("2").with_column(9))
655            .lines_down(1);
656        assert_eq!(error.to_string(), "Pitch error: cannot spell it");
657        assert_eq!(
658            error.unplaced(),
659            &Error::Pitch("cannot spell it".to_string())
660        );
661        let mut wanted = Position::line(5).with_column(9);
662        wanted.measure = Some("2".to_string());
663        assert_eq!(error.position(), Some(&wanted));
664        assert!(error.format_error().is_none());
665
666        let read = Error::abc("x").placed(|| Position::line(1));
667        assert_eq!(read.unplaced(), &read);
668    }
669
670    #[test]
671    fn test_source_none() {
672        let errors = [
673            Error::Music21Object("music21".to_string()),
674            Error::Chord("chord".to_string()),
675            Error::Pitch("pitch".to_string()),
676            Error::Microtone("microtone".to_string()),
677            Error::Accidental("accidental".to_string()),
678            Error::ChordTables("chordtables".to_string()),
679            Error::Interval("interval".to_string()),
680            Error::StepName("step".to_string()),
681            Error::PitchClass("pitch class".to_string()),
682            Error::Ordinal("ordinal".to_string()),
683            Error::Polyrhythm("polyrhythm".to_string()),
684            Error::TuningSystem("tuning system".to_string()),
685            Error::midi("midi"),
686            Error::Analysis("analysis".to_string()),
687        ];
688
689        for err in errors.iter() {
690            // Ensure that source() returns None for each Error.
691            assert!(
692                err.source().is_none(),
693                "Expected None for source() in {err:?}"
694            );
695        }
696    }
697
698    #[test]
699    fn test_all_errors_display() {
700        let cases = [
701            (
702                Error::Music21Object("music21".to_string()),
703                "Music21Object error: music21",
704            ),
705            (Error::Chord("chord".to_string()), "Chord error: chord"),
706            (Error::Pitch("pitch".to_string()), "Pitch error: pitch"),
707            (
708                Error::Microtone("microtone".to_string()),
709                "Microtone error: microtone",
710            ),
711            (
712                Error::Accidental("accidental".to_string()),
713                "Accidental error: accidental",
714            ),
715            (
716                Error::ChordTables("chordtables".to_string()),
717                "ChordTables error: chordtables",
718            ),
719            (
720                Error::Interval("interval".to_string()),
721                "Interval error: interval",
722            ),
723            (Error::StepName("step".to_string()), "StepName error: step"),
724            (
725                Error::PitchClass("pitchclass".to_string()),
726                "PitchClass error: pitchclass",
727            ),
728            (
729                Error::Ordinal("ordinal".to_string()),
730                "Ordinal error: ordinal",
731            ),
732            (
733                Error::Polyrhythm("polyrhythm".to_string()),
734                "Polyrhythm error: polyrhythm",
735            ),
736            (
737                Error::TuningSystem("tuning system".to_string()),
738                "TuningSystem error: tuning system",
739            ),
740            (Error::midi("midi"), "Midi error: midi"),
741            (
742                Error::Analysis("analysis".to_string()),
743                "Analysis error: analysis",
744            ),
745            (Error::Serial("serial".to_string()), "Serial error: serial"),
746        ];
747
748        for (err, expected) in cases.iter() {
749            assert_eq!(format!("{err}"), *expected);
750            assert!(err.source().is_none());
751        }
752    }
753}