haste_fhir_profiling/
lib.rs1use 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(
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 #[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
76pub 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
110pub 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}