Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Major refactor: flatten code and object structures #34

Merged
merged 22 commits into from
Aug 20, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ jobs:
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
run: cargo test --verbose --features string_index
- name: Run Benchmarks
sempervictus marked this conversation as resolved.
Show resolved Hide resolved
run: cargo bench
run: cargo bench --features string_index
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ description = "HL7 Parser and object builder? query'er? - experimental only at a
license = "MIT OR Apache-2.0"
repository = "https://github.com/wokket/rust-hl7/"

[features]
string_index = []

[lib]
name="rusthl7"
path="src/lib.rs"
Expand Down
50 changes: 37 additions & 13 deletions benches/simple_parse.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use criterion::{criterion_group, criterion_main, Criterion};
use rusthl7::{message::*, segments::Segment};
use rusthl7::{message::*, segments::Segment, segments::MshSegment};
use std::convert::TryFrom;

fn get_sample_message() -> &'static str {
Expand All @@ -19,7 +19,7 @@ fn get_segments_by_name(c: &mut Criterion) {
let m = Message::try_from(get_sample_message()).unwrap();

b.iter(|| {
let _segs = m.generic_segments_by_name("OBR").unwrap();
let _segs = m.segments_by_name("OBR").unwrap();
//assert!(segs.len() == 1);
})
});
Expand All @@ -31,11 +31,14 @@ fn get_msh_and_read_field(c: &mut Criterion) {

b.iter(|| {
let seg = m.segments.first();

if let Some(Segment::MSH(msh)) = seg {
let _app = msh.msh_3_sending_application.as_ref().unwrap(); // direct variable access
//println!("{}", _app.value());
}
let msh = m.msh();
match msh {
Ok(m) => {
let _app = m.msh_3_sending_application.as_ref().unwrap(); // direct variable access
//println!("{}", _app.value());
},
Hl7ParseError => panic!("Failed to parse MSH")
};
})
sempervictus marked this conversation as resolved.
Show resolved Hide resolved
});
}
Expand All @@ -45,12 +48,9 @@ fn get_pid_and_read_field_via_vec(c: &mut Criterion) {
let m = Message::try_from(get_sample_message()).unwrap();

b.iter(|| {
let seg = &m.segments[1];

if let Segment::Generic(pid) = seg {
let _field = pid[3];
assert_eq!(_field, "555-44-4444"); // lookup from vec
}
let pid = &m.segments[1];
let _field = pid[3];
assert_eq!(_field, "555-44-4444"); // lookup from vec
})
});
}
Expand All @@ -66,6 +66,30 @@ fn get_pid_and_read_field_via_query(c: &mut Criterion) {
});
}

#[cfg(feature = "string_index")]
fn get_pid_and_read_field_via_index(c: &mut Criterion) {
c.bench_function("Read Field from PID (index)", |b| {
let m = Message::try_from(get_sample_message()).unwrap();

b.iter(|| {
let _val = m["PID.F3"]; // query via Message
assert_eq!(_val, "555-44-4444"); // lookup from vec
})
});
}

#[cfg(feature = "string_index")]
criterion_group!(
benches,
message_parse,
get_segments_by_name,
get_msh_and_read_field,
get_pid_and_read_field_via_vec,
get_pid_and_read_field_via_query,
get_pid_and_read_field_via_index
);

#[cfg(not(feature = "string_index"))]
criterion_group!(
benches,
message_parse,
Expand Down
8 changes: 4 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ This library is trying to provide the _tooling_ you need to build robust HL7 bas
- [x] Initially use hl7 default separator chars
- [x] Use separator chars from the message
- [X] Add support for sub-field (component/subcomponent) items
- [ ] Field repeats (via `~`) are currently missing ([#26](https://github.com/wokket/rust-hl7/issues/26))
- [X] Initially, avoid any per-segment knowledge, requirement to read the spec too much etc.
- [x] Field repeats (via `~`)
- [x] Initially, avoid any per-segment knowledge, requirement to read the spec too much etc.
- Implementing all the segments, across all the hl7 versions, version-specific parsing etc is tooooo much while we're getting started.
- [-] Add support for [HL7 escape sequences](https://www.lyniate.com/knowledge-hub/hl7-escape-sequences/) ([#22](https://github.com/wokket/rust-hl7/issues/22))
- [x] Decoding of the most common escape sequences including `\E\`, `\R\`, `\S\` & `\T\`
Expand All @@ -28,5 +28,5 @@ This library is trying to provide the _tooling_ you need to build robust HL7 bas
- [x] Parse a message using a `TryFrom<&str>` impl rather than a dedicated parser
- [x] Index into messages using HL7 string index notation and binary methods
- [x] Index into sub-fields using HL7 string index notation and binary methods
- [X] Index into the segment enum using HL7 string index notation and binary methods
- [ ] Implement buffer-copy-free generic indexing into MSH
- [x] Index into the segment enum using HL7 string index notation and binary methods
- [x] Implement buffer-copy-free generic indexing into MSH
4 changes: 2 additions & 2 deletions src/escape_sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ use std::borrow::Cow;
///
/// ## Details
///
/// This decoder will replace some, **but not all** of the standard HL7 escape sequences.
/// This decoder will replace some, **but not all** of the standard HL7 escape sequences.
/// - `\E\`,`\F\`, '\R\`, `\S\`, `\T\` are all handled, and replaced with the Escape, Field, Repeat, Component and Sub-Component separator chars respectively
/// - `\X..\` hexidecimal erscape sequences are supported (2 hex digits per char)
///
///
/// The following sequences are **NOT** replaced by design and will be left in the string:
/// - `\H\` Indicates the start of highlighted text, this is a consuming application problem and will not be replaced.
/// - `\N\` Indicates the end of highlighted text and resumption of normal text. This is a consuming application problem and will not be replaced.
Expand Down
75 changes: 55 additions & 20 deletions src/fields/mod.rs → src/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,33 @@ impl<'a> Field<'a> {
delims: &Separators,
) -> Result<Field<'a>, Hl7ParseError> {
let input = input.into();
let components = input.split(delims.component).collect::<Vec<&'a str>>();
let subcomponents = components
let components: Vec<&'a str> = input
.split(delims.component)
.collect::<Vec<&'a str>>()
.iter()
.map(|c| c.split(delims.repeat).collect::<Vec<&'a str>>())
.flatten()
.collect();
let subcomponents: Vec<Vec<&'a str>> = components
.iter()
wokket marked this conversation as resolved.
Show resolved Hide resolved
.map(|c| c.split(delims.subcomponent).collect::<Vec<&'a str>>())
.collect();

let field = Field {
source: input,
delims: *delims,
components,
subcomponents,
};

Ok(field)
}

/// Convenience method to check whether Field is a single data element or broken out into multiple internally
// Not used internally - marked to allow dead code as a result
#[allow(dead_code)]
fn is_subdivided(&self) -> bool {
self.components.len() > 1 || self.subcomponents[0].len() > 1
}

sempervictus marked this conversation as resolved.
Show resolved Hide resolved
/// Used to hide the removal of NoneError for #2... If passed `Some()` value it returns a field with that value. If passed `None() it returns an `Err(Hl7ParseError::MissingRequiredValue{})`
pub fn parse_mandatory(
input: Option<&'a str>,
Expand Down Expand Up @@ -125,9 +136,9 @@ impl<'a> Clone for Field<'a> {
}
}

/// Access string reference of a Field component by numeric index
impl<'a> Index<usize> for Field<'a> {
type Output = &'a str;
/// Access string reference of a Field component by numeric index
fn index(&self, idx: usize) -> &Self::Output {
if idx > self.components.len() - 1 {
return &""; //TODO: We're returning &&str here which doesn't seem right?!?
Expand All @@ -137,9 +148,9 @@ impl<'a> Index<usize> for Field<'a> {
}
}

/// Access string reference of a Field subcomponent by numeric index
impl<'a> Index<(usize, usize)> for Field<'a> {
type Output = &'a str;
/// Access string reference of a Field subcomponent by numeric index
fn index(&self, idx: (usize, usize)) -> &Self::Output {
if idx.0 > self.components.len() - 1 || idx.1 > self.subcomponents[idx.0].len() - 1 {
return &""; //TODO: We're returning &&str here which doesn't seem right?!?
Expand All @@ -149,12 +160,12 @@ impl<'a> Index<(usize, usize)> for Field<'a> {
}
}

/// DEPRECATED. Access string reference of a Field component by String index
/// Adjust the index by one as medical people do not count from zero
#[allow(useless_deprecated)]
#[deprecated(note = "This will be removed in a future version")]
#[cfg(feature = "string_index")]
impl<'a> Index<String> for Field<'a> {
type Output = &'a str;

/// Access string reference of a Field component by String index
#[cfg(feature = "string_index")]
fn index(&self, sidx: String) -> &Self::Output {
let parts = sidx.split('.').collect::<Vec<&str>>();

Expand Down Expand Up @@ -188,12 +199,12 @@ impl<'a> Index<String> for Field<'a> {
}
}

#[cfg(feature = "string_index")]
impl<'a> Index<&str> for Field<'a> {
type Output = &'a str;

/// DEPRECATED. Access Segment, Field, or sub-field string references by string index
#[allow(useless_deprecated)]
#[deprecated(note = "This will be removed in a future version")]
/// Access Segment, Field, or sub-field string references by string index
#[cfg(feature = "string_index")]
fn index(&self, idx: &str) -> &Self::Output {
&self[String::from(idx)]
}
Expand Down Expand Up @@ -294,13 +305,37 @@ mod tests {
}

#[test]
fn test_string_index() {
fn test_is_subdivided() {
let d = Separators::default();
let f = Field::parse_mandatory(Some("xxx^yyy&zzz"), &d).unwrap();
let idx0 = String::from("R2");
let oob = "R2.C3";
assert_eq!(f.query(&*idx0), "yyy&zzz");
assert_eq!(f.query("R2.C2"), "zzz");
assert_eq!(f.query(oob), "");
let f0 = Field::parse_mandatory(Some("xxx^yyy&zzz"), &d).unwrap();
let f1 = Field::parse_mandatory(Some("yyy&zzz"), &d).unwrap();
let f2 = Field::parse_mandatory(Some("xxx"), &d).unwrap();
assert_eq!(f0.is_subdivided(), true);
assert_eq!(f1.is_subdivided(), true);
assert_eq!(f2.is_subdivided(), false);
}
sempervictus marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(feature = "string_index")]
mod string_index_tests {
use super::*;
#[test]
fn test_string_index() {
let d = Separators::default();
let f = Field::parse_mandatory(Some("xxx^yyy&zzz"), &d).unwrap();
let idx0 = String::from("R2");
assert_eq!(f["R2"], "yyy&zzz");
assert_eq!(f["R2.C2"], "zzz");
assert_eq!(f["R2.C3"], "");
}
sempervictus marked this conversation as resolved.
Show resolved Hide resolved
#[test]
fn test_string_index_repeating() {
let d = Separators::default();
let f = Field::parse_mandatory(Some("A~S"), &d).unwrap();
let idx0 = String::from("R2");
let oob = "R2.C3";
assert_eq!(f["R1"], "A");
assert_eq!(f.query("R2"), "S");
assert_eq!(f["R3"], "");
}
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# RustHl7 - A HL7 V2 message parser and library

This crate is attempting to provide the tooling for a fully spec-compliant HL7 V2 message parser. Note that _interpreting_ the parsed message elements into a strongly
typed segment/message format is specifically **out of scope** as there's simply too many variants over too many versions for me to go there (maybe
typed segment/message format is specifically **out of scope** as there's simply too many variants over too many versions for me to go there (maybe
someone else could code-gen a crate using this this crate to provide the source information?).

This crate tries to provide the tools to build HL7 systems without dictating _how_ to build your system, there's no such thing as one-size-fits all in healthcare!
Expand Down
Loading