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};
34use rdfx::vocabulary::{self, VocabularyMut};
35use std::hash::Hash;
36
37mod document;
38mod iri;
39mod node;
40mod property;
41mod value;
42
43pub use document::*;
44pub use iri::IriConfusedWithPrefix;
45pub(crate) use iri::{compact_iri, compact_iri_with, compact_iri_with_memo};
46use node::compact_indexed_node_with;
47use property::compact_property;
48use value::compact_indexed_value_with;
49
50#[derive(Debug, thiserror::Error)]
51pub enum Error<E = std::convert::Infallible> {
53 #[error("IRI confused with prefix")]
54 IriConfusedWithPrefix,
59
60 #[error("Invalid `@nest` value")]
61 InvalidNestValue,
64
65 #[error("Colliding compacted entry")]
66 CollidingEntry,
71
72 #[error("Context processing failed: {0}")]
73 ContextProcessing(jsonld_context_processing::Error<E>),
79}
80
81impl<E> Error<E> {
82 pub fn code(&self) -> ErrorCode {
84 match self {
85 Self::IriConfusedWithPrefix => ErrorCode::IriConfusedWithPrefix,
86 Self::InvalidNestValue => ErrorCode::InvalidNestValue,
87 Self::CollidingEntry => ErrorCode::ConflictingIndexes,
88 Self::ContextProcessing(e) => e.code(),
89 }
90 }
91}
92
93impl<E> From<jsonld_context_processing::Error<E>> for Error<E> {
94 fn from(e: jsonld_context_processing::Error<E>) -> Self {
95 Self::ContextProcessing(e)
96 }
97}
98
99impl<E> From<IriConfusedWithPrefix> for Error<E> {
100 fn from(_: IriConfusedWithPrefix) -> Self {
101 Self::IriConfusedWithPrefix
102 }
103}
104
105pub type CompactFragmentResult<E> = Result<jstrict::Value, Error<E>>;
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub struct Options {
111 pub processing_mode: ProcessingMode,
113
114 pub compact_to_relative: bool,
123
124 pub compact_arrays: bool,
130
131 pub ordered: bool,
136}
137
138impl From<Options> for jsonld_context_processing::Options {
139 fn from(options: Options) -> jsonld_context_processing::Options {
140 jsonld_context_processing::Options {
141 processing_mode: options.processing_mode,
142 ..Default::default()
143 }
144 }
145}
146
147impl From<jsonld_expansion::Options> for Options {
148 fn from(options: jsonld_expansion::Options) -> Options {
149 Options {
150 processing_mode: options.processing_mode,
151 ordered: options.ordered,
152 ..Options::default()
153 }
154 }
155}
156
157impl Default for Options {
158 fn default() -> Options {
159 Options {
160 processing_mode: ProcessingMode::default(),
161 compact_to_relative: true,
162 compact_arrays: true,
163 ordered: false,
164 }
165 }
166}
167
168pub trait CompactFragment<I, B> {
170 async fn compact_fragment_full<'a, N, L>(
181 &'a self,
182 vocabulary: &'a mut N,
183 active_context: &'a Context<I, B>,
184 type_scoped_context: &'a Context<I, B>,
185 active_property: Option<&'a str>,
186 loader: &'a L,
187 options: Options,
188 ) -> CompactFragmentResult<L::Error>
189 where
190 N: VocabularyMut<Iri = I, BlankId = B>,
191 I: Clone + Hash + Eq,
192 B: Clone + Hash + Eq,
193 L: Loader;
194
195 #[inline(always)]
196 async fn compact_fragment_with<'a, N, L>(
200 &'a self,
201 vocabulary: &'a mut N,
202 active_context: &'a Context<I, B>,
203 loader: &'a mut L,
204 ) -> CompactFragmentResult<L::Error>
205 where
206 N: VocabularyMut<Iri = I, BlankId = B>,
207 I: Clone + Hash + Eq,
208 B: Clone + Hash + Eq,
209 L: Loader,
210 {
211 self.compact_fragment_full(vocabulary, active_context, active_context, None, loader, Options::default())
212 .await
213 }
214
215 #[inline(always)]
216 async fn compact_fragment<'a, L>(&'a self, active_context: &'a Context<I, B>, loader: &'a mut L) -> CompactFragmentResult<L::Error>
220 where
221 (): VocabularyMut<Iri = I, BlankId = B>,
222 I: Clone + Hash + Eq,
223 B: Clone + Hash + Eq,
224 L: Loader,
225 {
226 self.compact_fragment_full(
227 vocabulary::no_vocabulary_mut(),
228 active_context,
229 active_context,
230 None,
231 loader,
232 Options::default(),
233 )
234 .await
235 }
236}
237
238#[derive(PartialEq)]
239enum TypeLangValue<'a, I> {
240 Type(TypeSelection<I>),
241 Lang(LangSelection<'a>),
242}
243
244pub trait CompactIndexedFragment<I, B> {
253 async fn compact_indexed_fragment<'a, N, L>(
260 &'a self,
261 vocabulary: &'a mut N,
262 index: Option<&'a str>,
263 active_context: &'a Context<I, B>,
264 type_scoped_context: &'a Context<I, B>,
265 active_property: Option<&'a str>,
266 loader: &'a L,
267 options: Options,
268 ) -> CompactFragmentResult<L::Error>
269 where
270 N: VocabularyMut<Iri = I, BlankId = B>,
271 I: Clone + Hash + Eq,
272 B: Clone + Hash + Eq,
273 L: Loader;
274}
275
276impl<I, B, T: CompactIndexedFragment<I, B>> CompactFragment<I, B> for Indexed<T> {
277 async fn compact_fragment_full<'a, N, L>(
278 &'a self,
279 vocabulary: &'a mut N,
280 active_context: &'a Context<I, B>,
281 type_scoped_context: &'a Context<I, B>,
282 active_property: Option<&'a str>,
283 loader: &'a L,
284 options: Options,
285 ) -> CompactFragmentResult<L::Error>
286 where
287 N: VocabularyMut<Iri = I, BlankId = B>,
288 I: Clone + Hash + Eq,
289 B: Clone + Hash + Eq,
290 L: Loader,
291 {
292 self.inner()
293 .compact_indexed_fragment(vocabulary, self.index(), active_context, type_scoped_context, active_property, loader, options)
294 .await
295 }
296}
297
298impl<I, B, T: Any<I, B>> CompactIndexedFragment<I, B> for T {
299 async fn compact_indexed_fragment<'a, N, L>(
300 &'a self,
301 vocabulary: &'a mut N,
302 index: Option<&'a str>,
303 active_context: &'a Context<I, B>,
304 type_scoped_context: &'a Context<I, B>,
305 active_property: Option<&'a str>,
306 loader: &'a L,
307 options: Options,
308 ) -> CompactFragmentResult<L::Error>
309 where
310 N: VocabularyMut<Iri = I, BlankId = B>,
311 I: Clone + Hash + Eq,
312 B: Clone + Hash + Eq,
313 L: Loader,
314 {
315 use jsonld_core::object::Ref;
316 match self.as_ref() {
317 Ref::Value(value) => compact_indexed_value_with(vocabulary, value, index, active_context, active_property, loader, options).await,
318 Ref::Node(node) => compact_indexed_node_with(vocabulary, node, index, active_context, type_scoped_context, active_property, loader, options).await,
319 Ref::List(list) => {
320 let mut active_context = active_context;
321 if let Some(previous_context) = active_context.previous_context() {
326 active_context = previous_context;
327 }
328
329 let mut active_context = ContextRef::Borrowed(active_context);
337 let mut list_container = false;
338 if let Some(active_property) = active_property
339 && let Some(active_property_definition) = type_scoped_context.get(active_property)
340 {
341 if let Some(local_context) = active_property_definition.context() {
342 active_context = ContextRef::owned(
343 local_context
344 .process_with(
345 vocabulary,
346 active_context.as_ref(),
347 loader,
348 active_property_definition.base_url().cloned(),
349 ProcessingOptions::from(options).with_override(),
350 )
351 .await?
352 .into_processed(),
353 );
354 }
355
356 list_container = active_property_definition.container().contains(ContainerKind::List);
357 }
358
359 if list_container {
360 compact_collection_with(
361 vocabulary,
362 list.iter(),
363 active_context.as_ref(),
364 active_context.as_ref(),
365 active_property,
366 loader,
367 options,
368 )
369 .await
370 } else {
371 let mut result = jstrict::Object::default();
372 compact_property(
373 vocabulary,
374 &mut result,
375 Term::Keyword(Keyword::List),
376 list,
377 active_context.as_ref(),
378 loader,
379 false,
380 options,
381 )
382 .await?;
383
384 if let Some(index) = index {
387 let mut index_container = false;
388 if let Some(active_property) = active_property
389 && let Some(active_property_definition) = active_context.get(active_property)
390 && active_property_definition.container().contains(ContainerKind::Index)
391 {
392 index_container = true;
395 }
396
397 if !index_container {
398 let alias = crate::iri::keyword_alias(vocabulary, active_context.as_ref(), options, Keyword::Index);
400
401 result.insert(alias.into(), jstrict::Value::String(index.into()));
403 }
404 }
405
406 Ok(jstrict::Value::Object(result))
407 }
408 }
409 }
410 }
411}
412
413fn add_value(map: &mut jstrict::Object, key: &str, value: jstrict::Value, as_array: bool) {
423 match value {
424 jstrict::Value::Array(values) => {
425 match map.get_unique_mut(key).ok().flatten() {
432 Some(existing) if !existing.is_array() => {
433 let prev = std::mem::replace(existing, jstrict::Value::Array(Vec::new()));
434 if let jstrict::Value::Array(arr) = existing {
435 arr.push(prev);
436 }
437 }
438 None if as_array => {
439 map.insert(key.into(), jstrict::Value::Array(Vec::new()));
440 }
441 _ => {}
442 }
443 for v in values {
444 add_value(map, key, v, false);
445 }
446 }
447 scalar => match map.get_unique_mut(key).ok().flatten() {
448 Some(jstrict::Value::Array(arr)) => {
449 arr.push(scalar);
450 }
451 Some(existing) => {
452 let prev = std::mem::replace(existing, jstrict::Value::Array(Vec::new()));
454 if let jstrict::Value::Array(arr) = existing {
455 arr.push(prev);
456 arr.push(scalar);
457 }
458 }
459 None if as_array => {
460 map.insert(key.into(), jstrict::Value::Array(vec![scalar]));
461 }
462 None => {
463 map.insert(key.into(), scalar);
464 }
465 },
466 }
467}
468
469fn value_value<I>(value: &Value<I>) -> jstrict::Value {
472 use jsonld_core::object::Literal;
473 match value {
474 Value::Literal(lit, _ty) => match lit {
475 Literal::Null => jstrict::Value::Null,
476 Literal::Boolean(b) => jstrict::Value::Boolean(*b),
477 Literal::Number(n) => jstrict::Value::Number(n.clone()),
478 Literal::String(s) => jstrict::Value::String(s.as_str().into()),
479 },
480 Value::LangString(s) => jstrict::Value::String(s.as_str().into()),
481 Value::Json(json) => json.clone(),
482 }
483}
484
485async fn compact_collection_with<'a, N, L, O, T>(
493 vocabulary: &'a mut N,
494 items: O,
495 active_context: &'a Context<N::Iri, N::BlankId>,
496 type_scoped_context: &'a Context<N::Iri, N::BlankId>,
497 active_property: Option<&'a str>,
498 loader: &'a L,
499 options: Options,
500) -> CompactFragmentResult<L::Error>
501where
502 N: VocabularyMut,
503 N::Iri: Clone + Hash + Eq,
504 N::BlankId: Clone + Hash + Eq,
505 T: 'a + CompactFragment<N::Iri, N::BlankId>,
506 O: 'a + Iterator<Item = &'a T>,
507 L: Loader,
508{
509 let mut result = Vec::new();
510
511 for item in items {
512 let compacted_item = Box::pin(item.compact_fragment_full(vocabulary, active_context, type_scoped_context, active_property, loader, options)).await?;
513
514 if !compacted_item.is_null() {
515 result.push(compacted_item);
516 }
517 }
518
519 let mut list_or_set = false;
520 if let Some(active_property) = active_property
521 && let Some(active_property_definition) = active_context.get(active_property)
522 {
523 list_or_set =
524 active_property_definition.container().contains(ContainerKind::List) || active_property_definition.container().contains(ContainerKind::Set);
525 }
526
527 if result.len() != 1 || !options.compact_arrays || active_property == Some("@graph") || active_property == Some("@set") || list_or_set {
528 return Ok(jstrict::Value::Array(result.into_iter().collect()));
529 }
530
531 Ok(result.into_iter().next().unwrap_or(jstrict::Value::Null))
534}
535
536impl<T: CompactFragment<I, B>, I, B> CompactFragment<I, B> for IndexSet<T> {
537 async fn compact_fragment_full<'a, N, L>(
538 &'a self,
539 vocabulary: &'a mut N,
540 active_context: &'a Context<I, B>,
541 type_scoped_context: &'a Context<I, B>,
542 active_property: Option<&'a str>,
543 loader: &'a L,
544 options: Options,
545 ) -> CompactFragmentResult<L::Error>
546 where
547 N: VocabularyMut<Iri = I, BlankId = B>,
548 I: Clone + Hash + Eq,
549 B: Clone + Hash + Eq,
550 L: Loader,
551 {
552 compact_collection_with(vocabulary, self.iter(), active_context, type_scoped_context, active_property, loader, options).await
553 }
554}
555
556impl<T: CompactFragment<I, B>, I, B> CompactFragment<I, B> for Vec<T> {
557 async fn compact_fragment_full<'a, N, L>(
558 &'a self,
559 vocabulary: &'a mut N,
560 active_context: &'a Context<I, B>,
561 type_scoped_context: &'a Context<I, B>,
562 active_property: Option<&'a str>,
563 loader: &'a L,
564 options: Options,
565 ) -> CompactFragmentResult<L::Error>
566 where
567 N: VocabularyMut<Iri = I, BlankId = B>,
568 I: Clone + Hash + Eq,
569 B: Clone + Hash + Eq,
570 L: Loader,
571 {
572 compact_collection_with(vocabulary, self.iter(), active_context, type_scoped_context, active_property, loader, options).await
573 }
574}
575
576impl<T: CompactFragment<I, B>, I, B> CompactFragment<I, B> for [T] {
577 async fn compact_fragment_full<'a, N, L>(
578 &'a self,
579 vocabulary: &'a mut N,
580 active_context: &'a Context<I, B>,
581 type_scoped_context: &'a Context<I, B>,
582 active_property: Option<&'a str>,
583 loader: &'a L,
584 options: Options,
585 ) -> CompactFragmentResult<L::Error>
586 where
587 N: VocabularyMut<Iri = I, BlankId = B>,
588 I: Clone + Hash + Eq,
589 B: Clone + Hash + Eq,
590 L: Loader,
591 {
592 compact_collection_with(vocabulary, self.iter(), active_context, type_scoped_context, active_property, loader, options).await
593 }
594}