chordsketch_convert/error.rs
1//! Error and warning types shared by every conversion direction.
2
3/// Reason a conversion can fail.
4///
5/// The `NotImplemented` variant is the placeholder every
6/// pre-implementation function returns. Subsequent issues
7/// (#2053 iReal→ChordPro, #2061 ChordPro→iReal) replace those
8/// returns with real implementations; new variants are added at
9/// the bottom of the enum to preserve compatibility with code that
10/// matches on the existing variants.
11///
12/// Marked `#[non_exhaustive]` so adding a new variant in a follow-up
13/// PR is non-breaking for downstream `match` expressions, matching
14/// the additive-evolution contract documented at the crate root.
15///
16/// # Note for future implementers
17///
18/// Once #2053 / #2061 land, [`Self::InvalidSource`] and
19/// [`Self::UnrepresentableTarget`] will carry parser-derived,
20/// potentially attacker-controlled text. Implementations SHOULD
21/// truncate or sanitise their messages before constructing these
22/// variants; downstream `Display` consumers and log forwarders
23/// MUST NOT assume bounded length until that bound is enforced
24/// upstream. (Same pattern as `chordsketch_ireal::json::truncate_for_message`.)
25#[derive(Debug, Clone, PartialEq, Eq)]
26#[non_exhaustive]
27pub enum ConversionError {
28 /// The conversion direction is recognised but not yet
29 /// implemented in this crate version. The contained `&'static
30 /// str` is the URL of the issue tracking the implementation
31 /// so callers can give a useful diagnostic.
32 NotImplemented(&'static str),
33 /// The source value was structurally invalid and could not be
34 /// converted at all (distinct from a lossy-but-successful
35 /// conversion, which returns `Ok` with warnings).
36 InvalidSource(String),
37 /// The conversion would produce a target value that is not
38 /// representable in the target format (e.g. an out-of-range
39 /// time signature on the iReal side).
40 UnrepresentableTarget(String),
41}
42
43impl std::fmt::Display for ConversionError {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 match self {
46 Self::NotImplemented(tracking_url) => {
47 write!(
48 f,
49 "conversion not yet implemented (tracked at {tracking_url})"
50 )
51 }
52 Self::InvalidSource(msg) => write!(f, "invalid source: {msg}"),
53 Self::UnrepresentableTarget(msg) => {
54 write!(f, "unrepresentable target: {msg}")
55 }
56 }
57 }
58}
59
60impl std::error::Error for ConversionError {}
61
62/// A non-fatal information loss recorded during conversion.
63///
64/// Conversions return `Ok(ConversionOutput { warnings, .. })` for
65/// lossy-but-successful runs; callers decide whether to fail on a
66/// non-empty warning list. This keeps the strictness policy in the
67/// caller's hands rather than baking it into the converter.
68///
69/// Marked `#[non_exhaustive]` so additional diagnostic fields can be
70/// appended in a follow-up PR without breaking downstream code that
71/// currently constructs `ConversionWarning` values only via
72/// [`ConversionWarning::new`].
73#[derive(Debug, Clone, PartialEq, Eq)]
74#[non_exhaustive]
75pub struct ConversionWarning {
76 /// Class of information loss.
77 pub kind: WarningKind,
78 /// Human-readable description of what was dropped or
79 /// approximated.
80 pub message: String,
81}
82
83impl ConversionWarning {
84 /// Constructs a warning with the given kind and message.
85 #[must_use]
86 pub fn new(kind: WarningKind, message: impl Into<String>) -> Self {
87 Self {
88 kind,
89 message: message.into(),
90 }
91 }
92}
93
94/// Class of information loss in a [`ConversionWarning`].
95///
96/// New variants are appended; existing variants are stable across
97/// minor versions. Marked `#[non_exhaustive]` so adding a variant
98/// is non-breaking for downstream `match` arms.
99///
100/// `Copy` is intentional here because every variant is fieldless,
101/// matching the per-warning struct (`ConversionWarning`) which
102/// owns the attached message and is therefore not `Copy`.
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104#[non_exhaustive]
105pub enum WarningKind {
106 /// A feature in the source format has no equivalent in the
107 /// target format and was dropped (e.g. lyrics on a
108 /// ChordPro→iReal conversion — iReal has no lyrics surface).
109 LossyDrop,
110 /// A feature in the source format was approximated to the
111 /// nearest equivalent in the target format (e.g. an unusual
112 /// section label mapped to the closest iReal letter).
113 Approximated,
114 /// A feature in the source format is not yet supported by the
115 /// converter, even though the target format could in principle
116 /// represent it. Distinct from `LossyDrop` because resolving
117 /// it is a future converter-side change rather than an inherent
118 /// format limitation.
119 Unsupported,
120}