hl7-net 0.1.0

Lightweight HL7 V2 parser/writer, ported from the Efferent HL7-V2 .NET library
Documentation
# hl7-net

[![Crates.io](https://img.shields.io/crates/v/hl7-net.svg)](https://crates.io/crates/hl7-net)
[![Documentation](https://docs.rs/hl7-net/badge.svg)](https://docs.rs/hl7-net)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.txt)

A lightweight **HL7 v2** message parser and writer for Rust.

`hl7-net` is an idiomatic Rust port of the
[Efferent HL7-V2](https://github.com/Efferent-Health/HL7-V2) .NET library. It keeps
the original library's HL7 semantics — delimiter handling, escaping, "present but
null" values, MLLP framing and the round-trip self-check — while reworking the API
around Rust conventions (`snake_case`, `Result`-based error handling, and a pure-data
element tree).

## Features

- Parse HL7 v2 messages into a `Segment` / `Field` / `Component` / `SubComponent` tree.
- Read and write values by path (`PID.5.1`, `MSH.9.3`, `PID(2).3.1`).
- Full HL7 escaping/unescaping (`\F\`, `\S\`, `\R\`, `\E\`, `\T\`, `\Xnn\` hex, and
  the `<B>`/`</B>`/`<BR>` formatting sequences).
- "Present but null" (`""`) values modelled as `Option<String>`.
- Generate ACK / NACK responses.
- MLLP framing helpers (`<VT> … <FS><CR>`) for stream transport.
- HL7 date/time parsing and formatting via [`jiff`]https://docs.rs/jiff.
- A parse-time round-trip check: a message is only considered valid if re-serializing
  it reproduces the (hex-normalized) original.

## Installation

Add the crate with Cargo:

```sh
cargo add hl7-net
```

Or add it to `Cargo.toml` manually:

```toml
[dependencies]
hl7-net = "0.1"
```

The minimum supported Rust version (MSRV) is **1.88** (Rust 2024 edition).

## Usage

### Parsing a message and reading values

```rust
use hl7_net::Message;

let text = "MSH|^~\\&|App|Fac|App2|Fac2|20200101000000||ADT^A01^ADT_A01|MSGID|P|2.5\r\
            PID|1||PATID1234^5^M11||EVERYMAN^ADAM^A^III||19610615|M\r";

let mut message = Message::with_message(text);

// `parse` returns Ok(true) when the message round-trips cleanly.
assert!(message.parse(false).unwrap());

// Read by path: segment.field.component.subcomponent (all 1-based).
assert_eq!(message.get_value("MSH.9.1").unwrap(), "ADT");
assert_eq!(message.get_value("PID.5.1").unwrap(), "EVERYMAN");

// Message-level metadata extracted from MSH.
assert_eq!(message.version, "2.5");
assert_eq!(message.message_control_id, "MSGID");
```

### Updating values

```rust
use hl7_net::Message;

let mut message = Message::parse_str(
    "MSH|^~\\&|App|Fac|App2|Fac2|20200101000000||ADT^A01^ADT_A01|MSGID|P|2.5\r\
     PID|1||PATID1234^5^M11||EVERYMAN^ADAM^A^III||19610615|M\r",
    false,
).unwrap();

message.set_value("PID.5.1", "SMITH").unwrap();
assert_eq!(message.get_value("PID.5.1").unwrap(), "SMITH");

// Serialize back to HL7 text.
let out = message.serialize().unwrap();
assert!(out.contains("SMITH^ADAM"));
```

### Acknowledgements and MLLP framing

```rust
use hl7_net::Message;

let message = Message::parse_str(
    "MSH|^~\\&|App|Fac|App2|Fac2|20200101000000||ADT^A01^ADT_A01|MSGID|P|2.5\r",
    false,
).unwrap();

// Positive acknowledgement (sender/receiver swapped, MSA|AA).
let ack = message.get_ack(false).unwrap();
assert_eq!(ack.get_value("MSA.1").unwrap(), "AA");

// Wrap a message in an MLLP frame for transport over a socket.
let framed: Vec<u8> = message.get_mllp().unwrap();
assert_eq!(framed[0], 0x0B); // <VT>
```

### Encoding helpers and date handling

```rust
use hl7_net::HL7Encoding;
use hl7_net::helper;

let enc = HL7Encoding::default();
let encoded = enc.encode("Smith & Sons");
assert_eq!(enc.decode(&encoded), "Smith & Sons");

// Parse an HL7 timestamp (with optional fraction and timezone offset).
let dt = helper::parse_date_time("20200101120000.5+0100").unwrap();
assert_eq!(dt.to_utc().unwrap().to_string(), "2020-01-01T11:00:00.5Z");
```

## API overview

| Type            | Role                                                              |
| --------------- | ---------------------------------------------------------------- |
| `Message`       | A parsed message; metadata plus the segment tree and value access |
| `Segment`       | A named segment (`MSH`, `PID`, …) holding a list of fields         |
| `Field`         | A field; either componentized or carrying repetitions             |
| `Component`     | A component made of one or more subcomponents                      |
| `SubComponent`  | The smallest data unit                                            |
| `HL7Encoding`   | Delimiter set and the escape/unescape routines                    |
| `Hl7Error`      | Error type carrying a message and an optional category code        |
| `helper`        | Date/time, message splitting and MLLP framing utilities           |

The element tree is **pure data**: `HL7Encoding` is threaded into the parse,
serialize and value methods (e.g. `field.value(&enc)`) rather than stored on each
node, which keeps the tree borrow-friendly and cheap to clone.

## Building from source

```sh
git clone https://github.com/Tirax-Tech/hl7-net.git
cd hl7-net
cargo build
cargo test       # unit tests, helper tests and doc tests
cargo clippy --all-targets
```

## License

Licensed under the [MIT License](LICENSE.txt).

This project is a derivative work of the
[Efferent HL7-V2](https://github.com/Efferent-Health/HL7-V2) .NET library, which is
also MIT licensed; the original copyright notice is retained in `LICENSE.txt`.