swarm / companies/starter/work/project/src/fhir/validator.rs

raw file 2128 bytes
use serde_json::{Value, json};
use std::collections::HashSet;

/// The set of top-level FHIR R4 resource types we accept.
const KNOWN_RESOURCE_TYPES: [&str; 12] = [
    "Patient",
    "Observation",
    "Condition",
    "Encounter",
    "Procedure",
    "MedicationRequest",
    "MedicationStatement",
    "MedicationDispense",
    "AllergyIntolerance",
    "DiagnosticReport",
    "Immunization",
    "CarePlan",
];

/// Outcome of validating a single inbound FHIR payload.
#[derive(Debug, Clone)]
pub enum ValidationResult {
    Valid,
    Invalid {
        message: String,
        /// JSON pointer to the offending field, e.g. "/resourceType".
        pointer: Option<String>,
    },
}

impl ValidationResult {
    pub fn is_valid(&self) -> bool {
        matches!(self, ValidationResult::Valid)
    }
}

/// Validate a raw FHIR R4 payload.
///
/// Returns `ValidationResult::Valid` if the payload is a well-formed
/// FHIR resource with a known resourceType, or `Invalid` with a
/// human-readable message and a JSON pointer to the problem.
pub fn validate_fhir_payload(raw: &str) -> ValidationResult {
    // Step 1: must be valid JSON.
    let value: Value = match serde_json::from_str(raw) {
        Ok(v) => v,
        Err(e) => {
            return ValidationResult::Invalid {
                message: format!(
                    "Payload is not valid JSON: {}. FHIR R4 resources must be JSON objects.",
                    e
                ),
                pointer: None,
            };
        }
    };

    // Step 2: must be a JSON object.
    let obj = match value.as_object() {
        Some(o) => o,
        None => {
            return ValidationResult::Invalid {
                message: "Payload must be a JSON object (FHIR resource).".to_string(),
                pointer: None,
            };
        }
    };

    // Step 3: required field "resourceType" must be present and a string.
    let resource_type = match obj.get("resourceType") {
        Some(Value::String(s)) => s.as_str(),
        Some(_) => {
            return ValidationResult::Invalid {
                message: "\"resourceType\" must