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(
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}