Crates

hl7-3-soap

HL7 v3 carried in a SOAP envelope over HTTP

Transports v0.1.1 Specified

Install cargo add hl7-3-soap
Rust path hl7_3_soap
Dependencies hl7-2-xml-lite-helper
Links crates.io docs.rs source spec

What it is

Unlike v2 — where MLLP is the usual transport and SOAP is the exception — SOAP is HL7 v3's own historically dominant transport. v3 was designed alongside SOAP and WS-*, and real deployments carry it that way: NHS England's Personal Demographics Service, IHE profiles built on v3.

This crate is that transport, and it is deliberately the same shape as its hl7-2-soap cousin: it does the protocol and nothing else.

What it does, and what it does not

It does:

  • Parse a SOAP envelope and take the single business payload out of its body — a complete HL7 v3 message, root element named for the interaction.
  • Faults, each carrying the HTTP status that belongs with it.
  • Read which interaction a payload is, its control ID, and its claimed assigning authority, and check a payload against what the interface accepts.
  • Build the real HL7 v3 acknowledgement — MCCI_IN000002UV01, with acknowledgement/typeCode — and read one as accepted or rejected.
  • Serve a WSDL that describes the endpoint at the address it was reached on.

It does not:

No HTTP client and no HTTP server. No RIM decoding and no domain-payload interpretation either — hl7-3 owns those.

Receiving

rust
use hl7_3_soap::{Fault, message, response};

fn handle(request_body: &str) -> (u16, String) {
    match accept(request_body) {
        Ok(control_id) => (200, response::success(&control_id)),
        Err(fault) => (fault.status, fault.to_envelope()),
    }
}

fn accept(request_body: &str) -> Result<String, Fault> {
    let envelope = hl7_3_soap::parse(request_body)?;
    let payload = envelope.payload()?;
    message::check(payload, &["PRPA_IN201305UV02".to_string()], &[])?;
    // ...decode the payload with hl7-3, and forward it, here...
    Ok(message::control_id(payload).unwrap_or_default().to_string())
}

Sending

rust
use hl7_3_soap::{envelope, response::{self, Outcome}};

let body = envelope::wrap_xml(
    r#"<PRPA_IN201305UV02><id extension="9"/></PRPA_IN201305UV02>"#,
);
// ...POST body with Content-Type: text/xml; charset=utf-8...

match response::evaluate(status, &reply) {
    Outcome::Accepted => {}
    Outcome::Rejected(reason) => eprintln!("not delivered: {reason}"),
}

Three things it is opinionated about

The same three as its v2 cousin, for the same reasons. One payload per body — SOAP permits several, no HL7 interface means several, and a body with none or many is a fault rather than a silent choice of the first child. Prefixes do not mattersoapenv:, soap:, SOAP-ENV:, and unprefixed are all read alike, because insisting on one prefix rejects valid documents from every other tool. A fault carries its HTTP statusClient is a 400 and must not be retried, Client.Authorization is a 403, Server is a 500 and should be.

Reading a response

response::evaluate reads the same three places hl7-2-soap does, in the order that cannot be talked out of a rejection: a non-2xx status, then a Fault element (even under HTTP 200), then acknowledgement/typeCode/@code, then acceptance.

AA and CA are accepted, matching v2's MSA-1 — the two standards draw the acceptance codes from the same conceptual vocabulary.

Dependencies

One: hl7-2-xml-lite-helper, re-exported here as xml, shared with hl7-2-soap and the other crates in the family that read XML. It has no dependencies of its own, so the audit surface stays small.