Skip to main content

tp_lib_core/detections/
error.rs

1//! Detection error types (T006).
2//!
3//! Thin domain error per Constitution VIII (typed, fail-fast).
4
5use chrono::{DateTime, FixedOffset};
6use thiserror::Error;
7
8/// Errors produced by the detections pipeline (load/validate/filter/resolve).
9///
10/// `ConflictingDetections` and `UnknownNetelement` are FATAL per data-model.md
11/// and abort path calculation. All other variants surface either as parse-time
12/// failures or as recoverable `DiscardReason`s in `DetectionRecord`.
13#[derive(Debug, Error)]
14pub enum DetectionError {
15    /// Input file extension is not `.csv`, `.geojson`, or `.json`.
16    #[error("unsupported detections file extension: {0:?}")]
17    UnsupportedExtension(String),
18
19    /// Required column / property missing or malformed schema.
20    #[error("invalid detections schema: {0}")]
21    InvalidSchema(String),
22
23    /// Generic parser failure (CSV row, GeoJSON feature).
24    #[error("detection parse error at {source_file}:{source_row}: {message}")]
25    Parse {
26        source_file: String,
27        source_row: usize,
28        message: String,
29    },
30
31    /// Timestamp could not be parsed or lacked a timezone offset.
32    #[error("invalid timestamp at {source_file}:{source_row}: {message}")]
33    InvalidTimestamp {
34        source_file: String,
35        source_row: usize,
36        message: String,
37    },
38
39    /// `intrinsic` / `start_intrinsic` / `end_intrinsic` not in `[0, 1]`.
40    #[error("invalid intrinsic value {value} at {source_file}:{source_row} (must be in [0, 1])")]
41    InvalidIntrinsic {
42        source_file: String,
43        source_row: usize,
44        value: f64,
45    },
46
47    /// Coordinate row supplied without a `crs` column / property.
48    #[error(
49        "missing crs at {source_file}:{source_row}: coordinate detections require an explicit CRS"
50    )]
51    MissingCrs {
52        source_file: String,
53        source_row: usize,
54    },
55
56    /// Two punctual detections at the same timestamp resolve to different netelements (FATAL, D4).
57    #[error(
58        "conflicting detections at {timestamp}: netelement '{netelement_a}' vs '{netelement_b}'"
59    )]
60    ConflictingDetections {
61        timestamp: DateTime<FixedOffset>,
62        netelement_a: String,
63        netelement_b: String,
64    },
65
66    /// Linear detection has `t_from > t_to`.
67    #[error(
68        "invalid time range at {source_file}:{source_row}: t_from ({t_from}) is after t_to ({t_to})"
69    )]
70    InvalidTimeRange {
71        source_file: String,
72        source_row: usize,
73        t_from: DateTime<FixedOffset>,
74        t_to: DateTime<FixedOffset>,
75    },
76
77    /// `netelement_id` does not exist in the supplied railway network (FATAL, FR-006).
78    #[error("unknown netelement '{netelement_id}' at {source_file}:{source_row}")]
79    UnknownNetelement {
80        source_file: String,
81        source_row: usize,
82        netelement_id: String,
83    },
84
85    /// Internal invariant violation: a detection resolved twice.
86    #[error("duplicate resolution for detection at {source_file}:{source_row}")]
87    DuplicateResolution {
88        source_file: String,
89        source_row: usize,
90    },
91
92    /// Wrapped `std::io::Error`.
93    #[error("detection IO error: {0}")]
94    Io(#[from] std::io::Error),
95}