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
98/// Details of [`MapperError::MissingGroupEntrySegment`].
99#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
100#[error(
101 "Cannot render PID {pid}: group {group_path} ({source_path}) would contain {present_segments:?} but not its entry segment '{entry_segment}'{}",
102 entry_segment_hint(.entities, .entry_fields)
103)]
104pub struct GroupEntrySegmentError {
105 pub pid: String,
106 /// MIG group path, e.g. `"SG4.SG8.SG10"`.
107 pub group_path: String,
108 /// The group in TOML `source_path` notation, e.g. `"sg4.sg8_z03.sg10"`.
109 pub source_path: String,
110 /// Tag of the missing entry segment, e.g. `"CCI"`.
111 pub entry_segment: String,
112 /// Tags of the segments the group would have carried.
113 pub present_segments: Vec<String>,
114 /// BO4E entities mapped from this group (e.g. `["Zaehler"]`).
115 pub entities: Vec<String>,
116 /// `Entity.field` values the entry segment is built from
117 /// (e.g. `["Zaehler.zaehlertypMerkmal"]`); supplying one fixes the input.
118 pub entry_fields: Vec<String>,
119}
120
121fn entry_segment_hint(entities: &[String], entry_fields: &[String]) -> String {
122 if !entry_fields.is_empty() {
123 format!(" (set {})", entry_fields.join(" or "))
124 } else if !entities.is_empty() {
125 format!(" (entity {})", entities.join(", "))
126 } else {
127 String::new()
128 }
129}