Skip to main content

target_match/
error.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Error type for `target-match`.
6//!
7//! Every fallible entry point returns [`Result`]. Coordinate parsing and domain
8//! checks happen in [`skymath`] when the consumer builds an
9//! [`skymath::Equatorial`]. This crate validates optics, captured boundaries,
10//! common-plane projections, and caller-supplied geometry search parameters.
11
12use thiserror::Error;
13
14/// Everything that can go wrong constructing `target-match` values.
15///
16/// # Example
17///
18/// ```
19/// use target_match::{Error, Field, Optics};
20///
21/// let err = Field::from_optics(Optics {
22///     focal_mm: 0.0, pixel_um: (3.76, 3.76), binning: (1, 1), pixels: (6248, 4176),
23/// })
24/// .unwrap_err();
25/// assert!(matches!(err, Error::InvalidOptics(_)));
26/// ```
27#[derive(Debug, Clone, PartialEq, Error)]
28#[non_exhaustive]
29pub enum Error {
30    /// Optics or field inputs were non-positive, non-finite, or insufficient to
31    /// derive a field of view.
32    #[error("invalid optics: {0}")]
33    InvalidOptics(String),
34    /// An ordered captured boundary is degenerate, self-intersecting, or does
35    /// not contain its declared centre.
36    #[error("invalid footprint {provenance:?}: {reason}")]
37    InvalidFootprint {
38        /// Caller-supplied evidence identity.
39        provenance: String,
40        /// Validation failure.
41        reason: String,
42    },
43    /// Compared geometry uses different coordinate epochs.
44    #[error("footprint coordinates must use one epoch")]
45    FootprintEpochMismatch,
46    /// A common tangent anchor cannot be selected for antipodal geometry.
47    #[error("antipodal geometry has no unique common tangent plane")]
48    AntipodalGeometry,
49    /// A point is at or beyond the selected gnomonic projection horizon.
50    #[error("gnomonic projection failed for {0}")]
51    ProjectionFailed(String),
52    /// A footprint union requires at least one input.
53    #[error("footprint union requires at least one footprint")]
54    EmptyFootprintSet,
55    /// Per-panel evidence requires unique provenance identities.
56    #[error("duplicate footprint provenance: {0}")]
57    DuplicateFootprintProvenance(String),
58    /// A normalized coverage band is non-finite, reversed, or outside `[0, 1]`.
59    #[error("invalid coverage band: {0}")]
60    InvalidCoverageBand(String),
61    /// A residual-rotation search domain or resolution is invalid.
62    #[error("invalid rotation search: {0}")]
63    InvalidRotationSearch(String),
64    /// An ellipse has invalid axes, orientation, or sampling resolution.
65    #[error("invalid ellipse: {0}")]
66    InvalidEllipse(String),
67    /// Caller-supplied object geometry cannot produce a positive planar area.
68    #[error("invalid object geometry: {0}")]
69    InvalidObjectGeometry(String),
70}
71
72/// Convenience alias for `Result<T, `[`Error`](enum@Error)`>`.
73pub type Result<T> = core::result::Result<T, Error>;
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn display_messages() {
81        assert_eq!(
82            Error::InvalidOptics("focal <= 0".into()).to_string(),
83            "invalid optics: focal <= 0"
84        );
85        assert_eq!(
86            Error::AntipodalGeometry.to_string(),
87            "antipodal geometry has no unique common tangent plane"
88        );
89    }
90}