Skip to main content

jsonld_compaction/
document.rs

1use jsonld_core::{ExpandedDocument, FlattenedDocument, Loader};
2use jsonld_syntax::{IntoJson, Keyword};
3use rdfx::vocabulary::{self, Vocabulary};
4use std::hash::Hash;
5
6use crate::{CompactFragment, iri::IriConfusedWithPrefix};
7
8/// Result of compacting a whole document.
9pub type CompactDocumentResult<E> = Result<jstrict::Value, crate::Error<E>>;
10
11/// Documents that a `@context` can be embedded into.
12///
13/// The final step of document compaction is to record, in the output, the
14/// context the output was compacted against, so that the result round-trips
15/// back to the same expanded document.
16pub trait EmbedContext {
17    /// Embeds `context` into this document as a `@context` entry.
18    ///
19    /// The context is written in its original, unprocessed form, as the first
20    /// entry of the document's top-level object. A document that compacted to an
21    /// array is first wrapped in an object under whatever `@graph` compacts to,
22    /// since only an object can carry `@context`. Nothing is embedded when the
23    /// document compacted to `null` or to nothing at all, or when the context
24    /// itself is null or empty.
25    ///
26    /// # Errors
27    ///
28    /// Returns an error when the context cannot be embedded in the compacted document.
29    fn embed_context<N>(
30        &mut self,
31        vocabulary: &N,
32        context: jsonld_context_processing::ProcessedRef<N::Iri, N::BlankId>,
33        options: crate::Options,
34    ) -> Result<(), IriConfusedWithPrefix>
35    where
36        N: Vocabulary,
37        N::Iri: Clone + Hash + Eq,
38        N::BlankId: Clone + Hash + Eq;
39}
40
41/// Whole documents that can be compacted against a processed `@context`.
42///
43/// Implemented for [`ExpandedDocument`] and [`FlattenedDocument`]. Unlike
44/// [`CompactFragment`], these methods take the context in both its processed and
45/// its original form: the processed form drives compaction, and the original form
46/// is embedded into the output as its `@context`.
47pub trait Compact<I, B> {
48    /// Compacts this document with the given [`Options`][crate::Options].
49    async fn compact_full<'a, N, L>(
50        &'a self,
51        vocabulary: &'a mut N,
52        context: jsonld_context_processing::ProcessedRef<'a, 'a, I, B>,
53        loader: &'a L,
54        options: crate::Options,
55    ) -> CompactDocumentResult<L::Error>
56    where
57        N: rdfx::vocabulary::VocabularyMut<Iri = I, BlankId = B>,
58        I: Clone + Hash + Eq,
59        B: Clone + Hash + Eq,
60        L: Loader;
61
62    /// Compacts this document with the default
63    /// [`Options`][crate::Options], using `vocabulary` to resolve IRI and blank
64    /// node identifiers.
65    async fn compact_with<'a, N, L>(
66        &'a self,
67        vocabulary: &'a mut N,
68        context: jsonld_context_processing::ProcessedRef<'a, 'a, I, B>,
69        loader: &'a L,
70    ) -> CompactDocumentResult<L::Error>
71    where
72        N: rdfx::vocabulary::VocabularyMut<Iri = I, BlankId = B>,
73        I: Clone + Hash + Eq,
74        B: Clone + Hash + Eq,
75        L: Loader,
76    {
77        self.compact_full(vocabulary, context, loader, crate::Options::default()).await
78    }
79
80    /// Compacts this document with the default [`Options`][crate::Options], for
81    /// documents that store IRIs and blank node identifiers inline instead of
82    /// indexing them through a vocabulary.
83    async fn compact<'a, L>(&'a self, context: jsonld_context_processing::ProcessedRef<'a, 'a, I, B>, loader: &'a L) -> CompactDocumentResult<L::Error>
84    where
85        (): rdfx::vocabulary::VocabularyMut<Iri = I, BlankId = B>,
86        I: Clone + Hash + Eq,
87        B: Clone + Hash + Eq,
88        L: Loader,
89    {
90        self.compact_with(vocabulary::no_vocabulary_mut(), context, loader).await
91    }
92}
93
94impl<I, B> Compact<I, B> for ExpandedDocument<I, B> {
95    async fn compact_full<'a, N, L>(
96        &'a self,
97        vocabulary: &'a mut N,
98        context: jsonld_context_processing::ProcessedRef<'a, 'a, I, B>,
99        loader: &'a L,
100        options: crate::Options,
101    ) -> CompactDocumentResult<L::Error>
102    where
103        N: rdfx::vocabulary::VocabularyMut<Iri = I, BlankId = B>,
104        I: Clone + Hash + Eq,
105        B: Clone + Hash + Eq,
106        L: Loader,
107    {
108        let mut compacted_output = self
109            .objects()
110            .compact_fragment_full(vocabulary, context.processed(), context.processed(), None, loader, options)
111            .await?;
112
113        compacted_output.embed_context(vocabulary, context, options)?;
114
115        Ok(compacted_output)
116    }
117}
118
119impl<I, B> Compact<I, B> for FlattenedDocument<I, B> {
120    async fn compact_full<'a, N, L>(
121        &'a self,
122        vocabulary: &'a mut N,
123        context: jsonld_context_processing::ProcessedRef<'a, 'a, I, B>,
124        loader: &'a L,
125        options: crate::Options,
126    ) -> CompactDocumentResult<L::Error>
127    where
128        N: rdfx::vocabulary::VocabularyMut<Iri = I, BlankId = B>,
129        I: Clone + Hash + Eq,
130        B: Clone + Hash + Eq,
131        L: Loader,
132    {
133        let mut compacted_output = self
134            .compact_fragment_full(vocabulary, context.processed(), context.processed(), None, loader, options)
135            .await?;
136
137        compacted_output.embed_context(vocabulary, context, options)?;
138
139        Ok(compacted_output)
140    }
141}
142
143impl EmbedContext for jstrict::Value {
144    fn embed_context<N>(
145        &mut self,
146        vocabulary: &N,
147        context: jsonld_context_processing::ProcessedRef<N::Iri, N::BlankId>,
148        options: crate::Options,
149    ) -> Result<(), IriConfusedWithPrefix>
150    where
151        N: Vocabulary,
152        N::Iri: Clone + Hash + Eq,
153        N::BlankId: Clone + Hash + Eq,
154    {
155        let value = self.take();
156
157        let obj = match value {
158            jstrict::Value::Array(array) => {
159                let mut obj = jstrict::Object::new();
160
161                if !array.is_empty() {
162                    let key = crate::iri::keyword_alias(vocabulary, context.processed(), options, Keyword::Graph);
163
164                    obj.insert(key.into(), array.into());
165                }
166
167                Some(obj)
168            }
169            jstrict::Value::Object(obj) => Some(obj),
170            _null => None,
171        };
172
173        if let Some(mut obj) = obj {
174            let json_context = IntoJson::into_json(context.unprocessed().clone());
175
176            if !obj.is_empty() && !json_context.is_null() && !json_context.is_empty_array_or_object() {
177                obj.insert_front("@context".into(), json_context);
178            }
179
180            *self = obj.into();
181        }
182
183        Ok(())
184    }
185}