1use jsonld_context_processing::{Options as ProcessingOptions, Process, WarningHandler};
21use jsonld_core::{
22 Context,
23 ContextRef,
24 IndexSet,
25 Indexed,
26 Loader,
27 ProcessingMode,
28 Term,
29 Value,
30 context::inverse::{LangSelection, TypeSelection},
31 object::Any,
32 warning,
33};
34use jsonld_syntax::{ContainerKind, ErrorCode, Keyword, native_stack::Recursion};
35use rdfx::vocabulary::{self, VocabularyMut};
36use std::hash::Hash;
37
38mod document;
39mod iri;
40mod node;
41mod property;
42mod value;
43mod value_hint;
44
45#[cfg(test)]
46mod test_fixtures;
47
48pub use document::*;
49pub(crate) use iri::compact_iri_with_memo;
50pub use iri::{IriConfusedWithPrefix, compact_iri, compact_iri_with, compact_iri_with_hint, keyword_alias};
51use node::compact_indexed_node_with;
52pub use node::{compact_node_reference, types_as_array};
53use property::compact_property;
54pub use property::values_as_array;
55use value::compact_indexed_value_with;
56pub use value::compact_value;
57pub use value_hint::{ValueHint, ValueKind};
58
59#[derive(Debug, thiserror::Error)]
60pub enum Error<E = std::convert::Infallible> {
62 #[error("IRI confused with prefix")]
63 IriConfusedWithPrefix,
68
69 #[error("Invalid `@nest` value")]
70 InvalidNestValue,
73
74 #[error("Colliding compacted entry")]
75 CollidingEntry,
80
81 #[error("Context processing failed: {0}")]
82 ContextProcessing(jsonld_context_processing::Error<E>),
88}
89
90impl<E> Error<E> {
91 pub fn code(&self) -> ErrorCode {
93 match self {
94 Self::IriConfusedWithPrefix => ErrorCode::IriConfusedWithPrefix,
95 Self::InvalidNestValue => ErrorCode::InvalidNestValue,
96 Self::CollidingEntry => ErrorCode::ConflictingIndexes,
97 Self::ContextProcessing(e) => e.code(),
98 }
99 }
100}
101
102impl<E> From<jsonld_context_processing::Error<E>> for Error<E> {
103 fn from(e: jsonld_context_processing::Error<E>) -> Self {
104 Self::ContextProcessing(e)
105 }
106}
107
108impl<E> From<IriConfusedWithPrefix> for Error<E> {
109 fn from(_: IriConfusedWithPrefix) -> Self {
110 Self::IriConfusedWithPrefix
111 }
112}
113
114pub type CompactFragmentResult<E> = Result<jstrict::Value, Error<E>>;
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub struct Options {
120 pub processing_mode: ProcessingMode,
122
123 pub compact_to_relative: bool,
132
133 pub compact_arrays: bool,
139
140 pub ordered: bool,
145}
146
147impl From<Options> for jsonld_context_processing::Options {
148 fn from(options: Options) -> jsonld_context_processing::Options {
149 jsonld_context_processing::Options {
150 processing_mode: options.processing_mode,
151 ..Default::default()
152 }
153 }
154}
155
156impl From<jsonld_expansion::Options> for Options {
157 fn from(options: jsonld_expansion::Options) -> Options {
158 Options {
159 processing_mode: options.processing_mode,
160 ordered: options.ordered,
161 ..Options::default()
162 }
163 }
164}
165
166impl Default for Options {
167 fn default() -> Options {
168 Options {
169 processing_mode: ProcessingMode::default(),
170 compact_to_relative: true,
171 compact_arrays: true,
172 ordered: false,
173 }
174 }
175}
176
177pub trait CompactFragment<I, B> {
179 async fn compact_fragment_full<'a, N, L, W>(
191 &'a self,
192 vocabulary: &'a mut N,
193 active_context: &'a Context<I, B>,
194 type_scoped_context: &'a Context<I, B>,
195 active_property: Option<&'a str>,
196 loader: &'a L,
197 options: Options,
198 warnings: &mut W,
199 ) -> CompactFragmentResult<L::Error>
200 where
201 N: VocabularyMut<Iri = I, BlankId = B>,
202 I: Clone + Hash + Eq,
203 B: Clone + Hash + Eq,
204 L: Loader,
205 W: WarningHandler<N>;
206
207 #[inline(always)]
208 async fn compact_fragment_with<'a, N, L>(
214 &'a self,
215 vocabulary: &'a mut N,
216 active_context: &'a Context<I, B>,
217 loader: &'a mut L,
218 ) -> CompactFragmentResult<L::Error>
219 where
220 N: VocabularyMut<Iri = I, BlankId = B>,
221 I: Clone + Hash + Eq,
222 B: Clone + Hash + Eq,
223 L: Loader,
224 {
225 self.compact_fragment_full(
226 vocabulary,
227 active_context,
228 active_context,
229 None,
230 loader,
231 Options::default(),
232 &mut warning::Print,
233 )
234 .await
235 }
236
237 #[inline(always)]
238 async fn compact_fragment<'a, L>(&'a self, active_context: &'a Context<I, B>, loader: &'a mut L) -> CompactFragmentResult<L::Error>
244 where
245 (): VocabularyMut<Iri = I, BlankId = B>,
246 I: Clone + Hash + Eq,
247 B: Clone + Hash + Eq,
248 L: Loader,
249 {
250 self.compact_fragment_full(
251 vocabulary::no_vocabulary_mut(),
252 active_context,
253 active_context,
254 None,
255 loader,
256 Options::default(),
257 &mut warning::Print,
258 )
259 .await
260 }
261}
262
263#[derive(PartialEq)]
264enum TypeLangValue<'a, I> {
265 Type(TypeSelection<I>),
266 Lang(LangSelection<'a>),
267}
268
269pub trait CompactIndexedFragment<I, B> {
278 async fn compact_indexed_fragment<'a, N, L, W>(
285 &'a self,
286 vocabulary: &'a mut N,
287 index: Option<&'a str>,
288 active_context: &'a Context<I, B>,
289 type_scoped_context: &'a Context<I, B>,
290 active_property: Option<&'a str>,
291 loader: &'a L,
292 options: Options,
293 warnings: &mut W,
294 ) -> CompactFragmentResult<L::Error>
295 where
296 N: VocabularyMut<Iri = I, BlankId = B>,
297 I: Clone + Hash + Eq,
298 B: Clone + Hash + Eq,
299 L: Loader,
300 W: WarningHandler<N>;
301}
302
303impl<I, B, T: CompactIndexedFragment<I, B>> CompactFragment<I, B> for Indexed<T> {
304 async fn compact_fragment_full<'a, N, L, W>(
305 &'a self,
306 vocabulary: &'a mut N,
307 active_context: &'a Context<I, B>,
308 type_scoped_context: &'a Context<I, B>,
309 active_property: Option<&'a str>,
310 loader: &'a L,
311 options: Options,
312 warnings: &mut W,
313 ) -> CompactFragmentResult<L::Error>
314 where
315 N: VocabularyMut<Iri = I, BlankId = B>,
316 I: Clone + Hash + Eq,
317 B: Clone + Hash + Eq,
318 L: Loader,
319 W: WarningHandler<N>,
320 {
321 self.inner()
322 .compact_indexed_fragment(
323 vocabulary,
324 self.index(),
325 active_context,
326 type_scoped_context,
327 active_property,
328 loader,
329 options,
330 warnings,
331 )
332 .await
333 }
334}
335
336impl<I, B, T: Any<I, B>> CompactIndexedFragment<I, B> for T {
337 async fn compact_indexed_fragment<'a, N, L, W>(
338 &'a self,
339 vocabulary: &'a mut N,
340 index: Option<&'a str>,
341 active_context: &'a Context<I, B>,
342 type_scoped_context: &'a Context<I, B>,
343 active_property: Option<&'a str>,
344 loader: &'a L,
345 options: Options,
346 warnings: &mut W,
347 ) -> CompactFragmentResult<L::Error>
348 where
349 N: VocabularyMut<Iri = I, BlankId = B>,
350 I: Clone + Hash + Eq,
351 B: Clone + Hash + Eq,
352 L: Loader,
353 W: WarningHandler<N>,
354 {
355 use jsonld_core::object::Ref;
356 match self.as_ref() {
357 Ref::Value(value) => compact_indexed_value_with(vocabulary, value, index, active_context, active_property, loader, options, warnings).await,
358 Ref::Node(node) => {
359 compact_indexed_node_with(
360 vocabulary,
361 node,
362 index,
363 active_context,
364 type_scoped_context,
365 active_property,
366 loader,
367 options,
368 warnings,
369 )
370 .await
371 }
372 Ref::List(list) => {
373 let mut active_context = active_context;
374 if let Some(previous_context) = active_context.previous_context() {
379 active_context = previous_context;
380 }
381
382 let mut active_context = ContextRef::Borrowed(active_context);
390 let mut list_container = false;
391 if let Some(active_property) = active_property
392 && let Some(active_property_definition) = type_scoped_context.get(active_property)
393 {
394 if let Some(local_context) = active_property_definition.context() {
395 active_context = ContextRef::owned(
396 local_context
397 .process_full(
398 vocabulary,
399 active_context.as_ref(),
400 loader,
401 active_property_definition.base_url().cloned(),
402 ProcessingOptions::from(options).with_override(),
403 &mut *warnings,
404 )
405 .await?
406 .into_processed(),
407 );
408 }
409
410 list_container = active_property_definition.container().contains(ContainerKind::List);
411 }
412
413 if list_container {
414 compact_collection_with(
415 vocabulary,
416 list.iter(),
417 active_context.as_ref(),
418 active_context.as_ref(),
419 active_property,
420 loader,
421 options,
422 warnings,
423 )
424 .await
425 } else {
426 let mut result = jstrict::Object::default();
427 compact_property(
428 vocabulary,
429 &mut result,
430 Term::Keyword(Keyword::List),
431 list,
432 active_context.as_ref(),
433 loader,
434 false,
435 options,
436 warnings,
437 )
438 .await?;
439
440 if let Some(index) = index {
443 let mut index_container = false;
444 if let Some(active_property) = active_property
445 && let Some(active_property_definition) = active_context.get(active_property)
446 && active_property_definition.container().contains(ContainerKind::Index)
447 {
448 index_container = true;
451 }
452
453 if !index_container {
454 let alias = crate::iri::keyword_alias(vocabulary, active_context.as_ref(), options, Keyword::Index);
456
457 result.insert(alias.into(), jstrict::Value::String(index.into()));
459 }
460 }
461
462 Ok(jstrict::Value::Object(result))
463 }
464 }
465 }
466 }
467}
468
469fn add_value(map: &mut jstrict::Object, key: &str, value: jstrict::Value, as_array: bool) {
479 match value {
480 jstrict::Value::Array(values) => {
481 match map.get_unique_mut(key).ok().flatten() {
488 Some(existing) if !existing.is_array() => {
489 let prev = std::mem::replace(existing, jstrict::Value::Array(Vec::new().into()));
490 if let jstrict::Value::Array(arr) = existing {
491 arr.push(prev);
492 }
493 }
494 None if as_array => {
495 map.insert(key.into(), jstrict::Value::Array(Vec::new().into()));
496 }
497 _ => {}
498 }
499 for v in values {
500 add_value(map, key, v, false);
501 }
502 }
503 scalar => match map.get_unique_mut(key).ok().flatten() {
504 Some(jstrict::Value::Array(arr)) => {
505 arr.push(scalar);
506 }
507 Some(existing) => {
508 let prev = std::mem::replace(existing, jstrict::Value::Array(Vec::new().into()));
510 if let jstrict::Value::Array(arr) = existing {
511 arr.push(prev);
512 arr.push(scalar);
513 }
514 }
515 None if as_array => {
516 map.insert(key.into(), jstrict::Value::Array(vec![scalar].into()));
517 }
518 None => {
519 map.insert(key.into(), scalar);
520 }
521 },
522 }
523}
524
525fn value_value<I>(value: &Value<I>) -> jstrict::Value {
528 use jsonld_core::object::Literal;
529 match value {
530 Value::Literal(lit, _ty) => match lit {
531 Literal::Null => jstrict::Value::Null,
532 Literal::Boolean(b) => jstrict::Value::Boolean(*b),
533 Literal::Number(n) => jstrict::Value::Number(n.clone()),
534 Literal::String(s) => jstrict::Value::String(s.as_str().into()),
535 },
536 Value::LangString(s) => jstrict::Value::String(s.as_str().into()),
537 Value::Json(json) => json.clone(),
538 }
539}
540
541async fn compact_collection_with<'a, N, L, O, T, W>(
549 vocabulary: &'a mut N,
550 items: O,
551 active_context: &'a Context<N::Iri, N::BlankId>,
552 type_scoped_context: &'a Context<N::Iri, N::BlankId>,
553 active_property: Option<&'a str>,
554 loader: &'a L,
555 options: Options,
556 warnings: &mut W,
557) -> CompactFragmentResult<L::Error>
558where
559 N: VocabularyMut,
560 N::Iri: Clone + Hash + Eq,
561 N::BlankId: Clone + Hash + Eq,
562 T: 'a + CompactFragment<N::Iri, N::BlankId>,
563 O: 'a + Iterator<Item = &'a T>,
564 L: Loader,
565 W: WarningHandler<N>,
566{
567 let mut result = Vec::new();
568
569 for item in items {
570 let compacted_item =
571 Recursion::new(item.compact_fragment_full(vocabulary, active_context, type_scoped_context, active_property, loader, options, warnings)).await?;
572
573 if !compacted_item.is_null() {
574 result.push(compacted_item);
575 }
576 }
577
578 let mut list_or_set = false;
579 if let Some(active_property) = active_property
580 && let Some(active_property_definition) = active_context.get(active_property)
581 {
582 list_or_set =
583 active_property_definition.container().contains(ContainerKind::List) || active_property_definition.container().contains(ContainerKind::Set);
584 }
585
586 if result.len() != 1 || !options.compact_arrays || active_property == Some("@graph") || active_property == Some("@set") || list_or_set {
587 return Ok(jstrict::Value::Array(result.into_iter().collect()));
588 }
589
590 Ok(result.into_iter().next().unwrap_or(jstrict::Value::Null))
593}
594
595impl<T: CompactFragment<I, B>, I, B> CompactFragment<I, B> for IndexSet<T> {
596 async fn compact_fragment_full<'a, N, L, W>(
597 &'a self,
598 vocabulary: &'a mut N,
599 active_context: &'a Context<I, B>,
600 type_scoped_context: &'a Context<I, B>,
601 active_property: Option<&'a str>,
602 loader: &'a L,
603 options: Options,
604 warnings: &mut W,
605 ) -> CompactFragmentResult<L::Error>
606 where
607 N: VocabularyMut<Iri = I, BlankId = B>,
608 I: Clone + Hash + Eq,
609 B: Clone + Hash + Eq,
610 L: Loader,
611 W: WarningHandler<N>,
612 {
613 compact_collection_with(
614 vocabulary,
615 self.iter(),
616 active_context,
617 type_scoped_context,
618 active_property,
619 loader,
620 options,
621 warnings,
622 )
623 .await
624 }
625}
626
627impl<T: CompactFragment<I, B>, I, B> CompactFragment<I, B> for Vec<T> {
628 async fn compact_fragment_full<'a, N, L, W>(
629 &'a self,
630 vocabulary: &'a mut N,
631 active_context: &'a Context<I, B>,
632 type_scoped_context: &'a Context<I, B>,
633 active_property: Option<&'a str>,
634 loader: &'a L,
635 options: Options,
636 warnings: &mut W,
637 ) -> CompactFragmentResult<L::Error>
638 where
639 N: VocabularyMut<Iri = I, BlankId = B>,
640 I: Clone + Hash + Eq,
641 B: Clone + Hash + Eq,
642 L: Loader,
643 W: WarningHandler<N>,
644 {
645 compact_collection_with(
646 vocabulary,
647 self.iter(),
648 active_context,
649 type_scoped_context,
650 active_property,
651 loader,
652 options,
653 warnings,
654 )
655 .await
656 }
657}
658
659impl<T: CompactFragment<I, B>, I, B> CompactFragment<I, B> for [T] {
660 async fn compact_fragment_full<'a, N, L, W>(
661 &'a self,
662 vocabulary: &'a mut N,
663 active_context: &'a Context<I, B>,
664 type_scoped_context: &'a Context<I, B>,
665 active_property: Option<&'a str>,
666 loader: &'a L,
667 options: Options,
668 warnings: &mut W,
669 ) -> CompactFragmentResult<L::Error>
670 where
671 N: VocabularyMut<Iri = I, BlankId = B>,
672 I: Clone + Hash + Eq,
673 B: Clone + Hash + Eq,
674 L: Loader,
675 W: WarningHandler<N>,
676 {
677 compact_collection_with(
678 vocabulary,
679 self.iter(),
680 active_context,
681 type_scoped_context,
682 active_property,
683 loader,
684 options,
685 warnings,
686 )
687 .await
688 }
689}