edifact-mapper 0.4.0

EDIFACT to BO4E bidirectional conversion for the German energy market
Documentation
//! 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>),

    /// 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()
    }
}