1use indexmap::IndexSet;
8use json_ld_context_processing_next::{Options as ProcessingOptions, Process};
9use json_ld_core_next::{
10 context::inverse::{LangSelection, TypeSelection},
11 object::Any,
12 Context, Indexed, Loader, ProcessingMode, Term, Value,
13};
14use json_ld_syntax_next::{ContainerKind, ErrorCode, Keyword};
15use json_syntax::object::Entry;
16use mown::Mown;
17use rdf_types::{vocabulary, VocabularyMut};
18use std::hash::Hash;
19
20mod document;
21mod iri;
22mod node;
23mod property;
24mod value;
25
26pub use document::*;
27pub(crate) use iri::*;
28use node::*;
29use property::*;
30use value::*;
31
32#[derive(Debug, thiserror::Error)]
33pub enum Error {
34 #[error("IRI confused with prefix")]
35 IriConfusedWithPrefix,
36
37 #[error("Invalid `@nest` value")]
38 InvalidNestValue,
39
40 #[error("Context processing failed: {0}")]
41 ContextProcessing(json_ld_context_processing_next::Error),
42}
43
44impl Error {
45 pub fn code(&self) -> ErrorCode {
46 match self {
47 Self::IriConfusedWithPrefix => ErrorCode::IriConfusedWithPrefix,
48 Self::InvalidNestValue => ErrorCode::InvalidNestValue,
49 Self::ContextProcessing(e) => e.code(),
50 }
51 }
52}
53
54impl From<json_ld_context_processing_next::Error> for Error {
55 fn from(e: json_ld_context_processing_next::Error) -> Self {
56 Self::ContextProcessing(e)
57 }
58}
59
60impl From<IriConfusedWithPrefix> for Error {
61 fn from(_: IriConfusedWithPrefix) -> Self {
62 Self::IriConfusedWithPrefix
63 }
64}
65
66pub type CompactFragmentResult = Result<json_syntax::Value, Error>;
67
68#[derive(Clone, Copy)]
70pub struct Options {
71 pub processing_mode: ProcessingMode,
73
74 pub compact_to_relative: bool,
76
77 pub compact_arrays: bool,
80
81 pub ordered: bool,
84}
85
86impl Options {
87 pub fn unordered(self) -> Self {
88 Self {
89 ordered: false,
90 ..self
91 }
92 }
93}
94
95impl From<Options> for json_ld_context_processing_next::Options {
96 fn from(options: Options) -> json_ld_context_processing_next::Options {
97 json_ld_context_processing_next::Options {
98 processing_mode: options.processing_mode,
99 ..Default::default()
100 }
101 }
102}
103
104impl From<json_ld_expansion_next::Options> for Options {
105 fn from(options: json_ld_expansion_next::Options) -> Options {
106 Options {
107 processing_mode: options.processing_mode,
108 ordered: options.ordered,
109 ..Options::default()
110 }
111 }
112}
113
114impl Default for Options {
115 fn default() -> Options {
116 Options {
117 processing_mode: ProcessingMode::default(),
118 compact_to_relative: true,
119 compact_arrays: true,
120 ordered: false,
121 }
122 }
123}
124
125pub trait CompactFragment<I, B> {
126 #[allow(async_fn_in_trait)]
127 async fn compact_fragment_full<'a, N, L>(
128 &'a self,
129 vocabulary: &'a mut N,
130 active_context: &'a Context<I, B>,
131 type_scoped_context: &'a Context<I, B>,
132 active_property: Option<&'a str>,
133 loader: &'a L,
134 options: Options,
135 ) -> CompactFragmentResult
136 where
137 N: VocabularyMut<Iri = I, BlankId = B>,
138 I: Clone + Hash + Eq,
139 B: Clone + Hash + Eq,
140 L: Loader;
141
142 #[allow(async_fn_in_trait)]
143 #[inline(always)]
144 async fn compact_fragment_with<'a, N, L>(
145 &'a self,
146 vocabulary: &'a mut N,
147 active_context: &'a Context<I, B>,
148 loader: &'a mut L,
149 ) -> CompactFragmentResult
150 where
151 N: VocabularyMut<Iri = I, BlankId = B>,
152 I: Clone + Hash + Eq,
153 B: Clone + Hash + Eq,
154 L: Loader,
155 {
156 self.compact_fragment_full(
157 vocabulary,
158 active_context,
159 active_context,
160 None,
161 loader,
162 Options::default(),
163 )
164 .await
165 }
166
167 #[allow(async_fn_in_trait)]
168 #[inline(always)]
169 async fn compact_fragment<'a, L>(
170 &'a self,
171 active_context: &'a Context<I, B>,
172 loader: &'a mut L,
173 ) -> CompactFragmentResult
174 where
175 (): VocabularyMut<Iri = I, BlankId = B>,
176 I: Clone + Hash + Eq,
177 B: Clone + Hash + Eq,
178 L: Loader,
179 {
180 self.compact_fragment_full(
181 vocabulary::no_vocabulary_mut(),
182 active_context,
183 active_context,
184 None,
185 loader,
186 Options::default(),
187 )
188 .await
189 }
190}
191
192enum TypeLangValue<'a, I> {
193 Type(TypeSelection<I>),
194 Lang(LangSelection<'a>),
195}
196
197pub trait CompactIndexedFragment<I, B> {
199 #[allow(async_fn_in_trait)]
200 #[allow(clippy::too_many_arguments)]
201 async fn compact_indexed_fragment<'a, N, L>(
202 &'a self,
203 vocabulary: &'a mut N,
204 index: Option<&'a str>,
205 active_context: &'a Context<I, B>,
206 type_scoped_context: &'a Context<I, B>,
207 active_property: Option<&'a str>,
208 loader: &'a L,
209 options: Options,
210 ) -> CompactFragmentResult
211 where
212 N: VocabularyMut<Iri = I, BlankId = B>,
213 I: Clone + Hash + Eq,
214 B: Clone + Hash + Eq,
215 L: Loader;
216}
217
218impl<I, B, T: CompactIndexedFragment<I, B>> CompactFragment<I, B> for Indexed<T> {
219 async fn compact_fragment_full<'a, N, L>(
220 &'a self,
221 vocabulary: &'a mut N,
222 active_context: &'a Context<I, B>,
223 type_scoped_context: &'a Context<I, B>,
224 active_property: Option<&'a str>,
225 loader: &'a L,
226 options: Options,
227 ) -> CompactFragmentResult
228 where
229 N: VocabularyMut<Iri = I, BlankId = B>,
230 I: Clone + Hash + Eq,
231 B: Clone + Hash + Eq,
232 L: Loader,
233 {
234 self.inner()
235 .compact_indexed_fragment(
236 vocabulary,
237 self.index(),
238 active_context,
239 type_scoped_context,
240 active_property,
241 loader,
242 options,
243 )
244 .await
245 }
246}
247
248impl<I, B, T: Any<I, B>> CompactIndexedFragment<I, B> for T {
249 async fn compact_indexed_fragment<'a, N, L>(
250 &'a self,
251 vocabulary: &'a mut N,
252 index: Option<&'a str>,
253 active_context: &'a Context<I, B>,
254 type_scoped_context: &'a Context<I, B>,
255 active_property: Option<&'a str>,
256 loader: &'a L,
257 options: Options,
258 ) -> CompactFragmentResult
259 where
260 N: VocabularyMut<Iri = I, BlankId = B>,
261 I: Clone + Hash + Eq,
262 B: Clone + Hash + Eq,
263 L: Loader,
264 {
265 use json_ld_core_next::object::Ref;
266 match self.as_ref() {
267 Ref::Value(value) => {
268 compact_indexed_value_with(
269 vocabulary,
270 value,
271 index,
272 active_context,
273 active_property,
274 loader,
275 options,
276 )
277 .await
278 }
279 Ref::Node(node) => {
280 compact_indexed_node_with(
281 vocabulary,
282 node,
283 index,
284 active_context,
285 type_scoped_context,
286 active_property,
287 loader,
288 options,
289 )
290 .await
291 }
292 Ref::List(list) => {
293 let mut active_context = active_context;
294 if let Some(previous_context) = active_context.previous_context() {
299 active_context = previous_context
300 }
301
302 let mut active_context = Mown::Borrowed(active_context);
306 let mut list_container = false;
307 if let Some(active_property) = active_property {
308 if let Some(active_property_definition) =
309 type_scoped_context.get(active_property)
310 {
311 if let Some(local_context) = active_property_definition.context() {
312 active_context = Mown::Owned(
313 local_context
314 .process_with(
315 vocabulary,
316 active_context.as_ref(),
317 loader,
318 active_property_definition.base_url().cloned(),
319 ProcessingOptions::from(options).with_override(),
320 )
321 .await?
322 .into_processed(),
323 )
324 }
325
326 list_container = active_property_definition
327 .container()
328 .contains(ContainerKind::List);
329 }
330 }
331
332 if list_container {
333 compact_collection_with(
334 vocabulary,
335 list.iter(),
336 active_context.as_ref(),
337 active_context.as_ref(),
338 active_property,
339 loader,
340 options,
341 )
342 .await
343 } else {
344 let mut result = json_syntax::Object::default();
345 compact_property(
346 vocabulary,
347 &mut result,
348 Term::Keyword(Keyword::List),
349 list,
350 active_context.as_ref(),
351 loader,
352 false,
353 options,
354 )
355 .await?;
356
357 if let Some(index) = index {
360 let mut index_container = false;
361 if let Some(active_property) = active_property {
362 if let Some(active_property_definition) =
363 active_context.get(active_property)
364 {
365 if active_property_definition
366 .container()
367 .contains(ContainerKind::Index)
368 {
369 index_container = true;
372 }
373 }
374 }
375
376 if !index_container {
377 let alias = compact_key(
379 vocabulary,
380 active_context.as_ref(),
381 &Term::Keyword(Keyword::Index),
382 true,
383 false,
384 options,
385 )?;
386
387 result.insert(alias.unwrap(), json_syntax::Value::String(index.into()));
389 }
390 }
391
392 Ok(json_syntax::Value::Object(result))
393 }
394 }
395 }
396 }
397}
398
399fn add_value(map: &mut json_syntax::Object, key: &str, value: json_syntax::Value, as_array: bool) {
401 match map
402 .get_unique(key)
403 .ok()
404 .unwrap()
405 .map(|entry| entry.is_array())
406 {
407 Some(false) => {
408 let Entry { key, value } = map.remove_unique(key).ok().unwrap().unwrap();
409 map.insert(key, json_syntax::Value::Array(vec![value]));
410 }
411 None if as_array => {
412 map.insert(key.into(), json_syntax::Value::Array(Vec::new()));
413 }
414 _ => (),
415 }
416
417 match value {
418 json_syntax::Value::Array(values) => {
419 for value in values {
420 add_value(map, key, value, false)
421 }
422 }
423 value => {
424 if let Some(array) = map.get_unique_mut(key).ok().unwrap() {
425 array.as_array_mut().unwrap().push(value);
426 return;
427 }
428
429 map.insert(key.into(), value);
430 }
431 }
432}
433
434fn value_value<I>(value: &Value<I>) -> json_syntax::Value {
436 use json_ld_core_next::object::Literal;
437 match value {
438 Value::Literal(lit, _ty) => match lit {
439 Literal::Null => json_syntax::Value::Null,
440 Literal::Boolean(b) => json_syntax::Value::Boolean(*b),
441 Literal::Number(n) => json_syntax::Value::Number(n.clone()),
442 Literal::String(s) => json_syntax::Value::String(s.as_str().into()),
443 },
444 Value::LangString(s) => json_syntax::Value::String(s.as_str().into()),
445 Value::Json(json) => json.clone(),
446 }
447}
448
449async fn compact_collection_with<'a, N, L, O, T>(
450 vocabulary: &'a mut N,
451 items: O,
452 active_context: &'a Context<N::Iri, N::BlankId>,
453 type_scoped_context: &'a Context<N::Iri, N::BlankId>,
454 active_property: Option<&'a str>,
455 loader: &'a L,
456 options: Options,
457) -> CompactFragmentResult
458where
459 N: VocabularyMut,
460 N::Iri: Clone + Hash + Eq,
461 N::BlankId: Clone + Hash + Eq,
462 T: 'a + CompactFragment<N::Iri, N::BlankId>,
463 O: 'a + Iterator<Item = &'a T>,
464 L: Loader,
465{
466 let mut result = Vec::new();
467
468 for item in items {
469 let compacted_item = Box::pin(item.compact_fragment_full(
470 vocabulary,
471 active_context,
472 type_scoped_context,
473 active_property,
474 loader,
475 options,
476 ))
477 .await?;
478
479 if !compacted_item.is_null() {
480 result.push(compacted_item)
481 }
482 }
483
484 let mut list_or_set = false;
485 if let Some(active_property) = active_property {
486 if let Some(active_property_definition) = active_context.get(active_property) {
487 list_or_set = active_property_definition
488 .container()
489 .contains(ContainerKind::List)
490 || active_property_definition
491 .container()
492 .contains(ContainerKind::Set);
493 }
494 }
495
496 if result.is_empty()
497 || result.len() > 1
498 || !options.compact_arrays
499 || active_property == Some("@graph")
500 || active_property == Some("@set")
501 || list_or_set
502 {
503 return Ok(json_syntax::Value::Array(result.into_iter().collect()));
504 }
505
506 Ok(result.into_iter().next().unwrap())
507}
508
509impl<T: CompactFragment<I, B>, I, B> CompactFragment<I, B> for IndexSet<T> {
510 async fn compact_fragment_full<'a, N, L>(
511 &'a self,
512 vocabulary: &'a mut N,
513 active_context: &'a Context<I, B>,
514 type_scoped_context: &'a Context<I, B>,
515 active_property: Option<&'a str>,
516 loader: &'a L,
517 options: Options,
518 ) -> CompactFragmentResult
519 where
520 N: VocabularyMut<Iri = I, BlankId = B>,
521 I: Clone + Hash + Eq,
522 B: Clone + Hash + Eq,
523 L: Loader,
524 {
525 compact_collection_with(
526 vocabulary,
527 self.iter(),
528 active_context,
529 type_scoped_context,
530 active_property,
531 loader,
532 options,
533 )
534 .await
535 }
536}
537
538impl<T: CompactFragment<I, B>, I, B> CompactFragment<I, B> for Vec<T> {
539 async fn compact_fragment_full<'a, N, L>(
540 &'a self,
541 vocabulary: &'a mut N,
542 active_context: &'a Context<I, B>,
543 type_scoped_context: &'a Context<I, B>,
544 active_property: Option<&'a str>,
545 loader: &'a L,
546 options: Options,
547 ) -> CompactFragmentResult
548 where
549 N: VocabularyMut<Iri = I, BlankId = B>,
550 I: Clone + Hash + Eq,
551 B: Clone + Hash + Eq,
552 L: Loader,
553 {
554 compact_collection_with(
555 vocabulary,
556 self.iter(),
557 active_context,
558 type_scoped_context,
559 active_property,
560 loader,
561 options,
562 )
563 .await
564 }
565}
566
567impl<T: CompactFragment<I, B> + Send + Sync, I, B> CompactFragment<I, B> for [T] {
568 async fn compact_fragment_full<'a, N, L>(
569 &'a self,
570 vocabulary: &'a mut N,
571 active_context: &'a Context<I, B>,
572 type_scoped_context: &'a Context<I, B>,
573 active_property: Option<&'a str>,
574 loader: &'a L,
575 options: Options,
576 ) -> CompactFragmentResult
577 where
578 N: VocabularyMut<Iri = I, BlankId = B>,
579 I: Clone + Hash + Eq,
580 B: Clone + Hash + Eq,
581 L: Loader,
582 {
583 compact_collection_with(
584 vocabulary,
585 self.iter(),
586 active_context,
587 type_scoped_context,
588 active_property,
589 loader,
590 options,
591 )
592 .await
593 }
594}