Skip to main content

jsonld_core/
serialization.rs

1use crate::HashSet;
2use iri_rs::{Iri, iri};
3use jstrict::Parse;
4use rdfx::{
5    GeneralizedQuad as Quad,
6    LiteralTypeRef,
7    interpretation::{ReverseIdInterpretation, ReverseIriInterpretation, ReverseTermInterpretation},
8    vocabulary::{BlankIdVocabulary, IriVocabulary, Vocabulary},
9};
10
11/// Term as it appears while serializing: either a node identifier or a literal
12/// value.
13///
14/// `rdfx` models terms with a richer type than serialization needs here, so
15/// this local two-variant enum keeps the match arms below exhaustive without
16/// carrying cases that cannot occur.
17enum SerTerm<I, L> {
18    Id(I),
19    Literal(L),
20}
21use std::{
22    collections::{BTreeMap, BTreeSet},
23    hash::Hash,
24    str::FromStr,
25};
26
27use crate::{
28    ExpandedDocument,
29    Id,
30    Indexed,
31    IndexedObject,
32    LangString,
33    Node,
34    Object,
35    ValidId,
36    Value,
37    object::{List, Literal},
38    rdf::{RDF_FIRST, RDF_JSON, RDF_NIL, RDF_REST, RDF_TYPE, XSD_BOOLEAN, XSD_DOUBLE, XSD_INTEGER, XSD_STRING},
39};
40
41struct SerDataset<R> {
42    named_graphs: BTreeMap<R, SerGraph<R>>,
43    default_graph: SerGraph<R>,
44}
45
46impl<R> SerDataset<R> {
47    fn new() -> Self {
48        Self {
49            named_graphs: BTreeMap::new(),
50            default_graph: SerGraph::new(),
51        }
52    }
53}
54
55impl<R: Ord> SerDataset<R> {
56    fn graph_mut(&mut self, label: Option<R>) -> &mut SerGraph<R>
57    where
58        R: Ord,
59    {
60        match label {
61            Some(g) => self.named_graphs.entry(g).or_insert_with(SerGraph::new),
62            None => &mut self.default_graph,
63        }
64    }
65
66    fn fold_into_default_graph(mut self) -> SerGraph<R> {
67        for (id, graph) in self.named_graphs {
68            self.default_graph.resource_mut(id).graph = Some(graph);
69        }
70
71        self.default_graph
72    }
73}
74
75struct SerGraph<R> {
76    resources: BTreeMap<R, SerResource<R>>,
77}
78
79struct SerList<R> {
80    first: HashSet<R>,
81    rest: HashSet<R>,
82    reverse_rest: HashSet<R>,
83    values: Option<Vec<R>>,
84}
85
86impl<R> Default for SerList<R> {
87    fn default() -> Self {
88        Self {
89            first: HashSet::default(),
90            rest: HashSet::default(),
91            reverse_rest: HashSet::default(),
92            values: None,
93        }
94    }
95}
96
97impl<R> SerList<R> {
98    fn is_well_formed(&self) -> bool {
99        self.first.len() == 1 && self.rest.len() == 1
100    }
101
102    fn is_empty(&self) -> bool {
103        self.first.is_empty() && self.rest.is_empty()
104    }
105}
106
107struct SerResource<R> {
108    types: BTreeSet<RdfType<R>>,
109    properties: BTreeMap<R, BTreeSet<R>>,
110    graph: Option<SerGraph<R>>,
111    list: SerList<R>,
112    references: usize,
113}
114
115impl<R> Default for SerResource<R> {
116    fn default() -> Self {
117        Self {
118            types: BTreeSet::new(),
119            properties: BTreeMap::new(),
120            graph: None,
121            list: SerList::default(),
122            references: 0,
123        }
124    }
125}
126
127impl<R> SerResource<R> {
128    fn is_empty(&self) -> bool {
129        self.types.is_empty() && self.properties.is_empty() && self.graph.is_none() && self.list.is_empty()
130    }
131
132    fn is_list_node(&self) -> bool {
133        self.types.iter().all(RdfType::is_list) && self.properties.is_empty() && self.graph.is_none() && self.list.is_well_formed()
134    }
135
136    fn insert(&mut self, prop: R, object: R)
137    where
138        R: Ord,
139    {
140        self.properties.entry(prop).or_default().insert(object);
141    }
142}
143
144impl<R> SerGraph<R> {
145    fn new() -> Self {
146        Self { resources: BTreeMap::new() }
147    }
148
149    fn get(&self, id: &R) -> Option<&SerResource<R>>
150    where
151        R: Ord,
152    {
153        self.resources.get(id)
154    }
155
156    fn resource_mut(&mut self, id: R) -> &mut SerResource<R>
157    where
158        R: Ord,
159    {
160        self.resources.entry(id).or_default()
161    }
162}
163
164enum RdfProperty {
165    Type,
166    First,
167    Rest,
168}
169
170fn rdf_property<V: IriVocabulary, I: ReverseIriInterpretation<Iri = V::Iri>>(vocabulary: &V, interpretation: &I, id: &I::Resource) -> Option<RdfProperty> {
171    for i in interpretation.iris_of(id) {
172        if let Some(iri) = vocabulary.iri(i) {
173            if iri == RDF_TYPE {
174                return Some(RdfProperty::Type);
175            } else if iri == RDF_FIRST {
176                return Some(RdfProperty::First);
177            } else if iri == RDF_REST {
178                return Some(RdfProperty::Rest);
179            }
180        }
181    }
182
183    None
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
187enum RdfType<R> {
188    List,
189    Other(R),
190}
191
192impl<R> RdfType<R> {
193    fn is_list(&self) -> bool {
194        matches!(self, Self::List)
195    }
196}
197
198const RDF_LIST: Iri<&'static str> = iri!("http://www.w3.org/1999/02/22-rdf-syntax-ns#List");
199
200fn rdf_type<'a, V: IriVocabulary, I: ReverseIriInterpretation<Iri = V::Iri>>(
201    vocabulary: &V,
202    interpretation: &I,
203    id: &'a I::Resource,
204) -> RdfType<&'a I::Resource> {
205    for i in interpretation.iris_of(id) {
206        if let Some(iri) = vocabulary.iri(i)
207            && iri == RDF_LIST
208        {
209            return RdfType::List;
210        }
211    }
212
213    RdfType::Other(id)
214}
215
216fn is_anonymous<I: ReverseTermInterpretation>(interpretation: &I, id: &I::Resource) -> bool {
217    interpretation.iris_of(id).next().is_none() && interpretation.literals_of(id).next().is_none()
218}
219
220#[derive(Debug, thiserror::Error)]
221pub enum SerializationError {
222    #[error("invalid JSON")]
223    InvalidJson(Box<ld_core::ContextIris>, jstrict::parse::Error),
224
225    #[error("invalid boolean value")]
226    InvalidBoolean(Box<ld_core::ContextIris>, String),
227
228    #[error("invalid number value")]
229    Number(Box<ld_core::ContextIris>, String),
230
231    #[error("literal handle does not resolve in the given vocabulary")]
232    /// A literal handle of the interpretation does not resolve in the given
233    /// vocabulary: the `vocabulary` and `interpretation` arguments do not
234    /// belong together.
235    UnresolvedLiteral,
236}
237
238#[derive(Clone, Copy)]
239pub struct RdfTerms<R> {
240    list: Option<R>,
241    first: Option<R>,
242    rest: Option<R>,
243}
244
245impl<I, B> ExpandedDocument<I, B> {
246    /// Builds a document from interpreted RDF quads, using the given
247    /// vocabulary and interpretation.
248    ///
249    /// # Errors
250    ///
251    /// Returns an error when the dataset cannot be expressed as JSON-LD, for instance a literal whose datatype has no IRI in the vocabulary.
252    pub fn from_interpreted_quads_in<'a, V, T>(
253        vocabulary: &V,
254        interpretation: &T,
255        quads: impl IntoIterator<Item = Quad<&'a T::Resource, &'a T::Resource, &'a T::Resource, &'a T::Resource>>,
256        context: ld_core::Context<T>,
257    ) -> Result<Self, SerializationError>
258    where
259        V: Vocabulary<Iri = I, BlankId = B>,
260        T: ReverseTermInterpretation<Iri = I, BlankId = B, Literal = V::Literal> + rdfx::interpretation::ReverseInterpretation,
261        T::Resource: 'a + Ord + Hash,
262        I: Clone + Eq + Hash,
263        B: Clone + Eq + Hash,
264    {
265        let mut node_map: SerDataset<&'a T::Resource> = SerDataset::new();
266
267        let mut nil = None;
268        let mut rdf_terms = RdfTerms {
269            list: None,
270            first: None,
271            rest: None,
272        };
273
274        for quad in quads {
275            let graph = node_map.graph_mut(quad.3);
276            let subject = graph.resource_mut(quad.0);
277
278            match rdf_property(vocabulary, interpretation, quad.1) {
279                Some(RdfProperty::Type) => {
280                    let ty = rdf_type(vocabulary, interpretation, quad.2);
281
282                    if ty.is_list() {
283                        rdf_terms.list = Some(quad.2);
284                    }
285
286                    subject.types.insert(ty);
287                }
288                Some(RdfProperty::First) => {
289                    rdf_terms.first = Some(quad.1);
290                    subject.list.first.insert(quad.2);
291                }
292                Some(RdfProperty::Rest) => {
293                    rdf_terms.rest = Some(quad.1);
294                    if nil.is_none() {
295                        for i in ReverseIriInterpretation::iris_of(interpretation, quad.2) {
296                            if let Some(iri) = vocabulary.iri(i)
297                                && iri == RDF_NIL
298                            {
299                                nil = Some(quad.2);
300                            }
301                        }
302                    }
303
304                    subject.list.rest.insert(quad.2);
305                    // Record the *subject* pointing at this object through
306                    // `rdf:rest`, so list chains can be walked backwards from
307                    // `rdf:nil` when folding them into `@list` values.
308                    graph.resource_mut(quad.2).list.reverse_rest.insert(quad.0);
309                }
310                None => {
311                    subject.insert(quad.1, quad.2);
312                }
313            }
314
315            let object = graph.resource_mut(quad.2);
316            if quad.1 == quad.2 {
317                object.references = usize::MAX;
318            } else {
319                let r = object.references;
320                object.references = r.saturating_add(1);
321            }
322        }
323
324        let mut graph = node_map.fold_into_default_graph();
325
326        let mut lists = Vec::new();
327        if let Some(nil_id) = nil
328            && let Some(nil) = graph.get(&nil_id)
329        {
330            for &node_id in &nil.list.reverse_rest {
331                let mut head_id = node_id;
332                if is_anonymous(interpretation, head_id)
333                    && let Some(mut head) = graph.get(&head_id)
334                    && head.references == 1
335                    && head.is_list_node()
336                {
337                    let mut values = Vec::new();
338
339                    // `head.is_list_node()` guarantees a non-empty `first`;
340                    // `reverse_rest` is empty at the head of the list, in
341                    // which case the chain ends there.
342                    while let Some(first) = head.list.first.iter().next().copied() {
343                        values.push(first);
344                        let Some(parent_id) = head.list.reverse_rest.iter().next().copied() else {
345                            break;
346                        };
347
348                        if is_anonymous(interpretation, parent_id)
349                            && let Some(parent) = graph.get(&parent_id)
350                            && parent.references == 1
351                            && parent.is_list_node()
352                        {
353                            head_id = parent_id;
354                            head = parent;
355                            continue;
356                        }
357
358                        break;
359                    }
360
361                    values.reverse();
362                    lists.push((head_id, values));
363                }
364            }
365        }
366
367        for (id, values) in lists {
368            graph.resource_mut(id).list.values = Some(values);
369        }
370
371        let mut result = ExpandedDocument::new();
372        for (id, resource) in &graph.resources {
373            if resource.references != 1 && !resource.is_empty() {
374                result.insert(render_object(vocabulary, interpretation, rdf_terms, &graph, id, resource, context)?);
375            }
376        }
377
378        Ok(result)
379    }
380
381    /// Builds a document from interpreted RDF quads.
382    ///
383    /// # Errors
384    ///
385    /// Returns an error when the dataset cannot be expressed as JSON-LD.
386    pub fn from_interpreted_quads<'a, V, T>(
387        vocabulary: &V,
388        interpretation: &T,
389        quads: impl IntoIterator<Item = Quad<&'a T::Resource, &'a T::Resource, &'a T::Resource, &'a T::Resource>>,
390    ) -> Result<Self, SerializationError>
391    where
392        V: Vocabulary<Iri = I, BlankId = B>,
393        T: ReverseTermInterpretation<Iri = I, BlankId = B, Literal = V::Literal> + rdfx::interpretation::ReverseInterpretation,
394        T::Resource: 'a + Ord + Hash,
395        I: Clone + Eq + Hash,
396        B: Clone + Eq + Hash,
397    {
398        Self::from_interpreted_quads_in(vocabulary, interpretation, quads, ld_core::Context::default())
399    }
400}
401
402fn render_object<V, I>(
403    vocabulary: &V,
404    interpretation: &I,
405    rdf_terms: RdfTerms<&I::Resource>,
406    graph: &SerGraph<&I::Resource>,
407    id: &I::Resource,
408    resource: &SerResource<&I::Resource>,
409    context: ld_core::Context<I>,
410) -> Result<IndexedObject<V::Iri, V::BlankId>, SerializationError>
411where
412    V: Vocabulary,
413    I: ReverseTermInterpretation<Iri = V::Iri, BlankId = V::BlankId, Literal = V::Literal> + rdfx::interpretation::ReverseInterpretation,
414    V::Iri: Clone + Eq + Hash,
415    V::BlankId: Clone + Eq + Hash,
416    I::Resource: Ord,
417{
418    let context = context.with_subject(id);
419    if resource.is_empty() {
420        render_reference(vocabulary, interpretation, id, context)
421    } else if let Some(values) = &resource.list.values {
422        let mut objects = Vec::with_capacity(values.len());
423
424        for value in values {
425            objects.push(render_object_or_reference(vocabulary, interpretation, rdf_terms, graph, value, context)?);
426        }
427
428        Ok(Indexed::none(Object::List(List::new(objects))))
429    } else {
430        let mut node: Node<V::Iri, V::BlankId> = Node::new();
431
432        if let Some(id) = id_of(interpretation, id) {
433            node.id = Some(id);
434        }
435
436        let mut types = Vec::with_capacity(resource.types.len());
437        for ty in &resource.types {
438            let ty_resource = match ty {
439                // SAFETY: a `RdfType::List` is only produced when the
440                // RDF list type was actually observed in the input.
441                RdfType::List => unsafe { rdf_terms.list.unwrap_unchecked() },
442                RdfType::Other(o) => o,
443            };
444
445            if let Some(ty_id) = id_of(interpretation, ty_resource) {
446                types.push(ty_id);
447            }
448        }
449
450        if !types.is_empty() {
451            node.types = Some(types);
452        }
453
454        if let Some(graph) = &resource.graph {
455            let mut value = crate::object::Graph::default();
456
457            for (id, resource) in &graph.resources {
458                if resource.references != 1 && !resource.is_empty() {
459                    value.push(render_object(vocabulary, interpretation, rdf_terms, graph, id, resource, context)?);
460                }
461            }
462
463            node.graph = Some(value);
464        }
465
466        for (prop, objects) in &resource.properties {
467            insert_property(vocabulary, interpretation, rdf_terms, graph, &mut node, prop, objects.iter().copied(), context)?;
468        }
469
470        if !resource.list.first.is_empty() {
471            // SAFETY: `resource.list.first` is non-empty so `rdf_terms.first`
472            // was set when `rdf:first` was first observed.
473            let rdf_first_id = unsafe { rdf_terms.first.unwrap_unchecked() };
474            insert_property(
475                vocabulary,
476                interpretation,
477                rdf_terms,
478                graph,
479                &mut node,
480                rdf_first_id,
481                resource.list.first.iter().copied(),
482                context,
483            )?;
484        }
485
486        if !resource.list.rest.is_empty() {
487            // SAFETY: `resource.list.rest` is non-empty so `rdf_terms.rest`
488            // was set when `rdf:rest` was first observed.
489            let rdf_rest_id = unsafe { rdf_terms.rest.unwrap_unchecked() };
490            insert_property(
491                vocabulary,
492                interpretation,
493                rdf_terms,
494                graph,
495                &mut node,
496                rdf_rest_id,
497                resource.list.rest.iter().copied(),
498                context,
499            )?;
500        }
501
502        Ok(Indexed::none(Object::node(node)))
503    }
504}
505
506fn insert_property<'a, V, I, O>(
507    vocabulary: &V,
508    interpretation: &I,
509    rdf_terms: RdfTerms<&'a I::Resource>,
510    graph: &SerGraph<&'a I::Resource>,
511    node: &mut Node<V::Iri, V::BlankId>,
512    prop: &I::Resource,
513    values: O,
514    context: ld_core::Context<I>,
515) -> Result<(), SerializationError>
516where
517    V: Vocabulary,
518    I: ReverseTermInterpretation<Iri = V::Iri, BlankId = V::BlankId, Literal = V::Literal> + rdfx::interpretation::ReverseInterpretation,
519    V::Iri: Clone + Eq + Hash,
520    V::BlankId: Clone + Eq + Hash,
521    I::Resource: 'a + Ord,
522    O: IntoIterator<Item = &'a I::Resource>,
523    O::IntoIter: ExactSizeIterator,
524{
525    let context = context.with_predicate(prop);
526    match id_of(interpretation, prop) {
527        Some(prop) => {
528            let mut values = values.into_iter();
529
530            while values.len() > 1 {
531                // `values.len() > 1` guarantees the iterator is not exhausted.
532                let Some(value) = values.next() else { break };
533                let v = render_object_or_reference(vocabulary, interpretation, rdf_terms, graph, value, context)?;
534                node.insert(prop.clone(), v);
535            }
536
537            if let Some(value) = values.next() {
538                let v = render_object_or_reference(vocabulary, interpretation, rdf_terms, graph, value, context)?;
539                node.insert(prop, v);
540            }
541
542            Ok(())
543        }
544        None => Ok(()),
545    }
546}
547
548fn render_object_or_reference<V, I>(
549    vocabulary: &V,
550    interpretation: &I,
551    rdf_terms: RdfTerms<&I::Resource>,
552    graph: &SerGraph<&I::Resource>,
553    id: &I::Resource,
554    context: ld_core::Context<I>,
555) -> Result<IndexedObject<V::Iri, V::BlankId>, SerializationError>
556where
557    V: Vocabulary,
558    I: ReverseTermInterpretation<Iri = V::Iri, BlankId = V::BlankId, Literal = V::Literal> + rdfx::interpretation::ReverseInterpretation,
559    V::Iri: Clone + Eq + Hash,
560    V::BlankId: Clone + Eq + Hash,
561    I::Resource: Ord,
562{
563    match graph.get(&id) {
564        Some(resource) => {
565            if resource.references == 1 && !resource.is_empty() {
566                render_object(vocabulary, interpretation, rdf_terms, graph, id, resource, context)
567            } else {
568                render_reference(vocabulary, interpretation, id, context)
569            }
570        }
571        None => render_reference(vocabulary, interpretation, id, context),
572    }
573}
574
575fn render_reference<V, I>(
576    vocabulary: &V,
577    interpretation: &I,
578    id: &I::Resource,
579    context: ld_core::Context<I>,
580) -> Result<IndexedObject<V::Iri, V::BlankId>, SerializationError>
581where
582    V: Vocabulary,
583    I: ReverseTermInterpretation<Iri = V::Iri, BlankId = V::BlankId, Literal = V::Literal>
584        + rdfx::interpretation::ReverseInterpretation
585        + rdfx::interpretation::ReverseInterpretation,
586    V::Iri: Clone,
587    V::BlankId: Clone,
588    I::Resource: Ord,
589{
590    match term_of(vocabulary, interpretation, id, context)? {
591        Some(SerTerm::Id(id)) => Ok(Indexed::none(Object::node(Node::with_id(id)))),
592        Some(SerTerm::Literal(value)) => Ok(Indexed::none(Object::Value(value))),
593        None => Ok(Indexed::none(Object::node(Node::new()))),
594    }
595}
596
597fn id_of<T>(interpretation: &T, resource: &T::Resource) -> Option<Id<T::Iri, T::BlankId>>
598where
599    T: ReverseIdInterpretation,
600    T::Iri: Clone,
601    T::BlankId: Clone,
602{
603    interpretation
604        .iris_of(resource)
605        .next()
606        .map(|i| Id::Valid(ValidId::Iri(i.clone())))
607        .or_else(|| interpretation.blank_ids_of(resource).next().map(|b| Id::Valid(ValidId::Blank(b.clone()))))
608}
609
610type ResourceTerm<V> = SerTerm<Id<<V as IriVocabulary>::Iri, <V as BlankIdVocabulary>::BlankId>, Value<<V as IriVocabulary>::Iri>>;
611
612fn term_of<V, T>(
613    vocabulary: &V,
614    interpretation: &T,
615    resource: &T::Resource,
616    context: ld_core::Context<T>,
617) -> Result<Option<ResourceTerm<V>>, SerializationError>
618where
619    V: Vocabulary,
620    T: ReverseTermInterpretation<Iri = V::Iri, BlankId = V::BlankId, Literal = V::Literal> + rdfx::interpretation::ReverseInterpretation,
621    V::Iri: Clone,
622    V::BlankId: Clone,
623{
624    match id_of(interpretation, resource) {
625        Some(id) => Ok(Some(SerTerm::Id(id))),
626        None => match rdfx::interpretation::ReverseLiteralInterpretation::literals_of(interpretation, resource).next() {
627            Some(l) => {
628                // Fails only when `interpretation` does not belong to
629                // `vocabulary`.
630                let l = vocabulary.literal(l).ok_or(SerializationError::UnresolvedLiteral)?;
631                let value = match l.type_ {
632                    LiteralTypeRef::Any(d) => {
633                        let ty = d.as_iri();
634                        // Literal types are inserted into the vocabulary as
635                        // part of literal insertion, so this fails only on a
636                        // mismatched `vocabulary`.
637                        let ty_handle = vocabulary.get(ty).ok_or(SerializationError::UnresolvedLiteral)?;
638                        if ty == RDF_JSON {
639                            let (json, _) = jstrict::Value::parse_str(l.value)
640                                .map_err(|e| SerializationError::InvalidJson(Box::new(context.into_iris(interpretation)), e))?;
641                            Value::Json(json)
642                        } else if ty == XSD_BOOLEAN {
643                            let b = match l.as_ref() {
644                                "true" | "1" => true,
645                                "false" | "0" => false,
646                                other => {
647                                    return Err(SerializationError::InvalidBoolean(
648                                        Box::new(context.into_iris(interpretation)),
649                                        other.to_owned(),
650                                    ));
651                                }
652                            };
653
654                            Value::Literal(Literal::Boolean(b), Some(ty_handle))
655                        } else if ty == XSD_INTEGER || ty == XSD_DOUBLE {
656                            let n = jstrict::NumberBuf::from_str(l.as_str())
657                                .map_err(|_| SerializationError::Number(Box::new(context.into_iris(interpretation)), l.as_ref().to_owned()))?;
658                            Value::Literal(Literal::Number(n), Some(ty_handle))
659                        } else if ty == XSD_STRING {
660                            Value::Literal(Literal::String(l.as_ref().into()), None)
661                        } else {
662                            Value::Literal(Literal::String(l.as_ref().into()), Some(ty_handle))
663                        }
664                    }
665                    LiteralTypeRef::LangString(tag) => Value::LangString(LangString::with_language(l.value.into(), tag.to_owned().into())),
666                    LiteralTypeRef::DirLangString { tag, .. } => Value::LangString(LangString::with_language(l.value.into(), tag.to_owned().into())),
667                };
668
669                Ok(Some(SerTerm::Literal(value)))
670            }
671            None => Ok(None),
672        },
673    }
674}
675
676// TODO: `ExpandedDocument` has no `LinkedDataDeserialize` impl. Providing one
677// means reshaping the deserialization logic against `ld_core`'s
678// `<I: Interpretation>`-only trait signature: the type comparisons this code
679// performs (`RDF_JSON`, `XSD_BOOLEAN`, …) need IRI and literal lexical values,
680// which the old vocabulary-taking signature supplied directly and the new
681// trait does not — they have to come from `interpretation.iris_of(...)` and
682// `literals_of(...)` instead. Until then, `from_interpreted_quads_in` below is
683// the entry point, and it still takes a vocabulary.