1use jsonld_context_processing::{Options as ProcessingOptions, Process};
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};
33use jsonld_syntax::{ContainerKind, ErrorCode, Keyword, native_stack::Recursion};
34use rdfx::vocabulary::{self, VocabularyMut};
35use std::hash::Hash;
36
37mod document;
38mod iri;
39mod node;
40mod property;
41mod value;
42mod value_hint;
43
44#[cfg(test)]
45mod test_fixtures;
46
47pub use document::*;
48pub(crate) use iri::compact_iri_with_memo;
49pub use iri::{IriConfusedWithPrefix, compact_iri, compact_iri_with, compact_iri_with_hint, keyword_alias};
50use node::compact_indexed_node_with;
51pub use node::{compact_node_reference, types_as_array};
52use property::compact_property;
53pub use property::values_as_array;
54use value::compact_indexed_value_with;
55pub use value::compact_value;
56pub use value_hint::{ValueHint, ValueKind};
57
58#[derive(Debug, thiserror::Error)]
59pub enum Error<E = std::convert::Infallible> {
61 #[error("IRI confused with prefix")]
62 IriConfusedWithPrefix,
67
68 #[error("Invalid `@nest` value")]
69 InvalidNestValue,
72
73 #[error("Colliding compacted entry")]
74 CollidingEntry,
79
80 #[error("Context processing failed: {0}")]
81 ContextProcessing(jsonld_context_processing::Error<E>),
87}
88
89impl<E> Error<E> {
90 pub fn code(&self) -> ErrorCode {
92 match self {
93 Self::IriConfusedWithPrefix => ErrorCode::IriConfusedWithPrefix,
94 Self::InvalidNestValue => ErrorCode::InvalidNestValue,
95 Self::CollidingEntry => ErrorCode::ConflictingIndexes,
96 Self::ContextProcessing(e) => e.code(),
97 }
98 }
99}
100
101impl<E> From<jsonld_context_processing::Error<E>> for Error<E> {
102 fn from(e: jsonld_context_processing::Error<E>) -> Self {
103 Self::ContextProcessing(e)
104 }
105}
106
107impl<E> From<IriConfusedWithPrefix> for Error<E> {
108 fn from(_: IriConfusedWithPrefix) -> Self {
109 Self::IriConfusedWithPrefix
110 }
111}
112
113pub type CompactFragmentResult<E> = Result<jstrict::Value, Error<E>>;
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub struct Options {
119 pub processing_mode: ProcessingMode,
121
122 pub compact_to_relative: bool,
131
132 pub compact_arrays: bool,
138
139 pub ordered: bool,
144}
145
146impl From<Options> for jsonld_context_processing::Options {
147 fn from(options: Options) -> jsonld_context_processing::Options {
148 jsonld_context_processing::Options {
149 processing_mode: options.processing_mode,
150 ..Default::default()
151 }
152 }
153}
154
155impl From<jsonld_expansion::Options> for Options {
156 fn from(options: jsonld_expansion::Options) -> Options {
157 Options {
158 processing_mode: options.processing_mode,
159 ordered: options.ordered,
160 ..Options::default()
161 }
162 }
163}
164
165impl Default for Options {
166 fn default() -> Options {
167 Options {
168 processing_mode: ProcessingMode::default(),
169 compact_to_relative: true,
170 compact_arrays: true,
171 ordered: false,
172 }
173 }
174}
175
176pub trait CompactFragment<I, B> {
178 async fn compact_fragment_full<'a, N, L>(
189 &'a self,
190 vocabulary: &'a mut N,
191 active_context: &'a Context<I, B>,
192 type_scoped_context: &'a Context<I, B>,
193 active_property: Option<&'a str>,
194 loader: &'a L,
195 options: Options,
196 ) -> CompactFragmentResult<L::Error>
197 where
198 N: VocabularyMut<Iri = I, BlankId = B>,
199 I: Clone + Hash + Eq,
200 B: Clone + Hash + Eq,
201 L: Loader;
202
203 #[inline(always)]
204 async fn compact_fragment_with<'a, N, L>(
208 &'a self,
209 vocabulary: &'a mut N,
210 active_context: &'a Context<I, B>,
211 loader: &'a mut L,
212 ) -> CompactFragmentResult<L::Error>
213 where
214 N: VocabularyMut<Iri = I, BlankId = B>,
215 I: Clone + Hash + Eq,
216 B: Clone + Hash + Eq,
217 L: Loader,
218 {
219 self.compact_fragment_full(vocabulary, active_context, active_context, None, loader, Options::default())
220 .await
221 }
222
223 #[inline(always)]
224 async fn compact_fragment<'a, L>(&'a self, active_context: &'a Context<I, B>, loader: &'a mut L) -> CompactFragmentResult<L::Error>
228 where
229 (): VocabularyMut<Iri = I, BlankId = B>,
230 I: Clone + Hash + Eq,
231 B: Clone + Hash + Eq,
232 L: Loader,
233 {
234 self.compact_fragment_full(
235 vocabulary::no_vocabulary_mut(),
236 active_context,
237 active_context,
238 None,
239 loader,
240 Options::default(),
241 )
242 .await
243 }
244}
245
246#[derive(PartialEq)]
247enum TypeLangValue<'a, I> {
248 Type(TypeSelection<I>),
249 Lang(LangSelection<'a>),
250}
251
252pub trait CompactIndexedFragment<I, B> {
261 async fn compact_indexed_fragment<'a, N, L>(
268 &'a self,
269 vocabulary: &'a mut N,
270 index: Option<&'a str>,
271 active_context: &'a Context<I, B>,
272 type_scoped_context: &'a Context<I, B>,
273 active_property: Option<&'a str>,
274 loader: &'a L,
275 options: Options,
276 ) -> CompactFragmentResult<L::Error>
277 where
278 N: VocabularyMut<Iri = I, BlankId = B>,
279 I: Clone + Hash + Eq,
280 B: Clone + Hash + Eq,
281 L: Loader;
282}
283
284impl<I, B, T: CompactIndexedFragment<I, B>> CompactFragment<I, B> for Indexed<T> {
285 async fn compact_fragment_full<'a, N, L>(
286 &'a self,
287 vocabulary: &'a mut N,
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 ) -> CompactFragmentResult<L::Error>
294 where
295 N: VocabularyMut<Iri = I, BlankId = B>,
296 I: Clone + Hash + Eq,
297 B: Clone + Hash + Eq,
298 L: Loader,
299 {
300 self.inner()
301 .compact_indexed_fragment(vocabulary, self.index(), active_context, type_scoped_context, active_property, loader, options)
302 .await
303 }
304}
305
306impl<I, B, T: Any<I, B>> CompactIndexedFragment<I, B> for T {
307 async fn compact_indexed_fragment<'a, N, L>(
308 &'a self,
309 vocabulary: &'a mut N,
310 index: Option<&'a str>,
311 active_context: &'a Context<I, B>,
312 type_scoped_context: &'a Context<I, B>,
313 active_property: Option<&'a str>,
314 loader: &'a L,
315 options: Options,
316 ) -> CompactFragmentResult<L::Error>
317 where
318 N: VocabularyMut<Iri = I, BlankId = B>,
319 I: Clone + Hash + Eq,
320 B: Clone + Hash + Eq,
321 L: Loader,
322 {
323 use jsonld_core::object::Ref;
324 match self.as_ref() {
325 Ref::Value(value) => compact_indexed_value_with(vocabulary, value, index, active_context, active_property, loader, options).await,
326 Ref::Node(node) => compact_indexed_node_with(vocabulary, node, index, active_context, type_scoped_context, active_property, loader, options).await,
327 Ref::List(list) => {
328 let mut active_context = active_context;
329 if let Some(previous_context) = active_context.previous_context() {
334 active_context = previous_context;
335 }
336
337 let mut active_context = ContextRef::Borrowed(active_context);
345 let mut list_container = false;
346 if let Some(active_property) = active_property
347 && let Some(active_property_definition) = type_scoped_context.get(active_property)
348 {
349 if let Some(local_context) = active_property_definition.context() {
350 active_context = ContextRef::owned(
351 local_context
352 .process_with(
353 vocabulary,
354 active_context.as_ref(),
355 loader,
356 active_property_definition.base_url().cloned(),
357 ProcessingOptions::from(options).with_override(),
358 )
359 .await?
360 .into_processed(),
361 );
362 }
363
364 list_container = active_property_definition.container().contains(ContainerKind::List);
365 }
366
367 if list_container {
368 compact_collection_with(
369 vocabulary,
370 list.iter(),
371 active_context.as_ref(),
372 active_context.as_ref(),
373 active_property,
374 loader,
375 options,
376 )
377 .await
378 } else {
379 let mut result = jstrict::Object::default();
380 compact_property(
381 vocabulary,
382 &mut result,
383 Term::Keyword(Keyword::List),
384 list,
385 active_context.as_ref(),
386 loader,
387 false,
388 options,
389 )
390 .await?;
391
392 if let Some(index) = index {
395 let mut index_container = false;
396 if let Some(active_property) = active_property
397 && let Some(active_property_definition) = active_context.get(active_property)
398 && active_property_definition.container().contains(ContainerKind::Index)
399 {
400 index_container = true;
403 }
404
405 if !index_container {
406 let alias = crate::iri::keyword_alias(vocabulary, active_context.as_ref(), options, Keyword::Index);
408
409 result.insert(alias.into(), jstrict::Value::String(index.into()));
411 }
412 }
413
414 Ok(jstrict::Value::Object(result))
415 }
416 }
417 }
418 }
419}
420
421fn add_value(map: &mut jstrict::Object, key: &str, value: jstrict::Value, as_array: bool) {
431 match value {
432 jstrict::Value::Array(values) => {
433 match map.get_unique_mut(key).ok().flatten() {
440 Some(existing) if !existing.is_array() => {
441 let prev = std::mem::replace(existing, jstrict::Value::Array(Vec::new().into()));
442 if let jstrict::Value::Array(arr) = existing {
443 arr.push(prev);
444 }
445 }
446 None if as_array => {
447 map.insert(key.into(), jstrict::Value::Array(Vec::new().into()));
448 }
449 _ => {}
450 }
451 for v in values {
452 add_value(map, key, v, false);
453 }
454 }
455 scalar => match map.get_unique_mut(key).ok().flatten() {
456 Some(jstrict::Value::Array(arr)) => {
457 arr.push(scalar);
458 }
459 Some(existing) => {
460 let prev = std::mem::replace(existing, jstrict::Value::Array(Vec::new().into()));
462 if let jstrict::Value::Array(arr) = existing {
463 arr.push(prev);
464 arr.push(scalar);
465 }
466 }
467 None if as_array => {
468 map.insert(key.into(), jstrict::Value::Array(vec![scalar].into()));
469 }
470 None => {
471 map.insert(key.into(), scalar);
472 }
473 },
474 }
475}
476
477fn value_value<I>(value: &Value<I>) -> jstrict::Value {
480 use jsonld_core::object::Literal;
481 match value {
482 Value::Literal(lit, _ty) => match lit {
483 Literal::Null => jstrict::Value::Null,
484 Literal::Boolean(b) => jstrict::Value::Boolean(*b),
485 Literal::Number(n) => jstrict::Value::Number(n.clone()),
486 Literal::String(s) => jstrict::Value::String(s.as_str().into()),
487 },
488 Value::LangString(s) => jstrict::Value::String(s.as_str().into()),
489 Value::Json(json) => json.clone(),
490 }
491}
492
493async fn compact_collection_with<'a, N, L, O, T>(
501 vocabulary: &'a mut N,
502 items: O,
503 active_context: &'a Context<N::Iri, N::BlankId>,
504 type_scoped_context: &'a Context<N::Iri, N::BlankId>,
505 active_property: Option<&'a str>,
506 loader: &'a L,
507 options: Options,
508) -> CompactFragmentResult<L::Error>
509where
510 N: VocabularyMut,
511 N::Iri: Clone + Hash + Eq,
512 N::BlankId: Clone + Hash + Eq,
513 T: 'a + CompactFragment<N::Iri, N::BlankId>,
514 O: 'a + Iterator<Item = &'a T>,
515 L: Loader,
516{
517 let mut result = Vec::new();
518
519 for item in items {
520 let compacted_item =
521 Recursion::new(item.compact_fragment_full(vocabulary, active_context, type_scoped_context, active_property, loader, options)).await?;
522
523 if !compacted_item.is_null() {
524 result.push(compacted_item);
525 }
526 }
527
528 let mut list_or_set = false;
529 if let Some(active_property) = active_property
530 && let Some(active_property_definition) = active_context.get(active_property)
531 {
532 list_or_set =
533 active_property_definition.container().contains(ContainerKind::List) || active_property_definition.container().contains(ContainerKind::Set);
534 }
535
536 if result.len() != 1 || !options.compact_arrays || active_property == Some("@graph") || active_property == Some("@set") || list_or_set {
537 return Ok(jstrict::Value::Array(result.into_iter().collect()));
538 }
539
540 Ok(result.into_iter().next().unwrap_or(jstrict::Value::Null))
543}
544
545impl<T: CompactFragment<I, B>, I, B> CompactFragment<I, B> for IndexSet<T> {
546 async fn compact_fragment_full<'a, N, L>(
547 &'a self,
548 vocabulary: &'a mut N,
549 active_context: &'a Context<I, B>,
550 type_scoped_context: &'a Context<I, B>,
551 active_property: Option<&'a str>,
552 loader: &'a L,
553 options: Options,
554 ) -> CompactFragmentResult<L::Error>
555 where
556 N: VocabularyMut<Iri = I, BlankId = B>,
557 I: Clone + Hash + Eq,
558 B: Clone + Hash + Eq,
559 L: Loader,
560 {
561 compact_collection_with(vocabulary, self.iter(), active_context, type_scoped_context, active_property, loader, options).await
562 }
563}
564
565impl<T: CompactFragment<I, B>, I, B> CompactFragment<I, B> for Vec<T> {
566 async fn compact_fragment_full<'a, N, L>(
567 &'a self,
568 vocabulary: &'a mut N,
569 active_context: &'a Context<I, B>,
570 type_scoped_context: &'a Context<I, B>,
571 active_property: Option<&'a str>,
572 loader: &'a L,
573 options: Options,
574 ) -> CompactFragmentResult<L::Error>
575 where
576 N: VocabularyMut<Iri = I, BlankId = B>,
577 I: Clone + Hash + Eq,
578 B: Clone + Hash + Eq,
579 L: Loader,
580 {
581 compact_collection_with(vocabulary, self.iter(), active_context, type_scoped_context, active_property, loader, options).await
582 }
583}
584
585impl<T: CompactFragment<I, B>, I, B> CompactFragment<I, B> for [T] {
586 async fn compact_fragment_full<'a, N, L>(
587 &'a self,
588 vocabulary: &'a mut N,
589 active_context: &'a Context<I, B>,
590 type_scoped_context: &'a Context<I, B>,
591 active_property: Option<&'a str>,
592 loader: &'a L,
593 options: Options,
594 ) -> CompactFragmentResult<L::Error>
595 where
596 N: VocabularyMut<Iri = I, BlankId = B>,
597 I: Clone + Hash + Eq,
598 B: Clone + Hash + Eq,
599 L: Loader,
600 {
601 compact_collection_with(vocabulary, self.iter(), active_context, type_scoped_context, active_property, loader, options).await
602 }
603}