1use jsonld_context_processing::WarningHandler;
2use jsonld_core::{ExpandedDocument, FlattenedDocument, Loader, warning};
3use jsonld_syntax::{IntoJson, Keyword};
4use rdfx::vocabulary::{self, Vocabulary};
5use std::hash::Hash;
6
7use crate::{CompactFragment, iri::IriConfusedWithPrefix};
8
9pub type CompactDocumentResult<E> = Result<jstrict::Value, crate::Error<E>>;
11
12pub trait EmbedContext {
18 fn embed_context<N>(
31 &mut self,
32 vocabulary: &N,
33 context: jsonld_context_processing::ProcessedRef<N::Iri, N::BlankId>,
34 options: crate::Options,
35 ) -> Result<(), IriConfusedWithPrefix>
36 where
37 N: Vocabulary,
38 N::Iri: Clone + Hash + Eq,
39 N::BlankId: Clone + Hash + Eq;
40}
41
42pub trait Compact<I, B> {
49 async fn compact_full<'a, N, L, W>(
54 &'a self,
55 vocabulary: &'a mut N,
56 context: jsonld_context_processing::ProcessedRef<'a, 'a, I, B>,
57 loader: &'a L,
58 options: crate::Options,
59 warnings: W,
60 ) -> CompactDocumentResult<L::Error>
61 where
62 N: rdfx::vocabulary::VocabularyMut<Iri = I, BlankId = B>,
63 I: Clone + Hash + Eq,
64 B: Clone + Hash + Eq,
65 L: Loader,
66 W: WarningHandler<N>;
67
68 async fn compact_with<'a, N, L>(
74 &'a self,
75 vocabulary: &'a mut N,
76 context: jsonld_context_processing::ProcessedRef<'a, 'a, I, B>,
77 loader: &'a L,
78 ) -> CompactDocumentResult<L::Error>
79 where
80 N: rdfx::vocabulary::VocabularyMut<Iri = I, BlankId = B>,
81 I: Clone + Hash + Eq,
82 B: Clone + Hash + Eq,
83 L: Loader,
84 {
85 self.compact_full(vocabulary, context, loader, crate::Options::default(), warning::Print).await
86 }
87
88 async fn compact<'a, L>(&'a self, context: jsonld_context_processing::ProcessedRef<'a, 'a, I, B>, loader: &'a L) -> CompactDocumentResult<L::Error>
94 where
95 (): rdfx::vocabulary::VocabularyMut<Iri = I, BlankId = B>,
96 I: Clone + Hash + Eq,
97 B: Clone + Hash + Eq,
98 L: Loader,
99 {
100 self.compact_with(vocabulary::no_vocabulary_mut(), context, loader).await
101 }
102}
103
104impl<I, B> Compact<I, B> for ExpandedDocument<I, B> {
105 async fn compact_full<'a, N, L, W>(
106 &'a self,
107 vocabulary: &'a mut N,
108 context: jsonld_context_processing::ProcessedRef<'a, 'a, I, B>,
109 loader: &'a L,
110 options: crate::Options,
111 mut warnings: W,
112 ) -> CompactDocumentResult<L::Error>
113 where
114 N: rdfx::vocabulary::VocabularyMut<Iri = I, BlankId = B>,
115 I: Clone + Hash + Eq,
116 B: Clone + Hash + Eq,
117 L: Loader,
118 W: WarningHandler<N>,
119 {
120 let mut compacted_output = self
121 .objects()
122 .compact_fragment_full(vocabulary, context.processed(), context.processed(), None, loader, options, &mut warnings)
123 .await?;
124
125 compacted_output.embed_context(vocabulary, context, options)?;
126
127 Ok(compacted_output)
128 }
129}
130
131impl<I, B> Compact<I, B> for FlattenedDocument<I, B> {
132 async fn compact_full<'a, N, L, W>(
133 &'a self,
134 vocabulary: &'a mut N,
135 context: jsonld_context_processing::ProcessedRef<'a, 'a, I, B>,
136 loader: &'a L,
137 options: crate::Options,
138 mut warnings: W,
139 ) -> CompactDocumentResult<L::Error>
140 where
141 N: rdfx::vocabulary::VocabularyMut<Iri = I, BlankId = B>,
142 I: Clone + Hash + Eq,
143 B: Clone + Hash + Eq,
144 L: Loader,
145 W: WarningHandler<N>,
146 {
147 let mut compacted_output = self
148 .compact_fragment_full(vocabulary, context.processed(), context.processed(), None, loader, options, &mut warnings)
149 .await?;
150
151 compacted_output.embed_context(vocabulary, context, options)?;
152
153 Ok(compacted_output)
154 }
155}
156
157impl EmbedContext for jstrict::Value {
158 fn embed_context<N>(
159 &mut self,
160 vocabulary: &N,
161 context: jsonld_context_processing::ProcessedRef<N::Iri, N::BlankId>,
162 options: crate::Options,
163 ) -> Result<(), IriConfusedWithPrefix>
164 where
165 N: Vocabulary,
166 N::Iri: Clone + Hash + Eq,
167 N::BlankId: Clone + Hash + Eq,
168 {
169 let value = self.take();
170
171 let obj = match value {
172 jstrict::Value::Array(array) => {
173 let mut obj = jstrict::Object::new();
174
175 if !array.is_empty() {
176 let key = crate::iri::keyword_alias(vocabulary, context.processed(), options, Keyword::Graph);
177
178 obj.insert(key.into(), array.into());
179 }
180
181 Some(obj)
182 }
183 jstrict::Value::Object(obj) => Some(obj),
184 _null => None,
185 };
186
187 if let Some(mut obj) = obj {
188 let json_context = IntoJson::into_json(context.unprocessed().clone());
189
190 if !obj.is_empty() && !json_context.is_null() && !json_context.is_empty_array_or_object() {
191 obj.insert_front("@context".into(), json_context);
192 }
193
194 *self = obj.into();
195 }
196
197 Ok(())
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 #![allow(clippy::unwrap_used)]
204
205 use crate::{Compact, Options, test_fixtures::json};
206 use jsonld_context_processing::{Process, ProcessedRef, Warning};
207 use jsonld_core::{ExpandedDocument, NoLoader, TryFromJson, warning::WarningBuf};
208 use jsonld_syntax::{TryFromJson as _, context::Context as SyntaxContext};
209 use rdfx::{BlankIdBuf, IriBuf, vocabulary::no_vocabulary_mut};
210
211 #[tokio::test]
215 async fn scoped_context_warnings_reach_the_compaction_handler() {
216 let context = SyntaxContext::try_from_json(&json(
217 r#"{
218 "p": { "@id": "https://example.com/p", "@context": { "bad": { "@id": "@foo" } } },
219 "q": "https://example.com/q"
220 }"#,
221 ))
222 .unwrap();
223 let mut processing_warnings = WarningBuf::<Warning>::new();
224 let processed = context
225 .process_full(
226 no_vocabulary_mut(),
227 &jsonld_core::Context::new(None),
228 &NoLoader,
229 None,
230 jsonld_context_processing::Options::default(),
231 &mut processing_warnings,
232 )
233 .await
234 .unwrap()
235 .into_processed();
236 let document = ExpandedDocument::<IriBuf, BlankIdBuf>::try_from_json_in(
237 no_vocabulary_mut(),
238 json(r#"[{ "https://example.com/p": [{ "https://example.com/q": [{ "@value": 1 }] }] }]"#),
239 )
240 .unwrap();
241
242 let mut warnings = WarningBuf::<Warning>::new();
243 document
244 .compact_full(
245 no_vocabulary_mut(),
246 ProcessedRef::new(&context, &processed),
247 &NoLoader,
248 Options::default(),
249 &mut warnings,
250 )
251 .await
252 .unwrap();
253
254 assert_eq!(warnings.0, vec![Warning::KeywordLikeValue(String::from("@foo"))]);
255 }
256}