Skip to main content

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}