From 481f280e6a5dba39de38037883babde97cfa25c2 Mon Sep 17 00:00:00 2001 From: Tim Thompson Date: Sat, 7 Aug 2021 15:24:10 +1000 Subject: [PATCH 01/14] fix #14: Breaking change to impl try_from rather than having a from_str function. --- benches/simple_parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/simple_parse.rs b/benches/simple_parse.rs index 3c7f73c..03015ee 100644 --- a/benches/simple_parse.rs +++ b/benches/simple_parse.rs @@ -11,7 +11,7 @@ fn message_parse(c: &mut Criterion) { c.bench_function("oru parse", |b| { b.iter(|| { - let m = Message::try_from(get_sample_message()).unwrap(); + let m = Message::try_from(msg).unwrap(); let seg = m.segments.first(); if let Some(Segment::MSH(msh)) = seg { From 9e4cac673adcb35a2589a02d59477366ac09fb36 Mon Sep 17 00:00:00 2001 From: Tim Thompson Date: Sun, 8 Aug 2021 07:28:27 +1000 Subject: [PATCH 02/14] fix: Broken benchmark --- benches/simple_parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/simple_parse.rs b/benches/simple_parse.rs index 03015ee..3c7f73c 100644 --- a/benches/simple_parse.rs +++ b/benches/simple_parse.rs @@ -11,7 +11,7 @@ fn message_parse(c: &mut Criterion) { c.bench_function("oru parse", |b| { b.iter(|| { - let m = Message::try_from(msg).unwrap(); + let m = Message::try_from(get_sample_message()).unwrap(); let seg = m.segments.first(); if let Some(Segment::MSH(msh)) = seg { From 99546a48f6162b6e3aece16a3e2d021b8c7a16cd Mon Sep 17 00:00:00 2001 From: Tim Thompson Date: Sun, 8 Aug 2021 14:04:41 +1000 Subject: [PATCH 03/14] Rebase onto master @ 0.4 --- src/message.rs | 42 ++++++++++++++++++++++++++++++++++++++++- src/segments/generic.rs | 40 +++++++++++++++++++++++++++++++++------ src/segments/msh.rs | 5 +++-- 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/message.rs b/src/message.rs index 8e2cd7a..fe654fc 100644 --- a/src/message.rs +++ b/src/message.rs @@ -78,6 +78,46 @@ impl<'a> Message<'a> { pub fn as_str(&self) -> &'a str { self.source } + + /// Access Segment, Field, or sub-field string references by string index + pub fn query(&self, idx: &str) -> &'a str { + // Parse index elements + let indices: Vec<&str> = idx.split('.').collect(); + let seg_name = indices[0]; + // Find our first segment without offending the borow checker + + let seg_index = self + .segments + .iter() + .position(|r| &r.as_str()[..seg_name.len()] == seg_name); + + match seg_index { //TODO: What is this doing... + Some(_) => {} + None => return &"", + } + + let seg = &self.segments[seg_index.unwrap()]; + + // Return the appropriate source reference + match seg { + // Short circuit for now + Segment::MSH(m) => &m.source, + // Parse out slice depth + Segment::Generic(g) => { + if indices.len() < 2 { + &g.source + } else { + let query = indices[1..].join("."); + &g.query_by_string(query) + } + } + } + } + + /// Access Segment, Field, or sub-field string references by string index + pub fn query_by_string(&self, idx: String) -> &'a str { + self.query(idx.as_str()) + } } impl<'a> TryFrom<&'a str> for Message<'a> { @@ -258,7 +298,7 @@ mod tests { fn ensure_index() -> Result<(), Hl7ParseError> { let hl7 = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rOBR|segment^sub&segment"; let msg = Message::try_from(hl7)?; - assert_eq!(msg[String::from("OBR.F1.R2.C1")], "sub"); + assert_eq!(msg.query("OBR.F1.R2.C1"), "sub"); Ok(()) } } diff --git a/src/segments/generic.rs b/src/segments/generic.rs index f910ca2..635464f 100644 --- a/src/segments/generic.rs +++ b/src/segments/generic.rs @@ -31,6 +31,34 @@ impl<'a> GenericSegment<'a> { pub fn as_str(&self) -> &'a str { self.source } + + /// Access Segment, Field, or sub-field string references by string index + pub fn query(&self, idx: &str) -> &'a str { + self.query_by_string(String::from(idx)) + } + + /// Access Field as string reference + pub fn query_by_string(&self, fidx: String) -> &'a str { + let sections = fidx.split('.').collect::>(); + match sections.len() { + 1 => { + let stringnum = sections[0] + .chars() + .filter(|c| c.is_digit(10)) + .collect::(); + let idx: usize = stringnum.parse().unwrap(); + &self[idx] + } + _ => { + let stringnum = sections[0] + .chars() + .filter(|c| c.is_digit(10)) + .collect::(); + let idx: usize = stringnum.parse().unwrap(); + &self.fields[idx][sections[1..].join(".")] + } + } + } } use std::fmt::Display; @@ -139,12 +167,12 @@ mod tests { let msg = Message::try_from(hl7).unwrap(); let (f, c, s, oob) = match &msg.segments[1] { Segment::Generic(x) => ( - x[String::from("F1")], - x[String::from("F1.R2")], - x[String::from("F1.R2.C1")], - String::from(x[String::from("F10")]) - + x[String::from("F1.R10")] - + x[String::from("F1.R2.C10")], + x.query_by_string(String::from("F1")), + x.query_by_string(String::from("F1.R2")), + x.query_by_string(String::from("F1.R2.C1")), + String::from(x.query("F10")) + + x.query("F1.R10") + + x.query("F1.R2.C10"), ), _ => ("", "", "", String::from("")), }; diff --git a/src/segments/msh.rs b/src/segments/msh.rs index b4fa4cc..931fcbc 100644 --- a/src/segments/msh.rs +++ b/src/segments/msh.rs @@ -1,3 +1,4 @@ +use std::fmt::Display; use super::fields::Field; use super::separators::Separators; use super::*; @@ -82,7 +83,7 @@ impl<'a> MshSegment<'a> { } } -use std::fmt::Display; + impl<'a> Display for MshSegment<'a> { /// Required for to_string() and other formatter consumers fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -143,7 +144,7 @@ mod tests { let msh = MshSegment::parse(hl7, &delims)?; let gen = msh.as_generic().unwrap(); - assert_eq!("ELAB-3", gen["F3"]); + assert_eq!("ELAB-3", gen.query("F3")); Ok(()) } From e2b86293407812cdefa142aaadf0c47675e0a5d2 Mon Sep 17 00:00:00 2001 From: Tim Thompson Date: Wed, 11 Aug 2021 07:34:49 +1000 Subject: [PATCH 04/14] rebase onto master @ 0.4.0 --- src/fields/mod.rs | 60 ++++++++++++++++++++++++++++++++++------- src/message.rs | 1 + src/segments/generic.rs | 22 ++++++++------- 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/src/fields/mod.rs b/src/fields/mod.rs index 99ec610..b40c9ad 100644 --- a/src/fields/mod.rs +++ b/src/fields/mod.rs @@ -1,9 +1,12 @@ +use std::fmt::Display; +use std::ops::Index; use super::separators::Separators; use super::*; + + /// Represents a single field inside the HL7. Note that fields can include repeats, components and sub-components. /// See [the spec](http://www.hl7.eu/HL7v2x/v251/std251/ch02.html#Heading13) for more info #[derive(Debug, PartialEq)] - pub struct Field<'a> { pub source: &'a str, pub delims: Separators, @@ -29,7 +32,7 @@ impl<'a> Field<'a> { Ok(field) } - /// Used to hide the removal of NoneError for #2... If passed `Some()` value it retursn a field with that value. If passed `None() it returns an `Err(Hl7ParseError::MissingRequiredValue{})` + /// 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>, delims: &Separators, @@ -62,9 +65,49 @@ impl<'a> Field<'a> { pub fn as_str(&self) -> &'a str { self.source } + + /// Access string reference of a Field component by String index + /// Adjust the index by one as medical people do not count from zero + pub fn query(&self, sidx: &str) -> &'a str { + let parts = sidx.split('.').collect::>(); + + if parts.len() == 1 { + let stringnums = parts[0] + .chars() + .filter(|c| c.is_digit(10)) + .collect::(); + let idx: usize = stringnums.parse().unwrap(); + + &self[idx - 1] + } else if parts.len() == 2 { + let stringnums = parts[0] + .chars() + .filter(|c| c.is_digit(10)) + .collect::(); + + let idx0: usize = stringnums.parse().unwrap(); + + let stringnums = parts[1] + .chars() + .filter(|c| c.is_digit(10)) + .collect::(); + + let idx1: usize = stringnums.parse().unwrap(); + + &self[(idx0 - 1, idx1 - 1)] + } else { + "" + } + } + + /// Access Segment, Field, or sub-field string references by string index + pub fn query_by_string(&self, idx: String) -> &'a str { + &self.query(idx.as_str()) + } + } -use std::fmt::Display; + impl<'a> Display for Field<'a> { /// Required for to_string() and other formatter consumers fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -72,7 +115,7 @@ impl<'a> Display for Field<'a> { } } -use std::ops::Index; + impl<'a> Clone for Field<'a> { /// Creates a new Message object using a clone of the original's source fn clone(&self) -> Self { @@ -250,10 +293,9 @@ mod tests { let d = Separators::default(); let f = Field::parse_mandatory(Some("xxx^yyy&zzz"), &d).unwrap(); let idx0 = String::from("R2"); - let idx1 = String::from("R2.C2"); - let oob = String::from("R2.C3"); - assert_eq!(f[idx0.clone()], "yyy&zzz"); - assert_eq!(f[idx1], "zzz"); - assert_eq!(f[oob], ""); + let oob = "R2.C3"; + assert_eq!(f.query_by_string(idx0.clone()), "yyy&zzz"); + assert_eq!(f.query("R2.C2"), "zzz"); + assert_eq!(f.query(oob), ""); } } diff --git a/src/message.rs b/src/message.rs index fe654fc..a4a63b4 100644 --- a/src/message.rs +++ b/src/message.rs @@ -116,6 +116,7 @@ impl<'a> Message<'a> { /// Access Segment, Field, or sub-field string references by string index pub fn query_by_string(&self, idx: String) -> &'a str { + //TODO: Determine if we actually need this function... in what scenario are we passing a String in here rather an &str? self.query(idx.as_str()) } } diff --git a/src/segments/generic.rs b/src/segments/generic.rs index 635464f..73934d5 100644 --- a/src/segments/generic.rs +++ b/src/segments/generic.rs @@ -1,6 +1,6 @@ -use super::fields::Field; -use super::separators::Separators; -use super::*; +use std::fmt::Display; +use std::ops::Index; +use super::{Hl7ParseError, fields::Field, separators::Separators}; /// A generic bag o' fields, representing an arbitrary segment. #[derive(Debug, PartialEq, Clone)] @@ -33,13 +33,14 @@ impl<'a> GenericSegment<'a> { } /// Access Segment, Field, or sub-field string references by string index - pub fn query(&self, idx: &str) -> &'a str { - self.query_by_string(String::from(idx)) + pub fn query_by_string(&self, idx: String) -> &'a str { + self.query(idx.as_str()) } /// Access Field as string reference - pub fn query_by_string(&self, fidx: String) -> &'a str { + pub fn query(&self, fidx: &str) -> &'a str { let sections = fidx.split('.').collect::>(); + match sections.len() { 1 => { let stringnum = sections[0] @@ -55,13 +56,16 @@ impl<'a> GenericSegment<'a> { .filter(|c| c.is_digit(10)) .collect::(); let idx: usize = stringnum.parse().unwrap(); - &self.fields[idx][sections[1..].join(".")] + let field = &self.fields[idx]; + let query = sections[1..].join("."); + + field.query_by_string(query) + } } } } -use std::fmt::Display; impl<'a> Display for GenericSegment<'a> { /// Required for to_string() and other formatter consumers fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -69,7 +73,7 @@ impl<'a> Display for GenericSegment<'a> { } } -use std::ops::Index; + impl<'a> Index for GenericSegment<'a> { type Output = &'a str; /// Access Field as string reference From 4af11c0a227e9690d7f96782b79f35c173c7a5ae Mon Sep 17 00:00:00 2001 From: Tim Thompson Date: Sun, 8 Aug 2021 17:06:47 +1000 Subject: [PATCH 05/14] docs: Getting sidetracked with simple docs here rather than querying --- benches/simple_parse.rs | 4 +--- src/fields/mod.rs | 18 +++++++++--------- src/message.rs | 35 ++++++++++++++++++++++++++++++----- src/segments/generic.rs | 10 +++------- src/segments/msh.rs | 5 ++--- 5 files changed, 45 insertions(+), 27 deletions(-) diff --git a/benches/simple_parse.rs b/benches/simple_parse.rs index 3c7f73c..a82273f 100644 --- a/benches/simple_parse.rs +++ b/benches/simple_parse.rs @@ -1,15 +1,13 @@ -use std::convert::TryFrom; use criterion::{criterion_group, criterion_main, Criterion}; use rusthl7::{message::*, segments::Segment}; +use std::convert::TryFrom; fn get_sample_message() -> &'static str { "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rPID|||555-44-4444||EVERYWOMAN^EVE^E^^^^L|JONES|19620320|F|||153 FERNWOOD DR.^^STATESVILLE^OH^35292||(206)3345232|(206)752-121||||AC555444444||67-A4335^OH^20030520\rOBR|1|845439^GHH OE|1045813^GHH LAB|15545^GLUCOSE|||200202150730|||||||||555-55-5555^PRIMARY^PATRICIA P^^^^MD^^|||||||||F||||||444-44-4444^HIPPOCRATES^HOWARD H^^^^MD\rOBX|1|SN|1554-5^GLUCOSE^POST 12H CFST:MCNC:PT:SER/PLAS:QN||^182|mg/dl|70_105|H|||F" } fn message_parse(c: &mut Criterion) { - c.bench_function("oru parse", |b| { - b.iter(|| { let m = Message::try_from(get_sample_message()).unwrap(); let seg = m.segments.first(); diff --git a/src/fields/mod.rs b/src/fields/mod.rs index b40c9ad..6d802bc 100644 --- a/src/fields/mod.rs +++ b/src/fields/mod.rs @@ -1,8 +1,7 @@ -use std::fmt::Display; -use std::ops::Index; use super::separators::Separators; use super::*; - +use std::fmt::Display; +use std::ops::Index; /// Represents a single field inside the HL7. Note that fields can include repeats, components and sub-components. /// See [the spec](http://www.hl7.eu/HL7v2x/v251/std251/ch02.html#Heading13) for more info @@ -29,6 +28,7 @@ impl<'a> Field<'a> { components, subcomponents, }; + Ok(field) } @@ -104,10 +104,8 @@ impl<'a> Field<'a> { pub fn query_by_string(&self, idx: String) -> &'a str { &self.query(idx.as_str()) } - } - impl<'a> Display for Field<'a> { /// Required for to_string() and other formatter consumers fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -115,7 +113,6 @@ impl<'a> Display for Field<'a> { } } - impl<'a> Clone for Field<'a> { /// Creates a new Message object using a clone of the original's source fn clone(&self) -> Self { @@ -128,8 +125,9 @@ impl<'a> Index for Field<'a> { type Output = &'a str; fn index(&self, idx: usize) -> &Self::Output { if idx > self.components.len() - 1 { - return &""; + return &""; //TODO: We're returning &&str here which doesn't seem right?!? } + &self.components[idx] } } @@ -139,8 +137,9 @@ impl<'a> Index<(usize, usize)> for Field<'a> { type Output = &'a str; fn index(&self, idx: (usize, usize)) -> &Self::Output { if idx.0 > self.components.len() - 1 || idx.1 > self.subcomponents[idx.0].len() - 1 { - return &""; + return &""; //TODO: We're returning &&str here which doesn't seem right?!? } + &self.subcomponents[idx.0][idx.1] } } @@ -256,6 +255,7 @@ mod tests { fn test_parse_components() { let d = Separators::default(); let f = Field::parse_mandatory(Some("xxx^yyy"), &d).unwrap(); + assert_eq!(f.components.len(), 2) } @@ -294,7 +294,7 @@ mod tests { let f = Field::parse_mandatory(Some("xxx^yyy&zzz"), &d).unwrap(); let idx0 = String::from("R2"); let oob = "R2.C3"; - assert_eq!(f.query_by_string(idx0.clone()), "yyy&zzz"); + assert_eq!(f.query_by_string(idx0), "yyy&zzz"); assert_eq!(f.query("R2.C2"), "zzz"); assert_eq!(f.query(oob), ""); } diff --git a/src/message.rs b/src/message.rs index a4a63b4..651976d 100644 --- a/src/message.rs +++ b/src/message.rs @@ -74,7 +74,19 @@ impl<'a> Message<'a> { Ok(vecs) } - /// Export source to str + /// Returns the source string slice used to create this Message initially. + /// ## Example: + /// ``` + /// # use rusthl7::Hl7ParseError; + /// # use rusthl7::message::Message; + /// # use std::convert::TryFrom; + /// # fn main() -> Result<(), Hl7ParseError> { + /// let source = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4"; + /// let m = Message::try_from(source)?; + /// assert_eq!(source, m.as_str()); + /// # Ok(()) + /// # } + /// ``` pub fn as_str(&self) -> &'a str { self.source } @@ -90,14 +102,15 @@ impl<'a> Message<'a> { .segments .iter() .position(|r| &r.as_str()[..seg_name.len()] == seg_name); - - match seg_index { //TODO: What is this doing... + + match seg_index { + //TODO: What is this doing... Some(_) => {} None => return &"", } let seg = &self.segments[seg_index.unwrap()]; - + // Return the appropriate source reference match seg { // Short circuit for now @@ -114,7 +127,8 @@ impl<'a> Message<'a> { } } - /// Access Segment, Field, or sub-field string references by string index + ///Access segment, field, or sub-field string references by passing a query string in dot notation. + ///See [`Self::query()`] for more information. pub fn query_by_string(&self, idx: String) -> &'a str { //TODO: Determine if we actually need this function... in what scenario are we passing a String in here rather an &str? self.query(idx.as_str()) @@ -151,6 +165,17 @@ impl<'a> Display for Message<'a> { impl<'a> Clone for Message<'a> { /// Creates a new cloned Message object referencing the same source slice as the original. + /// ## Example: + /// ``` + /// # use rusthl7::Hl7ParseError; + /// # use rusthl7::message::Message; + /// # use std::convert::TryFrom; + /// # fn main() -> Result<(), Hl7ParseError> { + /// let m = Message::try_from("MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4")?; + /// let cloned = m.clone(); // this object is looking at the same string slice as m + /// # Ok(()) + /// # } + /// ``` fn clone(&self) -> Self { Message::try_from(self.source).unwrap() } diff --git a/src/segments/generic.rs b/src/segments/generic.rs index 73934d5..b2dc005 100644 --- a/src/segments/generic.rs +++ b/src/segments/generic.rs @@ -1,6 +1,6 @@ +use super::{fields::Field, separators::Separators, Hl7ParseError}; use std::fmt::Display; use std::ops::Index; -use super::{Hl7ParseError, fields::Field, separators::Separators}; /// A generic bag o' fields, representing an arbitrary segment. #[derive(Debug, PartialEq, Clone)] @@ -58,9 +58,8 @@ impl<'a> GenericSegment<'a> { let idx: usize = stringnum.parse().unwrap(); let field = &self.fields[idx]; let query = sections[1..].join("."); - - field.query_by_string(query) + field.query_by_string(query) } } } @@ -73,7 +72,6 @@ impl<'a> Display for GenericSegment<'a> { } } - impl<'a> Index for GenericSegment<'a> { type Output = &'a str; /// Access Field as string reference @@ -174,9 +172,7 @@ mod tests { x.query_by_string(String::from("F1")), x.query_by_string(String::from("F1.R2")), x.query_by_string(String::from("F1.R2.C1")), - String::from(x.query("F10")) - + x.query("F1.R10") - + x.query("F1.R2.C10"), + String::from(x.query("F10")) + x.query("F1.R10") + x.query("F1.R2.C10"), ), _ => ("", "", "", String::from("")), }; diff --git a/src/segments/msh.rs b/src/segments/msh.rs index 931fcbc..32d49ad 100644 --- a/src/segments/msh.rs +++ b/src/segments/msh.rs @@ -1,7 +1,7 @@ -use std::fmt::Display; use super::fields::Field; use super::separators::Separators; use super::*; +use std::fmt::Display; /// The most important Segment, almost all HL7 messages have an MSH (MLLP simple ack I'm looking at you). /// Given the importance of this segment for driving application behaviour, it gets the special treatment @@ -83,7 +83,6 @@ impl<'a> MshSegment<'a> { } } - impl<'a> Display for MshSegment<'a> { /// Required for to_string() and other formatter consumers fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -92,7 +91,7 @@ impl<'a> Display for MshSegment<'a> { } impl<'a> Clone for MshSegment<'a> { - /// Creates a new Message object using a clone of the original's source + /// Creates a new Message object using _the same source_ slice as the original. fn clone(&self) -> Self { let delims = self.msh_2_encoding_characters; MshSegment::parse(self.source, &delims).unwrap() From 374f8542ae03d4b6b7e11fd24c2596121c1be766 Mon Sep 17 00:00:00 2001 From: Tim Thompson Date: Sat, 7 Aug 2021 15:24:10 +1000 Subject: [PATCH 06/14] fix #14: Breaking change to impl try_from rather than having a from_str function. --- src/message.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/message.rs b/src/message.rs index 651976d..0c3b4b6 100644 --- a/src/message.rs +++ b/src/message.rs @@ -325,6 +325,7 @@ mod tests { let hl7 = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rOBR|segment^sub&segment"; let msg = Message::try_from(hl7)?; assert_eq!(msg.query("OBR.F1.R2.C1"), "sub"); + assert_eq!(msg[String::from("OBR.F1.R2.C1")], "sub"); Ok(()) } } From 04b3ba807d6b45a9d03da8fde3ccfe673afb0440 Mon Sep 17 00:00:00 2001 From: Tim Thompson Date: Sun, 8 Aug 2021 07:28:27 +1000 Subject: [PATCH 07/14] fix: Broken benchmark --- benches/simple_parse.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/benches/simple_parse.rs b/benches/simple_parse.rs index a82273f..2fe80ed 100644 --- a/benches/simple_parse.rs +++ b/benches/simple_parse.rs @@ -1,6 +1,6 @@ +use std::convert::TryFrom; use criterion::{criterion_group, criterion_main, Criterion}; use rusthl7::{message::*, segments::Segment}; -use std::convert::TryFrom; fn get_sample_message() -> &'static str { "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rPID|||555-44-4444||EVERYWOMAN^EVE^E^^^^L|JONES|19620320|F|||153 FERNWOOD DR.^^STATESVILLE^OH^35292||(206)3345232|(206)752-121||||AC555444444||67-A4335^OH^20030520\rOBR|1|845439^GHH OE|1045813^GHH LAB|15545^GLUCOSE|||200202150730|||||||||555-55-5555^PRIMARY^PATRICIA P^^^^MD^^|||||||||F||||||444-44-4444^HIPPOCRATES^HOWARD H^^^^MD\rOBX|1|SN|1554-5^GLUCOSE^POST 12H CFST:MCNC:PT:SER/PLAS:QN||^182|mg/dl|70_105|H|||F" @@ -8,6 +8,7 @@ fn get_sample_message() -> &'static str { fn message_parse(c: &mut Criterion) { c.bench_function("oru parse", |b| { + b.iter(|| { let m = Message::try_from(get_sample_message()).unwrap(); let seg = m.segments.first(); From edd815e7ec30ab23e217644f29debbffdae04fce Mon Sep 17 00:00:00 2001 From: Tim Thompson Date: Sun, 8 Aug 2021 14:04:41 +1000 Subject: [PATCH 08/14] WIP: Moving away from string indexers to query functions --- src/message.rs | 11 ++++------- src/segments/generic.rs | 4 +++- src/segments/msh.rs | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/message.rs b/src/message.rs index 0c3b4b6..1cc2ba0 100644 --- a/src/message.rs +++ b/src/message.rs @@ -102,15 +102,14 @@ impl<'a> Message<'a> { .segments .iter() .position(|r| &r.as_str()[..seg_name.len()] == seg_name); - - match seg_index { - //TODO: What is this doing... + + match seg_index { //TODO: What is this doing... Some(_) => {} None => return &"", } let seg = &self.segments[seg_index.unwrap()]; - + // Return the appropriate source reference match seg { // Short circuit for now @@ -127,10 +126,8 @@ impl<'a> Message<'a> { } } - ///Access segment, field, or sub-field string references by passing a query string in dot notation. - ///See [`Self::query()`] for more information. + /// Access Segment, Field, or sub-field string references by string index pub fn query_by_string(&self, idx: String) -> &'a str { - //TODO: Determine if we actually need this function... in what scenario are we passing a String in here rather an &str? self.query(idx.as_str()) } } diff --git a/src/segments/generic.rs b/src/segments/generic.rs index b2dc005..7fd66f8 100644 --- a/src/segments/generic.rs +++ b/src/segments/generic.rs @@ -172,7 +172,9 @@ mod tests { x.query_by_string(String::from("F1")), x.query_by_string(String::from("F1.R2")), x.query_by_string(String::from("F1.R2.C1")), - String::from(x.query("F10")) + x.query("F1.R10") + x.query("F1.R2.C10"), + String::from(x.query("F10")) + + x.query("F1.R10") + + x.query("F1.R2.C10"), ), _ => ("", "", "", String::from("")), }; diff --git a/src/segments/msh.rs b/src/segments/msh.rs index 32d49ad..bc7ff61 100644 --- a/src/segments/msh.rs +++ b/src/segments/msh.rs @@ -1,7 +1,7 @@ +use std::fmt::Display; use super::fields::Field; use super::separators::Separators; use super::*; -use std::fmt::Display; /// The most important Segment, almost all HL7 messages have an MSH (MLLP simple ack I'm looking at you). /// Given the importance of this segment for driving application behaviour, it gets the special treatment From cb05b43afb51a187337507c6af8edb277cbcff83 Mon Sep 17 00:00:00 2001 From: Tim Thompson Date: Sun, 8 Aug 2021 14:30:48 +1000 Subject: [PATCH 09/14] feat: removed string indexer... not really applicable for querying by path --- src/fields/mod.rs | 7 ++++--- src/message.rs | 1 + src/segments/generic.rs | 6 ++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/fields/mod.rs b/src/fields/mod.rs index 6d802bc..7da90a6 100644 --- a/src/fields/mod.rs +++ b/src/fields/mod.rs @@ -1,7 +1,7 @@ -use super::separators::Separators; -use super::*; use std::fmt::Display; use std::ops::Index; +use super::separators::Separators; +use super::*; /// Represents a single field inside the HL7. Note that fields can include repeats, components and sub-components. /// See [the spec](http://www.hl7.eu/HL7v2x/v251/std251/ch02.html#Heading13) for more info @@ -106,6 +106,7 @@ impl<'a> Field<'a> { } } + impl<'a> Display for Field<'a> { /// Required for to_string() and other formatter consumers fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -294,7 +295,7 @@ mod tests { let f = Field::parse_mandatory(Some("xxx^yyy&zzz"), &d).unwrap(); let idx0 = String::from("R2"); let oob = "R2.C3"; - assert_eq!(f.query_by_string(idx0), "yyy&zzz"); + assert_eq!(f.query_by_string(idx0.clone()), "yyy&zzz"); assert_eq!(f.query("R2.C2"), "zzz"); assert_eq!(f.query(oob), ""); } diff --git a/src/message.rs b/src/message.rs index 1cc2ba0..50bd931 100644 --- a/src/message.rs +++ b/src/message.rs @@ -128,6 +128,7 @@ impl<'a> Message<'a> { /// Access Segment, Field, or sub-field string references by string index pub fn query_by_string(&self, idx: String) -> &'a str { + //TODO: Determine if we actually need this function... in what scenario are we passing a String in here rather an &str? self.query(idx.as_str()) } } diff --git a/src/segments/generic.rs b/src/segments/generic.rs index 7fd66f8..5bb7de9 100644 --- a/src/segments/generic.rs +++ b/src/segments/generic.rs @@ -1,6 +1,6 @@ -use super::{fields::Field, separators::Separators, Hl7ParseError}; use std::fmt::Display; use std::ops::Index; +use super::{Hl7ParseError, fields::Field, separators::Separators}; /// A generic bag o' fields, representing an arbitrary segment. #[derive(Debug, PartialEq, Clone)] @@ -58,8 +58,9 @@ impl<'a> GenericSegment<'a> { let idx: usize = stringnum.parse().unwrap(); let field = &self.fields[idx]; let query = sections[1..].join("."); - + field.query_by_string(query) + } } } @@ -107,6 +108,7 @@ impl<'a> Index<(usize, usize, usize)> for GenericSegment<'a> { &self.fields[fidx.0][(fidx.1, fidx.2)] } } + impl<'a> Index for GenericSegment<'a> { type Output = &'a str; /// Access Field as string reference From 7cfe6d4dcbbe388d2be1d4b4dcbae7703dd32f81 Mon Sep 17 00:00:00 2001 From: Tim Thompson Date: Sun, 8 Aug 2021 17:06:47 +1000 Subject: [PATCH 10/14] docs: Getting sidetracked with simple docs here rather than querying --- benches/simple_parse.rs | 3 +-- src/fields/mod.rs | 7 +++---- src/message.rs | 10 ++++++---- src/segments/generic.rs | 9 +++------ src/segments/msh.rs | 2 +- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/benches/simple_parse.rs b/benches/simple_parse.rs index 2fe80ed..a82273f 100644 --- a/benches/simple_parse.rs +++ b/benches/simple_parse.rs @@ -1,6 +1,6 @@ -use std::convert::TryFrom; use criterion::{criterion_group, criterion_main, Criterion}; use rusthl7::{message::*, segments::Segment}; +use std::convert::TryFrom; fn get_sample_message() -> &'static str { "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rPID|||555-44-4444||EVERYWOMAN^EVE^E^^^^L|JONES|19620320|F|||153 FERNWOOD DR.^^STATESVILLE^OH^35292||(206)3345232|(206)752-121||||AC555444444||67-A4335^OH^20030520\rOBR|1|845439^GHH OE|1045813^GHH LAB|15545^GLUCOSE|||200202150730|||||||||555-55-5555^PRIMARY^PATRICIA P^^^^MD^^|||||||||F||||||444-44-4444^HIPPOCRATES^HOWARD H^^^^MD\rOBX|1|SN|1554-5^GLUCOSE^POST 12H CFST:MCNC:PT:SER/PLAS:QN||^182|mg/dl|70_105|H|||F" @@ -8,7 +8,6 @@ fn get_sample_message() -> &'static str { fn message_parse(c: &mut Criterion) { c.bench_function("oru parse", |b| { - b.iter(|| { let m = Message::try_from(get_sample_message()).unwrap(); let seg = m.segments.first(); diff --git a/src/fields/mod.rs b/src/fields/mod.rs index 7da90a6..6d802bc 100644 --- a/src/fields/mod.rs +++ b/src/fields/mod.rs @@ -1,7 +1,7 @@ -use std::fmt::Display; -use std::ops::Index; use super::separators::Separators; use super::*; +use std::fmt::Display; +use std::ops::Index; /// Represents a single field inside the HL7. Note that fields can include repeats, components and sub-components. /// See [the spec](http://www.hl7.eu/HL7v2x/v251/std251/ch02.html#Heading13) for more info @@ -106,7 +106,6 @@ impl<'a> Field<'a> { } } - impl<'a> Display for Field<'a> { /// Required for to_string() and other formatter consumers fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -295,7 +294,7 @@ mod tests { let f = Field::parse_mandatory(Some("xxx^yyy&zzz"), &d).unwrap(); let idx0 = String::from("R2"); let oob = "R2.C3"; - assert_eq!(f.query_by_string(idx0.clone()), "yyy&zzz"); + assert_eq!(f.query_by_string(idx0), "yyy&zzz"); assert_eq!(f.query("R2.C2"), "zzz"); assert_eq!(f.query(oob), ""); } diff --git a/src/message.rs b/src/message.rs index 50bd931..0c3b4b6 100644 --- a/src/message.rs +++ b/src/message.rs @@ -102,14 +102,15 @@ impl<'a> Message<'a> { .segments .iter() .position(|r| &r.as_str()[..seg_name.len()] == seg_name); - - match seg_index { //TODO: What is this doing... + + match seg_index { + //TODO: What is this doing... Some(_) => {} None => return &"", } let seg = &self.segments[seg_index.unwrap()]; - + // Return the appropriate source reference match seg { // Short circuit for now @@ -126,7 +127,8 @@ impl<'a> Message<'a> { } } - /// Access Segment, Field, or sub-field string references by string index + ///Access segment, field, or sub-field string references by passing a query string in dot notation. + ///See [`Self::query()`] for more information. pub fn query_by_string(&self, idx: String) -> &'a str { //TODO: Determine if we actually need this function... in what scenario are we passing a String in here rather an &str? self.query(idx.as_str()) diff --git a/src/segments/generic.rs b/src/segments/generic.rs index 5bb7de9..c123345 100644 --- a/src/segments/generic.rs +++ b/src/segments/generic.rs @@ -1,6 +1,6 @@ +use super::{fields::Field, separators::Separators, Hl7ParseError}; use std::fmt::Display; use std::ops::Index; -use super::{Hl7ParseError, fields::Field, separators::Separators}; /// A generic bag o' fields, representing an arbitrary segment. #[derive(Debug, PartialEq, Clone)] @@ -58,9 +58,8 @@ impl<'a> GenericSegment<'a> { let idx: usize = stringnum.parse().unwrap(); let field = &self.fields[idx]; let query = sections[1..].join("."); - - field.query_by_string(query) + field.query_by_string(query) } } } @@ -174,9 +173,7 @@ mod tests { x.query_by_string(String::from("F1")), x.query_by_string(String::from("F1.R2")), x.query_by_string(String::from("F1.R2.C1")), - String::from(x.query("F10")) - + x.query("F1.R10") - + x.query("F1.R2.C10"), + String::from(x.query("F10")) + x.query("F1.R10") + x.query("F1.R2.C10"), ), _ => ("", "", "", String::from("")), }; diff --git a/src/segments/msh.rs b/src/segments/msh.rs index bc7ff61..32d49ad 100644 --- a/src/segments/msh.rs +++ b/src/segments/msh.rs @@ -1,7 +1,7 @@ -use std::fmt::Display; use super::fields::Field; use super::separators::Separators; use super::*; +use std::fmt::Display; /// The most important Segment, almost all HL7 messages have an MSH (MLLP simple ack I'm looking at you). /// Given the importance of this segment for driving application behaviour, it gets the special treatment From 6dea55c31cd573003dcd84d54fd5bcb564732cec Mon Sep 17 00:00:00 2001 From: Tim Thompson Date: Mon, 9 Aug 2021 09:29:11 +1000 Subject: [PATCH 11/14] tests: Added a couple more simple benchmarks --- benches/simple_parse.rs | 59 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/benches/simple_parse.rs b/benches/simple_parse.rs index a82273f..d18d4ce 100644 --- a/benches/simple_parse.rs +++ b/benches/simple_parse.rs @@ -7,18 +7,69 @@ fn get_sample_message() -> &'static str { } fn message_parse(c: &mut Criterion) { - c.bench_function("oru parse", |b| { + c.bench_function("ORU parse", |b| { + b.iter(|| { + let _ = Message::try_from(get_sample_message()).unwrap(); + }) + }); +} + +fn get_segments_by_name(c: &mut Criterion) { + c.bench_function("Get Segment By Name", |b| { + let m = Message::try_from(get_sample_message()).unwrap(); + + b.iter(|| { + let _segs = m.generic_segments_by_name("OBR").unwrap(); + //assert!(segs.len() == 1); + }) + }); +} + +fn get_msh_and_read_field(c: &mut Criterion) { + + c.bench_function("Read Field from MSH (variable)", |b| { + let m = Message::try_from(get_sample_message()).unwrap(); + b.iter(|| { - let m = Message::try_from(get_sample_message()).unwrap(); let seg = m.segments.first(); if let Some(Segment::MSH(msh)) = seg { - let _app = msh.msh_3_sending_application.as_ref().unwrap(); + let _app = msh.msh_3_sending_application.as_ref().unwrap(); // direct variable access //println!("{}", _app.value()); } }) }); } -criterion_group!(benches, message_parse); +fn get_pid_and_read_field_via_vec(c: &mut Criterion) { + + c.bench_function("Read Field from PID (lookup)", |b| { + 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 + } + }) + }); +} + +fn get_pid_and_read_field_via_query(c: &mut Criterion) { + + c.bench_function("Read Field from PID (query)", |b| { + let m = Message::try_from(get_sample_message()).unwrap(); + + b.iter(|| { + let _val = m.query("PID.F3"); // query via Message + assert_eq!(_val, "555-44-4444"); // lookup from vec + + }) + }); +} + + +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); criterion_main!(benches); From 5af320df696da5d19c6f30de20ca6bcb4a54551e Mon Sep 17 00:00:00 2001 From: Tim Thompson Date: Tue, 10 Aug 2021 06:14:04 +1000 Subject: [PATCH 12/14] chore: Prep for some future 0.5 release --- CHANGELOG.md | 9 ++++++--- Cargo.toml | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b57cda..30c7949 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,17 @@ # Changelog +## 0.5.0 + - Add `query` functions to replace the string based `Index` impls in the version version. These are functionally identical to the string `Index` implementations, but avoid some lifetime issues (returning `&&str`) and have visible documentation. + ## 0.4.0 - Large change (thanks @sempervictus) to allow querying of message content by both numerical indexer and dot-notation string indexers - Note that the string indexers will be replaced with a normal function call in a future release. ## 0.3.0 - - Changes from @sempervictus to expose internal values again + - Extensive work by @sempervictus to expose the segments/fields as collections (which I hadn't got back to after the re-write to slices.) ## 0.2.0 -- Re-write to avoid excessive string cloning by operating on slices of the source HL7 +- Re-Write to not expose cloned/copied vecs of vecs everywhere. We have all the data in a single string slice to begin with so lets return slices from that. ## 0.1.0 -- Initial string.clone() heavy library, nothing to see here... \ No newline at end of file +- Initial string.clone() heavy library, nothing to see here... diff --git a/Cargo.toml b/Cargo.toml index 1b3da5b..a5c96ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rust-hl7" -version = "0.4.0" -authors = ["wokket "] +version = "0.5.0-pre" +authors = ["wokket "] edition = "2018" description = "HL7 Parser and object builder? query'er? - experimental only at any rate" license = "MIT OR Apache-2.0" From e607ca8168e305dba0f68fb3763e715282b49c7c Mon Sep 17 00:00:00 2001 From: Tim Thompson Date: Wed, 11 Aug 2021 09:38:14 +1000 Subject: [PATCH 13/14] feat #23: Removed surplus methods that take strings by using Into<&str> params instead --- src/fields/mod.rs | 17 +++++++++-------- src/message.rs | 18 +++++++++--------- src/segments/generic.rs | 23 ++++++++++++----------- src/segments/mod.rs | 6 ++++-- src/segments/msh.rs | 5 ++++- 5 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/fields/mod.rs b/src/fields/mod.rs index 6d802bc..ddc3918 100644 --- a/src/fields/mod.rs +++ b/src/fields/mod.rs @@ -15,7 +15,8 @@ pub struct Field<'a> { impl<'a> Field<'a> { /// Convert the given line of text into a field. - pub fn parse(input: &'a str, delims: &Separators) -> Result, Hl7ParseError> { + pub fn parse>(input: S, delims: &Separators) -> Result, Hl7ParseError> { + let input = input.into(); let components = input.split(delims.component).collect::>(); let subcomponents = components .iter() @@ -57,18 +58,23 @@ impl<'a> Field<'a> { } /// Compatibility method to get the underlying value of this field. + #[inline] pub fn value(&self) -> &'a str { self.source } /// Export value to str + #[inline] pub fn as_str(&self) -> &'a str { self.source } /// Access string reference of a Field component by String index /// Adjust the index by one as medical people do not count from zero - pub fn query(&self, sidx: &str) -> &'a str { + pub fn query<'b, S>(&self, sidx: S) -> &'a str + where S: Into<&'b str> { + + let sidx = sidx.into(); let parts = sidx.split('.').collect::>(); if parts.len() == 1 { @@ -99,11 +105,6 @@ impl<'a> Field<'a> { "" } } - - /// Access Segment, Field, or sub-field string references by string index - pub fn query_by_string(&self, idx: String) -> &'a str { - &self.query(idx.as_str()) - } } impl<'a> Display for Field<'a> { @@ -294,7 +295,7 @@ mod tests { let f = Field::parse_mandatory(Some("xxx^yyy&zzz"), &d).unwrap(); let idx0 = String::from("R2"); let oob = "R2.C3"; - assert_eq!(f.query_by_string(idx0), "yyy&zzz"); + assert_eq!(f.query(&*idx0), "yyy&zzz"); assert_eq!(f.query("R2.C2"), "zzz"); assert_eq!(f.query(oob), ""); } diff --git a/src/message.rs b/src/message.rs index 0c3b4b6..abe06f8 100644 --- a/src/message.rs +++ b/src/message.rs @@ -74,7 +74,7 @@ impl<'a> Message<'a> { Ok(vecs) } - /// Returns the source string slice used to create this Message initially. + /// Returns the source string slice used to create this Message initially. This method does not allocate. /// ## Example: /// ``` /// # use rusthl7::Hl7ParseError; @@ -87,12 +87,17 @@ impl<'a> Message<'a> { /// # Ok(()) /// # } /// ``` + #[inline] pub fn as_str(&self) -> &'a str { self.source } /// Access Segment, Field, or sub-field string references by string index - pub fn query(&self, idx: &str) -> &'a str { + pub fn query<'b, S>(&self, idx: S) -> &'a str + where S: Into<&'b str> { + + let idx = idx.into(); + // Parse index elements let indices: Vec<&str> = idx.split('.').collect(); let seg_name = indices[0]; @@ -121,18 +126,12 @@ impl<'a> Message<'a> { &g.source } else { let query = indices[1..].join("."); - &g.query_by_string(query) + &g.query(&*query) } } } } - ///Access segment, field, or sub-field string references by passing a query string in dot notation. - ///See [`Self::query()`] for more information. - pub fn query_by_string(&self, idx: String) -> &'a str { - //TODO: Determine if we actually need this function... in what scenario are we passing a String in here rather an &str? - self.query(idx.as_str()) - } } impl<'a> TryFrom<&'a str> for Message<'a> { @@ -325,6 +324,7 @@ mod tests { let hl7 = "MSH|^~\\&|GHH LAB|ELAB-3|GHH OE|BLDG4|200202150930||ORU^R01|CNTRL-3456|P|2.4\rOBR|segment^sub&segment"; let msg = Message::try_from(hl7)?; assert_eq!(msg.query("OBR.F1.R2.C1"), "sub"); + assert_eq!(msg.query(&*"OBR.F1.R2.C1".to_string()), "sub"); // Test the Into param with a String assert_eq!(msg[String::from("OBR.F1.R2.C1")], "sub"); Ok(()) } diff --git a/src/segments/generic.rs b/src/segments/generic.rs index c123345..9e4588a 100644 --- a/src/segments/generic.rs +++ b/src/segments/generic.rs @@ -12,7 +12,9 @@ pub struct GenericSegment<'a> { impl<'a> GenericSegment<'a> { /// Convert the given line of text into a GenericSegment. - pub fn parse(input: &'a str, delims: &Separators) -> Result, Hl7ParseError> { + pub fn parse>(input: S, delims: &Separators) -> Result, Hl7ParseError> { + let input = input.into(); + let fields: Result>, Hl7ParseError> = input .split(delims.field) .map(|line| Field::parse(line, delims)) @@ -28,17 +30,16 @@ impl<'a> GenericSegment<'a> { } /// Export source to str + #[inline] pub fn as_str(&self) -> &'a str { self.source } - /// Access Segment, Field, or sub-field string references by string index - pub fn query_by_string(&self, idx: String) -> &'a str { - self.query(idx.as_str()) - } - /// Access Field as string reference - pub fn query(&self, fidx: &str) -> &'a str { + pub fn query<'b, S>(&self, fidx: S) -> &'a str + where S: Into<&'b str> { + + let fidx = fidx.into(); let sections = fidx.split('.').collect::>(); match sections.len() { @@ -59,7 +60,7 @@ impl<'a> GenericSegment<'a> { let field = &self.fields[idx]; let query = sections[1..].join("."); - field.query_by_string(query) + field.query(&*query) } } } @@ -170,9 +171,9 @@ mod tests { let msg = Message::try_from(hl7).unwrap(); let (f, c, s, oob) = match &msg.segments[1] { Segment::Generic(x) => ( - x.query_by_string(String::from("F1")), - x.query_by_string(String::from("F1.R2")), - x.query_by_string(String::from("F1.R2.C1")), + x.query("F1"), //&str + x.query("F1.R2"), // &str + x.query(&*String::from("F1.R2.C1")), //String String::from(x.query("F10")) + x.query("F1.R10") + x.query("F1.R2.C10"), ), _ => ("", "", "", String::from("")), diff --git a/src/segments/mod.rs b/src/segments/mod.rs index 00359cb..e84de02 100644 --- a/src/segments/mod.rs +++ b/src/segments/mod.rs @@ -1,6 +1,7 @@ pub mod generic; pub mod msh; +use std::fmt::Display; use super::fields::Field; use super::separators::Separators; use super::*; @@ -16,7 +17,9 @@ pub enum Segment<'a> { impl<'a> Segment<'a> { /// Convert the given line of text into a Segment. - pub fn parse(input: &'a str, delims: &Separators) -> Result, Hl7ParseError> { + pub fn parse>(input: S, delims: &Separators) -> Result, Hl7ParseError> { + let input = input.into(); + let fields: Result>, Hl7ParseError> = input .split(delims.field) .map(|line| Field::parse(line, delims)) @@ -41,7 +44,6 @@ impl<'a> Segment<'a> { } } -use std::fmt::Display; impl<'a> Display for Segment<'a> { /// Required for to_string() and other formatter consumers fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/src/segments/msh.rs b/src/segments/msh.rs index 32d49ad..a3de7b0 100644 --- a/src/segments/msh.rs +++ b/src/segments/msh.rs @@ -38,7 +38,9 @@ pub struct MshSegment<'a> { } impl<'a> MshSegment<'a> { - pub fn parse(input: &'a str, delims: &Separators) -> Result, Hl7ParseError> { + pub fn parse>(input: S, delims: &Separators) -> Result, Hl7ParseError> { + let input = input.into(); + let mut fields = input.split(delims.field); assert!(fields.next().unwrap() == "MSH"); @@ -72,6 +74,7 @@ impl<'a> MshSegment<'a> { } /// Export source to str + #[inline] pub fn as_str(&self) -> &'a str { self.source } From ee34aebd153848c87688567158777dad9fb29b7d Mon Sep 17 00:00:00 2001 From: Tim Thompson Date: Wed, 11 Aug 2021 15:20:05 +1000 Subject: [PATCH 14/14] chore: Cargo clippy and fmt pass --- benches/simple_parse.rs | 16 +++++++++------- src/fields/mod.rs | 18 +++++++++++------- src/message.rs | 20 ++++++++++---------- src/segments/generic.rs | 18 +++++++++++------- src/segments/mod.rs | 7 +++++-- src/segments/msh.rs | 5 ++++- 6 files changed, 50 insertions(+), 34 deletions(-) diff --git a/benches/simple_parse.rs b/benches/simple_parse.rs index d18d4ce..d003a6e 100644 --- a/benches/simple_parse.rs +++ b/benches/simple_parse.rs @@ -26,7 +26,6 @@ fn get_segments_by_name(c: &mut Criterion) { } fn get_msh_and_read_field(c: &mut Criterion) { - c.bench_function("Read Field from MSH (variable)", |b| { let m = Message::try_from(get_sample_message()).unwrap(); @@ -35,14 +34,13 @@ fn get_msh_and_read_field(c: &mut Criterion) { if let Some(Segment::MSH(msh)) = seg { let _app = msh.msh_3_sending_application.as_ref().unwrap(); // direct variable access - //println!("{}", _app.value()); + //println!("{}", _app.value()); } }) }); } fn get_pid_and_read_field_via_vec(c: &mut Criterion) { - c.bench_function("Read Field from PID (lookup)", |b| { let m = Message::try_from(get_sample_message()).unwrap(); @@ -58,18 +56,22 @@ fn get_pid_and_read_field_via_vec(c: &mut Criterion) { } fn get_pid_and_read_field_via_query(c: &mut Criterion) { - c.bench_function("Read Field from PID (query)", |b| { let m = Message::try_from(get_sample_message()).unwrap(); b.iter(|| { let _val = m.query("PID.F3"); // query via Message assert_eq!(_val, "555-44-4444"); // lookup from vec - }) }); } - -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); +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 +); criterion_main!(benches); diff --git a/src/fields/mod.rs b/src/fields/mod.rs index ddc3918..13ef7b3 100644 --- a/src/fields/mod.rs +++ b/src/fields/mod.rs @@ -15,7 +15,10 @@ pub struct Field<'a> { impl<'a> Field<'a> { /// Convert the given line of text into a field. - pub fn parse>(input: S, delims: &Separators) -> Result, Hl7ParseError> { + pub fn parse>( + input: S, + delims: &Separators, + ) -> Result, Hl7ParseError> { let input = input.into(); let components = input.split(delims.component).collect::>(); let subcomponents = components @@ -72,8 +75,9 @@ impl<'a> Field<'a> { /// Access string reference of a Field component by String index /// Adjust the index by one as medical people do not count from zero pub fn query<'b, S>(&self, sidx: S) -> &'a str - where S: Into<&'b str> { - + where + S: Into<&'b str>, + { let sidx = sidx.into(); let parts = sidx.split('.').collect::>(); @@ -84,7 +88,7 @@ impl<'a> Field<'a> { .collect::(); let idx: usize = stringnums.parse().unwrap(); - &self[idx - 1] + self[idx - 1] } else if parts.len() == 2 { let stringnums = parts[0] .chars() @@ -100,7 +104,7 @@ impl<'a> Field<'a> { let idx1: usize = stringnums.parse().unwrap(); - &self[(idx0 - 1, idx1 - 1)] + self[(idx0 - 1, idx1 - 1)] } else { "" } @@ -148,7 +152,7 @@ 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")] +#[deprecated(note = "This will be removed in a future version")] impl<'a> Index for Field<'a> { type Output = &'a str; fn index(&self, sidx: String) -> &Self::Output { @@ -189,7 +193,7 @@ impl<'a> Index<&str> for Field<'a> { /// 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")] + #[deprecated(note = "This will be removed in a future version")] fn index(&self, idx: &str) -> &Self::Output { &self[String::from(idx)] } diff --git a/src/message.rs b/src/message.rs index abe06f8..96feeb8 100644 --- a/src/message.rs +++ b/src/message.rs @@ -93,9 +93,10 @@ impl<'a> Message<'a> { } /// Access Segment, Field, or sub-field string references by string index - pub fn query<'b, S>(&self, idx: S) -> &'a str - where S: Into<&'b str> { - + pub fn query<'b, S>(&self, idx: S) -> &'a str + where + S: Into<&'b str>, + { let idx = idx.into(); // Parse index elements @@ -111,7 +112,7 @@ impl<'a> Message<'a> { match seg_index { //TODO: What is this doing... Some(_) => {} - None => return &"", + None => return "", } let seg = &self.segments[seg_index.unwrap()]; @@ -119,19 +120,18 @@ impl<'a> Message<'a> { // Return the appropriate source reference match seg { // Short circuit for now - Segment::MSH(m) => &m.source, + Segment::MSH(m) => m.source, // Parse out slice depth Segment::Generic(g) => { if indices.len() < 2 { - &g.source + g.source } else { let query = indices[1..].join("."); - &g.query(&*query) + g.query(&*query) } } } } - } impl<'a> TryFrom<&'a str> for Message<'a> { @@ -204,7 +204,7 @@ impl<'a> Index for Message<'a> { /// 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")] + #[deprecated(note = "This will be removed in a future version")] fn index(&self, idx: String) -> &Self::Output { // Parse index elements let indices: Vec<&str> = idx.split('.').collect(); @@ -240,7 +240,7 @@ impl<'a> Index<&str> for Message<'a> { /// 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")] + #[deprecated(note = "This will be removed in a future version")] fn index(&self, idx: &str) -> &Self::Output { &self[String::from(idx)] } diff --git a/src/segments/generic.rs b/src/segments/generic.rs index 9e4588a..d7f55f4 100644 --- a/src/segments/generic.rs +++ b/src/segments/generic.rs @@ -12,7 +12,10 @@ pub struct GenericSegment<'a> { impl<'a> GenericSegment<'a> { /// Convert the given line of text into a GenericSegment. - pub fn parse>(input: S, delims: &Separators) -> Result, Hl7ParseError> { + pub fn parse>( + input: S, + delims: &Separators, + ) -> Result, Hl7ParseError> { let input = input.into(); let fields: Result>, Hl7ParseError> = input @@ -37,8 +40,9 @@ impl<'a> GenericSegment<'a> { /// Access Field as string reference pub fn query<'b, S>(&self, fidx: S) -> &'a str - where S: Into<&'b str> { - + where + S: Into<&'b str>, + { let fidx = fidx.into(); let sections = fidx.split('.').collect::>(); @@ -49,7 +53,7 @@ impl<'a> GenericSegment<'a> { .filter(|c| c.is_digit(10)) .collect::(); let idx: usize = stringnum.parse().unwrap(); - &self[idx] + self[idx] } _ => { let stringnum = sections[0] @@ -140,7 +144,7 @@ impl<'a> Index<&str> for GenericSegment<'a> { /// 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")] + #[deprecated(note = "This will be removed in a future version")] fn index(&self, idx: &str) -> &Self::Output { &self[String::from(idx)] } @@ -171,8 +175,8 @@ mod tests { let msg = Message::try_from(hl7).unwrap(); let (f, c, s, oob) = match &msg.segments[1] { Segment::Generic(x) => ( - x.query("F1"), //&str - x.query("F1.R2"), // &str + x.query("F1"), //&str + x.query("F1.R2"), // &str x.query(&*String::from("F1.R2.C1")), //String String::from(x.query("F10")) + x.query("F1.R10") + x.query("F1.R2.C10"), ), diff --git a/src/segments/mod.rs b/src/segments/mod.rs index e84de02..af0a51a 100644 --- a/src/segments/mod.rs +++ b/src/segments/mod.rs @@ -1,12 +1,12 @@ pub mod generic; pub mod msh; -use std::fmt::Display; use super::fields::Field; use super::separators::Separators; use super::*; use generic::GenericSegment; use msh::MshSegment; +use std::fmt::Display; /// A single segment, 0x13 delimited line from a source HL7 message consisting of multiple fields. #[derive(Debug, PartialEq)] @@ -17,7 +17,10 @@ pub enum Segment<'a> { impl<'a> Segment<'a> { /// Convert the given line of text into a Segment. - pub fn parse>(input: S, delims: &Separators) -> Result, Hl7ParseError> { + pub fn parse>( + input: S, + delims: &Separators, + ) -> Result, Hl7ParseError> { let input = input.into(); let fields: Result>, Hl7ParseError> = input diff --git a/src/segments/msh.rs b/src/segments/msh.rs index a3de7b0..d9b8403 100644 --- a/src/segments/msh.rs +++ b/src/segments/msh.rs @@ -38,7 +38,10 @@ pub struct MshSegment<'a> { } impl<'a> MshSegment<'a> { - pub fn parse>(input: S, delims: &Separators) -> Result, Hl7ParseError> { + pub fn parse>( + input: S, + delims: &Separators, + ) -> Result, Hl7ParseError> { let input = input.into(); let mut fields = input.split(delims.field);