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