Skip to main content

haste_fhir_profiling/
lib.rs

1use haste_fhir_client::canonical_resolver::CanonicalResolver;
2use haste_fhir_model::r4::generated::{
3    resources::{OperationOutcome, Resource, ResourceType, StructureDefinition},
4    terminology::{IssueType, TypeDerivationRule},
5};
6use haste_fhir_operation_error::OperationOutcomeError;
7use haste_pointer::Path;
8use haste_reflect::MetaValue;
9use std::sync::Arc;
10
11use crate::element::validate_element;
12
13mod element;
14mod slicing;
15mod utilities;
16mod validators;
17
18pub struct FHIRProfileArguments<Resolver: CanonicalResolver> {
19    resolver: Arc<Resolver>,
20}
21
22impl<Resolver: CanonicalResolver> FHIRProfileArguments<Resolver> {
23    pub fn new(resolver: Arc<Resolver>) -> Self {
24        Self { resolver }
25    }
26}
27
28#[derive(Clone)]
29pub struct FHIRProfileCTX<'a, Resolver: CanonicalResolver> {
30    resolver: Arc<Resolver>,
31    profile: Arc<Resource>,
32    root: &'a dyn MetaValue,
33}
34
35impl<'a, Resolver: CanonicalResolver> FHIRProfileCTX<'a, Resolver> {
36    pub fn new(
37        resolver: Arc<Resolver>,
38        profile: Arc<Resource>,
39        root: &'a dyn MetaValue,
40    ) -> Result<Self, OperationOutcomeError> {
41        match &*profile {
42            Resource::StructureDefinition(_profile) => Ok(Self {
43                resolver,
44                profile,
45                root,
46            }),
47            _ => Err(OperationOutcomeError::error(
48                IssueType::Invalid(None),
49                "Profile resource must be a StructureDefinition".to_string(),
50            )),
51        }
52    }
53
54    pub fn profile(&'a self) -> &'a StructureDefinition {
55        match self.profile.as_ref() {
56            Resource::StructureDefinition(sd) => sd,
57            _ => panic!(
58                "Invalid state for profile ctx, profile field must be a StructureDefinition."
59            ),
60        }
61    }
62}
63
64pub async fn validate_profile<'a>(
65    ctx: Arc<FHIRProfileCTX<'a, impl CanonicalResolver>>,
66) -> Result<OperationOutcome, OperationOutcomeError> {
67    let mut outcome = OperationOutcome::default();
68    match ctx.profile().derivation.as_ref().map(|d| d.as_ref()) {
69        Some(TypeDerivationRule::Constraint(_)) => {
70            let element_location = Path::new()
71                .descend("snapshot")
72                .descend("element")
73                .descend("0");
74
75            let starting_path = Path::new();
76
77            let result = validate_element(ctx, &element_location, &starting_path).await?;
78            outcome.issue.extend(result);
79        }
80        _ => {
81            return Err(OperationOutcomeError::error(
82                IssueType::Invalid(None),
83                "Only profiles with derivation 'constraint' are supported".to_string(),
84            ));
85        }
86    }
87
88    Ok(outcome)
89}
90
91pub async fn validate_profile_by_url<'a>(
92    args: FHIRProfileArguments<impl CanonicalResolver>,
93    canonical_url: &str,
94    value: &'a dyn MetaValue,
95) -> Result<OperationOutcome, OperationOutcomeError> {
96    let Some(profile) = args
97        .resolver
98        .resolve(ResourceType::StructureDefinition, canonical_url)
99        .await?
100    else {
101        return Err(OperationOutcomeError::error(
102            IssueType::NotFound(None),
103            format!("Profile with url '{}' not found", canonical_url),
104        ));
105    };
106
107    let ctx = Arc::new(FHIRProfileCTX::new(args.resolver.clone(), profile, value)?);
108
109    validate_profile(ctx).await
110}