use std::fmt;
use ferrobridge_fhirpath::seam;
use ferrobridge_fhirpath::seam::ResolveContext;
use ferrobridge_fhirpath::seam::SeamError;
use fhir_types::codec::Value;
use crate::release::Release;
use crate::report::Reason;
use crate::set::PackageId;
use crate::set::ResolveError;
use crate::validate::ValidateError;
use crate::validate::Validator;
use crate::validate::resolver::Indexes;
use crate::validate::resolver::Resolution;
use crate::validate::resolver::from_bundle;
use crate::validate::terminology::Coded;
use crate::validate::terminology::Coding;
use crate::validate::terminology::Form;
use crate::validate::terminology::Membership;
use crate::validate::walk::Cursor;
use crate::validate::walk::Walk;
const DEPTH: u8 = 4;
#[derive(Debug, thiserror::Error)]
enum Unanswered {
#[error("{0}")]
Reason(Reason),
#[error("subsumption is not evaluated from the package set")]
Subsumption,
#[error("conformsTo() is evaluated on a resource or a complex datatype only")]
NotComplex,
#[error("conformsTo() nests more than {DEPTH} validations deep")]
TooDeep,
}
pub(super) struct References<'v, 'set, 's, 'i, R: Release> {
validator: &'v Validator<'set, R>,
scope: &'s [&'i Value],
indexes: &'s Indexes<'i>,
}
impl<'v, 'set, 's, 'i, R: Release> References<'v, 'set, 's, 'i, R> {
pub(super) const fn new(
validator: &'v Validator<'set, R>,
scope: &'s [&'i Value],
indexes: &'s Indexes<'i>,
) -> Self {
Self {
validator,
scope,
indexes,
}
}
}
impl<R: Release> fmt::Debug for References<'_, '_, '_, '_, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("References")
.field("scope", &self.scope.len())
.finish_non_exhaustive()
}
}
impl<R: Release> seam::Resolver for References<'_, '_, '_, '_, R> {
fn resolve(
&self,
reference: &str,
_context: ResolveContext<'_>,
) -> Result<Option<Value>, SeamError> {
let mut found = self
.validator
.resolver()
.resolve(reference, self.scope, self.indexes);
if matches!(found, Resolution::OutOfScope)
&& let Some(&innermost) = self.scope.last()
&& innermost.get("resourceType").and_then(Value::as_str) == Some("Bundle")
{
found = from_bundle(R::schemas(), innermost, reference, self.indexes);
}
match found {
Resolution::Found { target, .. } => Ok(Some(target.clone())),
Resolution::Missing => Ok(None),
Resolution::OutOfScope => {
Err(Box::new(Unanswered::Reason(Reason::ReferenceOutOfScope)))
}
Resolution::Ambiguous => Err(Box::new(Unanswered::Reason(Reason::AmbiguousReference))),
}
}
}
pub(super) struct Terminology<'v, 'set, R: Release> {
validator: &'v Validator<'set, R>,
scope: PackageId,
}
impl<'v, 'set, R: Release> Terminology<'v, 'set, R> {
pub(super) const fn new(validator: &'v Validator<'set, R>, scope: PackageId) -> Self {
Self { validator, scope }
}
}
impl<R: Release> fmt::Debug for Terminology<'_, '_, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Terminology")
.field("scope", &self.scope)
.finish_non_exhaustive()
}
}
const fn coding<'c>(position: usize, coding: &seam::Coding<'c>) -> Coding<'c> {
Coding {
system: coding.system,
version: coding.version,
code: coding.code,
display: None,
position,
}
}
impl<R: Release> seam::Terminology for Terminology<'_, '_, R> {
fn member_of(&self, coded: &seam::Coded<'_>, value_set: &str) -> Result<bool, SeamError> {
let question = match coded {
seam::Coded::Code(code) => Coded::code(code),
seam::Coded::Coding(one) => Coded {
form: Form::Coding,
codes: vec![coding(0, one)],
text: None,
},
seam::Coded::Concept(codings) => Coded {
form: Form::CodeableConcept,
codes: codings
.iter()
.enumerate()
.map(|(position, one)| coding(position, one))
.collect(),
text: None,
},
};
match self.validator.holds(value_set, self.scope, &question)? {
Membership::Member => Ok(true),
Membership::NotMember => Ok(false),
Membership::Unknown(reason) => Err(Box::new(Unanswered::Reason(reason))),
}
}
fn subsumes(
&self,
_ancestor: &seam::Coding<'_>,
_descendant: &seam::Coding<'_>,
) -> Result<bool, SeamError> {
Err(Box::new(Unanswered::Subsumption))
}
}
pub(super) struct Conformance<'v, 'set, R: Release> {
validator: &'v Validator<'set, R>,
depth: u8,
}
impl<'v, 'set, R: Release> Conformance<'v, 'set, R> {
pub(super) const fn new(validator: &'v Validator<'set, R>, depth: u8) -> Self {
Self { validator, depth }
}
}
impl<R: Release> fmt::Debug for Conformance<'_, '_, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Conformance")
.field("depth", &self.depth)
.finish_non_exhaustive()
}
}
impl<R: Release> seam::Profiles for Conformance<'_, '_, R> {
fn conforms_to(
&self,
value: &Value,
type_name: Option<&str>,
structure: &str,
) -> Result<bool, SeamError> {
if self.depth >= DEPTH {
return Err(Box::new(Unanswered::TooDeep));
}
let profile = match self.validator.structure(structure, None) {
Ok(profile) => profile,
Err(ValidateError::Resolve(
ResolveError::NotFound { .. } | ResolveError::Fragment { .. },
)) => return Err(Box::new(Unanswered::Reason(Reason::ProfileNotFound))),
Err(error) => return Err(Box::new(error)),
};
let mut walk = Walk::nested(self.validator, self.depth.saturating_add(1));
if value.get("resourceType").is_some() {
walk.resource(value, None, Some(profile))?;
} else {
let name = type_name
.filter(|_| value.as_object().is_some())
.ok_or(Unanswered::NotComplex)?;
if profile.type_name() != name {
return Ok(false);
}
walk.complex(value, name, &Cursor::new(profile, name), name)?;
}
Ok(walk.into_out().is_valid())
}
}