Skip to main content

mgrs_convert/
error.rs

1//! The error enum and the parse-failure kinds.
2
3use core::fmt;
4
5/// The reasons a conversion or a parse can fail.
6///
7/// Every variant is reachable and has a test that reaches it.
8#[non_exhaustive]
9#[derive(Clone, Debug, PartialEq)]
10pub enum Error {
11    /// Latitude is NaN, infinite, or outside [-90, 90].
12    LatOutOfRange {
13        /// The rejected latitude.
14        found: f64,
15    },
16    /// Longitude is NaN or infinite.
17    LonOutOfRange {
18        /// The rejected longitude.
19        found: f64,
20    },
21    /// UTM zone outside 1 to 60, or 0 passed to [`UtmUps::utm`](crate::UtmUps::utm).
22    ZoneOutOfRange {
23        /// The rejected zone number.
24        found: u8,
25    },
26    /// Easting outside the wide transfer range for the stated grid.
27    EastingOutOfRange {
28        /// The rejected easting in metres.
29        found: f64,
30    },
31    /// Northing outside the wide transfer range for the stated grid.
32    NorthingOutOfRange {
33        /// The rejected northing in metres.
34        found: f64,
35    },
36    /// Precision outside 0 to 11.
37    PrecisionOutOfRange {
38        /// The rejected digit-pair count.
39        found: u8,
40    },
41    /// The coordinate is valid UTM or UPS but outside the MGRS grid coverage.
42    OutsideMgrsExtent {
43        /// The easting of the rejected coordinate in metres.
44        easting: f64,
45        /// The northing of the rejected coordinate in metres.
46        northing: f64,
47    },
48    /// The string is not an MGRS reference. The kind names the first rule it broke.
49    Parse(ParseErrorKind),
50}
51
52/// The first parse rule an invalid MGRS string broke.
53#[non_exhaustive]
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55pub enum ParseErrorKind {
56    /// The string is empty after trimming ASCII whitespace.
57    Empty,
58    /// A zone-only reference such as "18T". Deliberately unsupported: a cell six
59    /// degrees wide has no useful center.
60    GridZoneOnly,
61    /// The leading digits are not a zone number in 1 to 60.
62    BadZoneNumber,
63    /// The letter after the zone is not a band letter C to X (`I` and `O` excluded).
64    BadBandLetter,
65    /// A 100 km square letter is not in the MGRS alphabets.
66    BadSquareLetters,
67    /// Square letters valid in the alphabet but not for this zone.
68    SquareZoneMismatch,
69    /// The implied northing is inconsistent with the band letter.
70    SquareBandMismatch,
71    /// The digit block has an odd number of digits.
72    OddDigitCount,
73    /// The digit block has more than 22 digits.
74    TooManyDigits,
75    /// The string contains a non-ASCII byte or internal whitespace.
76    NonAsciiOrWhitespace,
77    /// A character that is neither a letter nor a digit where digits were expected.
78    TrailingInput,
79}
80
81impl fmt::Display for Error {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self {
84            Error::LatOutOfRange { found } => {
85                write!(f, "latitude {found} is not a finite value in [-90, 90]")
86            }
87            Error::LonOutOfRange { found } => {
88                write!(f, "longitude {found} is not finite")
89            }
90            Error::ZoneOutOfRange { found } => {
91                write!(f, "zone {found} is outside 1 to 60")
92            }
93            Error::EastingOutOfRange { found } => {
94                write!(f, "easting {found} m is outside the transfer range")
95            }
96            Error::NorthingOutOfRange { found } => {
97                write!(f, "northing {found} m is outside the transfer range")
98            }
99            Error::PrecisionOutOfRange { found } => {
100                write!(f, "precision {found} is outside 0 to 11")
101            }
102            Error::OutsideMgrsExtent { easting, northing } => {
103                write!(
104                    f,
105                    "easting {easting} m, northing {northing} m is outside the MGRS grid coverage"
106                )
107            }
108            Error::Parse(kind) => write!(f, "invalid MGRS string: {kind}"),
109        }
110    }
111}
112
113impl fmt::Display for ParseErrorKind {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        let msg = match self {
116            ParseErrorKind::Empty => "empty string",
117            ParseErrorKind::GridZoneOnly => "grid-zone-only reference is not supported",
118            ParseErrorKind::BadZoneNumber => "zone number is not in 1 to 60",
119            ParseErrorKind::BadBandLetter => "band letter is not C to X (I and O excluded)",
120            ParseErrorKind::BadSquareLetters => "100 km square letters are not in the alphabets",
121            ParseErrorKind::SquareZoneMismatch => "square letters do not belong to this zone",
122            ParseErrorKind::SquareBandMismatch => {
123                "square letters imply a northing outside this band"
124            }
125            ParseErrorKind::OddDigitCount => "digit block has an odd number of digits",
126            ParseErrorKind::TooManyDigits => "digit block has more than 22 digits",
127            ParseErrorKind::NonAsciiOrWhitespace => "non-ASCII byte or internal whitespace",
128            ParseErrorKind::TrailingInput => "unexpected character after the square letters",
129        };
130        f.write_str(msg)
131    }
132}
133
134impl std::error::Error for Error {}