haste_fhir_profiling/
lib.rs1use haste_fhir_client::canonical_resolver::CanonicalResolver;
10use haste_fhir_model::r4::generated::{
11 resources::{OperationOutcome, Resource, ResourceType, StructureDefinition},
12 terminology::{IssueType, TypeDerivationRule},
13};
14use haste_fhir_operation_error::OperationOutcomeError;
15use haste_reflect::MetaValue;
16use std::sync::Arc;
17
18mod compile;
19mod graph;
20mod slicing;
21mod validate;
22mod validators;
23
24pub struct FHIRProfileArguments<Resolver: CanonicalResolver> {
25 resolver: Arc<Resolver>,
26}
27
28impl<Resolver: CanonicalResolver> FHIRProfileArguments<Resolver> {
29 pub fn new(resolver: Arc<Resolver>) -> Self {
30 Self { resolver }
31 }
32}
33
34#[derive(Clone)]
35pub struct FHIRProfileCTX<'a, Resolver: CanonicalResolver> {
36 resolver: Arc<Resolver>,
37 profile: Arc<Resource>,
38 root: &'a dyn MetaValue,
39}
40
41impl<'a, Resolver: CanonicalResolver> FHIRProfileCTX<'a, Resolver> {
42 pub fn new(
49 resolver: Arc<Resolver>,
50 profile: Arc<Resource>,
51 root: &'a dyn MetaValue,
52 ) -> Result<Self, OperationOutcomeError> {
53 match &*profile {
54 Resource::StructureDefinition(_profile) => Ok(Self {
55 resolver,
56 profile,
57 root,
58 }),
59 _ => Err(OperationOutcomeError::error(
60 IssueType::invalid(),
61 "Profile resource must be a StructureDefinition".to_string(),
62 )),
63 }
64 }
65
66 #[must_use]
72 pub fn profile(&'a self) -> &'a StructureDefinition {
73 match self.profile.as_ref() {
74 Resource::StructureDefinition(sd) => sd,
75 _ => panic!(
76 "Invalid state for profile ctx, profile field must be a StructureDefinition."
77 ),
78 }
79 }
80}
81
82pub async fn validate_profile(
90 ctx: Arc<FHIRProfileCTX<'_, impl CanonicalResolver>>,
91) -> Result<OperationOutcome, OperationOutcomeError> {
92 if ctx.profile().derivation.as_ref() != Some(&TypeDerivationRule::constraint()) {
93 return Err(OperationOutcomeError::error(
94 IssueType::invalid(),
95 "Only profiles with derivation 'constraint' are supported".to_string(),
96 ));
97 }
98 let graph = graph::graph_for(ctx.resolver.as_ref(), &ctx.profile).await?;
99 Ok(OperationOutcome {
100 issue: validate::validate(&graph, ctx.root)?,
101 ..Default::default()
102 })
103}
104
105pub async fn validate_profile_by_url(
112 args: FHIRProfileArguments<impl CanonicalResolver>,
113 canonical_url: &str,
114 value: &dyn MetaValue,
115) -> Result<OperationOutcome, OperationOutcomeError> {
116 let Some(profile) = args
117 .resolver
118 .resolve(ResourceType::StructureDefinition, canonical_url)
119 .await?
120 else {
121 tracing::warn!("Profile with url '{canonical_url}' not found");
122 return Ok(OperationOutcome::default());
123 };
124
125 let ctx = Arc::new(FHIRProfileCTX::new(args.resolver.clone(), profile, value)?);
126
127 validate_profile(ctx).await
128}