Skip to main content

edifact_mapper/
error.rs

1//! Error types for the edifact-mapper facade crate.
2
3/// Errors that can occur when using the [`Mapper`](crate::Mapper) API.
4#[derive(Debug, thiserror::Error)]
5pub enum MapperError {
6    /// The specified data directory does not exist on disk.
7    #[error("Data directory not found: {path}")]
8    DataDirNotFound { path: String },
9
10    /// No data bundle file found for the requested format version.
11    #[error("No data bundle found for format version {fv}")]
12    BundleNotFound { fv: String },
13
14    /// The requested variant (e.g., "UTILMD_Strom") is not present in the bundle.
15    #[error("No variant '{variant}' in bundle for {fv}")]
16    VariantNotFound { fv: String, variant: String },
17
18    /// No mapping engine definitions exist for the given PID within the variant.
19    #[error("No mapping engine for PID {pid} in {fv}/{variant}")]
20    PidNotFound {
21        fv: String,
22        variant: String,
23        pid: String,
24    },
25
26    /// An error from the MIG assembly layer.
27    #[error("Assembly error: {0}")]
28    Assembly(#[from] mig_assembly::AssemblyError),
29
30    /// An error from the TOML mapping engine.
31    #[error("Mapping error: {0}")]
32    Mapping(#[from] mig_bo4e::MappingError),
33
34    /// JSON serialization failed (e.g., when converting a typed struct to JSON).
35    #[error("Serialization error: {0}")]
36    Serialization(String),
37
38    /// The variant has no MIG schema (required for reverse pipeline).
39    #[error("No MIG schema for {fv}/{variant}")]
40    NoMigSchema { fv: String, variant: String },
41
42    /// The BO4E input fills a group's segments but not the segment that opens
43    /// the group in the MIG (e.g. `Zaehler.geraeteNummer` without
44    /// `Zaehler.zaehlertypMerkmal` would yield SG10 `CAV` without `CCI`).
45    /// Rendering it would produce EDIFACT no receiver can assemble, so the
46    /// conversion is refused. Boxed to keep `MapperError` small.
47    #[error(transparent)]
48    MissingGroupEntrySegment(Box<GroupEntrySegmentError>),
49
50    /// An [`EnvelopeOptions`](crate::EnvelopeOptions) date or time is not the
51    /// shape `UNB` takes.
52    ///
53    /// The value is written into the interchange header verbatim, so a wrongly
54    /// formatted one produces a malformed outermost envelope — the segment
55    /// whose defects surface at the receiving gateway rather than anywhere the
56    /// sender can see them. `datum` is `yymmdd` and `zeit` is `hhmm`, both
57    /// digits only.
58    #[error("UNB {field} must be {expected} ({digits} digits), got {value:?}")]
59    MalformedEnvelopeDateTime {
60        /// `"datum"` or `"zeit"`.
61        field: &'static str,
62        /// The expected pattern, e.g. `"yymmdd"`.
63        expected: &'static str,
64        /// How many digits that pattern is.
65        digits: usize,
66        /// What the caller supplied.
67        value: String,
68    },
69
70    /// The data bundle was produced by a different release than this crate.
71    ///
72    /// The bundle's serialisation format can be current while its mappings,
73    /// schemas and code lists are from another era — that combination loads
74    /// cleanly and renders a smaller message rather than failing (issue #158).
75    #[error(
76        "Data bundle for {fv} was produced by {}, but this is edifact-mapper {expected}. \
77         Re-download the bundle for this release (`edifact-data update`), or call \
78         DataDir::allow_bundle_from_other_release(true) if the mismatch is deliberate. \
79         Bundle: {path}",
80        built_by.as_deref().unwrap_or("a release before bundles recorded one")
81    )]
82    BundleFromOtherRelease {
83        /// Format version of the bundle.
84        fv: String,
85        /// The release that produced it, if it recorded one.
86        built_by: Option<String>,
87        /// The release reading it.
88        expected: String,
89        /// Where the bundle was read from.
90        path: String,
91    },
92
93    /// A standard I/O error.
94    #[error("IO error: {0}")]
95    Io(#[from] std::io::Error),
96
97    /// A transaction view cannot be built or split back: an entity name the
98    /// message and a transaction both use, message-level content the views
99    /// disagree on, transaction-level content in a view standing for no
100    /// transaction, or no view at all. See
101    /// [`Mapper::transaction_views`](crate::Mapper::transaction_views).
102    #[error("transaction view: {0}")]
103    TransactionView(String),
104}
105
106/// Details of [`MapperError::MissingGroupEntrySegment`].
107#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
108#[error(
109    "Cannot render PID {pid}: group {group_path} ({source_path}) would contain {present_segments:?} but not its entry segment '{entry_segment}'{}",
110    entry_segment_hint(.entities, .entry_fields)
111)]
112pub struct GroupEntrySegmentError {
113    pub pid: String,
114    /// MIG group path, e.g. `"SG4.SG8.SG10"`.
115    pub group_path: String,
116    /// The group in TOML `source_path` notation, e.g. `"sg4.sg8_z03.sg10"`.
117    pub source_path: String,
118    /// Tag of the missing entry segment, e.g. `"CCI"`.
119    pub entry_segment: String,
120    /// Tags of the segments the group would have carried.
121    pub present_segments: Vec<String>,
122    /// BO4E entities mapped from this group (e.g. `["Zaehler"]`).
123    pub entities: Vec<String>,
124    /// `Entity.field` values the entry segment is built from
125    /// (e.g. `["Zaehler.zaehlertypMerkmal"]`); supplying one fixes the input.
126    pub entry_fields: Vec<String>,
127}
128
129fn entry_segment_hint(entities: &[String], entry_fields: &[String]) -> String {
130    if !entry_fields.is_empty() {
131        format!(" (set {})", entry_fields.join(" or "))
132    } else if !entities.is_empty() {
133        format!(" (entity {})", entities.join(", "))
134    } else {
135        String::new()
136    }
137}