Skip to main content

rdfx/
generalized.rs

1//! Generalized RDF (RDF 1.1 §A / RDF 1.2 §A.1) types — no position invariants.
2//!
3//! Per [RDF 1.1 Concepts §A][s] / [RDF 1.2 Concepts §A.1][s2], generalized RDF
4//! relaxes the abstract syntax:
5//! - subjects may be literals,
6//! - predicates may be blank nodes or literals,
7//! - graph names may be literals,
8//! - triple terms may appear in any position (RDF 1.2 generalization).
9//!
10//! Use [`GeneralizedTriple`] / [`GeneralizedQuad`] when interoperating with
11//! generalized syntaxes; use [`Triple`] / [`Quad`] for strict RDF 1.1 / 1.2.
12//!
13//! Two flavors of generalized "local term" exist:
14//! - [`LocalTerm`] (default) — strict-RDF triple-term bodies inside any
15//!   triple-term object. Sufficient for most generalized-Turtle inputs.
16//! - [`GeneralizedLocalTerm`] — fully generalized: triple-term bodies may
17//!   themselves carry literal subjects / blank predicates / nested triple
18//!   terms. Reachable via [`FullyGeneralizedTriple`] / [`FullyGeneralizedQuad`].
19//!
20//! [s]:  https://www.w3.org/TR/rdf11-concepts/#section-generalized-rdf
21//! [s2]: https://www.w3.org/TR/rdf12-concepts/#section-generalized-rdf
22
23use std::{cmp::Ordering, fmt};
24
25use iri_rs::{Iri, IriBuf};
26
27use crate::{BlankId, BlankIdBuf, Id, IsGraph, IsObject, IsPredicate, IsSubject, Literal, LiteralRef, LocalTerm, Quad, RdfDisplay, Term, Triple, __seal};
28
29/// Reasons a [`GeneralizedTriple`] or [`GeneralizedQuad`] cannot be converted
30/// to a strict [`Triple`] / [`Quad`].
31#[derive(Debug, thiserror::Error)]
32pub enum GeneralizationError {
33    #[error("literal in subject position")]
34    /// A literal sits in subject position.
35    LiteralSubject,
36    #[error("literal in predicate position")]
37    /// A literal sits in predicate position.
38    LiteralPredicate,
39    #[error("blank node in predicate position")]
40    /// A blank node sits in predicate position.
41    BlankPredicate,
42    #[error("literal in graph position")]
43    /// A literal sits in graph position.
44    LiteralGraph,
45    /// A triple term sits in subject position. Strict RDF 1.2 only allows
46    /// triple terms in object position.
47    #[error("triple term in subject position")]
48    TripleTermSubject,
49    /// A triple term sits in predicate position.
50    #[error("triple term in predicate position")]
51    TripleTermPredicate,
52    /// A triple term sits in graph-name position.
53    #[error("triple term in graph position")]
54    TripleTermGraph,
55}
56
57/// Generalized RDF triple — no position bounds on its components.
58#[derive(Clone, Copy, Eq, Ord, Hash, Debug)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60pub struct GeneralizedTriple<S = LocalTerm, P = S, O = S>(pub S, pub P, pub O);
61
62impl<S, P, O> GeneralizedTriple<S, P, O> {
63    /// Creates a generalized triple from its components.
64    pub const fn new(subject: S, predicate: P, object: O) -> Self {
65        Self(subject, predicate, object)
66    }
67
68    /// Returns the subject of this triple.
69    pub const fn subject(&self) -> &S {
70        &self.0
71    }
72
73    /// Returns the predicate of this triple.
74    pub const fn predicate(&self) -> &P {
75        &self.1
76    }
77
78    /// Returns the object of this triple.
79    pub const fn object(&self) -> &O {
80        &self.2
81    }
82
83    /// Consumes this triple, returning its subject.
84    pub fn into_subject(self) -> S {
85        self.0
86    }
87
88    /// Consumes this triple, returning its predicate.
89    pub fn into_predicate(self) -> P {
90        self.1
91    }
92
93    /// Consumes this triple, returning its object.
94    pub fn into_object(self) -> O {
95        self.2
96    }
97
98    /// Consumes this triple, returning each component separately.
99    pub fn into_parts(self) -> (S, P, O) {
100        (self.0, self.1, self.2)
101    }
102
103    /// Lifts this triple into a generalized quad in the given graph.
104    pub fn into_quad<G>(self, graph: Option<G>) -> GeneralizedQuad<S, P, O, G> {
105        GeneralizedQuad(self.0, self.1, self.2, graph)
106    }
107
108    /// Maps the subject of this triple with `f`.
109    pub fn map_subject<U>(self, f: impl FnOnce(S) -> U) -> GeneralizedTriple<U, P, O> {
110        GeneralizedTriple(f(self.0), self.1, self.2)
111    }
112
113    /// Maps the predicate of this triple with `f`.
114    pub fn map_predicate<U>(self, f: impl FnOnce(P) -> U) -> GeneralizedTriple<S, U, O> {
115        GeneralizedTriple(self.0, f(self.1), self.2)
116    }
117
118    /// Maps the object of this triple with `f`.
119    pub fn map_object<U>(self, f: impl FnOnce(O) -> U) -> GeneralizedTriple<S, P, U> {
120        GeneralizedTriple(self.0, self.1, f(self.2))
121    }
122
123    /// Borrows the components of this triple.
124    pub const fn as_ref(&self) -> GeneralizedTriple<&S, &P, &O> {
125        GeneralizedTriple(&self.0, &self.1, &self.2)
126    }
127}
128
129impl<S, P, O> GeneralizedTriple<&S, &P, &O> {
130    /// Clones the borrowed components of this triple.
131    pub fn cloned(&self) -> GeneralizedTriple<S, P, O>
132    where
133        S: Clone,
134        P: Clone,
135        O: Clone,
136    {
137        GeneralizedTriple(self.0.clone(), self.1.clone(), self.2.clone())
138    }
139
140    /// Consumes this triple, cloning its borrowed components.
141    pub fn into_cloned(self) -> GeneralizedTriple<S, P, O>
142    where
143        S: Clone,
144        P: Clone,
145        O: Clone,
146    {
147        GeneralizedTriple(self.0.clone(), self.1.clone(), self.2.clone())
148    }
149
150    /// Copies the borrowed components of this triple.
151    pub const fn copied(&self) -> GeneralizedTriple<S, P, O>
152    where
153        S: Copy,
154        P: Copy,
155        O: Copy,
156    {
157        GeneralizedTriple(*self.0, *self.1, *self.2)
158    }
159
160    /// Consumes this triple, copying its borrowed components.
161    pub const fn into_copied(self) -> GeneralizedTriple<S, P, O>
162    where
163        S: Copy,
164        P: Copy,
165        O: Copy,
166    {
167        GeneralizedTriple(*self.0, *self.1, *self.2)
168    }
169}
170
171impl<T> GeneralizedTriple<T, T, T> {
172    /// Maps every component of this triple with `f`.
173    pub fn map<U>(self, mut f: impl FnMut(T) -> U) -> GeneralizedTriple<U, U, U> {
174        GeneralizedTriple(f(self.0), f(self.1), f(self.2))
175    }
176}
177
178impl GeneralizedTriple<LocalTerm, LocalTerm, LocalTerm> {
179    /// Tries to narrow this generalized triple into a strict [`Triple`].
180    /// Fails if a literal sits in subject position, the predicate is a
181    /// blank node, literal, or triple term, or a triple term sits in subject
182    /// position.
183    pub fn try_into_triple(self) -> Result<Triple<Id, IriBuf, LocalTerm>, GeneralizationError> {
184        let GeneralizedTriple(s, p, o) = self;
185        let s = local_term_to_id(s)?;
186        let p = local_term_to_iri(p)?;
187        Ok(Triple(s, p, o))
188    }
189}
190
191impl<S: IsSubject, P: IsPredicate, O: IsObject> From<Triple<S, P, O>> for GeneralizedTriple<S, P, O> {
192    fn from(t: Triple<S, P, O>) -> Self {
193        GeneralizedTriple(t.0, t.1, t.2)
194    }
195}
196
197impl<S1, P1, O1, S2, P2, O2> PartialEq<GeneralizedTriple<S2, P2, O2>> for GeneralizedTriple<S1, P1, O1>
198where
199    S1: PartialEq<S2>,
200    P1: PartialEq<P2>,
201    O1: PartialEq<O2>,
202{
203    fn eq(&self, other: &GeneralizedTriple<S2, P2, O2>) -> bool {
204        self.0 == other.0 && self.1 == other.1 && self.2 == other.2
205    }
206}
207
208impl<S1, P1, O1, S2, P2, O2> PartialOrd<GeneralizedTriple<S2, P2, O2>> for GeneralizedTriple<S1, P1, O1>
209where
210    S1: PartialOrd<S2>,
211    P1: PartialOrd<P2>,
212    O1: PartialOrd<O2>,
213{
214    fn partial_cmp(&self, other: &GeneralizedTriple<S2, P2, O2>) -> Option<Ordering> {
215        match self.0.partial_cmp(&other.0) {
216            Some(Ordering::Equal) => match self.1.partial_cmp(&other.1) {
217                Some(Ordering::Equal) => self.2.partial_cmp(&other.2),
218                cmp => cmp,
219            },
220            cmp => cmp,
221        }
222    }
223}
224
225impl<S: RdfDisplay, P: RdfDisplay, O: RdfDisplay> fmt::Display for GeneralizedTriple<S, P, O> {
226    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
227        write!(f, "{} {} {}", self.0.rdf_display(), self.1.rdf_display(), self.2.rdf_display())
228    }
229}
230
231impl<S: RdfDisplay, P: RdfDisplay, O: RdfDisplay> RdfDisplay for GeneralizedTriple<S, P, O> {
232    fn rdf_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
233        write!(f, "{} {} {}", self.0.rdf_display(), self.1.rdf_display(), self.2.rdf_display())
234    }
235}
236
237/// Generalized RDF quad — no position bounds on its components.
238#[derive(Clone, Copy, Eq, Ord, Hash, Debug)]
239#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
240pub struct GeneralizedQuad<S = LocalTerm, P = S, O = S, G = S>(pub S, pub P, pub O, pub Option<G>);
241
242impl<S, P, O, G> GeneralizedQuad<S, P, O, G> {
243    /// Creates a generalized quad from its components.
244    pub const fn new(subject: S, predicate: P, object: O, graph: Option<G>) -> Self {
245        Self(subject, predicate, object, graph)
246    }
247
248    /// Returns the subject of this quad.
249    pub const fn subject(&self) -> &S {
250        &self.0
251    }
252
253    /// Returns the predicate of this quad.
254    pub const fn predicate(&self) -> &P {
255        &self.1
256    }
257
258    /// Returns the object of this quad.
259    pub const fn object(&self) -> &O {
260        &self.2
261    }
262
263    /// Returns the graph of this quad.
264    pub const fn graph(&self) -> Option<&G> {
265        self.3.as_ref()
266    }
267
268    /// Consumes this quad, returning its subject.
269    pub fn into_subject(self) -> S {
270        self.0
271    }
272
273    /// Consumes this quad, returning its predicate.
274    pub fn into_predicate(self) -> P {
275        self.1
276    }
277
278    /// Consumes this quad, returning its object.
279    pub fn into_object(self) -> O {
280        self.2
281    }
282
283    /// Consumes this quad, returning its graph.
284    pub fn into_graph(self) -> Option<G> {
285        self.3
286    }
287
288    /// Consumes this quad, returning each component separately.
289    pub fn into_parts(self) -> (S, P, O, Option<G>) {
290        (self.0, self.1, self.2, self.3)
291    }
292
293    /// Splits this quad into a generalized triple and its graph.
294    pub fn into_triple(self) -> (GeneralizedTriple<S, P, O>, Option<G>) {
295        (GeneralizedTriple(self.0, self.1, self.2), self.3)
296    }
297
298    /// Maps the subject of this quad with `f`.
299    pub fn map_subject<U>(self, f: impl FnOnce(S) -> U) -> GeneralizedQuad<U, P, O, G> {
300        GeneralizedQuad(f(self.0), self.1, self.2, self.3)
301    }
302
303    /// Maps the predicate of this quad with `f`.
304    pub fn map_predicate<U>(self, f: impl FnOnce(P) -> U) -> GeneralizedQuad<S, U, O, G> {
305        GeneralizedQuad(self.0, f(self.1), self.2, self.3)
306    }
307
308    /// Maps the object of this quad with `f`.
309    pub fn map_object<U>(self, f: impl FnOnce(O) -> U) -> GeneralizedQuad<S, P, U, G> {
310        GeneralizedQuad(self.0, self.1, f(self.2), self.3)
311    }
312
313    /// Maps the graph of this quad with `f`.
314    pub fn map_graph<U>(self, f: impl FnOnce(Option<G>) -> Option<U>) -> GeneralizedQuad<S, P, O, U> {
315        GeneralizedQuad(self.0, self.1, self.2, f(self.3))
316    }
317
318    /// Returns this quad moved into the given graph.
319    pub fn with_graph(self, g: Option<G>) -> Self {
320        Self(self.0, self.1, self.2, g)
321    }
322
323    /// Maps every component of this quad with its own function.
324    pub fn map_all<S2, P2, O2, G2>(
325        self,
326        s: impl FnOnce(S) -> S2,
327        p: impl FnOnce(P) -> P2,
328        o: impl FnOnce(O) -> O2,
329        g: impl FnOnce(Option<G>) -> Option<G2>,
330    ) -> GeneralizedQuad<S2, P2, O2, G2> {
331        GeneralizedQuad(s(self.0), p(self.1), o(self.2), g(self.3))
332    }
333
334    /// Borrows the components of this quad.
335    pub const fn as_ref(&self) -> GeneralizedQuad<&S, &P, &O, &G> {
336        GeneralizedQuad(&self.0, &self.1, &self.2, self.3.as_ref())
337    }
338}
339
340impl<S, P, O, G> GeneralizedQuad<&S, &P, &O, &G> {
341    /// Clones the borrowed components of this quad.
342    pub fn cloned(&self) -> GeneralizedQuad<S, P, O, G>
343    where
344        S: Clone,
345        P: Clone,
346        O: Clone,
347        G: Clone,
348    {
349        GeneralizedQuad(self.0.clone(), self.1.clone(), self.2.clone(), self.3.cloned())
350    }
351
352    /// Consumes this quad, cloning its borrowed components.
353    pub fn into_cloned(self) -> GeneralizedQuad<S, P, O, G>
354    where
355        S: Clone,
356        P: Clone,
357        O: Clone,
358        G: Clone,
359    {
360        GeneralizedQuad(self.0.clone(), self.1.clone(), self.2.clone(), self.3.cloned())
361    }
362
363    /// Copies the borrowed components of this quad.
364    pub const fn copied(&self) -> GeneralizedQuad<S, P, O, G>
365    where
366        S: Copy,
367        P: Copy,
368        O: Copy,
369        G: Copy,
370    {
371        GeneralizedQuad(*self.0, *self.1, *self.2, self.3.copied())
372    }
373
374    /// Consumes this quad, copying its borrowed components.
375    pub const fn into_copied(self) -> GeneralizedQuad<S, P, O, G>
376    where
377        S: Copy,
378        P: Copy,
379        O: Copy,
380        G: Copy,
381    {
382        GeneralizedQuad(*self.0, *self.1, *self.2, self.3.copied())
383    }
384}
385
386impl<T> GeneralizedQuad<T, T, T, T> {
387    /// Maps every component of this quad with `f`.
388    pub fn map<U>(self, mut f: impl FnMut(T) -> U) -> GeneralizedQuad<U, U, U, U> {
389        GeneralizedQuad(f(self.0), f(self.1), f(self.2), self.3.map(f))
390    }
391}
392
393impl GeneralizedQuad<LocalTerm, LocalTerm, LocalTerm, LocalTerm> {
394    /// Tries to narrow this generalized quad into a strict [`Quad`].
395    pub fn try_into_quad(self) -> Result<Quad<Id, IriBuf, LocalTerm, Id>, GeneralizationError> {
396        let GeneralizedQuad(s, p, o, g) = self;
397        let s = local_term_to_id(s)?;
398        let p = local_term_to_iri(p)?;
399        let g = match g {
400            Some(t) => Some(local_term_to_graph_id(t)?),
401            None => None,
402        };
403        Ok(Quad(s, p, o, g))
404    }
405}
406
407fn local_term_to_id(t: LocalTerm) -> Result<Id, GeneralizationError> {
408    match t {
409        LocalTerm::BlankId(b) => Ok(Id::BlankId(b)),
410        LocalTerm::Named(Term::Iri(iri)) => Ok(Id::Iri(iri)),
411        LocalTerm::Named(Term::Literal(_)) => Err(GeneralizationError::LiteralSubject),
412        LocalTerm::Triple(_) => Err(GeneralizationError::TripleTermSubject),
413    }
414}
415
416fn local_term_to_iri(t: LocalTerm) -> Result<IriBuf, GeneralizationError> {
417    match t {
418        LocalTerm::BlankId(_) => Err(GeneralizationError::BlankPredicate),
419        LocalTerm::Named(Term::Iri(iri)) => Ok(iri),
420        LocalTerm::Named(Term::Literal(_)) => Err(GeneralizationError::LiteralPredicate),
421        LocalTerm::Triple(_) => Err(GeneralizationError::TripleTermPredicate),
422    }
423}
424
425fn local_term_to_graph_id(t: LocalTerm) -> Result<Id, GeneralizationError> {
426    match t {
427        LocalTerm::BlankId(b) => Ok(Id::BlankId(b)),
428        LocalTerm::Named(Term::Iri(iri)) => Ok(Id::Iri(iri)),
429        LocalTerm::Named(Term::Literal(_)) => Err(GeneralizationError::LiteralGraph),
430        LocalTerm::Triple(_) => Err(GeneralizationError::TripleTermGraph),
431    }
432}
433
434impl<S: IsSubject, P: IsPredicate, O: IsObject, G: IsGraph> From<Quad<S, P, O, G>> for GeneralizedQuad<S, P, O, G> {
435    fn from(q: Quad<S, P, O, G>) -> Self {
436        GeneralizedQuad(q.0, q.1, q.2, q.3)
437    }
438}
439
440impl<S1, P1, O1, G1, S2, P2, O2, G2> PartialEq<GeneralizedQuad<S2, P2, O2, G2>> for GeneralizedQuad<S1, P1, O1, G1>
441where
442    S1: PartialEq<S2>,
443    P1: PartialEq<P2>,
444    O1: PartialEq<O2>,
445    G1: PartialEq<G2>,
446{
447    fn eq(&self, other: &GeneralizedQuad<S2, P2, O2, G2>) -> bool {
448        self.0 == other.0
449            && self.1 == other.1
450            && self.2 == other.2
451            && match (&self.3, &other.3) {
452                (Some(a), Some(b)) => a == b,
453                (None, None) => true,
454                _ => false,
455            }
456    }
457}
458
459impl<S1, P1, O1, G1, S2, P2, O2, G2> PartialOrd<GeneralizedQuad<S2, P2, O2, G2>> for GeneralizedQuad<S1, P1, O1, G1>
460where
461    S1: PartialOrd<S2>,
462    P1: PartialOrd<P2>,
463    O1: PartialOrd<O2>,
464    G1: PartialOrd<G2>,
465{
466    fn partial_cmp(&self, other: &GeneralizedQuad<S2, P2, O2, G2>) -> Option<Ordering> {
467        match self.0.partial_cmp(&other.0) {
468            Some(Ordering::Equal) => match self.1.partial_cmp(&other.1) {
469                Some(Ordering::Equal) => match self.2.partial_cmp(&other.2) {
470                    Some(Ordering::Equal) => match (&self.3, &other.3) {
471                        (Some(a), Some(b)) => a.partial_cmp(b),
472                        (Some(_), None) => Some(Ordering::Greater),
473                        (None, Some(_)) => Some(Ordering::Less),
474                        (None, None) => Some(Ordering::Equal),
475                    },
476                    cmp => cmp,
477                },
478                cmp => cmp,
479            },
480            cmp => cmp,
481        }
482    }
483}
484
485impl<S: RdfDisplay, P: RdfDisplay, O: RdfDisplay, G: RdfDisplay> fmt::Display for GeneralizedQuad<S, P, O, G> {
486    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
487        match self.graph() {
488            Some(graph) => write!(
489                f,
490                "{} {} {} {}",
491                self.0.rdf_display(),
492                self.1.rdf_display(),
493                self.2.rdf_display(),
494                graph.rdf_display()
495            ),
496            None => write!(f, "{} {} {}", self.0.rdf_display(), self.1.rdf_display(), self.2.rdf_display()),
497        }
498    }
499}
500
501impl<S: RdfDisplay, P: RdfDisplay, O: RdfDisplay, G: RdfDisplay> RdfDisplay for GeneralizedQuad<S, P, O, G> {
502    fn rdf_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
503        match self.graph() {
504            Some(graph) => write!(
505                f,
506                "{} {} {} {}",
507                self.0.rdf_display(),
508                self.1.rdf_display(),
509                self.2.rdf_display(),
510                graph.rdf_display()
511            ),
512            None => write!(f, "{} {} {}", self.0.rdf_display(), self.1.rdf_display(), self.2.rdf_display()),
513        }
514    }
515}
516
517// =========================================================================
518// Fully-generalized local term (RDF 1.2 §A.1)
519// =========================================================================
520
521/// Fully-generalized local term — like [`LocalTerm`] but its triple-term
522/// bodies may themselves carry literal subjects, blank predicates, and
523/// nested triple terms in any position.
524///
525/// Reachable as the component type of [`FullyGeneralizedTriple`] /
526/// [`FullyGeneralizedQuad`].
527#[derive(Debug, Clone, PartialEq, Eq, Hash)]
528pub enum GeneralizedLocalTerm {
529    /// A blank node identifier.
530    BlankId(BlankIdBuf),
531    /// An IRI or a literal.
532    Named(Term),
533    /// Fully-generalized triple term — body components are themselves
534    /// [`GeneralizedLocalTerm`].
535    Triple(Box<GeneralizedTriple<GeneralizedLocalTerm>>),
536}
537
538impl GeneralizedLocalTerm {
539    /// Creates a term from an IRI.
540    pub const fn iri(iri: IriBuf) -> Self {
541        Self::Named(Term::Iri(iri))
542    }
543
544    /// Creates a term from a literal.
545    pub const fn literal(literal: Literal) -> Self {
546        Self::Named(Term::Literal(literal))
547    }
548
549    /// Creates a term from a triple term.
550    pub fn triple(t: GeneralizedTriple<GeneralizedLocalTerm>) -> Self {
551        Self::Triple(Box::new(t))  // GeneralizedLocalTerm keeps Box (no shared-clone story; users construct in tree-shape)
552    }
553
554    /// Checks whether this term is a blank node identifier.
555    pub const fn is_blank_id(&self) -> bool {
556        matches!(self, Self::BlankId(_))
557    }
558
559    /// Checks whether this term is a triple term.
560    pub const fn is_triple(&self) -> bool {
561        matches!(self, Self::Triple(_))
562    }
563
564    /// Returns the blank node identifier of this term, if it is one.
565    pub fn as_blank_id(&self) -> Option<&BlankId> {
566        match self {
567            Self::BlankId(b) => Some(b),
568            _ => None,
569        }
570    }
571
572    /// Returns the IRI of this term, if it is one.
573    pub fn as_iri(&self) -> Option<Iri<&str>> {
574        match self {
575            Self::Named(Term::Iri(iri)) => Some(iri.as_ref()),
576            _ => None,
577        }
578    }
579
580    /// Returns the literal of this term, if it is one.
581    pub fn as_literal(&self) -> Option<LiteralRef<'_>> {
582        match self {
583            Self::Named(Term::Literal(l)) => Some(l.as_ref()),
584            _ => None,
585        }
586    }
587
588    /// Returns the triple term of this term, if it is one.
589    pub fn as_triple(&self) -> Option<&GeneralizedTriple<GeneralizedLocalTerm>> {
590        match self {
591            Self::Triple(t) => Some(t),
592            _ => None,
593        }
594    }
595
596    /// Narrows this fully-generalized term to a strict [`LocalTerm`].
597    pub fn try_into_local_term(self) -> Result<LocalTerm, GeneralizationError> {
598        match self {
599            Self::BlankId(b) => Ok(LocalTerm::BlankId(b)),
600            Self::Named(t) => Ok(LocalTerm::Named(t)),
601            Self::Triple(b) => {
602                let GeneralizedTriple(s, p, o) = *b;
603                let s = generalized_to_id(s, GeneralizationError::LiteralSubject)?;
604                let p = generalized_to_iri(p)?;
605                let o = o.try_into_local_term()?;
606                Ok(LocalTerm::triple(Triple(s, p, o)))
607            }
608        }
609    }
610}
611
612impl From<LocalTerm> for GeneralizedLocalTerm {
613    fn from(value: LocalTerm) -> Self {
614        match value {
615            LocalTerm::BlankId(b) => Self::BlankId(b),
616            LocalTerm::Named(t) => Self::Named(t),
617            LocalTerm::Triple(b) => {
618                let Triple(s, p, o) = std::sync::Arc::try_unwrap(b).unwrap_or_else(|arc| (*arc).clone());
619                let s = match s {
620                    Id::Iri(iri) => Self::Named(Term::Iri(iri)),
621                    Id::BlankId(b) => Self::BlankId(b),
622                };
623                let p = Self::Named(Term::Iri(p));
624                let o = Self::from(o);
625                Self::triple(GeneralizedTriple(s, p, o))
626            }
627        }
628    }
629}
630
631impl From<BlankIdBuf> for GeneralizedLocalTerm {
632    fn from(value: BlankIdBuf) -> Self {
633        Self::BlankId(value)
634    }
635}
636
637impl From<IriBuf> for GeneralizedLocalTerm {
638    fn from(value: IriBuf) -> Self {
639        Self::iri(value)
640    }
641}
642
643impl From<Literal> for GeneralizedLocalTerm {
644    fn from(value: Literal) -> Self {
645        Self::literal(value)
646    }
647}
648
649impl From<Term> for GeneralizedLocalTerm {
650    fn from(value: Term) -> Self {
651        Self::Named(value)
652    }
653}
654
655impl fmt::Display for GeneralizedLocalTerm {
656    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
657        match self {
658            Self::BlankId(b) => b.fmt(f),
659            Self::Named(t) => t.fmt(f),
660            Self::Triple(t) => {
661                f.write_str("<<( ")?;
662                t.rdf_fmt(f)?;
663                f.write_str(" )>>")
664            }
665        }
666    }
667}
668
669impl RdfDisplay for GeneralizedLocalTerm {
670    fn rdf_fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
671        match self {
672            Self::BlankId(b) => b.rdf_fmt(f),
673            Self::Named(t) => t.rdf_fmt(f),
674            Self::Triple(t) => {
675                f.write_str("<<( ")?;
676                t.rdf_fmt(f)?;
677                f.write_str(" )>>")
678            }
679        }
680    }
681}
682
683// Bare position-trait impls — fully generalized terms are usable in every
684// position. No `Resource` impl (consistent with how `LocalTerm` is sealed:
685// only the positions strictly required by RDF, not as a free-form resource).
686impl __seal::Sealed for GeneralizedLocalTerm {}
687impl IsSubject for GeneralizedLocalTerm {}
688impl IsPredicate for GeneralizedLocalTerm {}
689impl IsObject for GeneralizedLocalTerm {}
690impl IsGraph for GeneralizedLocalTerm {}
691
692/// Fully-generalized triple — body components are [`GeneralizedLocalTerm`].
693pub type FullyGeneralizedTriple = GeneralizedTriple<GeneralizedLocalTerm>;
694
695/// Fully-generalized quad — body components are [`GeneralizedLocalTerm`].
696pub type FullyGeneralizedQuad = GeneralizedQuad<GeneralizedLocalTerm>;
697
698impl FullyGeneralizedTriple {
699    /// Tries to narrow this fully-generalized triple into a strict [`Triple`].
700    pub fn try_into_strict_triple(self) -> Result<Triple<Id, IriBuf, LocalTerm>, GeneralizationError> {
701        let GeneralizedTriple(s, p, o) = self;
702        let s = generalized_to_id(s, GeneralizationError::LiteralSubject)?;
703        let p = generalized_to_iri(p)?;
704        let o = o.try_into_local_term()?;
705        Ok(Triple(s, p, o))
706    }
707}
708
709impl FullyGeneralizedQuad {
710    /// Tries to narrow this fully-generalized quad into a strict [`Quad`].
711    pub fn try_into_strict_quad(self) -> Result<Quad<Id, IriBuf, LocalTerm, Id>, GeneralizationError> {
712        let GeneralizedQuad(s, p, o, g) = self;
713        let s = generalized_to_id(s, GeneralizationError::LiteralSubject)?;
714        let p = generalized_to_iri(p)?;
715        let o = o.try_into_local_term()?;
716        let g = match g {
717            Some(t) => Some(generalized_to_id(t, GeneralizationError::LiteralGraph)?),
718            None => None,
719        };
720        Ok(Quad(s, p, o, g))
721    }
722}
723
724fn generalized_to_id(t: GeneralizedLocalTerm, on_literal: GeneralizationError) -> Result<Id, GeneralizationError> {
725    match t {
726        GeneralizedLocalTerm::BlankId(b) => Ok(Id::BlankId(b)),
727        GeneralizedLocalTerm::Named(Term::Iri(iri)) => Ok(Id::Iri(iri)),
728        GeneralizedLocalTerm::Named(Term::Literal(_)) => Err(on_literal),
729        GeneralizedLocalTerm::Triple(_) => Err(match on_literal {
730            GeneralizationError::LiteralGraph => GeneralizationError::TripleTermGraph,
731            _ => GeneralizationError::TripleTermSubject,
732        }),
733    }
734}
735
736fn generalized_to_iri(t: GeneralizedLocalTerm) -> Result<IriBuf, GeneralizationError> {
737    match t {
738        GeneralizedLocalTerm::BlankId(_) => Err(GeneralizationError::BlankPredicate),
739        GeneralizedLocalTerm::Named(Term::Iri(iri)) => Ok(iri),
740        GeneralizedLocalTerm::Named(Term::Literal(_)) => Err(GeneralizationError::LiteralPredicate),
741        GeneralizedLocalTerm::Triple(_) => Err(GeneralizationError::TripleTermPredicate),
742    }
743}
744