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