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
130
131
132
133
134
135
136
137
//! 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),
/// A transaction view cannot be built or split back: an entity name the
/// message and a transaction both use, message-level content the views
/// disagree on, transaction-level content in a view standing for no
/// transaction, or no view at all. See
/// [`Mapper::transaction_views`](crate::Mapper::transaction_views).
#[error("transaction view: {0}")]
TransactionView(String),
}
/// 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()
}
}