///|
pub(all) enum ConstraintKind {
Required
Type
Date
Reference
NonEmpty
} derive(Debug, Eq, ToJson)
///|
pub(all) struct ResourceConstraint {
path : String
kind : ConstraintKind
expected : String
message : String
} derive(Debug, Eq, ToJson)
///|
pub(all) struct ConstraintSet {
name : String
constraints : Array[ResourceConstraint]
} derive(Debug, Eq, ToJson)
///|
pub fn ResourceConstraint::required(
path : String,
message? : String = "",
) -> ResourceConstraint {
{ path, kind: Required, expected: "", message }
}
///|
pub fn ResourceConstraint::type_is(
path : String,
expected : String,
message? : String = "",
) -> ResourceConstraint {
{ path, kind: Type, expected, message }
}
///|
pub fn ResourceConstraint::date(
path : String,
message? : String = "",
) -> ResourceConstraint {
{ path, kind: Date, expected: "", message }
}
///|
pub fn ResourceConstraint::reference(
path : String,
message? : String = "",
) -> ResourceConstraint {
{ path, kind: Reference, expected: "", message }
}
///|
pub fn ResourceConstraint::non_empty(
path : String,
message? : String = "",
) -> ResourceConstraint {
{ path, kind: NonEmpty, expected: "", message }
}
///|
pub fn ConstraintSet::new(
name : String,
constraints : Array[ResourceConstraint],
) -> ConstraintSet {
{ name, constraints }
}
///|
fn constraint_message(
constraint : ResourceConstraint,
fallback : String,
) -> String {
if constraint.message == "" {
fallback
} else {
constraint.message
}
}
///|
fn constraint_type_matches(value : Json, expected : String) -> Bool {
match expected {
"object" => value is Object(_)
"array" => value is Array(_)
"string" => value is String(_)
"number" => value is Number(_, ..)
_ => true
}
}
///|
fn validate_constraint(value : Json, constraint : ResourceConstraint) -> Issue? {
let path = constraint.path
match constraint.kind {
Required =>
if query_first(value, path) is None {
Some(
issue(
"/\{path}",
"required",
constraint_message(constraint, "\{path} is required"),
),
)
} else {
None
}
Type =>
match query_first(value, path) {
None => None
Some(actual) =>
if constraint_type_matches(actual, constraint.expected) {
None
} else {
Some(
issue(
"/\{path}",
"type",
constraint_message(
constraint,
"\{path} must be \{constraint.expected}",
),
),
)
}
}
Date => {
let text = query_text(value, path, fallback="")
if text == "" || is_valid_fhir_datetime(text) {
None
} else {
Some(
issue(
"/\{path}",
"value",
constraint_message(constraint, "\{path} is not a valid date"),
),
)
}
}
Reference => {
let text = query_text(value, path, fallback="")
if text == "" || reference_is_well_formed(text) {
None
} else {
Some(
issue(
"/\{path}",
"value",
constraint_message(constraint, "\{path} is not a valid reference"),
),
)
}
}
NonEmpty =>
match query_first(value, path) {
None => None
Some(actual) =>
match as_scalar_text(actual) {
Some(text) =>
if text == "" {
Some(
issue(
"/\{path}",
"empty",
constraint_message(constraint, "\{path} must not be empty"),
),
)
} else {
None
}
None => None
}
}
}
}
///|
pub fn validate_constraints(value : Json, set : ConstraintSet) -> Array[Issue] {
set.constraints.filter_map(fn(constraint) {
validate_constraint(value, constraint)
})
}
///|
pub fn constraints_are_valid(value : Json, set : ConstraintSet) -> Bool {
validate_constraints(value, set).length() == 0
}
///|
pub fn constraint_count(set : ConstraintSet) -> Int {
set.constraints.length()
}
///|
pub fn common_observation_constraints() -> ConstraintSet {
ConstraintSet::new("observation-integration", [
ResourceConstraint::required("resourceType"),
ResourceConstraint::required("status"),
ResourceConstraint::type_is("code", "object"),
ResourceConstraint::non_empty("status"),
ResourceConstraint::reference("subject.reference"),
])
}
///|
pub fn common_patient_constraints() -> ConstraintSet {
ConstraintSet::new("patient-integration", [
ResourceConstraint::required("resourceType"),
ResourceConstraint::type_is("name", "array"),
ResourceConstraint::date("birthDate"),
])
}
///|
pub fn constraints_summary(set : ConstraintSet) -> String {
"\{set.name}: \{set.constraints.length()} constraint(s)"
}