use fhir_types::codec::Value;
use fhir_types::schema::Schemas;
use crate::issue::IssueType;
use crate::issue::MessageId;
use crate::issue::Severity;
use crate::release::Release;
use crate::report::Constraint;
use crate::report::Reason;
use crate::validate::reference::literal;
use crate::validate::structure::Structure;
use crate::validate::walk::Occurrence;
use crate::validate::walk::Walk;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Form {
Specific,
Independent,
Assumed,
}
fn reference_form(schemas: &Schemas, reference: &str) -> Form {
if reference.starts_with('#') || reference.contains(':') && !reference.contains("://") {
return Form::Assumed;
}
let path = reference.split(['?', '#']).next().unwrap_or(reference);
let segments: Vec<&str> = path.split('/').collect();
let resource = |at: usize| {
segments
.get(at)
.is_some_and(|name| schemas.is_resource(name))
};
let count = segments.len();
let history = count >= 4 && segments.get(count - 2) == Some(&"_history") && resource(count - 4);
if history {
return Form::Specific;
}
if count >= 2 && resource(count - 2) {
return Form::Independent;
}
Form::Assumed
}
fn canonical_form(value: &str) -> Form {
if value.contains('|') {
Form::Specific
} else {
Form::Independent
}
}
impl<'i, R: Release> Walk<'_, '_, 'i, R> {
pub(crate) fn versioning(
&mut self,
structure: &Structure<R>,
id: &str,
rule: &str,
code: Option<&str>,
occurrence: &Occurrence<'i>,
) {
let location = &occurrence.location;
let form = match code {
Some("Reference") => {
let Some(object) = occurrence.value.filter(|value| value.as_object().is_some())
else {
return;
};
let Some(reference) = literal(object) else {
self.version_issue(
location,
format!("{id} sets the versioning {rule}, and the reference carries no literal reference"),
);
return;
};
reference_form(R::schemas(), reference)
}
Some("canonical") => match occurrence.value.and_then(Value::as_str) {
Some(value) => canonical_form(value),
None => return,
},
_ => return,
};
let fault = match (rule, form) {
("specific", Form::Independent) => "names no version",
("independent", Form::Specific) => "names a version",
("independent", Form::Assumed) => {
self.out().unevaluated(
structure.url(),
id,
Constraint::Versioning {
rule: rule.to_owned(),
},
location,
Reason::NotRestfulReference,
);
return;
}
("specific" | "independent" | "either", _) => return,
_ => {
self.out().unevaluated(
structure.url(),
id,
Constraint::Versioning {
rule: rule.to_owned(),
},
location,
Reason::VersioningRule(rule.to_owned()),
);
return;
}
};
self.version_issue(
location,
format!("{id} sets the versioning {rule}, and the reference {fault}"),
);
}
fn version_issue(&mut self, location: &str, message: String) {
self.out().issue(
Severity::Error,
IssueType::Structure,
MessageId::VersioningMismatch,
location,
message,
);
}
}
#[cfg(test)]
mod tests {
use super::Form;
use super::canonical_form;
use fhir_types::r4::schema::SCHEMAS;
use super::reference_form;
#[test]
fn a_literal_reference_is_read_for_its_version() {
assert_eq!(reference_form(&SCHEMAS, "Patient/1"), Form::Independent);
assert_eq!(
reference_form(&SCHEMAS, "Patient/1/_history/2"),
Form::Specific
);
assert_eq!(
reference_form(&SCHEMAS, "http://example.org/fhir/Patient/1/_history/2"),
Form::Specific
);
assert_eq!(
reference_form(&SCHEMAS, "http://example.org/fhir/Patient/1"),
Form::Independent
);
assert_eq!(reference_form(&SCHEMAS, "#p1"), Form::Assumed);
assert_eq!(
reference_form(&SCHEMAS, "urn:uuid:00000000-0000-0000-0000-000000000001"),
Form::Assumed
);
assert_eq!(
reference_form(&SCHEMAS, "http://example.org/document.pdf"),
Form::Assumed
);
}
#[test]
fn a_canonical_names_a_version_after_a_bar() {
assert_eq!(canonical_form("http://example.org/vs|1.0"), Form::Specific);
assert_eq!(canonical_form("http://example.org/vs"), Form::Independent);
}
}