use ferrobridge_fhirpath::ast::Expr;
use ferrobridge_fhirpath::ast::Literal;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Step {
Member(String),
Extension(String),
OfType(String),
Resolve,
}
pub(crate) fn parse(path: &str) -> Option<Vec<Step>> {
let expression = ferrobridge_fhirpath::parse::parse(path).ok()?;
let mut steps = Vec::new();
collect(&expression, &mut steps).then_some(steps)
}
fn collect(expression: &Expr, steps: &mut Vec<Step>) -> bool {
match expression {
Expr::This => steps.is_empty(),
Expr::Member { input, name } => {
if let Some(input) = input
&& !collect(input, steps)
{
return false;
}
steps.push(Step::Member(name.clone()));
true
}
Expr::Call { input, name, args } => {
if let Some(input) = input
&& !collect(input, steps)
{
return false;
}
let step = match (name.as_str(), args.as_slice()) {
("resolve", []) => Step::Resolve,
("extension", [Expr::Literal(Literal::String(url))]) => {
Step::Extension(url.clone())
}
("ofType" | "as", [argument]) => match argument.as_type_specifier() {
Some(specifier) => Step::OfType(specifier.name),
None => return false,
},
_ => return false,
};
steps.push(step);
true
}
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::Step;
use super::parse;
#[test]
fn the_subset_forms_parse_into_steps() {
assert_eq!(
parse("system"),
Some(vec![Step::Member("system".to_owned())])
);
assert_eq!(parse("$this"), Some(Vec::new()));
assert_eq!(
parse("$this.resolve()"),
Some(vec![Step::Resolve]),
"resolve() from $this"
);
assert_eq!(
parse("extension('http://x/e').value"),
Some(vec![
Step::Extension("http://x/e".to_owned()),
Step::Member("value".to_owned())
])
);
assert_eq!(
parse("value.ofType(Quantity)"),
Some(vec![
Step::Member("value".to_owned()),
Step::OfType("Quantity".to_owned())
])
);
assert_eq!(
parse("value.as(Quantity)"),
Some(vec![
Step::Member("value".to_owned()),
Step::OfType("Quantity".to_owned())
])
);
assert_eq!(
parse("code.coding.code"),
Some(vec![
Step::Member("code".to_owned()),
Step::Member("coding".to_owned()),
Step::Member("code".to_owned())
])
);
}
#[test]
fn a_path_outside_the_subset_is_refused() {
assert_eq!(parse("value@$this.function"), None);
assert_eq!(parse("code.where(system = 'x')"), None);
assert_eq!(parse("a | b"), None);
assert_eq!(parse("name.$this"), None);
assert_eq!(parse("first()"), None);
}
}