Skip to main content

Mapper

Struct Mapper 

Source
pub struct Mapper { /* private fields */ }

Implementations§

Source§

impl Mapper

Source

pub fn from_data_dir(data_dir: DataDir) -> Result<Self, MapperError>

Create a new Mapper from a DataDir configuration.

Any format versions marked as eager are loaded immediately. All others are loaded lazily on first access.

Source

pub fn conversion_service( &self, fv: &str, variant: &str, ) -> Result<ConversionService, MapperError>

Get a ConversionService for the given format version and variant.

The service can tokenize EDIFACT input and assemble it into a MIG tree.

Source

pub fn engine( &self, fv: &str, variant: &str, pid: &str, ) -> Result<MappingEngine, MapperError>

Get a MappingEngine for a specific PID within a format version and variant.

The engine can convert between assembled MIG trees and BO4E JSON.

Source

pub fn pid_requirements( &self, fv: &str, variant: &str, pid: &str, ) -> Result<PidRequirements, MapperError>

Return the [PidRequirements] for a specific PID within a format version and variant.

Requirements describe every entity and field the PID expects, including AHB status, cardinality, valid code values, and message vs transaction scope.

Source

pub fn bo4e_catalog(&self, fv: &str) -> Result<Bo4eCatalog, MapperError>

Return the PID-agnostic [Bo4eCatalog] for a format version.

The catalog contains one entry per BO4E type (BO, COM, Enum) parsed from bo4e-german source at compile-mappings time. Used by Stammdatenaufbau in downstream services.

Source

pub fn list_pids(&self) -> Result<Vec<PidListEntry>, MapperError>

List all PIDs available across all format versions found in the data directory.

Scans for edifact-data-{FV}.bin files, loads each bundle, and returns one entry per PID per variant. Results are sorted by PID.

Source

pub fn validate_pid( &self, json: &Value, fv: &str, variant: &str, pid: &str, ) -> Result<Vec<PidValidationError>, MapperError>

Validate a BO4E JSON object against PID requirements.

Returns a list of validation errors. Empty list = valid. The json should be the transaction-level stammdaten (the entity map).

Source

pub fn validate_pid_struct( &self, value: &impl Serialize, fv: &str, variant: &str, pid: &str, ) -> Result<Vec<PidValidationError>, MapperError>

Validate a typed BO4E struct against PID requirements.

Convenience wrapper that serializes the struct to JSON first. Works with any Pid*Interchange or Pid*MessageStammdaten type.

§Example
ⓘ
let interchange = build_55001_interchange();
let errors = mapper.validate_pid_struct(&interchange, "FV2504", "UTILMD_Strom", "55001")?;
assert!(errors.is_empty(), "Errors:\n{}", ValidationReport(errors));
Source

pub fn validate_pid_with_conditions( &self, json: &Value, fv: &str, variant: &str, pid: &str, ) -> Result<Vec<PidValidationError>, MapperError>

Validate with AHB condition awareness.

Reverse-maps the JSON to EDIFACT segments, evaluates AHB conditions, and reports fields as required/optional based on the actual data present.

Falls back to basic validation (without conditions) if no condition evaluator is available for the given variant/format version combination.

Source

pub fn to_edifact( &self, msg_stammdaten: &Value, tx_stammdaten: &[Value], fv: &str, variant: &str, pid: &str, ) -> Result<String, MapperError>

Convert BO4E JSON back to an EDIFACT string.

Takes message-level stammdaten, a slice of per-transaction stammdaten, and produces an EDIFACT message body (UNH through UNT content segments, without UNB/UNZ interchange envelope).

§Arguments
  • msg_stammdaten — message-level entities (e.g., Marktteilnehmer from SG2)
  • tx_stammdaten — per-transaction entities (one per transaction/SG4 instance)
  • fv — format version (e.g., “FV2504”)
  • variant — message variant (e.g., “UTILMD_Strom”)
  • pid — Pruefidentifikator (e.g., “55001”)
§Round-tripping output of from_edifact

msg_stammdaten is only half of what the forward direction produced. The message header — nachrichtentyp, nachrichtennummer, erstellungsdatum, i.e. the wire’s BGM and DTM+137 — is in nachrichtendaten, not in stammdaten, so passing stammdaten alone renders a body without its header and reports nothing (issue #158). Use to_edifact_nachricht, which takes both halves.

§Example
ⓘ
let edifact = mapper.to_edifact(
    &msg_json,
    &[tx_json],
    "FV2504",
    "UTILMD_Strom",
    "55001",
)?;
§Errors

Besides lookup failures, returns MapperError::MissingGroupEntrySegment when the BO4E fills some of a segment group’s fields but not the one its entry segment is built from — e.g. a zaehler with geraeteNummer but no zaehlertypMerkmal, which would render SG10 CAV without CCI. Such a message cannot be parsed back; its group content would be lost.

Source

pub fn to_edifact_nachricht( &self, nachricht: &Nachricht<Value, Value>, fv: &str, variant: &str, pid: &str, ) -> Result<String, MapperError>

Render one message body from a Nachricht as from_edifact produced it.

The forward direction splits a message in two: the business objects go to stammdaten, and the message header — nachrichtentyp, nachrichtennummer, erstellungsdatum, which are the BGM and DTM+137 of the wire — goes to nachrichtendaten beside it. to_edifact takes only the first half, so handing it stammdaten alone renders a body without its header and says nothing (issue #158).

This takes both, so a caller can give back what it was given:

ⓘ
let interchange = mapper.from_edifact::<Value, Value>(&edifact, fv, variant, pid)?;
let body = mapper.to_edifact_nachricht(&interchange.nachrichten[0], fv, variant, pid)?;

Only the body: the UNB/UNH/UNT/UNZ envelope is to_edifact_interchange’s job.

§Errors

As to_edifact.

Source

pub fn to_edifact_struct( &self, nachricht: &impl Serialize, fv: &str, variant: &str, pid: &str, ) -> Result<String, MapperError>

Convert a typed BO4E struct to an EDIFACT string.

Convenience wrapper that serializes the struct to JSON first. The struct should serialize to the Nachricht shape: { "stammdaten": {...}, "transaktionen": [{...}] }

Source

pub fn from_edifact<M, T>( &self, edifact: &str, fv: &str, variant: &str, pid: &str, ) -> Result<Interchange<M, T>, MapperError>

Parse an EDIFACT interchange string into a typed PID interchange struct.

Runs the full pipeline: tokenize → split messages → assemble → forward-map → deserialize. The type parameters M and T are the message-level and transaction-level stammdaten types from the generated PID module.

§Example
ⓘ
use bo4e_edifact_types::generated::fv2504::utilmd::pids::pid_55001::*;

let interchange: Interchange<Pid55001MsgStammdaten, Pid55001TxStammdaten> =
    mapper.from_edifact(edifact_str, "FV2504", "UTILMD_Strom", "55001")?;

let tx = &interchange.nachrichten[0].transaktionen[0];
println!("Vorgang: {}", tx.prozessdaten.vorgang_id);

Mapping is lossy for content the assembler cannot place: segments the PID’s AHB does not cover, and segments whose group lacks its entry segment (e.g. SG10 CAV without CCI). They have no BO4E representation and are dropped. The conversion still succeeds, so that everything else in the message is available; each dropped segment is logged as a tracing warning. Use from_edifact_with_diagnostics to inspect them in code (e.g. to reject such messages).

Source

pub fn from_edifact_with_diagnostics<M, T>( &self, edifact: &str, fv: &str, variant: &str, pid: &str, ) -> Result<(Interchange<M, T>, Vec<StructureDiagnostic>), MapperError>

from_edifact, plus the structure diagnostics raised while assembling.

A non-empty diagnostic list does not mean the conversion failed — it means the BO4E result does not represent everything the EDIFACT carried. In particular SkippedUnknownSegment marks a segment outside the PID’s AHB that the assembler advanced past, and OrphanedGroupSegment a segment the MIG defines but whose group’s entry segment is missing; in both cases its content is absent from the result.

Source

pub fn detect_pid(&self, edifact: &str) -> Result<String, MapperError>

Detect the PID (Pruefidentifikator) from a raw EDIFACT interchange.

Tokenizes the input, splits into messages, and extracts the PID from the first message using the RFF+Z13 segment (primary) or BGM+STS fallback.

This enables inbound message processing where the PID is not known upfront:

ⓘ
let pid = mapper.detect_pid(edifact_str)?;
let interchange: MyType = mapper.from_edifact(edifact_str, "FV2504", "UTILMD_Strom", &pid)?;
Source

pub fn validate_edifact( &self, edifact: &str, fv: &str, level: ValidationLevel, ) -> Result<ValidationReport, MapperError>

Validate raw EDIFACT against its AHB rules.

This is the same pipeline as the v2 API’s POST /api/v2/validate (run_validation) — both call validate_edifact_message — exposed here as a library call so consumers (e.g. mako.hive) get full raw-EDIFACT validation without running the API server. Detects the PID, resolves the owning variant + its pre-built AhbWorkflow from the loaded bundle, assembles the message, and runs the shared validation core.

Requires the bundle for fv to carry pid_ahb_workflows (baked in at compile-mappings). Returns MapperError::PidNotFound if no loaded variant has a workflow for the detected PID.

Source

pub fn validate_edifact_for_pid( &self, edifact: &str, fv: &str, variant: &str, pid: &str, level: ValidationLevel, ) -> Result<ValidationReport, MapperError>

validate_edifact, but validating against a PID the caller already knows.

Use this when the PID comes from somewhere other than the message — a form, a route, a job definition. It skips PID detection, which only works for message types that carry the Prüfidentifikator in RFF+Z13 (UTILMD); for ORDERS, MSCONS, IFTSTA and the rest, detection cannot recover a PID that the caller already has.

Source

pub fn validate_bo4e( &self, msg_stammdaten: &Value, tx_stammdaten: &[Value], fv: &str, variant: &str, pid: &str, envelope: Option<&InterchangeEnvelope>, level: ValidationLevel, ) -> Result<ValidationReport, MapperError>

Validate BO4E JSON against the AHB rules of its Prüfidentifikator.

This is validate_edifact with a reverse-mapping front end: the BO4E input is rendered to a complete EDIFACT interchange (to_edifact_interchange) and that interchange is validated. Because it is literally the same call, the findings are the ones the EDIFACT validation reports for the message this BO4E describes — including the bo4e_path enrichment that points each finding back at the BO4E field it came from. Callers working in BO4E (forms, assistants) therefore do not need their own EDIFACT-path-to-BO4E-path translation.

envelope fills UNB/UNZ. Pass None unless the message type’s MIG covers the interchange envelope (e.g. MSCONS) — for the others the envelope is outside the AHB and a neutral placeholder is used.

Two classes of finding cannot appear here, because the BO4E input has no counterpart for them: the UNT segment-count check (the trailer is regenerated) and skipped-unknown-segment diagnostics (segments outside the AHB have no BO4E representation).

Source

pub fn association_code( &self, fv: &str, variant: &str, ) -> Result<String, MapperError>

Get the UNH association code for a variant (e.g., "S2.1", "2.4c").

This is the version string from the MIG schema, used as the last component of the UNH S009 composite: UTILMD:D:11A:UN:S2.1.

§Example
ⓘ
let code = mapper.association_code("FV2604", "UTILMD_Strom")?;
assert_eq!(code, "S2.1");
Source

pub fn message_metadata( &self, fv: &str, variant: &str, ) -> Result<MessageMetadata, MapperError>

Get full message metadata for a variant, including the UNH S009 components.

Returns the message type, UN/EDIFACT release code, and association code needed to construct UNH segments.

Source

pub fn to_edifact_interchange( &self, envelope: &InterchangeEnvelope, messages: &[InterchangeMessage], ) -> Result<String, MapperError>

Convert BO4E JSON to a complete EDIFACT interchange with envelope segments.

Produces a full interchange including UNA, UNB, UNH, message body, UNT, and UNZ.

§The envelope is regenerated, not reproduced

This always emits a UNA service string advice and stamps the UNB date and time from the clock, so a render is never byte-identical to the interchange it came from: an input carrying no UNA gains one, and its interchange date becomes today (issue #161). That is right for a re-send, and wrong for a caller checking that a conversion did not change the message.

Two ways to check that instead:

Neither reproduces non-default delimiters: the whole render uses EdifactDelimiters::default.

§Example
ⓘ
let edifact = mapper.to_edifact_interchange(
    &InterchangeEnvelope {
        sender: EdifactParty::bdew("9900000000003"),
        receiver: EdifactParty::bdew("9900000000001"),
        interchange_ref: "REF001".to_string(),
    },
    &[InterchangeMessage {
        message_ref: "MSG001".to_string(),
        msg_stammdaten: serde_json::json!({"marktteilnehmer": []}),
        tx_stammdaten: vec![serde_json::json!({"prozessdaten": {"pruefidentifikator": "55001"}})],
        fv: "FV2604".to_string(),
        variant: "UTILMD_Strom".to_string(),
        pid: "55001".to_string(),
    }],
)?;
assert!(edifact.starts_with("UNA:+.? '"));
§Errors

Fails like to_edifact, including MapperError::MissingGroupEntrySegment for a group that would be rendered without its entry segment.

Source

pub fn to_edifact_interchange_with( &self, envelope: &InterchangeEnvelope, messages: &[InterchangeMessage], options: &EnvelopeOptions, ) -> Result<String, MapperError>

Like to_edifact_interchange, with control over how the envelope is built.

The default regenerates it — a fresh UNA and a UNB timestamped from the clock — which is right for a re-send but means a render can never equal its input. EnvelopeOptions lets a caller that has the original ask for it back instead (issue #161).

§Errors

As to_edifact_interchange.

Source

pub fn loaded_format_versions(&self) -> Vec<String>

List all format versions currently loaded in memory.

Source

pub fn variants(&self, fv: &str) -> Result<Vec<String>, MapperError>

List all variants available in a format version’s bundle.

Loads the bundle if not already loaded.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more