Skip to main content

jsonld_core/
container.rs

1pub use jsonld_syntax::ContainerKind;
2use jsonld_syntax::{Nullable, context::definition::TypeContainer};
3
4/// Error raised when a set of `@container` values cannot be combined.
5///
6/// Only certain combinations are allowed by the JSON-LD grammar — `@set` pairs
7/// with most other values, but `@list` and `@language` do not pair with
8/// `@index`, for instance.
9#[derive(Debug, Clone, Copy, thiserror::Error)]
10#[error("invalid container")]
11pub struct InvalidContainer;
12
13#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
14/// Container mapping of a term: the combination of `@container` values it
15/// was defined with.
16pub enum Container {
17    /// Empty container: no `@container` value.
18    None,
19
20    /// `@graph`: the values of the term are graph objects.
21    Graph,
22    /// `@id`: the values of the term form an id map, keyed by node identifier.
23    Id,
24    /// `@index`: the values of the term form an index map, keyed by an
25    /// arbitrary index string.
26    Index,
27    /// `@language`: the values of the term form a language map, keyed by
28    /// language tag.
29    Language,
30    /// `@list`: the values of the term are an ordered list.
31    List,
32    /// `@set`: the values of the term are always represented as an array.
33    Set,
34    /// `@type`: the values of the term form a type map, keyed by node type.
35    Type,
36
37    /// `@graph` and `@set`.
38    GraphSet,
39    /// `@graph` and `@id`.
40    GraphId,
41    /// `@graph` and `@index`.
42    GraphIndex,
43    /// `@id` and `@set`.
44    IdSet,
45    /// `@index` and `@set`.
46    IndexSet,
47    /// `@language` and `@set`.
48    LanguageSet,
49    /// `@set` and `@type`.
50    SetType,
51
52    /// `@graph`, `@id` and `@set`.
53    GraphIdSet,
54    /// `@graph`, `@index` and `@set`.
55    GraphIndexSet,
56}
57
58impl Default for Container {
59    fn default() -> Self {
60        Self::new()
61    }
62}
63
64impl Container {
65    /// Creates a new `Container`.
66    #[must_use]
67    pub fn new() -> Container {
68        Container::None
69    }
70
71    /// Builds a container mapping from its syntactic form.
72    ///
73    /// # Errors
74    ///
75    /// Returns an error when the `@container` value is not one of the combinations the grammar allows.
76    pub fn from_syntax(r: Nullable<&jsonld_syntax::Container>) -> Result<Self, InvalidContainer> {
77        match r {
78            Nullable::Null => Ok(Self::None),
79            Nullable::Some(jsonld_syntax::Container::One(c)) => Ok((*c).into()),
80            Nullable::Some(jsonld_syntax::Container::Many(m)) => {
81                let mut container = Container::new();
82
83                for t in m {
84                    if !container.add(*t) {
85                        return Err(InvalidContainer);
86                    }
87                }
88
89                Ok(container)
90            }
91        }
92    }
93
94    /// Builds a container mapping from the `@container` values it combines.
95    ///
96    /// # Errors
97    ///
98    /// Returns the offending `@container` entry when the combination is not allowed by the grammar.
99    pub fn from<'a, I: IntoIterator<Item = &'a ContainerKind>>(iter: I) -> Result<Container, ContainerKind> {
100        let mut container = Container::new();
101        for item in iter {
102            if !container.add(*item) {
103                return Err(*item);
104            }
105        }
106
107        Ok(container)
108    }
109
110    /// Returns the combined `@container` values as a slice.
111    #[must_use]
112    pub fn as_slice(&self) -> &[ContainerKind] {
113        use Container::{
114            Graph,
115            GraphId,
116            GraphIdSet,
117            GraphIndex,
118            GraphIndexSet,
119            GraphSet,
120            Id,
121            IdSet,
122            Index,
123            IndexSet,
124            Language,
125            LanguageSet,
126            List,
127            None,
128            Set,
129            SetType,
130            Type,
131        };
132        match self {
133            None => &[],
134            Graph => &[ContainerKind::Graph],
135            Id => &[ContainerKind::Id],
136            Index => &[ContainerKind::Index],
137            Language => &[ContainerKind::Language],
138            List => &[ContainerKind::List],
139            Set => &[ContainerKind::Set],
140            Type => &[ContainerKind::Type],
141            GraphSet => &[ContainerKind::Graph, ContainerKind::Set],
142            GraphId => &[ContainerKind::Graph, ContainerKind::Id],
143            GraphIndex => &[ContainerKind::Graph, ContainerKind::Index],
144            IdSet => &[ContainerKind::Id, ContainerKind::Set],
145            IndexSet => &[ContainerKind::Index, ContainerKind::Set],
146            LanguageSet => &[ContainerKind::Language, ContainerKind::Set],
147            SetType => &[ContainerKind::Type, ContainerKind::Set],
148            GraphIdSet => &[ContainerKind::Graph, ContainerKind::Id, ContainerKind::Set],
149            GraphIndexSet => &[ContainerKind::Graph, ContainerKind::Index, ContainerKind::Set],
150        }
151    }
152
153    /// Returns an iterator over the combined `@container` values.
154    pub fn iter(&self) -> impl Iterator<Item = &ContainerKind> {
155        self.as_slice().iter()
156    }
157
158    /// Returns the number of combined `@container` values.
159    #[must_use]
160    pub fn len(&self) -> usize {
161        self.as_slice().len()
162    }
163
164    /// Checks whether the container mapping is empty.
165    #[must_use]
166    pub fn is_empty(&self) -> bool {
167        matches!(self, Container::None)
168    }
169
170    /// Checks whether the mapping includes the given `@container` value.
171    #[must_use]
172    pub fn contains(&self, c: ContainerKind) -> bool {
173        self.as_slice().contains(&c)
174    }
175
176    /// Returns this mapping extended with the given `@container` value, or
177    /// `None` if the combination is not allowed by the JSON-LD grammar.
178    #[must_use]
179    pub fn with(&self, c: ContainerKind) -> Option<Container> {
180        let new_container = match (self, c) {
181            (Container::None, c) => c.into(),
182            (Container::Graph, ContainerKind::Graph) => *self,
183            (Container::Graph, ContainerKind::Set) => Container::GraphSet,
184            (Container::Graph, ContainerKind::Id) => Container::GraphId,
185            (Container::Graph, ContainerKind::Index) => Container::GraphIndex,
186            (Container::Id, ContainerKind::Id) => *self,
187            (Container::Id, ContainerKind::Graph) => Container::GraphId,
188            (Container::Id, ContainerKind::Set) => Container::IdSet,
189            (Container::Index, ContainerKind::Index) => *self,
190            (Container::Index, ContainerKind::Graph) => Container::GraphIndex,
191            (Container::Index, ContainerKind::Set) => Container::IndexSet,
192            (Container::Language, ContainerKind::Language) => *self,
193            (Container::Language, ContainerKind::Set) => Container::LanguageSet,
194            (Container::List, ContainerKind::List) => *self,
195            (Container::Set, ContainerKind::Set) => *self,
196            (Container::Set, ContainerKind::Graph) => Container::GraphSet,
197            (Container::Set, ContainerKind::Id) => Container::IdSet,
198            (Container::Set, ContainerKind::Index) => Container::IndexSet,
199            (Container::Set, ContainerKind::Language) => Container::LanguageSet,
200            (Container::Set, ContainerKind::Type) => Container::SetType,
201            (Container::Type, ContainerKind::Type) => *self,
202            (Container::Type, ContainerKind::Set) => Container::SetType,
203            (Container::GraphSet, ContainerKind::Graph) => *self,
204            (Container::GraphSet, ContainerKind::Set) => *self,
205            (Container::GraphSet, ContainerKind::Id) => Container::GraphIdSet,
206            (Container::GraphSet, ContainerKind::Index) => Container::GraphIndexSet,
207            (Container::GraphId, ContainerKind::Graph) => *self,
208            (Container::GraphId, ContainerKind::Id) => *self,
209            (Container::GraphId, ContainerKind::Set) => Container::GraphIdSet,
210            (Container::GraphIndex, ContainerKind::Graph) => *self,
211            (Container::GraphIndex, ContainerKind::Index) => *self,
212            (Container::GraphIndex, ContainerKind::Set) => Container::GraphIndexSet,
213            (Container::IdSet, ContainerKind::Id) => *self,
214            (Container::IdSet, ContainerKind::Set) => *self,
215            (Container::IdSet, ContainerKind::Graph) => Container::GraphIdSet,
216            (Container::IndexSet, ContainerKind::Index) => *self,
217            (Container::IndexSet, ContainerKind::Set) => *self,
218            (Container::IndexSet, ContainerKind::Graph) => Container::GraphIndexSet,
219            (Container::LanguageSet, ContainerKind::Language) => *self,
220            (Container::LanguageSet, ContainerKind::Set) => *self,
221            (Container::SetType, ContainerKind::Set) => *self,
222            (Container::SetType, ContainerKind::Type) => *self,
223            (Container::GraphIdSet, ContainerKind::Graph) => *self,
224            (Container::GraphIdSet, ContainerKind::Id) => *self,
225            (Container::GraphIdSet, ContainerKind::Set) => *self,
226            (Container::GraphIndexSet, ContainerKind::Graph) => *self,
227            (Container::GraphIndexSet, ContainerKind::Index) => *self,
228            (Container::GraphIndexSet, ContainerKind::Set) => *self,
229            _ => return None,
230        };
231
232        Some(new_container)
233    }
234
235    /// Adds the given `@container` value to the mapping in place. Returns
236    /// `false` (leaving the mapping unchanged) if the combination is not
237    /// allowed by the JSON-LD grammar.
238    pub fn add(&mut self, c: ContainerKind) -> bool {
239        match self.with(c) {
240            Some(container) => {
241                *self = container;
242                true
243            }
244            None => false,
245        }
246    }
247
248    /// Converts the mapping into its syntactic form: a single `@container`
249    /// value when it combines exactly one, an array when it combines several,
250    /// and `None` when it is empty.
251    #[must_use]
252    pub fn into_syntax(self) -> Option<jsonld_syntax::Container> {
253        let slice = self.as_slice();
254
255        match slice.len() {
256            0 => None,
257            1 => Some(jsonld_syntax::Container::One(slice[0])),
258            _ => Some(jsonld_syntax::Container::Many(slice.to_vec())),
259        }
260    }
261}
262
263impl From<ContainerKind> for Container {
264    fn from(c: ContainerKind) -> Self {
265        match c {
266            ContainerKind::Graph => Self::Graph,
267            ContainerKind::Id => Self::Id,
268            ContainerKind::Index => Self::Index,
269            ContainerKind::Language => Self::Language,
270            ContainerKind::List => Self::List,
271            ContainerKind::Set => Self::Set,
272            ContainerKind::Type => Self::Type,
273        }
274    }
275}
276
277impl From<TypeContainer> for Container {
278    fn from(c: TypeContainer) -> Self {
279        match c {
280            TypeContainer::Set => Container::Set,
281        }
282    }
283}