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    /// A standard I/O error.
51    #[error("IO error: {0}")]
52    Io(#[from] std::io::Error),
53}
54
55/// Details of [`MapperError::MissingGroupEntrySegment`].
56#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
57#[error(
58    "Cannot render PID {pid}: group {group_path} ({source_path}) would contain {present_segments:?} but not its entry segment '{entry_segment}'{}",
59    entry_segment_hint(.entities, .entry_fields)
60)]
61pub struct GroupEntrySegmentError {
62    pub pid: String,
63    /// MIG group path, e.g. `"SG4.SG8.SG10"`.
64    pub group_path: String,
65    /// The group in TOML `source_path` notation, e.g. `"sg4.sg8_z03.sg10"`.
66    pub source_path: String,
67    /// Tag of the missing entry segment, e.g. `"CCI"`.
68    pub entry_segment: String,
69    /// Tags of the segments the group would have carried.
70    pub present_segments: Vec<String>,
71    /// BO4E entities mapped from this group (e.g. `["Zaehler"]`).
72    pub entities: Vec<String>,
73    /// `Entity.field` values the entry segment is built from
74    /// (e.g. `["Zaehler.zaehlertypMerkmal"]`); supplying one fixes the input.
75    pub entry_fields: Vec<String>,
76}
77
78fn entry_segment_hint(entities: &[String], entry_fields: &[String]) -> String {
79    if !entry_fields.is_empty() {
80        format!(" (set {})", entry_fields.join(" or "))
81    } else if !entities.is_empty() {
82        format!(" (entity {})", entities.join(", "))
83    } else {
84        String::new()
85    }
86}