Elements
17 May 2015The FHIR Element
definition lists only id
and extension
values. However, as we've previously established, a FHIR element is a key/value pair, and has optional id
and extension
values.
Given that definition, the design of Element
falls pretty cleanly into place. I've modeled Element
as follows:
pub struct Element {
pub name: String,
pub value: ElementType
pub id: Option<String>,
pub extensions: Option<Vec<Extension>>
}
Even if you don't grok Rust, this is pretty straightforward: we've defined Element
as a struct
with four fields. The first, name
is a string (for various reasons, we might change that definition to a reference value at some point). At this point, name
could be any Unicode value; if I want to be perfectly pedantic, I might at some point define a Rust type to encapsulate a lowercase ASCII string (assuming name
is so constrained -- this is not clearly documented yet).
The Option
type is the Rust idiom for values that might not have a value. Unlike traditional null
or nil
values (but like C# nullables), Rust generally makes you consider the None
case explicitly, avoiding the dreaded "Null Pointer Exception".
As we previously discovered, the value
of an Element
is either a primitive atomic value, or a sub-elements bag of Element
. (Bag because due to element cardinality, an Element
can contain multiples of the same sub-element, and because FHIR does not enforce ordering, although some representations of FHIR may). (NOTE: This definition is wrong: read the posts on JSON serialization and id
s to learn more)
Again, this definition translates nicely into a type declaration for ElementType
:
pub enum ElementType {
Atom(Primitive),
Elt(Vec<Element>)
}
Again, a little explanation: a Rust enum
is a tagged union type. This just represents our definition above: the value of an Element
is either a Primitive
valued Atom
, or an Elt
valued as a Vec
(vector) of Element
s. As an implementation detail, the Rust Vec
type is ordered. This isn't a problem, as FHIR doesn't prohibit ordering, but if I implement equality checking, I need to do so in an order-free way.
We still need to define what Primitive
and Extension
are (and the latter is almost, but not quite, an Element
), but what's neat about this definition is that with appropriate serialization and deserialization,we almost have a functional, if not terribly developer friendly, implementation. I say almost because there's a minor detail we'll have to deal with when it comes to JSON serialization: we need to consider the defined, not just the actual, cardinality of sub-elements. More about that later.
For instance, simple printing of an element (to a non-standard serialization) can be recursively defined (without dealing, for now, with id
s and extension
s):
impl Element {
pub fn to_string(&self) -> String {
self.recursive_to_string(0)
}
fn recursive_to_string(&self,level: usize) -> String {
fn recursive_elt_vec_to_string (v: &Vec<Element>, level: usize) -> String {
v.iter().map(|e| e.recursive_to_string(level)).collect::<Vec<String>>().connect("\n")
}
let spaces: String = repeat(" ").take(level).collect::<Vec<&str>>().concat();
let label = format!("{}: ",self.name);
let value = match self.value {
ElementType::Atom(ref v) => format!("{}",v),
ElementType::Elt(ref v) => format!("\n{}",recursive_elt_vec_to_string(v, level + 1))
};
format!("{}{}{}", spaces, label, value)
}
}
Next we will deal with primitives, so that code will work, and we'll pause to consider what we've learned so far.