1use crate::{
2 HashMap,
3 Id,
4 IndexSet,
5 Indexed,
6 IndexedObject,
7 Node,
8 Object,
9 Relabel,
10 TryFromJson,
11 object::{FragmentRef, InvalidExpandedJson, Traverse},
12};
13use iri_rs::IriBuf;
14use rdfx::{
15 BlankIdBuf,
16 LocalGenerator,
17 vocabulary::{Vocabulary, VocabularyMut},
18};
19use smallvec::SmallVec;
20use std::{
21 collections::HashSet,
22 hash::{BuildHasher, Hash, Hasher},
23};
24
25struct Discriminant<'a, T, B>(&'a IndexedObject<T, B>);
37
38impl<T: Hash, B: Hash> Hash for Discriminant<'_, T, B> {
39 #[inline]
40 fn hash<H: Hasher>(&self, state: &mut H) {
41 self.0.index().hash(state);
42
43 match self.0.inner() {
44 Object::Value(_) => 0u8.hash(state),
45 Object::List(_) => 1u8.hash(state),
46 Object::Node(node) => {
47 2u8.hash(state);
48 node.id.hash(state);
49 }
50 }
51 }
52}
53
54#[derive(Debug, Clone)]
60pub struct ExpandedDocument<T = IriBuf, B = BlankIdBuf> {
61 objects: Vec<IndexedObject<T, B>>,
62
63 buckets: HashMap<u64, SmallVec<[Entry; 1]>>,
66}
67
68#[derive(Debug, Clone)]
70struct Entry {
71 index: usize,
73
74 full_hash: Option<u64>,
85}
86
87impl<T, B> Default for ExpandedDocument<T, B> {
88 #[inline(always)]
89 fn default() -> Self {
90 Self {
91 objects: Vec::new(),
92 buckets: HashMap::default(),
93 }
94 }
95}
96
97impl<T, B> ExpandedDocument<T, B> {
98 #[inline(always)]
99 #[must_use]
101 pub fn new() -> Self {
102 Self::default()
103 }
104
105 #[inline(always)]
106 #[must_use]
112 pub fn with_capacity(capacity: usize) -> Self {
113 Self {
114 objects: Vec::with_capacity(capacity),
115 buckets: HashMap::with_capacity_and_hasher(capacity, crate::hash::DefaultBuildHasher::default()),
116 }
117 }
118
119 #[inline(always)]
120 #[must_use]
122 pub fn len(&self) -> usize {
123 self.objects.len()
124 }
125
126 #[inline(always)]
127 #[must_use]
129 pub fn is_empty(&self) -> bool {
130 self.objects.is_empty()
131 }
132
133 #[inline(always)]
134 #[must_use]
136 pub fn objects(&self) -> &[IndexedObject<T, B>] {
137 &self.objects
138 }
139
140 #[inline(always)]
141 #[must_use]
144 pub fn into_objects(self) -> Vec<IndexedObject<T, B>> {
145 self.objects
146 }
147
148 #[inline(always)]
149 pub fn iter(&self) -> std::slice::Iter<'_, IndexedObject<T, B>> {
152 self.objects.iter()
153 }
154
155 #[inline(always)]
156 #[must_use]
159 pub fn traverse(&self) -> Traverse<'_, T, B> {
160 Traverse::new(self.iter().map(|o| FragmentRef::IndexedObject(o)))
161 }
162
163 #[inline(always)]
164 pub fn count(&self, f: impl FnMut(&FragmentRef<T, B>) -> bool) -> usize {
166 self.traverse().filter(f).count()
167 }
168
169 #[inline(always)]
176 pub fn identify_all_with<V: VocabularyMut<Iri = T, BlankId = B>, G: LocalGenerator>(
177 &mut self,
178 vocabulary: &mut V,
179 generator: &mut G,
180 ) -> Result<(), crate::id::GeneratedIdError>
181 where
182 T: Eq + Hash,
183 B: Eq + Hash,
184 {
185 self.try_rewrite(|object| object.identify_all_with(vocabulary, generator))
186 }
187
188 #[inline(always)]
195 pub fn identify_all<G: LocalGenerator>(&mut self, generator: &mut G) -> Result<(), crate::id::GeneratedIdError>
196 where
197 T: Eq + Hash,
198 B: Eq + Hash,
199 (): Vocabulary<Iri = T, BlankId = B>,
200 {
201 self.identify_all_with(rdfx::vocabulary::no_vocabulary_mut(), generator)
202 }
203
204 #[inline(always)]
211 pub fn relabel_and_canonicalize_with<V: VocabularyMut<Iri = T, BlankId = B>, G: LocalGenerator>(
212 &mut self,
213 vocabulary: &mut V,
214 generator: &mut G,
215 ) -> Result<(), crate::id::GeneratedIdError>
216 where
217 T: Clone + Eq + Hash,
218 B: Clone + Eq + Hash,
219 {
220 let mut relabeling = HashMap::default();
221 let mut buffer = ryu_js::Buffer::new();
222 self.try_rewrite(|object| {
223 object.relabel_with(vocabulary, generator, &mut relabeling)?;
224 object.canonicalize_with(&mut buffer);
225 Ok(())
226 })
227 }
228
229 #[inline(always)]
236 pub fn relabel_and_canonicalize<G: LocalGenerator>(&mut self, generator: &mut G) -> Result<(), crate::id::GeneratedIdError>
237 where
238 T: Clone + Eq + Hash,
239 B: Clone + Eq + Hash,
240 (): Vocabulary<Iri = T, BlankId = B>,
241 {
242 self.relabel_and_canonicalize_with(rdfx::vocabulary::no_vocabulary_mut(), generator)
243 }
244
245 #[inline(always)]
252 pub fn relabel_with<V: VocabularyMut<Iri = T, BlankId = B>, G: LocalGenerator>(
253 &mut self,
254 vocabulary: &mut V,
255 generator: &mut G,
256 ) -> Result<(), crate::id::GeneratedIdError>
257 where
258 T: Clone + Eq + Hash,
259 B: Clone + Eq + Hash,
260 {
261 let mut relabeling = HashMap::default();
262 self.try_rewrite(|object| object.relabel_with(vocabulary, generator, &mut relabeling))
263 }
264
265 #[inline(always)]
272 pub fn relabel<G: LocalGenerator>(&mut self, generator: &mut G) -> Result<(), crate::id::GeneratedIdError>
273 where
274 T: Clone + Eq + Hash,
275 B: Clone + Eq + Hash,
276 (): Vocabulary<Iri = T, BlankId = B>,
277 {
278 self.relabel_with(rdfx::vocabulary::no_vocabulary_mut(), generator)
279 }
280
281 pub fn canonicalize_with(&mut self, buffer: &mut ryu_js::Buffer)
284 where
285 T: Eq + Hash,
286 B: Eq + Hash,
287 {
288 for mut object in self.drain() {
289 object.canonicalize_with(buffer);
290 self.insert(object);
291 }
292 }
293
294 pub fn canonicalize(&mut self)
296 where
297 T: Eq + Hash,
298 B: Eq + Hash,
299 {
300 let mut buffer = ryu_js::Buffer::new();
301 self.canonicalize_with(&mut buffer);
302 }
303
304 pub fn map_ids<U, C>(self, mut map_iri: impl FnMut(T) -> U, mut map_id: impl FnMut(Id<T, B>) -> Id<U, C>) -> ExpandedDocument<U, C>
307 where
308 U: Eq + Hash,
309 C: Eq + Hash,
310 {
311 self.objects
312 .into_iter()
313 .map(|i| i.map_inner(|o| o.map_ids(&mut map_iri, &mut map_id)))
314 .collect()
315 }
316
317 #[must_use]
319 pub fn blank_ids(&self) -> HashSet<&B>
320 where
321 B: Eq + Hash,
322 {
323 self.traverse().filter_map(|f| f.into_id().and_then(Id::into_blank)).collect()
324 }
325
326 #[must_use]
331 pub fn main_node(&self) -> Option<&Node<T, B>> {
332 let mut result = None;
333
334 for object in self {
335 if let Object::Node(node) = object.inner() {
336 if result.is_some() {
337 return None;
338 }
339
340 result = Some(&**node);
341 }
342 }
343
344 result
345 }
346
347 #[must_use]
352 pub fn into_main_node(self) -> Option<Node<T, B>> {
353 let mut result = None;
354
355 for object in self {
356 if let Object::Node(node) = object.into_inner() {
357 if result.is_some() {
358 return None;
359 }
360
361 result = Some(*node);
362 }
363 }
364
365 result
366 }
367}
368
369impl<T: Hash + Eq, B: Hash + Eq> ExpandedDocument<T, B> {
370 pub fn insert(&mut self, object: IndexedObject<T, B>) -> bool {
374 let hasher = self.buckets.hasher().clone();
378 let discriminant = hasher.hash_one(Discriminant(&object));
379
380 let Self { objects, buckets } = self;
383 let bucket = buckets.entry(discriminant).or_default();
384
385 let full_hash = if bucket.is_empty() {
386 None
387 } else {
388 let full_hash = hasher.hash_one(&object);
389
390 for entry in bucket.iter_mut() {
391 let candidate = entry.full_hash.get_or_insert_with(|| hasher.hash_one(&objects[entry.index]));
392
393 if *candidate == full_hash && objects[entry.index] == object {
394 return false;
395 }
396 }
397
398 Some(full_hash)
399 };
400
401 bucket.push(Entry {
402 index: objects.len(),
403 full_hash,
404 });
405 objects.push(object);
406 true
407 }
408
409 pub fn contains(&self, object: &IndexedObject<T, B>) -> bool {
411 let discriminant = self.buckets.hasher().hash_one(Discriminant(object));
412
413 match self.buckets.get(&discriminant) {
414 Some(bucket) => bucket.iter().any(|entry| self.objects[entry.index] == *object),
415 None => false,
416 }
417 }
418
419 fn drain(&mut self) -> std::vec::IntoIter<IndexedObject<T, B>> {
425 self.buckets.clear();
426 std::mem::take(&mut self.objects).into_iter()
427 }
428
429 fn try_rewrite<E>(&mut self, mut f: impl FnMut(&mut IndexedObject<T, B>) -> Result<(), E>) -> Result<(), E> {
437 let mut objects = self.drain();
438 let mut result = Ok(());
439 for mut object in objects.by_ref() {
440 let object_result = f(&mut object);
441 self.insert(object);
442 if let Err(e) = object_result {
443 result = Err(e);
444 break;
445 }
446 }
447 for object in objects {
448 self.insert(object);
449 }
450 result
451 }
452}
453
454impl<T: Eq + Hash, B: Eq + Hash> From<Indexed<Node<T, B>>> for ExpandedDocument<T, B> {
455 fn from(value: Indexed<Node<T, B>>) -> Self {
456 let mut result = Self::default();
457
458 result.insert(value.map_inner(Object::node));
459
460 result
461 }
462}
463
464impl<T: Eq + Hash, B: Eq + Hash> TryFromJson<T, B> for ExpandedDocument<T, B> {
465 fn try_from_json_in(vocabulary: &mut impl VocabularyMut<Iri = T, BlankId = B>, value: jstrict::Value) -> Result<Self, InvalidExpandedJson> {
466 match value {
467 jstrict::Value::Array(items) => {
468 let mut result = Self::new();
469
470 for item in items {
471 result.insert(Indexed::try_from_json_in(vocabulary, item)?);
472 }
473
474 Ok(result)
475 }
476 other => Err(InvalidExpandedJson::Unexpected(other.kind(), jstrict::Kind::Array)),
477 }
478 }
479}
480
481impl<T: Eq + Hash, B: Eq + Hash> PartialEq for ExpandedDocument<T, B> {
482 fn eq(&self, other: &Self) -> bool {
487 self.len() == other.len() && self.objects.iter().all(|object| other.contains(object))
488 }
489}
490
491impl<T: Eq + Hash, B: Eq + Hash> Eq for ExpandedDocument<T, B> {}
492
493impl<T, B> IntoIterator for ExpandedDocument<T, B> {
494 type IntoIter = IntoIter<T, B>;
495 type Item = IndexedObject<T, B>;
496
497 #[inline(always)]
498 fn into_iter(self) -> Self::IntoIter {
499 IntoIter(self.objects.into_iter())
500 }
501}
502
503impl<'a, T, B> IntoIterator for &'a ExpandedDocument<T, B> {
504 type IntoIter = std::slice::Iter<'a, IndexedObject<T, B>>;
505 type Item = &'a IndexedObject<T, B>;
506
507 #[inline(always)]
508 fn into_iter(self) -> Self::IntoIter {
509 self.iter()
510 }
511}
512pub struct IntoIter<T, B>(std::vec::IntoIter<IndexedObject<T, B>>);
514
515impl<T, B> Iterator for IntoIter<T, B> {
516 type Item = IndexedObject<T, B>;
517
518 fn next(&mut self) -> Option<Self::Item> {
519 self.0.next()
520 }
521}
522
523impl<T: Hash + Eq, B: Hash + Eq> FromIterator<IndexedObject<T, B>> for ExpandedDocument<T, B> {
524 fn from_iter<I: IntoIterator<Item = IndexedObject<T, B>>>(iter: I) -> Self {
525 let iter = iter.into_iter();
526 let mut result = Self::with_capacity(iter.size_hint().0);
527 result.extend(iter);
528 result
529 }
530}
531
532impl<T: Hash + Eq, B: Hash + Eq> Extend<IndexedObject<T, B>> for ExpandedDocument<T, B> {
533 fn extend<I: IntoIterator<Item = IndexedObject<T, B>>>(&mut self, iter: I) {
534 for object in iter {
535 self.insert(object);
536 }
537 }
538}
539
540impl<T: Hash + Eq, B: Hash + Eq> From<IndexSet<IndexedObject<T, B>>> for ExpandedDocument<T, B> {
541 fn from(set: IndexSet<IndexedObject<T, B>>) -> Self {
542 set.into_iter().collect()
543 }
544}
545
546impl<T: Hash + Eq, B: Hash + Eq> From<Vec<IndexedObject<T, B>>> for ExpandedDocument<T, B> {
547 fn from(items: Vec<IndexedObject<T, B>>) -> Self {
548 items.into_iter().collect()
549 }
550}
551
552#[cfg(all(test, feature = "serde-json"))]
553#[allow(clippy::unwrap_used, clippy::expect_used)]
555mod serde_json_tests {
556 use super::*;
557 use crate::Id;
558 use iri_rs::iri;
559
560 #[test]
561 fn expanded_into_serde_json_with_default_vocab() {
562 let id = Id::iri(IriBuf::from(iri!("https://example.com/x")));
563 let node = Node::<IriBuf, BlankIdBuf>::with_id(id);
564 let doc: ExpandedDocument<IriBuf, BlankIdBuf> = Indexed::new(node, None).into();
565
566 let json = doc.into_serde_json();
567 let arr = json.as_array().expect("expanded document is an array");
568 assert_eq!(arr.len(), 1);
569 assert_eq!(arr[0].get("@id").and_then(|v| v.as_str()), Some("https://example.com/x"));
570 }
571}
572
573#[cfg(all(test, feature = "sonic-rs"))]
575#[allow(clippy::expect_used)]
576mod sonic_rs_tests {
577 use super::*;
578 use crate::Id;
579 use iri_rs::iri;
580 use sonic_rs::JsonValueTrait;
581
582 #[test]
583 fn expanded_into_sonic_rs_with_default_vocab() {
584 let id = Id::iri(IriBuf::from(iri!("https://example.com/x")));
585 let node = Node::<IriBuf, BlankIdBuf>::with_id(id);
586 let doc: ExpandedDocument<IriBuf, BlankIdBuf> = Indexed::new(node, None).into();
587
588 let json = doc.into_sonic_rs();
589 let arr = json.into_array().expect("expanded document is an array");
590 assert_eq!(arr.len(), 1);
591 assert_eq!(arr[0].get("@id").as_str(), Some("https://example.com/x"));
592 }
593}
594
595#[cfg(feature = "serde-json")]
596impl<T, B> ExpandedDocument<T, B> {
597 pub fn into_serde_json_with<N>(self, vocabulary: &N) -> serde_json::Value
600 where
601 N: Vocabulary<Iri = T, BlankId = B>,
602 {
603 use jsonld_syntax::IntoJsonWithContext;
604 self.objects.into_json_with(vocabulary).into_serde_json()
605 }
606}
607
608#[cfg(feature = "serde-json")]
609impl<T, B> ExpandedDocument<T, B>
610where
611 (): Vocabulary<Iri = T, BlankId = B>,
612{
613 #[must_use]
616 pub fn into_serde_json(self) -> serde_json::Value {
617 self.into_serde_json_with(rdfx::vocabulary::no_vocabulary())
618 }
619}
620
621#[cfg(feature = "sonic-rs")]
622impl<T, B> ExpandedDocument<T, B> {
623 pub fn into_sonic_rs_with<N>(self, vocabulary: &N) -> sonic_rs::Value
626 where
627 N: Vocabulary<Iri = T, BlankId = B>,
628 {
629 use jsonld_syntax::IntoJsonWithContext;
630 self.objects.into_json_with(vocabulary).into_sonic_rs()
631 }
632}
633
634#[cfg(feature = "sonic-rs")]
635impl<T, B> ExpandedDocument<T, B>
636where
637 (): Vocabulary<Iri = T, BlankId = B>,
638{
639 #[must_use]
642 pub fn into_sonic_rs(self) -> sonic_rs::Value {
643 self.into_sonic_rs_with(rdfx::vocabulary::no_vocabulary())
644 }
645}