pub mod convert;
pub mod equality;
pub mod error;
mod functions;
mod navigate;
mod operators;
mod order;
use std::collections::BTreeMap;
use std::fmt;
use std::sync::Arc;
use fhir_types::codec::Value;
use crate::ast::{Expr, Literal};
use crate::model::{Model, named_type, path_type};
use crate::quantity::Quantity;
use crate::seam::{Definitions, Profiles, Resolver, Terminology};
use crate::value::node::{Doc, Node};
use crate::value::{Collection, Item};
use convert::UCUM;
use error::EvalError;
pub const STU3_FUNCTIONS: [&str; 13] = [
"comparable",
"decode",
"encode",
"escape",
"highBoundary",
"join",
"lowBoundary",
"matchesFull",
"precision",
"sort",
"split",
"trim",
"unescape",
];
pub struct Environment<'a> {
model: &'a dyn Model,
variables: BTreeMap<String, Collection<'a>>,
resolver: Option<&'a dyn Resolver>,
terminology: Option<&'a dyn Terminology>,
profiles: Option<&'a dyn Profiles>,
definitions: Option<&'a dyn Definitions>,
now: Option<jiff::Timestamp>,
strict_ordering: bool,
stu3_functions: bool,
}
impl fmt::Debug for Environment<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Environment")
.field("model", &self.model)
.field("variables", &self.variables.keys().collect::<Vec<_>>())
.field("resolver", &self.resolver.is_some())
.field("terminology", &self.terminology.is_some())
.field("profiles", &self.profiles.is_some())
.field("definitions", &self.definitions.is_some())
.field("now", &self.now)
.field("strict_ordering", &self.strict_ordering)
.field("stu3_functions", &self.stu3_functions)
.finish()
}
}
impl<'a> Environment<'a> {
#[must_use]
pub fn new(model: &'a dyn Model) -> Self {
Self {
model,
variables: BTreeMap::new(),
resolver: None,
terminology: None,
profiles: None,
definitions: None,
now: None,
strict_ordering: false,
stu3_functions: false,
}
}
#[must_use]
pub fn with_variable(mut self, name: impl Into<String>, value: Collection<'a>) -> Self {
self.variables.insert(name.into(), value);
self
}
#[must_use]
pub fn with_resolver(mut self, resolver: &'a dyn Resolver) -> Self {
self.resolver = Some(resolver);
self
}
#[must_use]
pub fn with_terminology(mut self, terminology: &'a dyn Terminology) -> Self {
self.terminology = Some(terminology);
self
}
#[must_use]
pub fn with_profiles(mut self, profiles: &'a dyn Profiles) -> Self {
self.profiles = Some(profiles);
self
}
#[must_use]
pub fn with_definitions(mut self, definitions: &'a dyn Definitions) -> Self {
self.definitions = Some(definitions);
self
}
#[must_use]
pub const fn with_now(mut self, now: jiff::Timestamp) -> Self {
self.now = Some(now);
self
}
#[must_use]
pub const fn with_strict_ordering(mut self) -> Self {
self.strict_ordering = true;
self
}
#[must_use]
pub const fn with_fhirpath_stu3_functions(mut self) -> Self {
self.stu3_functions = true;
self
}
#[must_use]
pub fn model(&self) -> &'a dyn Model {
self.model
}
}
#[derive(Clone)]
enum Start<'a> {
Resource(&'a Value),
Element {
value: Option<&'a Value>,
element: Option<&'a Value>,
type_name: String,
definition: Option<String>,
},
Items(Collection<'a>),
}
#[derive(Clone)]
pub struct Focus<'a> {
start: Start<'a>,
resource: Option<&'a Value>,
root: Option<&'a Value>,
}
impl fmt::Debug for Focus<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let start = match &self.start {
Start::Resource(_) => String::from("resource"),
Start::Element { type_name, .. } => format!("element of {type_name}"),
Start::Items(items) => format!("{} items", items.len()),
};
f.debug_struct("Focus")
.field("start", &start)
.field("resource", &self.resource.is_some())
.field("root", &self.root.is_some())
.finish()
}
}
impl<'a> Focus<'a> {
#[must_use]
pub const fn resource(resource: &'a Value) -> Self {
Self {
start: Start::Resource(resource),
resource: Some(resource),
root: Some(resource),
}
}
#[must_use]
pub const fn contained(resource: &'a Value, container: &'a Value) -> Self {
Self {
start: Start::Resource(resource),
resource: Some(resource),
root: Some(container),
}
}
#[must_use]
pub fn element(value: &'a Value, type_name: impl Into<String>, resource: &'a Value) -> Self {
Self {
start: Start::Element {
value: Some(value),
element: None,
type_name: type_name.into(),
definition: None,
},
resource: Some(resource),
root: Some(resource),
}
}
#[must_use]
pub fn primitive(
value: Option<&'a Value>,
element: Option<&'a Value>,
type_name: impl Into<String>,
resource: &'a Value,
) -> Self {
Self {
start: Start::Element {
value,
element,
type_name: type_name.into(),
definition: None,
},
resource: Some(resource),
root: Some(resource),
}
}
#[must_use]
pub fn defined_by(mut self, path: impl Into<String>) -> Self {
if let Start::Element { definition, .. } = &mut self.start {
*definition = Some(path.into());
}
self
}
#[must_use]
pub const fn items(items: Collection<'a>, resource: Option<&'a Value>) -> Self {
Self {
start: Start::Items(items),
resource,
root: resource,
}
}
#[must_use]
pub fn within(mut self, resource: Option<&'a Value>, root: Option<&'a Value>) -> Self {
self.resource = resource;
self.root = root;
self
}
#[must_use]
pub const fn none() -> Self {
Self {
start: Start::Items(Vec::new()),
resource: None,
root: None,
}
}
}
pub(crate) struct Engine<'e, 'a> {
env: &'e Environment<'a>,
context: Collection<'a>,
resource: Collection<'a>,
root: Collection<'a>,
now: jiff::Timestamp,
}
#[derive(Clone)]
pub(crate) struct Frame<'a> {
focus: Collection<'a>,
index: Option<usize>,
total: Option<Collection<'a>>,
}
impl<'a> Frame<'a> {
pub(crate) const fn top(focus: Collection<'a>) -> Self {
Self {
focus,
index: None,
total: None,
}
}
}
pub(crate) fn evaluate<'a>(
expr: &Expr,
env: &Environment<'a>,
focus: Focus<'a>,
) -> Result<Collection<'a>, EvalError> {
let model = env.model;
let as_resource = |value: Option<&'a Value>| -> Collection<'a> {
value
.map(|value| Item::Node(Node::resource(model, Doc::Borrowed(value))))
.into_iter()
.collect()
};
let context = match focus.start {
Start::Resource(value) => as_resource(Some(value)),
Start::Element {
value,
element,
type_name,
definition,
} => {
let ty = path_type(model, &type_name)
.or_else(|| named_type(model, &type_name))
.ok_or(EvalError::UnknownType { name: type_name })?;
let definition = match definition {
Some(path) => match model.schemas().element(&path) {
Some((_, field)) => Some(field.path),
None => return Err(EvalError::UnknownType { name: path }),
},
None => None,
};
let node = match (value, element) {
(Some(value), element) => Some(Node::typed(
ty,
Doc::Borrowed(value),
element.map(Doc::Borrowed),
)),
(None, Some(element)) => Some(Node::typed(
ty,
Doc::Owned(Arc::new(Value::Null)),
Some(Doc::Borrowed(element)),
)),
(None, None) => None,
};
node.map(|node| match definition {
Some(path) => node.with_definition(path),
None => node,
})
.map(Item::Node)
.into_iter()
.collect()
}
Start::Items(items) => items,
};
let engine = Engine {
env,
resource: as_resource(focus.resource),
root: as_resource(focus.root),
now: env.now.unwrap_or_else(jiff::Timestamp::now),
context: context.clone(),
};
engine.eval(expr, &Frame::top(context))
}
impl<'e, 'a> Engine<'e, 'a> {
pub(crate) fn model(&self) -> &'a dyn Model {
self.env.model
}
pub(crate) fn eval(&self, expr: &Expr, frame: &Frame<'a>) -> Result<Collection<'a>, EvalError> {
match expr {
Expr::Literal(literal) => Ok(vec![literal_item(literal)]),
Expr::Empty => Ok(Vec::new()),
Expr::External(name) => self.external(name),
Expr::This => Ok(frame.focus.clone()),
Expr::Index => Ok(frame
.index
.and_then(|index| i64::try_from(index).ok())
.map(Item::Integer)
.into_iter()
.collect()),
Expr::Total => Ok(frame.total.clone().unwrap_or_default()),
Expr::Member { input, name } => {
let items = self.input(input.as_deref(), frame)?;
self.navigate(&items, name, input.is_none())
}
Expr::Call { input, name, args } => {
if order::ORDERED_FUNCTIONS.contains(&name.as_str()) {
self.require_order(name, input.as_deref())?;
}
let items = self.input(input.as_deref(), frame)?;
self.call(name, items, args, frame)
}
Expr::Invocation { input, variable } => {
let items = self.eval(input, frame)?;
self.eval(variable, &Frame::top(items))
}
Expr::Indexer { input, index } => {
self.require_order("[]", Some(input))?;
let items = self.eval(input, frame)?;
let index =
convert::integer(self.model(), &self.eval(index, frame)?, "the indexer")?;
Ok(index
.and_then(|index| usize::try_from(index).ok())
.and_then(|index| items.into_iter().nth(index))
.into_iter()
.collect())
}
Expr::Negate(operand) => operators::negate(self.model(), &self.eval(operand, frame)?),
Expr::Plus(operand) => operators::plus(self.model(), &self.eval(operand, frame)?),
Expr::Binary { op, left, right } => self.binary(*op, left, right, frame),
Expr::Is {
operand,
type_specifier,
} => {
let items = self.eval(operand, frame)?;
let ty = self.resolve_type(type_specifier)?;
match convert::single(&items, "is")? {
Some(item) => Ok(vec![Item::Boolean(self.is_instance(item, &ty))]),
None => Ok(Vec::new()),
}
}
Expr::As {
operand,
type_specifier,
} => {
let items = self.eval(operand, frame)?;
let ty = self.resolve_type(type_specifier)?;
match convert::single(&items, "as")? {
Some(item) if self.is_type(item, &ty) => Ok(vec![item.clone()]),
_ => Ok(Vec::new()),
}
}
Expr::SortKey { key, .. } => self.eval(key, frame),
}
}
fn require_order(&self, operation: &str, input: Option<&Expr>) -> Result<(), EvalError> {
if self.env.strict_ordering && input.is_some_and(order::unordered) {
return Err(EvalError::Unordered {
operation: String::from(operation),
});
}
Ok(())
}
fn input(&self, input: Option<&Expr>, frame: &Frame<'a>) -> Result<Collection<'a>, EvalError> {
match input {
Some(input) => self.eval(input, frame),
None => Ok(frame.focus.clone()),
}
}
fn external(&self, name: &str) -> Result<Collection<'a>, EvalError> {
if let Some(value) = self.env.variables.get(name) {
return Ok(value.clone());
}
let text = |value: &str| Ok(vec![Item::String(String::from(value))]);
match name {
"ucum" => text(UCUM),
"sct" => text("http://snomed.info/sct"),
"loinc" => text("http://loinc.org"),
"context" => Ok(self.context.clone()),
"resource" => Ok(self.resource.clone()),
"rootResource" => Ok(self.root.clone()),
_ => {
if let Some(id) = name.strip_prefix("vs-") {
text(&format!("http://hl7.org/fhir/ValueSet/{id}"))
} else if let Some(id) = name.strip_prefix("ext-") {
text(&format!("http://hl7.org/fhir/StructureDefinition/{id}"))
} else {
Err(EvalError::UnknownVariable {
name: String::from(name),
})
}
}
}
}
pub(crate) fn resource(&self) -> &Collection<'a> {
&self.resource
}
pub(crate) fn root(&self) -> &Collection<'a> {
&self.root
}
pub(crate) const fn now(&self) -> jiff::Timestamp {
self.now
}
pub(crate) const fn env(&self) -> &'e Environment<'a> {
self.env
}
}
fn literal_item<'a>(literal: &Literal) -> Item<'a> {
match literal {
Literal::Boolean(value) => Item::Boolean(*value),
Literal::String(value) => Item::String(value.clone()),
Literal::Integer(value) => Item::Integer(*value),
Literal::Decimal(value) => Item::Decimal(*value),
Literal::Date(value) => Item::Date(*value),
Literal::DateTime(value) => Item::DateTime(*value),
Literal::Time(value) => Item::Time(*value),
Literal::Quantity(value, unit) => Item::Quantity(Quantity::new(*value, unit.clone())),
}
}