1use crate::for_all_versions;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ParsedReference<'a> {
10 Local {
12 id: &'a str,
14 },
15 Relative {
17 resource_type: &'a str,
19 id: &'a str,
21 version_id: Option<&'a str>,
23 },
24 Absolute {
27 url: &'a str,
29 resource_type: Option<&'a str>,
32 id: Option<&'a str>,
35 },
36}
37
38impl<'a> ParsedReference<'a> {
39 #[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 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 #[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 #[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
102macro_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
146macro_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 #[must_use]
161 pub fn parse(&self) -> Option<ParsedReference<'_>> {
162 let url = self.reference.as_ref()?;
163 Some(ParsedReference::new(url))
164 }
165
166 #[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 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 #[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 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);