Skip to main content

ion_rs/
symbol_ref.rs

1use crate::raw_symbol_ref::{AsRawSymbolRef, RawSymbolRef};
2use crate::result::IonFailure;
3use crate::{IonResult, Str, Symbol};
4use std::borrow::Borrow;
5use std::fmt::{Debug, Formatter};
6use std::hash::{Hash, Hasher};
7
8/// A reference to a fully resolved symbol. Like `Symbol` (a fully resolved symbol with a
9/// static lifetime), a `SymbolRef` may have known or undefined text (i.e. `$0`).
10#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
11pub struct SymbolRef<'a> {
12    text: Option<&'a str>,
13}
14
15impl Debug for SymbolRef<'_> {
16    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
17        write!(f, "{}", self.text().unwrap_or("$0"))
18    }
19}
20
21impl<'a> SymbolRef<'a> {
22    /// If this symbol has known text, returns `Some(&str)`. Otherwise, returns `None`.
23    pub fn text(&self) -> Option<&'a str> {
24        self.text
25    }
26
27    /// Constructs a `SymbolRef` with unknown text.
28    pub fn with_unknown_text() -> Self {
29        SymbolRef { text: None }
30    }
31
32    /// Constructs a `SymbolRef` with the specified text.
33    pub fn with_text(text: &'a str) -> SymbolRef<'a> {
34        SymbolRef { text: Some(text) }
35    }
36
37    pub fn to_owned(self) -> Symbol {
38        match self.text {
39            None => Symbol::unknown_text(),
40            Some(text) => Symbol::owned(Str::from(text)),
41        }
42    }
43
44    pub fn expect_text(&self) -> IonResult<&'a str> {
45        match self.text() {
46            Some(text) => Ok(text),
47            None => IonResult::decoding_error("symbol has unknown text"),
48        }
49    }
50}
51
52impl<A> PartialEq<A> for SymbolRef<'_>
53where
54    A: AsSymbolRef,
55{
56    fn eq(&self, other: &A) -> bool {
57        let other_symbol_ref = other.as_symbol_ref();
58        self == &other_symbol_ref
59    }
60}
61
62/// Allows a `SymbolRef` to be constructed from a source value. This enables non-symbol types to be
63/// viewed as a symbol with little to no runtime overhead.
64pub trait AsSymbolRef {
65    fn as_symbol_ref(&self) -> SymbolRef<'_>;
66}
67
68// All text types can be viewed as a `SymbolRef`.
69impl<A: AsRef<str>> AsSymbolRef for A {
70    fn as_symbol_ref(&self) -> SymbolRef<'_> {
71        SymbolRef {
72            text: Some(self.as_ref()),
73        }
74    }
75}
76
77impl Hash for SymbolRef<'_> {
78    fn hash<H: Hasher>(&self, state: &mut H) {
79        match self.text() {
80            None => 0.hash(state),
81            Some(text) => text.hash(state),
82        }
83    }
84}
85
86impl<'a> From<&'a str> for SymbolRef<'a> {
87    fn from(text: &'a str) -> Self {
88        Self { text: Some(text) }
89    }
90}
91
92impl<'a> From<&'a Symbol> for SymbolRef<'a> {
93    fn from(symbol: &'a Symbol) -> Self {
94        Self {
95            text: symbol.text(),
96        }
97    }
98}
99
100// Note that this method panics if the SymbolRef has unknown text! This is unfortunate but is required
101// in order to allow a HashMap<SymbolRef, _> to do lookups with a &str instead of a &SymbolRef
102impl Borrow<str> for SymbolRef<'_> {
103    fn borrow(&self) -> &str {
104        self.text()
105            .expect("cannot borrow a &str from a SymbolRef with unknown text")
106    }
107}
108
109// Owned `Symbol` values can be viewed as a `SymbolRef`. Due to lifetime conflicts in the
110// trait definitions, this cannot be achieved with `AsRef` or `Borrow`.
111impl AsSymbolRef for Symbol {
112    fn as_symbol_ref(&self) -> SymbolRef<'_> {
113        self.text()
114            .map(SymbolRef::with_text)
115            .unwrap_or_else(SymbolRef::with_unknown_text)
116    }
117}
118
119impl AsSymbolRef for &Symbol {
120    fn as_symbol_ref(&self) -> SymbolRef<'_> {
121        self.text()
122            .map(SymbolRef::with_text)
123            .unwrap_or_else(SymbolRef::with_unknown_text)
124    }
125}
126
127impl AsRawSymbolRef for SymbolRef<'_> {
128    fn as_raw_symbol_ref(&self) -> RawSymbolRef<'_> {
129        match &self.text {
130            None => RawSymbolRef::SymbolId(0),
131            Some(text) => RawSymbolRef::Text(text),
132        }
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn symbol_ref_with_text() {
142        let symbol_ref = SymbolRef::with_text("foo");
143        assert_eq!(Some("foo"), symbol_ref.text());
144    }
145
146    #[test]
147    fn symbol_ref_with_unknown_text() {
148        let symbol_ref = SymbolRef::with_unknown_text();
149        assert_eq!(None, symbol_ref.text());
150    }
151
152    #[test]
153    fn str_as_symbol_ref() {
154        let symbol_ref: SymbolRef<'_> = "foo".as_symbol_ref();
155        assert_eq!(Some("foo"), symbol_ref.text());
156    }
157
158    #[test]
159    fn symbol_as_symbol_ref() {
160        let symbol = Symbol::owned("foo");
161        let symbol_ref: SymbolRef<'_> = symbol.as_symbol_ref();
162        assert_eq!(Some("foo"), symbol_ref.text());
163    }
164
165    #[test]
166    fn symbol_with_unknown_text_as_symbol_ref() {
167        let symbol = Symbol::unknown_text();
168        let symbol_ref: SymbolRef<'_> = symbol.as_symbol_ref();
169        assert_eq!(None, symbol_ref.text());
170    }
171}