edi-energy 0.20.0

EDI@Energy EDIFACT parser and validator for the German energy market
Documentation
//! `edi-energy` — EDI@Energy EDIFACT parser and validator for the German energy market.
//!
//! # Quick start
//!
//! ```rust,no_run
//! use edi_energy::{Platform, AnyMessage, EdiEnergyMessage};
//!
//! let input: &[u8] = b"UNB+UNOA:3+SENDER+RECEIVER+200101:0900+1'UNH+1+UTILMD:D:11A:UN:S2.2'BGM+E01+11001+9'UNT+2+1'UNZ+1+1'";
//! let msg = Platform::with_all_profiles().parse(input)?;
//! let report = msg.validate()?;
//! assert!(report.is_valid());
//! # Ok::<(), edi_energy::Error>(())
//! ```
//!
//! # Profiles
//!
//! Every Formatversion of every message type is a [`Profile`]: the MIG's
//! Nachrichtenstruktur and Segmentlayouts plus the AHB's Prüfschablonen,
//! extracted from the BDEW publications and embedded at build time. A message
//! with a wire release code no embedded profile carries parses but cannot be
//! validated ([`Error::ProfileNotFound`]); [`Profile::from_json`] loads a
//! profile from any source into a [`ReleaseRegistry`] of your own.

#![deny(unsafe_code)]
#![deny(clippy::undocumented_unsafe_blocks)]
#![deny(missing_docs)]
#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
// `Error::Validation` intentionally carries a full `EdiEnergyReport` for rich diagnostics.
// Boxing it would change the public pattern-matching API and is not worth the churn.
#![allow(clippy::result_large_err)]
#![allow(clippy::must_use_candidate)]
// Builder methods that return `Self` all have obvious must-use semantics.
#![allow(clippy::return_self_not_must_use)]

mod agency_code;
mod any_message;
mod custom_rule_pack;
mod error;
mod interchange;
mod light_message;
mod lokationstyp;
mod message;
mod message_type;
mod parse;
mod pid_scan;
mod platform;
mod pruefidentifikator;
mod release;
mod report;

/// Fluent builder APIs for constructing EDI@Energy messages.
pub mod builders;
/// Concrete EDI@Energy message type structs, one sub-module per message type.
pub mod messages;
/// The loaded MIG/AHB profiles: message structure, Prüfschablonen, Bedingungen.
pub mod profile;
/// Profile registry mapping `(MessageType, Release)` pairs to validation rules.
pub mod registry;
pub mod utilmd_codes;

// `register_profiles` and the `releases` module, generated by build.rs from
// the profile directories.
include!(concat!(env!("OUT_DIR"), "/profiles.rs"));

pub use agency_code::AgencyCode;
pub use any_message::AnyMessage;
pub use custom_rule_pack::CustomRulePack;
pub use error::{Error, ProfileError};
pub use interchange::{InterchangeHeader, MessageEnvelope, ParsedInterchange, ReceiptContext};
pub use light_message::LightMessage;
pub use lokationstyp::Lokationstyp;
pub use message::EdiEnergyMessage;
pub use message_type::MessageType;
pub use parse::{
    DEFAULT_MAX_SEGMENT_BYTES, InterchangeIter, ParseConfig, Parser, parse, parse_envelope_only,
    parse_interchange,
};
pub use platform::Platform;
pub use profile::{Profile, Pruefschablone};
pub use pruefidentifikator::{Pruefidentifikator, ablehnung_pid, answer_pids, bestaetigung_pid};
pub use registry::{
    DEFAULT_RECEIVE_TOLERANCE_DAYS, ProcessContext, ReleaseRegistry, TransitionState,
};
pub use release::{Release, ReleaseKind, ReleaseTrack};
pub use report::{EdiEnergyReport, RuleOrigin};

// Re-export edifact-rs types users may need
pub use edifact_rs::{
    EdifactDeserialize, EdifactSerialize, ReaderConfig, ValidationIssue, ValidationReport,
    ValidationSeverity,
};
// ValidationIssueSummary is unconditionally available.
// serde::Serialize is available on the type when the `serde` feature is enabled.
pub use report::ValidationIssueSummary;

// Re-export typed hierarchy structs (segment groups) for each message type.
#[cfg(feature = "aperak")]
pub use messages::aperak::AperakError;
#[cfg(feature = "contrl")]
pub use messages::contrl::{ContrlElementError, ContrlMessageResponse, ContrlSegmentError};
#[cfg(feature = "mscons")]
pub use messages::mscons::{
    MsconsDeliveryPoint, MsconsLineItem, MsconsQuantity, MsconsReference, MsconsTimeSeries,
};
#[cfg(feature = "utilmd")]
pub use messages::utilmd::{UtilmdReference, UtilmdTransaction};

/// Validate `msg` and additionally enforce that its Prüfidentifikator matches
/// `expected`.
///
/// This is the free-function replacement for the former
/// `EdiEnergyMessage::validate_pruefidentifikator` trait method.  Splitting the
/// concern out of the trait reduces boilerplate in every implementation and
/// keeps the trait surface minimal.
///
/// Behaviour:
/// - Calls [`EdiEnergyMessage::validate`] to obtain the standard validation
///   report (all layers L1–L5).
/// - If [`EdiEnergyMessage::detect_pruefidentifikator`] returns `Ok(pid)` and
///   `pid == expected`, the report is returned unchanged.
/// - If the detected PID does not match `expected`, or if no PID can be
///   detected, a rule-`EE-PID-001` error is appended to the report and the
///   (now-invalid) report is returned.
///
/// # Errors
///
/// Returns `Err` only when [`EdiEnergyMessage::validate`] itself fails (e.g.
/// parse failure, profile not registered).  A PID mismatch is always surfaced
/// as a validation issue inside the returned `Ok(EdiEnergyReport)`, not as an
/// `Err`.
#[must_use = "validation result must be checked for errors"]
pub fn validate_and_check_pid(
    msg: &impl EdiEnergyMessage,
    expected: Pruefidentifikator,
) -> Result<EdiEnergyReport, Error> {
    let report = msg.validate()?;
    match msg.detect_pruefidentifikator() {
        Ok(actual) if actual == expected => Ok(report),
        Ok(actual) => {
            let mut inner = report.into_inner();
            inner.add_error(
                edifact_rs::ValidationIssue::new(
                    edifact_rs::ValidationSeverity::Error,
                    format!("expected Pruefidentifikator {expected}, found {actual}"),
                )
                .with_rule_id("EE-PID-001")
                .with_segment("BGM"),
            );
            Ok(EdiEnergyReport::new(inner))
        }
        Err(_) => {
            let mut inner = report.into_inner();
            inner.add_error(
                edifact_rs::ValidationIssue::new(
                    edifact_rs::ValidationSeverity::Error,
                    format!("expected Pruefidentifikator {expected}, but none was found"),
                )
                .with_rule_id("EE-PID-001")
                .with_segment("BGM"),
            );
            Ok(EdiEnergyReport::new(inner))
        }
    }
}