Sign in

    MoonEdifact

    Bounded byte-oriented UN/EDIFACT syntax 3 parser, envelope validator and native CLI

    edifact
    edi
    parser
    validation
    Download zip
    Version
    0.1.1
    License
    Apache-2.0
    Last updated
    11 hours ago
    Downloads
    2

    #MoonEDIFACT

    MoonBit 的 UN/EDIFACT 字节解析与封套检查库,附 native 文件 CLI。0.1.0 初验版支持语法版本 3、默认/自定义 UNA、release 转义和单交换中的多条直接消息,返回通用模型、原始字节范围和稳定诊断。源码仓库:ShiChengxu-creater/MoonEdifact。

    结果分别报告 syntax、envelope、rules。本版业务规则始终为 not_checked;is_valid() 只表示语法和封套通过。未知业务段会保留在模型中,ORDERS、INVOIC 等消息的业务顺序、代码表和伙伴规则需由调用者处理。

    #支持矩阵

    能力0.1.0 状态
    语法版本 3;UNOA 限定字符;UNOB 可打印 ASCII 子集支持
    UNA 或默认 : + . ? 空格 ';自定义服务字符支持
    转义、空元素/组件、原始半开字节范围支持
    单交换中的直接单/多消息,UNB/UNZ、UNH/UNT 顺序、引用及计数支持
    核心库 native / JavaScript / wasm-gc本地三个目标验证通过
    文件 CLInative;Windows 已验证,Linux 由提交后的 CI 验证
    语法版本 4、功能组、交互式报文、连续交换明确拒绝
    UTF-8/非 ASCII 值、完整业务目录、业务规则、增量解析、序列化未实现

    准确字符范围和服务字段检查边界见 语法配置;本版不声明完整 ISO 9735 或业务目录合规。

    #从源码运行

    已验证工具链:moon 0.1.20260920 (914d7da)、moonc v0.10.14+7d59c7ec9。CI 使用官方安装器的固定发行参数 0.10.14+7d59c7ec9。native 需要可用的 C 编译器;JS 需要 Node.js;wasm-gc 使用工具链中的 moonrun。辅助验收脚本需要 Python 3.12 或更新版本,只使用标准库。

    安装 MoonBit 的入口是 官方下载页。取得本仓库源码后,在仓库根目录执行:

    git clone https://github.com/ShiChengxu-creater/MoonEdifact.git cd MoonEdifact moon check --deny-warn moon test --deny-warn moon build --target native --deny-warn moon run examples/parse_minimal moon run cmd/main -- inspect testdata/minimal.edi --json moon run cmd/main -- validate testdata/minimal.edi

    验证成功的文本输出:

    syntax=passed envelope=passed rules=not_checked messages=1 segments=5

    五个可运行示例、预期输出和目标限制见 examples/README.md。python scripts/cli_smoke.py 检查实际二进制与库的输出一致。

    #嵌入 MoonBit 项目

    包名为 ShiChengxu/MoonEdifact,当前发行版本为 0.1.1。Mooncakes 页面:ShiChengxu/MoonEdifact。在消费项目中执行:

    moon add ShiChengxu/MoonEdifact@0.1.1

    在消费包的 moon.pkg 添加:

    import {
    "ShiChengxu/MoonEdifact" @edifact,
    }

    调用示例:

    ///|
    fn main {
    let input = b"UNB+UNOA:3+S+R+261011:1200+I'UNH+1+ORDERS:D:96A:UN'BGM+220+X'UNT+3+1'UNZ+1+I'"
    let limits = { ..@edifact.default_limits(), max_input_bytes: 1048576, }
    let result = @edifact.parse(input, limits~)
    println(@edifact.report_text(result))
    if result.document is Some(doc) {
    println(doc.segments[0].tag)
    }
    }

    公开接口包括 parse、detect_syntax、validate_envelope、default_limits、report_json、report_text 与 ParseResult::is_valid;完整签名见 pkg.generated.mbti。推荐通过 parse 接收不可信字节;独立 validate_envelope 面向已解析、保持模型不变量的 Document。

    Component.value 是去除 release 后的 Bytes;Component.span 仍对应原始值字节。所有范围为从 0 开始的 [start,end);段序号从 1 开始,UNA 不计作段。消息的 first_segment、last_segment 均包含边界服务段。

    发布前可执行 python scripts/clean_consumer.py,从当前 Git 提交创建无构建缓存的独立消费项目,在三个目标测试公开 API。它使用本地 workspace 依赖,不能替代 Mooncakes 注册表安装证明。

    #CLI 与输入限制

    native 构建产物在本地 Windows 为 _build/native/debug/build/cmd/main/main.exe,可复制或重命名为 moonedifact.exe:

    moonedifact inspect file.edi --json moonedifact validate file.edi moonedifact validate file.edi --json --max-bytes 1048576 moonedifact inspect file.edi --json --show-values

    参数ParseLimits 字段默认上限
    --max-bytesmax_input_bytes8,388,608 字节
    --max-segmentsmax_segments100,000 段
    --max-segment-bytesmax_segment_bytes65,536 原始字节,含终止符
    --max-elementsmax_elements每段 1,024 个数据元
    --max-componentsmax_components每数据元 128 个组件
    --max-diagnosticsmax_diagnostics100 条诊断

    所有上限必须是正的 32 位十进制整数。输入仅接受单个普通文件;使用 -- 可传入以短横线开头的文件名。CLI 先检查文件大小,再以至多 4 KiB 的读取缓冲累计到预算,文件变大也不能绕过输入上限。解析仍构建完整 AST,内存会随输入和结构数量增长。

    退出码含义
    0语法/封套通过,或 help/version 正常完成
    1报文语法、封套或资源上限错误
    2参数、文件打开/读取等 I/O 错误

    报告写 stdout,用法/I/O 错误写 stderr。默认 JSON 仅含结构、长度、位置和诊断,省略值和引用;--show-values 必须同时指定 --json,会公开全部逻辑值。结构信息也可能敏感,见 安全说明。

    典型错误:E_MESSAGE_COUNT 表示 UNT 段数错误,计数含 UNH 与 UNT;E_MESSAGE_REFERENCE 表示 UNT 与 UNH 引用不一致;S_VERSION_UNSUPPORTED 拒绝版本 4;R_INPUT_BYTES 表示输入超过预算。完整代码表及 JSON 契约见 诊断说明。

    #验证与文档

    moon info moon fmt moon fmt --check moon check --target native --deny-warn moon build --target native --deny-warn moon test --target native --deny-warn moon check --target js --deny-warn moon build --target js --deny-warn moon test --target js --deny-warn moon check --target wasm-gc --deny-warn moon build --target wasm-gc --deny-warn moon test --target wasm-gc --deny-warn python scripts/repository_checks.py python scripts/examples_smoke.py python scripts/cli_smoke.py python scripts/clean_consumer.py moon package python scripts/package_checks.py

    测试数量、环境、畸形语料及属性测试方法见 测试说明。CI 在 push、PR 或手动运行时执行相同检查;不配置自动发布。初验进度与待维护者完成的远程步骤见 初验记录 和 发布清单。

    实现采用 Apache-2.0:LICENSE。规范、原创测试数据和工具链依赖的边界见 来源、第三方材料;AI 辅助范围见 AI_USAGE.md。贡献流程见 CONTRIBUTING.md。

    CheckStatus

    pub(all) enum CheckStatus {
    Passed
    Failed
    NotChecked
    } derive(Eq,
    Debug
    )

    A separate state for each validation layer.

    CheckStatus::equal

    fn CheckStatus::equal(CheckStatus, CheckStatus) -> Bool

    CheckStatus::not_equal

    fn CheckStatus::not_equal(x : CheckStatus, y : CheckStatus) -> Bool

    Component

    pub(all) struct Component {
    value : Bytes
    span : SourceSpan
    } derive(Eq,
    Debug
    )

    A logical byte value and its raw span, including release markers.

    Component::equal

    fn Component::equal(Component, Component) -> Bool

    Component::not_equal

    fn Component::not_equal(x : Component, y : Component) -> Bool

    Diagnostic

    pub(all) struct Diagnostic {
    code : String
    layer : Layer
    span : SourceSpan
    segment_index : Int?
    severity : String
    message : String
    hint : String?
    } derive(Eq,
    Debug
    )

    All diagnostics are errors in 0.1.0. Messages never include input field values.

    Diagnostic::equal

    fn Diagnostic::equal(Diagnostic, Diagnostic) -> Bool

    Diagnostic::not_equal

    fn Diagnostic::not_equal(x : Diagnostic, y : Diagnostic) -> Bool

    Document

    pub(all) struct Document {
    profile : SyntaxProfile
    segments : Array[Segment]
    span : SourceSpan
    } derive(Eq,
    Debug
    )

    Generic syntax model. Business segment semantics are not checked.

    Document::equal

    fn Document::equal(Document, Document) -> Bool

    Document::not_equal

    fn Document::not_equal(x : Document, y : Document) -> Bool

    Document::to_repr

    Element

    pub(all) struct Element {
    components : Array[Component]
    span : SourceSpan
    } derive(Eq,
    Debug
    )

    Components are never dropped, including empty leading, middle and trailing values.

    Element::equal

    fn Element::equal(Element, Element) -> Bool

    Element::not_equal

    fn Element::not_equal(x : Element, y : Element) -> Bool

    Element::to_repr

    EnvelopeResult

    pub(all) struct EnvelopeResult {
    interchange : Interchange?
    diagnostics : Array[Diagnostic]
    status : CheckStatus
    } derive(Eq,
    Debug
    )

    Envelope validation independently reports its status and diagnostics.

    EnvelopeResult::equal

    EnvelopeResult::not_equal

    fn EnvelopeResult::not_equal(x : EnvelopeResult, y : EnvelopeResult) -> Bool

    Interchange

    pub(all) struct Interchange {
    messages : Array[Message]
    span : SourceSpan
    } derive(Eq,
    Debug
    )

    Direct messages in a single interchange. Invalid envelopes remain inspectable.

    Interchange::equal

    fn Interchange::equal(Interchange, Interchange) -> Bool

    Interchange::not_equal

    fn Interchange::not_equal(x : Interchange, y : Interchange) -> Bool

    Layer

    pub(all) enum Layer {
    Syntax
    Envelope
    Resource
    } derive(Eq,
    Debug
    )

    The layer that produced a diagnostic.

    Layer::equal

    fn Layer::equal(Layer, Layer) -> Bool

    Layer::not_equal

    fn Layer::not_equal(x : Layer, y : Layer) -> Bool

    Layer::to_repr

    Message

    pub(all) struct Message {
    reference : Bytes
    first_segment : Int
    last_segment : Int
    span : SourceSpan
    } derive(Eq,
    Debug
    )

    A closed direct message with inclusive one-based indexes into Document.segments.

    Message::equal

    fn Message::equal(Message, Message) -> Bool

    Message::not_equal

    fn Message::not_equal(x : Message, y : Message) -> Bool

    Message::to_repr

    ParseLimits

    pub(all) struct ParseLimits {
    max_input_bytes : Int
    max_segments : Int
    max_segment_bytes : Int
    max_elements : Int
    max_components : Int
    max_diagnostics : Int
    } derive(Eq,
    Debug
    )

    Resource budgets. All values must be positive; raw segment lengths include terminators.

    ParseLimits::equal

    fn ParseLimits::equal(ParseLimits, ParseLimits) -> Bool

    ParseLimits::not_equal

    fn ParseLimits::not_equal(x : ParseLimits, y : ParseLimits) -> Bool

    ParseResult

    pub(all) struct ParseResult {
    document : Document?
    interchange : Interchange?
    diagnostics : Array[Diagnostic]
    syntax : CheckStatus
    envelope : CheckStatus
    rules : CheckStatus
    } derive(Eq,
    Debug
    )

    Syntax failures return no partial document. Business rules are always NotChecked.

    ParseResult::equal

    fn ParseResult::equal(ParseResult, ParseResult) -> Bool

    ParseResult::is_valid

    fn ParseResult::is_valid(self : ParseResult) -> Bool

    True only when syntax and service envelopes passed. This does not check business rules.

    ParseResult::not_equal

    fn ParseResult::not_equal(x : ParseResult, y : ParseResult) -> Bool

    Segment

    pub(all) struct Segment {
    tag : String
    index : Int
    elements : Array[Element]
    span : SourceSpan
    } derive(Eq,
    Debug
    )

    The span includes the segment terminator; index is one-based and excludes UNA.

    Segment::equal

    fn Segment::equal(Segment, Segment) -> Bool

    Segment::not_equal

    fn Segment::not_equal(x : Segment, y : Segment) -> Bool

    Segment::to_repr

    SourceSpan

    pub(all) struct SourceSpan {
    start : Int
    end : Int
    } derive(Eq,
    Debug
    )

    Half-open offsets into the caller's original Bytes. End excludes the last byte.

    SourceSpan::equal

    fn SourceSpan::equal(SourceSpan, SourceSpan) -> Bool

    SourceSpan::not_equal

    fn SourceSpan::not_equal(x : SourceSpan, y : SourceSpan) -> Bool

    SyntaxProfile

    pub(all) struct SyntaxProfile {
    component : Byte
    element : Byte
    decimal : Byte
    release : Byte
    reserved : Byte
    terminator : Byte
    charset : String
    version : Int
    explicit_una : Bool
    body_start : Int
    } derive(Eq,
    Debug
    )

    Syntax 3 service characters and the first byte after an optional UNA.

    SyntaxProfile::equal

    SyntaxProfile::not_equal

    fn SyntaxProfile::not_equal(x : SyntaxProfile, y : SyntaxProfile) -> Bool

    default_limits

    fn default_limits() -> ParseLimits

    Default offline budgets: 8 MiB input, 100,000 segments, 64 KiB per segment, 1,024 elements per segment, 128 components per element, 100 diagnostics.

    detect_syntax

    fn detect_syntax(input : Bytes) -> Result[SyntaxProfile, Diagnostic]

    Detect an optional UNA and the UNB syntax declaration. Only syntax 3 is supported.

    parse

    fn parse(input : Bytes, limits? : ParseLimits) -> ParseResult

    Parse bytes under explicit limits. No I/O, time, network or implicit decoding.

    report_json

    fn report_json(result : ParseResult, show_values? : Bool) -> String

    Deterministic JSON report. Logical field values require an explicit opt-in.

    report_text

    fn report_text(result : ParseResult) -> String

    A concise summary and source diagnostics, without logical input values.

    validate_envelope

    fn validate_envelope(document : Document, max_diagnostics? : Int) -> EnvelopeResult

    Validate direct UNB/UNZ and UNH/UNT envelopes. Unknown business tags are preserved. Groups, interactive envelopes and multiple interchanges are outside the 0.1.0 scope.