Skip to main content

jsonld_context_processing/algorithm/
merged.rs

1use iri_rs::IriRefBuf;
2use jsonld_syntax as syntax;
3use syntax::Nullable;
4
5/// A context definition viewed together with the definition it pulls in through
6/// `@import`.
7///
8/// The importing definition wins over the imported one for every entry, which is
9/// what the [specification][1] means by "merging context into import context,
10/// replacing common entries". Reading through this type lets the algorithm treat
11/// the pair as one definition without building a merged copy.
12///
13/// [1]: https://www.w3.org/TR/json-ld11-api/#context-processing-algorithm
14pub struct Merged<'a> {
15    base: &'a syntax::context::Definition,
16    imported: Option<syntax::context::Context>,
17}
18
19impl<'a> Merged<'a> {
20    /// Views `base` together with the context it imports.
21    ///
22    /// `imported` is the already-dereferenced `@import` target, or `None` when
23    /// the definition has no `@import`.
24    #[must_use]
25    pub fn new(base: &'a syntax::context::Definition, imported: Option<syntax::context::Context>) -> Self {
26        Self { base, imported }
27    }
28
29    /// Returns the imported context definition, if there is one.
30    ///
31    /// Yields `None` when nothing was imported, and also when what was imported
32    /// is not a single context definition object — a shape the caller has
33    /// already rejected before reaching here.
34    #[must_use]
35    pub fn imported(&self) -> Option<&syntax::context::Definition> {
36        self.imported.as_ref().and_then(|imported| match imported {
37            syntax::context::Context::One(syntax::ContextEntry::Definition(import_context)) => Some(import_context),
38            _ => None,
39        })
40    }
41
42    /// Returns the `@base` entry, falling back to the imported definition's.
43    pub fn base(&self) -> Option<syntax::Nullable<&IriRefBuf>> {
44        self.base
45            .base
46            .as_ref()
47            .or_else(|| self.imported().and_then(|i| i.base.as_ref()))
48            .map(Nullable::as_ref)
49    }
50
51    /// Returns the `@vocab` entry, falling back to the imported definition's.
52    pub fn vocab(&self) -> Option<syntax::Nullable<&syntax::context::definition::Vocab>> {
53        self.base
54            .vocab
55            .as_ref()
56            .or_else(|| self.imported().and_then(|i| i.vocab.as_ref()))
57            .map(Nullable::as_ref)
58    }
59
60    /// Returns the `@language` entry, falling back to the imported definition's.
61    pub fn language(&self) -> Option<syntax::Nullable<&syntax::LenientLangTagBuf>> {
62        self.base
63            .language
64            .as_ref()
65            .or_else(|| self.imported().and_then(|i| i.language.as_ref()))
66            .map(Nullable::as_ref)
67    }
68
69    /// Returns the `@direction` entry, falling back to the imported
70    /// definition's.
71    #[must_use]
72    pub fn direction(&self) -> Option<syntax::Nullable<syntax::Direction>> {
73        self.base.direction.or_else(|| self.imported().and_then(|i| i.direction))
74    }
75
76    /// Returns the `@protected` entry, falling back to the imported
77    /// definition's.
78    ///
79    /// This is the context-wide default applied to every term it defines, not
80    /// the flag of an individual term definition.
81    #[must_use]
82    pub fn protected(&self) -> Option<bool> {
83        self.base.protected.or_else(|| self.imported().and_then(|i| i.protected))
84    }
85
86    /// Returns the `@type` entry, falling back to the imported definition's.
87    ///
88    /// `@type` is the one keyword a context may redefine, to give it an `@set`
89    /// container or mark it protected.
90    #[must_use]
91    pub fn type_(&self) -> Option<syntax::context::definition::Type> {
92        self.base.type_.or_else(|| self.imported().and_then(|i| i.type_))
93    }
94
95    /// Iterates over every term definition of both definitions, without
96    /// repeating a term the importing definition overrides.
97    #[must_use]
98    pub fn bindings(&self) -> MergedBindings<'_> {
99        MergedBindings {
100            base: self.base,
101            base_bindings: self.base.bindings.iter(),
102            imported_bindings: self.imported().map(|i| i.bindings.iter()),
103        }
104    }
105
106    /// Returns the entry bound to `key`, preferring the importing definition's.
107    #[must_use]
108    pub fn get(&self, key: &syntax::context::definition::KeyOrKeyword) -> Option<syntax::context::definition::EntryValueRef<'_>> {
109        self.base.get(key).or_else(|| self.imported().and_then(|i| i.get(key)))
110    }
111}
112
113impl<'a> From<&'a syntax::context::Definition> for Merged<'a> {
114    fn from(base: &'a syntax::context::Definition) -> Self {
115        Self { base, imported: None }
116    }
117}
118
119type BindingRef<'a> = (&'a syntax::context::definition::Key, Nullable<&'a syntax::context::TermDefinition>);
120
121/// Iterator over the term definitions of a [`Merged`] context definition.
122///
123/// Yields the imported definition's terms first, skipping any the importing
124/// definition also defines, then the importing definition's own terms. Every
125/// term therefore appears exactly once, bound to the definition that wins.
126pub struct MergedBindings<'a> {
127    base: &'a syntax::context::Definition,
128    base_bindings: syntax::context::definition::BindingsIter<'a>,
129    imported_bindings: Option<syntax::context::definition::BindingsIter<'a>>,
130}
131
132impl<'a> Iterator for MergedBindings<'a> {
133    type Item = BindingRef<'a>;
134
135    fn next(&mut self) -> Option<Self::Item> {
136        match &mut self.imported_bindings {
137            Some(imported_bindings) => {
138                for (key_ref, def) in imported_bindings {
139                    let key = key_ref.to_owned();
140                    if self.base.get_binding(&key).is_none() {
141                        return Some((key_ref, def));
142                    }
143                }
144
145                self.base_bindings.next()
146            }
147            None => self.base_bindings.next(),
148        }
149    }
150}