///|
fn issue(
path : String,
code : String,
message : String,
severity? : String = "error",
) -> Issue {
{ path, code, message, severity }
}
///|
fn require_string(
issues : Array[Issue],
value : Json,
key : String,
path : String,
) -> Unit {
match object_get(value, key) {
Some(String(s)) =>
if s.length() == 0 {
issues.push(issue(path, "empty", "\{key} must not be empty"))
}
Some(_) => issues.push(issue(path, "type", "\{key} must be a JSON string"))
None => issues.push(issue(path, "required", "\{key} is required"))
}
}
///|
fn require_array(
issues : Array[Issue],
value : Json,
key : String,
path : String,
) -> Unit {
match object_get(value, key) {
Some(Array(items)) =>
if items.length() == 0 {
issues.push(
issue(path, "empty", "\{key} must contain at least one item"),
)
}
Some(_) => issues.push(issue(path, "type", "\{key} must be a JSON array"))
None => issues.push(issue(path, "required", "\{key} is required"))
}
}
///|
fn validate_patient(issues : Array[Issue], value : Json) -> Unit {
match object_get(value, "name") {
Some(Array(_)) | None => ()
Some(_) =>
issues.push(
issue("/name", "type", "Patient.name must be an array when present"),
)
}
match field_string(value, "gender") {
Some("male" | "female" | "other" | "unknown") | None => ()
Some(_) =>
issues.push(
issue(
"/gender",
"value",
"Patient.gender should be male, female, other, or unknown",
severity="warning",
),
)
}
}
///|
fn validate_observation(issues : Array[Issue], value : Json) -> Unit {
require_string(issues, value, "status", "/status")
match object_get(value, "code") {
Some(Object(_)) => ()
Some(_) =>
issues.push(issue("/code", "type", "Observation.code must be an object"))
None =>
issues.push(issue("/code", "required", "Observation.code is required"))
}
match field_string(value, "status") {
Some(
"registered"
| "preliminary"
| "final"
| "amended"
| "corrected"
| "cancelled"
| "entered-in-error"
| "unknown"
)
| None => ()
Some(_) =>
issues.push(
issue(
"/status",
"value",
"Observation.status is outside the common FHIR value set",
severity="warning",
),
)
}
}
///|
fn validate_medication(issues : Array[Issue], value : Json) -> Unit {
match object_get(value, "code") {
Some(Object(_)) | None => ()
Some(_) =>
issues.push(issue("/code", "type", "Medication.code must be an object"))
}
match field_string(value, "status") {
Some("active" | "inactive" | "entered-in-error") | None => ()
Some(_) =>
issues.push(
issue(
"/status",
"value",
"Medication.status is outside the common FHIR value set",
severity="warning",
),
)
}
}
///|
fn validate_medication_request(issues : Array[Issue], value : Json) -> Unit {
require_string(issues, value, "status", "/status")
match object_get(value, "medicationCodeableConcept") {
Some(Object(_)) | None => ()
Some(_) =>
issues.push(
issue(
"/medicationCodeableConcept", "type", "MedicationRequest.medicationCodeableConcept must be an object",
),
)
}
match field_string(value, "status") {
Some(
"active"
| "on-hold"
| "cancelled"
| "completed"
| "entered-in-error"
| "stopped"
| "draft"
| "unknown"
)
| None => ()
Some(_) =>
issues.push(
issue(
"/status",
"value",
"MedicationRequest.status is outside the common FHIR value set",
severity="warning",
),
)
}
}
///|
fn validate_condition(issues : Array[Issue], value : Json) -> Unit {
match object_get(value, "clinicalStatus") {
Some(Object(_)) | None => ()
Some(_) =>
issues.push(
issue(
"/clinicalStatus", "type", "Condition.clinicalStatus must be an object",
),
)
}
match object_get(value, "code") {
Some(Object(_)) => ()
Some(_) =>
issues.push(issue("/code", "type", "Condition.code must be an object"))
None =>
issues.push(issue("/code", "required", "Condition.code is required"))
}
}
///|
fn validate_procedure(issues : Array[Issue], value : Json) -> Unit {
require_string(issues, value, "status", "/status")
match object_get(value, "code") {
Some(Object(_)) => ()
Some(_) =>
issues.push(issue("/code", "type", "Procedure.code must be an object"))
None =>
issues.push(issue("/code", "required", "Procedure.code is required"))
}
}
///|
fn validate_immunization(issues : Array[Issue], value : Json) -> Unit {
require_string(issues, value, "status", "/status")
match object_get(value, "vaccineCode") {
Some(Object(_)) => ()
Some(_) =>
issues.push(
issue(
"/vaccineCode", "type", "Immunization.vaccineCode must be an object",
),
)
None =>
issues.push(
issue(
"/vaccineCode", "required", "Immunization.vaccineCode is required",
),
)
}
}
///|
fn validate_allergy_intolerance(issues : Array[Issue], value : Json) -> Unit {
match field_string(value, "clinicalStatus") {
Some(_) | None => ()
}
match object_get(value, "code") {
Some(Object(_)) => ()
Some(_) =>
issues.push(
issue("/code", "type", "AllergyIntolerance.code must be an object"),
)
None =>
issues.push(
issue("/code", "required", "AllergyIntolerance.code is required"),
)
}
}
///|
fn validate_care_plan(issues : Array[Issue], value : Json) -> Unit {
require_string(issues, value, "status", "/status")
require_string(issues, value, "intent", "/intent")
}
///|
fn validate_identity_resource(issues : Array[Issue], value : Json) -> Unit {
match resource_kind(value) {
Organization | Location | Practitioner =>
match field_string(value, "name") {
Some(name) =>
if name == "" {
issues.push(issue("/name", "empty", "name must not be empty"))
}
None => ()
}
_ => ()
}
}
///|
fn validate_workflow_resource(issues : Array[Issue], value : Json) -> Unit {
match resource_kind(value) {
ServiceRequest => {
require_string(issues, value, "status", "/status")
require_string(issues, value, "intent", "/intent")
match object_get(value, "code") {
Some(Object(_)) | None => ()
Some(_) =>
issues.push(
issue("/code", "type", "ServiceRequest.code must be an object"),
)
}
}
Goal => {
require_string(issues, value, "lifecycleStatus", "/lifecycleStatus")
match object_get(value, "description") {
Some(Object(_)) | None => ()
Some(_) =>
issues.push(
issue("/description", "type", "Goal.description must be an object"),
)
}
}
CareTeam => {
require_string(issues, value, "status", "/status")
require_array(issues, value, "participant", "/participant")
}
Device =>
match object_get(value, "type") {
Some(Object(_)) | None => ()
Some(_) =>
issues.push(issue("/type", "type", "Device.type must be an object"))
}
Specimen =>
match object_get(value, "type") {
Some(Object(_)) | None => ()
Some(_) =>
issues.push(issue("/type", "type", "Specimen.type must be an object"))
}
Questionnaire => require_string(issues, value, "status", "/status")
List => {
require_string(issues, value, "status", "/status")
require_string(issues, value, "mode", "/mode")
}
Composition => {
require_string(issues, value, "status", "/status")
match object_get(value, "type") {
Some(Object(_)) => ()
Some(_) =>
issues.push(
issue(
"/type", "required", "Composition.type is required and must be an object",
),
)
None =>
issues.push(
issue("/type", "required", "Composition.type is required"),
)
}
}
DocumentReference => require_string(issues, value, "status", "/status")
Provenance => require_array(issues, value, "agent", "/agent")
AuditEvent =>
match object_get(value, "type") {
Some(Object(_)) => ()
Some(_) =>
issues.push(issue("/type", "required", "AuditEvent.type is required"))
None =>
issues.push(issue("/type", "required", "AuditEvent.type is required"))
}
_ => ()
}
}
///|
fn validate_diagnostic_report(issues : Array[Issue], value : Json) -> Unit {
require_string(issues, value, "status", "/status")
match object_get(value, "code") {
Some(Object(_)) => ()
Some(_) =>
issues.push(
issue("/code", "type", "DiagnosticReport.code must be an object"),
)
None =>
issues.push(
issue("/code", "required", "DiagnosticReport.code is required"),
)
}
}
///|
fn validate_encounter(issues : Array[Issue], value : Json) -> Unit {
require_string(issues, value, "status", "/status")
match object_get(value, "class") {
Some(Object(_)) | None => ()
Some(_) =>
issues.push(issue("/class", "type", "Encounter.class must be an object"))
}
}
///|
fn validate_bundle(issues : Array[Issue], value : Json) -> Unit {
require_string(issues, value, "type", "/type")
require_array(issues, value, "entry", "/entry")
for index, entry in field_array(value, "entry").unwrap_or([]) {
match object_get(entry, "resource") {
Some(resource) =>
if !is_supported_resource(resource) {
issues.push(
issue(
"/entry/\{index}/resource/resourceType",
"unsupported",
"Bundle entry contains an unsupported resource type",
severity="warning",
),
)
}
None =>
issues.push(
issue(
"/entry/\{index}/resource",
"required",
"Bundle.entry.resource is required",
),
)
}
}
}
///|
pub fn validate_resource(
value : Json,
version? : FhirVersion = R4,
) -> ValidationReport {
let issues : Array[Issue] = []
match value {
Object(_) => ()
_ => issues.push(issue("", "type", "FHIR resource must be a JSON object"))
}
match resource_type(value) {
Some("") =>
issues.push(
issue("/resourceType", "empty", "resourceType must not be empty"),
)
Some(_) => ()
None =>
issues.push(
issue("/resourceType", "required", "resourceType is required"),
)
}
match resource_kind(value) {
Patient => validate_patient(issues, value)
Observation => validate_observation(issues, value)
Medication => validate_medication(issues, value)
MedicationRequest => validate_medication_request(issues, value)
DiagnosticReport => validate_diagnostic_report(issues, value)
Encounter => validate_encounter(issues, value)
Condition => validate_condition(issues, value)
Procedure => validate_procedure(issues, value)
Immunization => validate_immunization(issues, value)
AllergyIntolerance => validate_allergy_intolerance(issues, value)
CarePlan => validate_care_plan(issues, value)
Practitioner | Organization | Location =>
validate_identity_resource(issues, value)
ServiceRequest
| Goal
| CareTeam
| Device
| Specimen
| Questionnaire
| List
| Composition
| DocumentReference
| Provenance
| AuditEvent => validate_workflow_resource(issues, value)
Bundle => validate_bundle(issues, value)
Other(name) =>
if name.length() > 0 {
issues.push(
issue(
"/resourceType",
"unsupported",
"resource type \{name} is not in the first supported set",
severity="warning",
),
)
}
}
{ resource_type: resource_type(value).unwrap_or(""), version, issues }
}
///|
pub fn is_valid(value : Json, version? : FhirVersion = R4) -> Bool {
let report = validate_resource(value, version~)
for has_error = false, i = 0; i < report.issues.length(); {
if report.issues[i].severity == "error" {
continue true, report.issues.length()
} else {
continue has_error, i + 1
}
} nobreak {
!has_error
}
}