fhir_model/
references.rs

1//! References implementations.
2
3use crate::for_all_versions;
4
5/// Parsed parts of a FHIR reference. Can be one of local reference, relative
6/// reference or absolute reference. The absolute reference is unchecked and can
7/// be anything, it is used as fallback.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ParsedReference<'a> {
10	/// Local reference, the resource is to be found in the `contained` field.
11	Local {
12		/// Local ID of the resource.
13		id: &'a str,
14	},
15	/// Relative reference, the resource is on the same FHIR server.
16	Relative {
17		/// Resource type.
18		resource_type: &'a str,
19		/// Resource ID.
20		id: &'a str,
21		/// Targeted version ID if set.
22		version_id: Option<&'a str>,
23	},
24	/// Absolute reference, the resource can be anywhere, might not even be FHIR
25	/// server. Might not be a URL, but a URI like a `urn:uuid:...`.
26	Absolute {
27		/// Raw URL string.
28		url: &'a str,
29		/// Assumed resource type part if exists. Is just the positional URL
30		/// segment, could be wrong.
31		resource_type: Option<&'a str>,
32		/// Assumed resource ID part if exists. Is just the positional URL
33		/// segment, could be wrong.
34		id: Option<&'a str>,
35	},
36}
37
38impl<'a> ParsedReference<'a> {
39	/// Parse the reference from a reference string.
40	#[must_use]
41	pub fn new(reference: &'a str) -> Self {
42		if reference.starts_with('#') {
43			return Self::Local { id: reference.split_at(1).1 };
44		}
45
46		let Some((resource_type, id, version_id, is_absolute)) = Self::parse_segments(reference)
47		else {
48			return Self::Absolute { url: reference, resource_type: None, id: None };
49		};
50
51		if is_absolute {
52			Self::Absolute { url: reference, resource_type: Some(resource_type), id: Some(id) }
53		} else {
54			Self::Relative { resource_type, id, version_id }
55		}
56	}
57
58	/// Helper function to split the reference segments if possible.
59	/// Returns resource type, id, version id and if there is more segments if
60	/// possible.
61	fn parse_segments(reference: &'a str) -> Option<(&'a str, &'a str, Option<&'a str>, bool)> {
62		let mut segments = reference.rsplit('/');
63		let id_or_version = segments.next()?;
64		let history_or_type = segments.next()?;
65		Some(if history_or_type == "_history" {
66			let id = segments.next()?;
67			let resource_type = segments.next()?;
68			(resource_type, id, Some(id_or_version), segments.next().is_some())
69		} else {
70			(history_or_type, id_or_version, None, segments.next().is_some())
71		})
72	}
73
74	/// Get the resource type that this reference points to as string reference.
75	/// Might not be present/visible in absolute URLs or local references. In
76	/// absolute URLs, it might also return garbage, as the URL might have
77	/// enough segments, but the segment was not an actual resource type. Take
78	/// care when parsing to `ResourceType`.
79	#[must_use]
80	pub const fn resource_type(&self) -> Option<&'a str> {
81		match self {
82			Self::Local { .. } => None,
83			Self::Relative { resource_type, .. } => Some(resource_type),
84			Self::Absolute { resource_type, .. } => *resource_type,
85		}
86	}
87
88	/// Get the resource ID that this reference points to. Might not be
89	/// present/visible in absolute URLs. In absolute URLs, it might also return
90	/// garbage, as the URL might have enough segments, but the segment was not
91	/// an actual id. Take care when using it.
92	#[must_use]
93	pub const fn id(&self) -> Option<&'a str> {
94		match self {
95			Self::Local { id } => Some(id),
96			Self::Relative { id, .. } => Some(id),
97			Self::Absolute { id, .. } => *id,
98		}
99	}
100}
101
102/// Construct a `ReferenceInner` for the given version.
103macro_rules! make_reference_inner {
104	(stu3, $reference:expr, $type:expr) => {
105		ReferenceInner {
106			id: None,
107			extension: Vec::new(),
108			reference: $reference,
109			reference_ext: None,
110			identifier: None,
111			identifier_ext: None,
112			display: None,
113			display_ext: None,
114		}
115	};
116	(r4b, $reference:expr, $type:expr) => {
117		ReferenceInner {
118			id: None,
119			extension: Vec::new(),
120			reference: $reference,
121			reference_ext: None,
122			r#type: $type,
123			type_ext: None,
124			identifier: None,
125			identifier_ext: None,
126			display: None,
127			display_ext: None,
128		}
129	};
130	(r5, $reference:expr, $type:expr) => {
131		ReferenceInner {
132			id: None,
133			extension: Vec::new(),
134			reference: $reference,
135			reference_ext: None,
136			r#type: $type,
137			type_ext: None,
138			identifier: None,
139			identifier_ext: None,
140			display: None,
141			display_ext: None,
142		}
143	};
144}
145
146/// Macro to implement functions on `Reference`s.
147macro_rules! impl_reference {
148	($version:ident) => {
149		mod $version {
150			use $crate::$version::{
151				resources::{BaseResource, NamedResource, ResourceType},
152				types::{Reference, ReferenceInner},
153			};
154
155			use super::ParsedReference;
156
157			impl Reference {
158				/// Parse the [`Reference`] into a [`ParsedReference`]. Returns `None`
159				/// if the `reference` field is empty..
160				#[must_use]
161				pub fn parse(&self) -> Option<ParsedReference<'_>> {
162					let url = self.reference.as_ref()?;
163					Some(ParsedReference::new(url))
164				}
165
166				/// Create a new local reference for the given ID. Make sure the
167				/// resource is going to be in the `contained` field of the
168				/// referencing resource.
169				#[must_use]
170				#[allow(unused_variables, reason = "Unused for STU3")]
171				pub fn local(ty: ResourceType, id: &str) -> Self {
172					make_reference_inner!($version, Some(format!("#{id}")), Some(ty.to_string()))
173						.into()
174				}
175
176				/// Create local [`Reference`] to the given resource. Make sure the
177				/// resource is going to be in the `contained` field of the
178				/// referencing resource.
179				pub fn local_to<R>(resource: &R) -> Option<Self>
180				where
181					R: NamedResource + BaseResource,
182				{
183					Some(Self::local(R::TYPE, resource.id().as_ref()?))
184				}
185
186				/// Create a new relative reference for the given resource type and ID.
187				#[must_use]
188				pub fn relative(ty: ResourceType, id: &str) -> Self {
189					make_reference_inner!(
190						$version,
191						Some(format!("{ty}/{id}")),
192						Some(ty.to_string())
193					)
194					.into()
195				}
196
197				/// Create relative [`Reference`] to the given resource.
198				pub fn relative_to<R>(resource: &R) -> Option<Self>
199				where
200					R: NamedResource + BaseResource,
201				{
202					Some(Self::relative(R::TYPE, resource.id().as_ref()?))
203				}
204			}
205		}
206	};
207}
208for_all_versions!(impl_reference);