wokket / rust-hl7

Learning rust by playing with a HL7 parser. Use for real at your own risk!
18 stars 8 forks source link

Component and Subcomponent Parsing/Values #9

Closed sempervictus closed 3 years ago

sempervictus commented 3 years ago

I've implemented the basic structure for parsing out component and subcomponent &strs into vectors:

#[derive(Debug, PartialEq, Clone)]
pub enum Field<'a> {
    Generic(&'a str),
    Component(Vec<&'a str>),
    Subcomponent(Vec<Vec<&'a str>>),
}

impl<'a> Field<'a> {
    /// Convert the given line of text into a field.
    pub fn parse(input: &'a str, delims: &Separators) -> Result<Field<'a>, Hl7ParseError> {
        match input.find(delims.component) {
            Some(_x) => {
                match input.find(delims.subcomponent) {
                    Some(_x) => {
                        let components = input.split(delims.component).collect::<Vec<&str>>();
                        Ok(Field::Subcomponent(
                            components
                                .iter()
                                .map(|c| c.split(delims.subcomponent).collect::<Vec<&str>>())
                                .collect::<Vec<Vec<&str>>>()
                        ))
                    },
                    None => Ok(Field::Component(input.split(delims.component).collect())),
                }
            }
            None => Ok(Field::Generic(input))
        }
    }
...

However, getting data out of this construct is... non-trivial?

    pub fn value(&self) -> &'a str {
        let component = String::from(Separators::default().component);
        let subcomponent = String::from(Separators::default().subcomponent);
        match &self {
            Field::Generic(g) => g,
            Field::Component(c) => &c.join(&component),
            Field::Subcomponent(s) => {
                &s.into_iter().map(|sc|
                    &sc.join(&subcomponent)
                ).collect::<Vec<&str>>()
                .join(&component).as_str()
            }
        }
    }

because making an &str out of vectors of them or vectors of vectors of them requires some sort of borrowing function that i'm not quite getting.

@wokket - happen to have any insight to how i can implement that without

error[E0515]: cannot return value referencing temporary value
  --> src/fields/mod.rs:64:9
   |
64 | /         match &self {
65 | |             Field::Generic(g) => g,
66 | |             Field::Component(c) => &c.join(&component),
   | |                                     ------------------ temporary value created here
67 | |             _ => ""
...  |
73 | |             // }
74 | |         }
   | |_________^ returns a value referencing data owned by the current function

or

error[E0277]: a value of type `Vec<&str>` cannot be built from an iterator over elements of type `&String`
  --> src/fields/mod.rs:70:19
   |
70 |                 ).collect::<Vec<&str>>()
   |                   ^^^^^^^ value of type `Vec<&str>` cannot be built from `std::iter::Iterator<Item=&String>`
   |
   = help: the trait `FromIterator<&String>` is not implemented for `Vec<&str>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `rust-hl7`

biting me in the arse? The alternative approach would be to keep the field data as a string and perform components() and subcomponents() calls as functions against that, i think. Thoughts?

wokket commented 3 years ago

Hey mate,

Repeats/Components/Subcomponents are on the TODO list, but I haven't given much concrete through to it.

My general thinking was to have variants of Field where Generic was just a string, and Repeat etc had more logic/behaviour associated with them. The hard bit I had to work through in my head was how best to actually expose that data, as a consumer of the HL7 do I really just want a bag of bags of bags of strings?? Or something more like:

enum Field {
  Generic(str),
  Repeating(Vec<Repeat>)
  Componentised(Vec<Component>)
}

enum Repeat {
 Generic(str),
 Componentised(Vec<Component>)
}

enum Component {
  Generic(str),
  Subcomponentised(Vec<SubComponent/str>)

Given I really don't want to get into interpresting field types etc I'm envisaging a set of helper methods in the library (to say convert a str to a DateTime etc) is just exposing the strings enough? What value does a value() method even provide on a repeating field?

Thoughts?

sempervictus commented 3 years ago

For the time being, i implemented this functionally at the Field level to emulate how value() works and to return Vec<&str> and Vec<Vec<&str>> as appropriate. Looking at this from a consumer's perspective, i need to be able to rapidly access complex fields by index (see #11) which necessitates having an iterable set of dimensions which i can traverse to reach the desired position in each dimension requested by the FIELDNAME.FX.RX.CX index notation

sempervictus commented 3 years ago

Closed via #11