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    /// Creates a new FHIR profile context.
37    ///
38    /// # Errors
39    ///
40    /// Returns [`OperationOutcomeError`] if `profile` is not a
41    /// [`StructureDefinition`] resource.
42    pub fn new(
43        resolver: Arc<Resolver>,
44        profile: Arc<Resource>,
45        root: &'a dyn MetaValue,
46    ) -> Result<Self, OperationOutcomeError> {
47        match &*profile {
48            Resource::StructureDefinition(_profile) => Ok(Self {
49                resolver,
50                profile,
51                root,
52            }),
53            _ => Err(OperationOutcomeError::error(
54                IssueType::invalid(),
55                "Profile resource must be a StructureDefinition".to_string(),
56            )),
57        }
58    }
59
60    /// Returns the [`StructureDefinition`] associated with this profile context.
61    ///
62    /// # Panics
63    ///
64    /// Panics if the profile is not a [`Resource::StructureDefinition`].
65    #[must_use]
66    pub fn profile(&'a self) -> &'a StructureDefinition {
67        match self.profile.as_ref() {
68            Resource::StructureDefinition(sd) => sd,
69            _ => panic!(
70                "Invalid state for profile ctx, profile field must be a StructureDefinition."
71            ),
72        }
73    }
74}
75
76/// Validates the profile represented by the given context.
77///
78/// # Errors
79///
80/// Returns [`OperationOutcomeError`] if:
81/// - validating an element fails; or
82/// - the profile does not have a derivation of `constraint`.
83pub async fn validate_profile(
84    ctx: Arc<FHIRProfileCTX<'_, impl CanonicalResolver>>,
85) -> Result<OperationOutcome, OperationOutcomeError> {
86    let mut outcome = OperationOutcome::default();
87    match ctx.profile().derivation.as_ref() {
88        derivation if derivation == Some(&TypeDerivationRule::constraint()) => {
89            let element_location = Path::new()
90                .descend("snapshot")
91                .descend("element")
92                .descend("0");
93
94            let starting_path = Path::new();
95
96            let result = validate_element(ctx, &element_location, &starting_path).await?;
97            outcome.issue.extend(result);
98        }
99        _ => {
100            return Err(OperationOutcomeError::error(
101                IssueType::invalid(),
102                "Only profiles with derivation 'constraint' are supported".to_string(),
103            ));
104        }
105    }
106
107    Ok(outcome)
108}
109
110/// Validates a value against the profile identified by its canonical URL.
111///
112/// # Errors
113///
114/// Returns [`OperationOutcomeError`] if resolving the profile fails, if the
115/// resolved resource is not a supported profile, or if profile validation fails.
116pub async fn validate_profile_by_url(
117    args: FHIRProfileArguments<impl CanonicalResolver>,
118    canonical_url: &str,
119    value: &dyn MetaValue,
120) -> Result<OperationOutcome, OperationOutcomeError> {
121    let Some(profile) = args
122        .resolver
123        .resolve(ResourceType::StructureDefinition, canonical_url)
124        .await?
125    else {
126        tracing::warn!("Profile with url '{canonical_url}' not found");
127        return Ok(OperationOutcome::default());
128    };
129
130    let ctx = Arc::new(FHIRProfileCTX::new(args.resolver.clone(), profile, value)?);
131
132    validate_profile(ctx).await
133}