swarm / companies/starter/work/project/src/sync/fhir_validation.rs

raw file 1942 bytes
use std::fmt;

use serde_json::{Value, json};
use fhir::FhirResource;

#[derive(Debug, Clone)]
pub struct ValidationError {
    pub message: String,
    pub pointer: String,
}

impl fmt::Display for ValidationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "[{}] {}", self.pointer, self.message)
    }
}

pub fn validate_fhir_payload(data: &str) -> Result<Vec<Value>, Vec<ValidationError>> {
    let parsed: Value = serde_json::from_str(data)
        .map_err(|e| vec![ValidationError {
            message: format!("Invalid JSON: {}", e),
            pointer: "#".to_string(),
        }])?;

    let mut errors: Vec<ValidationError> = Vec::new();

    if parsed.get("resourceType").is_none() {
        errors.push(ValidationError {
            message: "Missing top-level 'resourceType' field".to_string(),
            pointer: "#".to_string(),
        });
        return Err(errors);
    }

    let resource_type = parsed["resourceType"].as_str().unwrap_or("");

    if resource_type == "Bundle" {
        validate_bundle(&parsed, &mut errors);
    } else {
        validate_single_resource(&parsed, &mut errors);
    }

    if !errors.is_empty() {
        return Err(errors);
    }

    Ok(vec![parsed])
}

fn validate_bundle(bundle: &Value, errors: &mut Vec<ValidationError>) {
    if bundle.get("type").is_none() {
        errors.push(ValidationError {
            message: "Bundle must have a 'type' field (e.g., 'collection', 'transaction')".to_string(),
            pointer: "#/type".to_string(),
        });
    }

    let entries = match bundle.get("entry") {
        Some(entries) => entries,
        None => {
            errors.push(ValidationError {
                message: "Bundle must have an 'entry' array".to_string(),
                pointer: "#/entry".to_string(),
            });
            return;
        }
    };

    let entries = match entries.as_array() {
        Some(arr