use crate::for_all_versions;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParsedReference<'a> {
Local {
id: &'a str,
},
Relative {
resource_type: &'a str,
id: &'a str,
version_id: Option<&'a str>,
},
Absolute {
url: &'a str,
resource_type: Option<&'a str>,
id: Option<&'a str>,
},
}
impl<'a> ParsedReference<'a> {
#[must_use]
pub fn new(reference: &'a str) -> Self {
if reference.starts_with('#') {
return Self::Local { id: reference.split_at(1).1 };
}
let Some((resource_type, id, version_id, is_absolute)) = Self::parse_segments(reference)
else {
return Self::Absolute { url: reference, resource_type: None, id: None };
};
if is_absolute {
Self::Absolute { url: reference, resource_type: Some(resource_type), id: Some(id) }
} else {
Self::Relative { resource_type, id, version_id }
}
}
fn parse_segments(reference: &'a str) -> Option<(&'a str, &'a str, Option<&'a str>, bool)> {
let mut segments = reference.rsplit('/');
let id_or_version = segments.next()?;
let history_or_type = segments.next()?;
Some(if history_or_type == "_history" {
let id = segments.next()?;
let resource_type = segments.next()?;
(resource_type, id, Some(id_or_version), segments.next().is_some())
} else {
(history_or_type, id_or_version, None, segments.next().is_some())
})
}
#[must_use]
pub const fn resource_type(&self) -> Option<&'a str> {
match self {
Self::Local { .. } => None,
Self::Relative { resource_type, .. } => Some(resource_type),
Self::Absolute { resource_type, .. } => *resource_type,
}
}
#[must_use]
pub const fn id(&self) -> Option<&'a str> {
match self {
Self::Local { id } => Some(id),
Self::Relative { id, .. } => Some(id),
Self::Absolute { id, .. } => *id,
}
}
}
macro_rules! make_reference_inner {
(stu3, $reference:expr, $type:expr) => {
ReferenceInner {
id: None,
extension: Vec::new(),
reference: $reference,
reference_ext: None,
identifier: None,
identifier_ext: None,
display: None,
display_ext: None,
}
};
(r4b, $reference:expr, $type:expr) => {
ReferenceInner {
id: None,
extension: Vec::new(),
reference: $reference,
reference_ext: None,
r#type: $type,
type_ext: None,
identifier: None,
identifier_ext: None,
display: None,
display_ext: None,
}
};
(r5, $reference:expr, $type:expr) => {
ReferenceInner {
id: None,
extension: Vec::new(),
reference: $reference,
reference_ext: None,
r#type: $type,
type_ext: None,
identifier: None,
identifier_ext: None,
display: None,
display_ext: None,
}
};
}
macro_rules! impl_reference {
($version:ident) => {
mod $version {
use $crate::$version::{
resources::{BaseResource, NamedResource, ResourceType},
types::{Reference, ReferenceInner},
};
use super::ParsedReference;
impl Reference {
#[must_use]
pub fn parse(&self) -> Option<ParsedReference<'_>> {
let url = self.reference.as_ref()?;
Some(ParsedReference::new(url))
}
#[must_use]
#[allow(unused_variables, reason = "Unused for STU3")]
pub fn local(ty: ResourceType, id: &str) -> Self {
make_reference_inner!($version, Some(format!("#{id}")), Some(ty.to_string()))
.into()
}
pub fn local_to<R>(resource: &R) -> Option<Self>
where
R: NamedResource + BaseResource,
{
Some(Self::local(R::TYPE, resource.id().as_ref()?))
}
#[must_use]
pub fn relative(ty: ResourceType, id: &str) -> Self {
make_reference_inner!(
$version,
Some(format!("{ty}/{id}")),
Some(ty.to_string())
)
.into()
}
pub fn relative_to<R>(resource: &R) -> Option<Self>
where
R: NamedResource + BaseResource,
{
Some(Self::relative(R::TYPE, resource.id().as_ref()?))
}
}
}
};
}
for_all_versions!(impl_reference);