egml_core/model/xlink/href.rs
1use crate::model::base::Id;
2
3/// An `xlink:href` reference to a GML object.
4///
5/// Distinguishes between local fragment references (same document) and remote
6/// URI references at the type level, keeping the `#` prefix as a
7/// serialization-only detail.
8///
9/// # Examples
10///
11/// ```
12/// use egml_core::model::xlink::HRef;
13///
14/// // Both forms produce the same local reference
15/// assert_eq!(HRef::from_local("#UUID_abc_def"), HRef::from_local("UUID_abc_def"));
16///
17/// let local: HRef = "#UUID_abc_def".parse().unwrap();
18/// assert_eq!(local, HRef::from_local("UUID_abc_def"));
19/// assert_eq!(local.local_id(), Some("UUID_abc_def"));
20/// assert_eq!(local.to_string(), "#UUID_abc_def");
21///
22/// let remote: HRef = "https://example.com/geom".parse().unwrap();
23/// assert_eq!(remote, HRef::Remote("https://example.com/geom".to_string()));
24/// assert_eq!(remote.local_id(), None);
25/// assert_eq!(remote.to_string(), "https://example.com/geom");
26/// ```
27#[derive(Debug, Clone, PartialEq, Eq, Hash)]
28pub enum HRef {
29 /// A fragment reference to an object in the same document.
30 /// Stores the bare ID without the `#` prefix.
31 Local(String),
32 /// A full URI reference to an external resource.
33 Remote(String),
34}
35
36impl HRef {
37 /// Constructs a local fragment reference, stripping a leading `#` if present.
38 ///
39 /// Accepts both raw `xlink:href` attribute values (`"#UUID_abc"`) and bare
40 /// GML ids (`"UUID_abc"`). The `#` prefix is added back on serialization.
41 pub fn from_local(id: impl Into<String>) -> Self {
42 let s = id.into();
43 let bare = s.strip_prefix('#').unwrap_or(&s).to_owned();
44 HRef::Local(bare)
45 }
46
47 /// Constructs a local fragment reference from a GML [`Id`].
48 pub fn from_local_id(id: Id) -> Self {
49 HRef::Local(id.into())
50 }
51
52 /// Constructs a remote URI reference.
53 pub fn from_remote(uri: impl Into<String>) -> Self {
54 HRef::Remote(uri.into())
55 }
56
57 /// Returns the bare GML `gml:id` if this is a local fragment reference.
58 pub fn local_id(&self) -> Option<&str> {
59 match self {
60 HRef::Local(id) => Some(id.as_str()),
61 HRef::Remote(_) => None,
62 }
63 }
64
65 /// Returns `true` if this is a local fragment reference.
66 pub fn is_local(&self) -> bool {
67 matches!(self, HRef::Local(_))
68 }
69}
70
71impl From<String> for HRef {
72 fn from(s: String) -> Self {
73 if let Some(id) = s.strip_prefix('#') {
74 HRef::Local(id.to_owned())
75 } else {
76 HRef::Remote(s)
77 }
78 }
79}
80
81impl From<&str> for HRef {
82 fn from(s: &str) -> Self {
83 HRef::from(s.to_owned())
84 }
85}
86
87impl std::fmt::Display for HRef {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 match self {
90 HRef::Local(id) => write!(f, "#{id}"),
91 HRef::Remote(uri) => f.write_str(uri),
92 }
93 }
94}
95
96impl std::str::FromStr for HRef {
97 type Err = std::convert::Infallible;
98
99 fn from_str(s: &str) -> Result<Self, Self::Err> {
100 Ok(HRef::from(s))
101 }
102}