Skip to main content

ion_rs/element/
annotations.rs

1use crate::element::iterators::{AnnotationsIntoIter, SymbolsIterator};
2use crate::ion_data::{IonDataHash, IonDataOrd};
3use crate::Symbol;
4use std::cmp::Ordering;
5use std::hash::Hasher;
6
7/// An ordered sequence of symbols that convey additional, application-specific information about
8/// their associated Ion value.
9///
10/// The [`IntoAnnotations`] trait is a convenient way to convert collections of symbol convertible
11/// things (including [`&str`] and [`String`]) into this sequence.
12///
13/// ```
14/// use ion_rs::{Annotations, IntoAnnotations};
15/// let annotations: Annotations = ["foo", "bar", "baz"].into_annotations();
16/// for annotation in &annotations {
17///     assert_eq!(annotation.text().map(|s| s.len()), Some(3));
18/// }
19/// ```
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct Annotations {
22    symbols: Vec<Symbol>,
23}
24
25impl Annotations {
26    // This is limited to crate visibility to allow us to change `Vec<_>` to something
27    // else (`thinvec`?) in the future. Users are unlikely to need to construct an `Annotations`
28    // themselves, but can use a `From` impl if necessary.
29    pub(crate) fn new(symbols: Vec<Symbol>) -> Self {
30        Annotations { symbols }
31    }
32
33    /// Constructs an Annotations object representing an empty symbol sequence
34    pub fn empty() -> Self {
35        Annotations {
36            symbols: Vec::new(),
37        }
38    }
39
40    /// Returns an [`Iterator`] that yields each of the [`Symbol`]s in this annotations
41    /// sequence in order.
42    pub fn iter(&self) -> SymbolsIterator<'_> {
43        SymbolsIterator::new(self.symbols.as_slice())
44    }
45
46    /// Returns the number of annotations in this sequence.
47    /// ```
48    /// use ion_rs::{Annotations, IntoAnnotations};
49    /// let annotations: Annotations = ["foo", "bar", "baz"].into_annotations();
50    /// assert_eq!(annotations.len(), 3);
51    /// ```
52    pub fn len(&self) -> usize {
53        self.symbols.len()
54    }
55
56    /// Returns `true` if this sequence contains zero annotations. Otherwise, returns `false`.
57    /// ```
58    /// use ion_rs::{Annotations, IntoAnnotations};
59    /// let annotations: Annotations = ["foo", "bar", "baz"].into_annotations();
60    /// assert!(!annotations.is_empty());
61    /// ```
62    pub fn is_empty(&self) -> bool {
63        self.symbols.len() == 0
64    }
65
66    /// Returns `true` if any symbol in this annotations sequence is equal to the provided text.
67    /// Otherwise, returns `false`.
68    /// ```
69    /// use ion_rs::{Annotations, IntoAnnotations};
70    /// let annotations: Annotations = ["foo", "bar", "baz"].into_annotations();
71    /// assert!(annotations.contains("foo"));
72    /// assert!(annotations.contains("bar"));
73    /// assert!(annotations.contains("baz"));
74    ///
75    /// assert!(!annotations.contains("quux"));
76    /// assert!(!annotations.contains("quuz"));
77    /// ```
78    pub fn contains<S: AsRef<str>>(&self, query: S) -> bool {
79        let query: &str = query.as_ref();
80        self.iter().any(|symbol| symbol.text() == Some(query))
81    }
82
83    /// Returns the text of the first annotation in this sequence.
84    ///
85    /// If the sequence is empty, returns `None`.
86    /// If the first annotation in the sequence is `$0` (symbol ID 0), returns `None`.
87    /// Otherwise, returns a `Some(&str)` containing the text.
88    ///
89    /// To view the first annotation as a [Symbol] rather than a `&str`, use
90    /// `annotations.iter().next()`.
91    /// ```
92    /// use ion_rs::{Annotations, IntoAnnotations};
93    /// use ion_rs::Symbol;
94    /// let annotations: Annotations = ["foo", "bar", "baz"].into_annotations();
95    /// assert_eq!(annotations.first(), Some("foo"));
96    ///
97    /// let empty_sequence: Vec<&str> = vec![];
98    /// let annotations: Annotations = empty_sequence.into_annotations();
99    /// assert_eq!(annotations.first(), None);
100    ///
101    /// let annotations: Annotations = [Symbol::unknown_text()].into_annotations();
102    /// assert_eq!(annotations.first(), None)
103    /// ```
104    pub fn first(&self) -> Option<&str> {
105        self.iter().next().and_then(|a| a.text())
106    }
107}
108
109impl AsRef<[Symbol]> for Annotations {
110    fn as_ref(&self) -> &[Symbol] {
111        self.symbols.as_slice()
112    }
113}
114
115impl From<Vec<Symbol>> for Annotations {
116    fn from(value: Vec<Symbol>) -> Self {
117        Annotations::new(value)
118    }
119}
120
121impl<S: Into<Symbol>> FromIterator<S> for Annotations {
122    fn from_iter<T: IntoIterator<Item = S>>(iter: T) -> Self {
123        iter.into_annotations()
124    }
125}
126
127impl<'a> IntoIterator for &'a Annotations {
128    type Item = &'a Symbol;
129    type IntoIter = SymbolsIterator<'a>;
130
131    fn into_iter(self) -> Self::IntoIter {
132        SymbolsIterator::new(self.symbols.as_slice())
133    }
134}
135
136impl IntoIterator for Annotations {
137    type Item = Symbol;
138    type IntoIter = AnnotationsIntoIter;
139
140    fn into_iter(self) -> Self::IntoIter {
141        AnnotationsIntoIter::new(self.symbols.into_iter())
142    }
143}
144
145impl IonDataOrd for Annotations {
146    fn ion_cmp(&self, other: &Self) -> Ordering {
147        self.symbols.ion_cmp(&other.symbols)
148    }
149}
150
151impl IonDataHash for Annotations {
152    fn ion_data_hash<H: Hasher>(&self, state: &mut H) {
153        self.symbols.ion_data_hash(state)
154    }
155}
156
157/// Defines conversion into [`Annotations`].
158///
159/// This trait allows us to have a blanket implementations that can cover many type combinations
160/// without conflicting in ways that blanket [`From`] implementations can.
161///
162/// With this we can convert for some `T` that is string-like (`&str`, `String`, `Symbol`, etc...)
163/// we can convert from collections of that type like `[T]`, `[T; n]`, `Vec<T>`, and
164/// iterators of `T` generically.
165pub trait IntoAnnotations {
166    fn into_annotations(self) -> Annotations;
167}
168
169impl<S, I> IntoAnnotations for I
170where
171    S: Into<Symbol>,
172    I: IntoIterator<Item = S>,
173{
174    fn into_annotations(self) -> Annotations {
175        let symbols: Vec<Symbol> = self.into_iter().map(|a| a.into()).collect();
176        Annotations::new(symbols)
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn test_into_iter() {
186        let expected = vec!["a", "b", "c"].into_annotations();
187        let collect: Annotations = expected.clone().into_iter().collect();
188        assert_eq!(expected, collect);
189    }
190
191    #[test]
192    fn test_from_vec() {
193        let expected = vec!["d", "e", "f"].into_annotations();
194        let symbols: Vec<_> = expected.clone().into_iter().collect();
195        let from: Annotations = symbols.into();
196        assert_eq!(expected, from);
197    }
198}