mod context;
mod document;
mod extension;
mod invariant;
mod matching;
mod primitive;
mod profiles;
mod reference;
mod resolver;
mod slicing;
mod structure;
mod terminology;
mod versioning;
mod walk;
use std::collections::BTreeMap;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::PoisonError;
use fhir_types::codec::EncodeError;
use fhir_types::codec::Value;
use crate::release::R4;
use crate::release::Release;
use crate::report::Reason;
use crate::report::Report;
use crate::set::PackageId;
use crate::set::PackageSet;
use crate::set::ResolveError;
use invariant::Compiled;
use primitive::PrimitiveRule;
use resolver::Bundled;
use resolver::Resolver;
use structure::Structure;
use terminology::index::CodeIndex;
const CORE: &str = "http://hl7.org/fhir/StructureDefinition/";
type Scoped = (String, Option<PackageId>);
pub(crate) type Element<R> = <R as Release>::ElementDefinition;
#[derive(Debug)]
pub struct Validator<'set, R: Release = R4> {
set: &'set PackageSet<R>,
structures: Mutex<BTreeMap<Scoped, Arc<Structure<R>>>>,
primitives: Mutex<BTreeMap<String, Arc<PrimitiveRule>>>,
code_systems: Mutex<BTreeMap<Scoped, Result<Arc<CodeIndex>, Reason>>>,
resolver: Box<dyn Resolver>,
expressions: Mutex<BTreeMap<String, Compiled>>,
checked: Mutex<BTreeMap<(String, String), Result<(), String>>>,
best_practice: BestPractice,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum BestPractice {
#[default]
Warning,
Error,
}
#[derive(Debug, thiserror::Error)]
pub enum ValidateError {
#[error(transparent)]
Resolve(#[from] ResolveError),
#[error("the StructureDefinition {url} carries no snapshot")]
NoSnapshot {
url: String,
},
#[error("the lexical form of {type_code} does not compile")]
Regex {
type_code: String,
#[source]
source: regex::Error,
},
#[error("the value of {element} in {structure} has no JSON form")]
Encode {
structure: String,
element: String,
#[source]
source: EncodeError,
},
#[error("a concept property value of {canonical} has no JSON form")]
CodeSystemValue {
canonical: String,
#[source]
source: EncodeError,
},
#[error("the element table carries no type {name}")]
UnknownType {
name: String,
},
}
impl<'set, R: Release> Validator<'set, R> {
#[must_use]
pub fn new(set: &'set PackageSet<R>) -> Self {
Self {
set,
structures: Mutex::new(BTreeMap::new()),
primitives: Mutex::new(BTreeMap::new()),
code_systems: Mutex::new(BTreeMap::new()),
resolver: Box::new(Bundled::new(R::schemas())),
expressions: Mutex::new(BTreeMap::new()),
checked: Mutex::new(BTreeMap::new()),
best_practice: BestPractice::Warning,
}
}
#[must_use]
pub const fn with_best_practice(mut self, best_practice: BestPractice) -> Self {
self.best_practice = best_practice;
self
}
pub fn validate(&self, resource: &Value) -> Result<Report, ValidateError> {
walk::Walk::new(self).run(resource, None)
}
pub fn validate_against(
&self,
resource: &Value,
profile: &str,
) -> Result<Report, ValidateError> {
walk::Walk::new(self).run(resource, Some(profile))
}
fn structure(
&self,
canonical: &str,
from: Option<&Structure<R>>,
) -> Result<Arc<Structure<R>>, ValidateError> {
let scope = from.map(Structure::origin);
let key = (canonical.to_owned(), scope);
if let Some(found) = self
.structures
.lock()
.unwrap_or_else(PoisonError::into_inner)
.get(&key)
{
return Ok(Arc::clone(found));
}
let (definition, origin) = self.set.structure_definition_in(canonical, scope)?;
let structure = Arc::new(Structure::new(definition, origin)?);
self.structures
.lock()
.unwrap_or_else(PoisonError::into_inner)
.insert(key, Arc::clone(&structure));
Ok(structure)
}
fn core(&self, name: &str) -> Result<Arc<Structure<R>>, ValidateError> {
self.structure(&format!("{CORE}{name}"), None)
}
fn resolver(&self) -> &dyn Resolver {
self.resolver.as_ref()
}
fn primitive(&self, code: &str) -> Result<Arc<PrimitiveRule>, ValidateError> {
if let Some(found) = self
.primitives
.lock()
.unwrap_or_else(PoisonError::into_inner)
.get(code)
{
return Ok(Arc::clone(found));
}
let rule = Arc::new(PrimitiveRule::read(&*self.core(code)?, code)?);
self.primitives
.lock()
.unwrap_or_else(PoisonError::into_inner)
.insert(code.to_owned(), Arc::clone(&rule));
Ok(rule)
}
}