Skip to main content

graphmind/rdf/
types.rs

1//! RDF type definitions
2//!
3//! This module provides wrapper types around the oxrdf library for RDF primitives.
4
5use oxrdf::{
6    BlankNode as OxBlankNode, Literal as OxLiteral, NamedNode as OxNamedNode, Subject as OxSubject,
7    Term as OxTerm, Triple as OxTriple,
8};
9use std::fmt;
10use thiserror::Error;
11
12/// RDF errors
13#[derive(Error, Debug)]
14#[allow(clippy::enum_variant_names)]
15pub enum RdfError {
16    /// Invalid IRI
17    #[error("Invalid IRI: {0}")]
18    InvalidIri(String),
19
20    /// Invalid blank node
21    #[error("Invalid blank node: {0}")]
22    InvalidBlankNode(String),
23
24    /// Invalid literal
25    #[error("Invalid literal: {0}")]
26    InvalidLiteral(String),
27}
28
29pub type RdfResult<T> = Result<T, RdfError>;
30
31/// Named node (IRI)
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
33pub struct NamedNode(OxNamedNode);
34
35impl NamedNode {
36    /// Create a new named node from an IRI string
37    pub fn new(iri: &str) -> RdfResult<Self> {
38        OxNamedNode::new(iri)
39            .map(Self)
40            .map_err(|e| RdfError::InvalidIri(e.to_string()))
41    }
42
43    /// Get the IRI string
44    pub fn as_str(&self) -> &str {
45        self.0.as_str()
46    }
47
48    /// Get the inner oxrdf NamedNode
49    pub fn inner(&self) -> &OxNamedNode {
50        &self.0
51    }
52}
53
54impl fmt::Display for NamedNode {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        write!(f, "<{}>", self.as_str())
57    }
58}
59
60impl From<OxNamedNode> for NamedNode {
61    fn from(node: OxNamedNode) -> Self {
62        Self(node)
63    }
64}
65
66impl From<NamedNode> for OxNamedNode {
67    fn from(node: NamedNode) -> Self {
68        node.0
69    }
70}
71
72/// Blank node (anonymous node)
73#[derive(Debug, Clone, PartialEq, Eq, Hash)]
74pub struct BlankNode(OxBlankNode);
75
76impl BlankNode {
77    /// Create a new blank node with a unique identifier
78    pub fn new() -> Self {
79        Self(OxBlankNode::default())
80    }
81
82    /// Create a blank node from a string identifier
83    #[allow(clippy::should_implement_trait)]
84    pub fn from_str(s: &str) -> RdfResult<Self> {
85        OxBlankNode::new(s)
86            .map(Self)
87            .map_err(|e| RdfError::InvalidBlankNode(e.to_string()))
88    }
89
90    /// Get the blank node identifier
91    pub fn as_str(&self) -> &str {
92        self.0.as_str()
93    }
94
95    /// Get the inner oxrdf BlankNode
96    pub fn inner(&self) -> &OxBlankNode {
97        &self.0
98    }
99}
100
101impl Default for BlankNode {
102    fn default() -> Self {
103        Self::new()
104    }
105}
106
107impl fmt::Display for BlankNode {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        write!(f, "_:{}", self.as_str())
110    }
111}
112
113impl From<OxBlankNode> for BlankNode {
114    fn from(node: OxBlankNode) -> Self {
115        Self(node)
116    }
117}
118
119impl From<BlankNode> for OxBlankNode {
120    fn from(node: BlankNode) -> Self {
121        node.0
122    }
123}
124
125/// RDF literal value
126#[derive(Debug, Clone, PartialEq, Eq, Hash)]
127pub struct Literal(OxLiteral);
128
129impl Literal {
130    /// Create a simple literal (plain string)
131    pub fn new_simple_literal(value: impl Into<String>) -> Self {
132        Self(OxLiteral::new_simple_literal(value))
133    }
134
135    /// Create a literal with language tag
136    pub fn new_language_tagged_literal(
137        value: impl Into<String>,
138        language: impl Into<String>,
139    ) -> RdfResult<Self> {
140        OxLiteral::new_language_tagged_literal(value, language)
141            .map(Self)
142            .map_err(|e| RdfError::InvalidLiteral(e.to_string()))
143    }
144
145    /// Create a typed literal
146    pub fn new_typed_literal(value: impl Into<String>, datatype: NamedNode) -> Self {
147        Self(OxLiteral::new_typed_literal(value, datatype.0))
148    }
149
150    /// Get the lexical value
151    pub fn value(&self) -> &str {
152        self.0.value()
153    }
154
155    /// Get the language tag if present
156    pub fn language(&self) -> Option<&str> {
157        self.0.language()
158    }
159
160    /// Get the datatype
161    pub fn datatype(&self) -> NamedNode {
162        NamedNode(self.0.datatype().into_owned())
163    }
164
165    /// Get the inner oxrdf Literal
166    pub fn inner(&self) -> &OxLiteral {
167        &self.0
168    }
169}
170
171impl fmt::Display for Literal {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        if let Some(lang) = self.language() {
174            write!(f, "\"{}\"@{}", self.value(), lang)
175        } else {
176            write!(f, "\"{}\"^^{}", self.value(), self.datatype())
177        }
178    }
179}
180
181impl From<OxLiteral> for Literal {
182    fn from(lit: OxLiteral) -> Self {
183        Self(lit)
184    }
185}
186
187impl From<Literal> for OxLiteral {
188    fn from(lit: Literal) -> Self {
189        lit.0
190    }
191}
192
193/// RDF subject (NamedNode or BlankNode)
194#[derive(Debug, Clone, PartialEq, Eq, Hash)]
195pub enum RdfSubject {
196    /// Named node (IRI)
197    NamedNode(NamedNode),
198    /// Blank node
199    BlankNode(BlankNode),
200}
201
202impl RdfSubject {
203    /// Check if this is a named node
204    pub fn is_named_node(&self) -> bool {
205        matches!(self, RdfSubject::NamedNode(_))
206    }
207
208    /// Check if this is a blank node
209    pub fn is_blank_node(&self) -> bool {
210        matches!(self, RdfSubject::BlankNode(_))
211    }
212}
213
214impl fmt::Display for RdfSubject {
215    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216        match self {
217            RdfSubject::NamedNode(n) => write!(f, "{}", n),
218            RdfSubject::BlankNode(b) => write!(f, "{}", b),
219        }
220    }
221}
222
223impl From<NamedNode> for RdfSubject {
224    fn from(node: NamedNode) -> Self {
225        RdfSubject::NamedNode(node)
226    }
227}
228
229impl From<BlankNode> for RdfSubject {
230    fn from(node: BlankNode) -> Self {
231        RdfSubject::BlankNode(node)
232    }
233}
234
235impl From<OxSubject> for RdfSubject {
236    fn from(subject: OxSubject) -> Self {
237        match subject {
238            OxSubject::NamedNode(n) => RdfSubject::NamedNode(n.into()),
239            OxSubject::BlankNode(b) => RdfSubject::BlankNode(b.into()),
240            #[allow(unreachable_patterns)]
241            _ => panic!("RDF-star triples not yet supported"),
242        }
243    }
244}
245
246impl From<RdfSubject> for OxSubject {
247    fn from(subject: RdfSubject) -> Self {
248        match subject {
249            RdfSubject::NamedNode(n) => OxSubject::NamedNode(n.0),
250            RdfSubject::BlankNode(b) => OxSubject::BlankNode(b.0),
251        }
252    }
253}
254
255/// RDF predicate (always a NamedNode)
256#[derive(Debug, Clone, PartialEq, Eq, Hash)]
257pub struct RdfPredicate(NamedNode);
258
259impl RdfPredicate {
260    /// Create a new predicate from an IRI
261    pub fn new(iri: &str) -> RdfResult<Self> {
262        Ok(Self(NamedNode::new(iri)?))
263    }
264
265    /// Get the underlying named node
266    pub fn as_named_node(&self) -> &NamedNode {
267        &self.0
268    }
269}
270
271impl fmt::Display for RdfPredicate {
272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273        write!(f, "{}", self.0)
274    }
275}
276
277impl From<NamedNode> for RdfPredicate {
278    fn from(node: NamedNode) -> Self {
279        RdfPredicate(node)
280    }
281}
282
283impl From<RdfPredicate> for NamedNode {
284    fn from(pred: RdfPredicate) -> Self {
285        pred.0
286    }
287}
288
289/// RDF object (NamedNode, BlankNode, or Literal)
290#[derive(Debug, Clone, PartialEq, Eq, Hash)]
291pub enum RdfObject {
292    /// Named node (IRI)
293    NamedNode(NamedNode),
294    /// Blank node
295    BlankNode(BlankNode),
296    /// Literal value
297    Literal(Literal),
298}
299
300impl RdfObject {
301    /// Check if this is a named node
302    pub fn is_named_node(&self) -> bool {
303        matches!(self, RdfObject::NamedNode(_))
304    }
305
306    /// Check if this is a blank node
307    pub fn is_blank_node(&self) -> bool {
308        matches!(self, RdfObject::BlankNode(_))
309    }
310
311    /// Check if this is a literal
312    pub fn is_literal(&self) -> bool {
313        matches!(self, RdfObject::Literal(_))
314    }
315}
316
317impl fmt::Display for RdfObject {
318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319        match self {
320            RdfObject::NamedNode(n) => write!(f, "{}", n),
321            RdfObject::BlankNode(b) => write!(f, "{}", b),
322            RdfObject::Literal(l) => write!(f, "{}", l),
323        }
324    }
325}
326
327impl From<NamedNode> for RdfObject {
328    fn from(node: NamedNode) -> Self {
329        RdfObject::NamedNode(node)
330    }
331}
332
333impl From<BlankNode> for RdfObject {
334    fn from(node: BlankNode) -> Self {
335        RdfObject::BlankNode(node)
336    }
337}
338
339impl From<Literal> for RdfObject {
340    fn from(lit: Literal) -> Self {
341        RdfObject::Literal(lit)
342    }
343}
344
345impl From<OxTerm> for RdfObject {
346    fn from(term: OxTerm) -> Self {
347        match term {
348            OxTerm::NamedNode(n) => RdfObject::NamedNode(n.into()),
349            OxTerm::BlankNode(b) => RdfObject::BlankNode(b.into()),
350            OxTerm::Literal(l) => RdfObject::Literal(l.into()),
351            #[allow(unreachable_patterns)]
352            _ => panic!("RDF-star triples not yet supported"),
353        }
354    }
355}
356
357impl From<RdfObject> for OxTerm {
358    fn from(object: RdfObject) -> Self {
359        match object {
360            RdfObject::NamedNode(n) => OxTerm::NamedNode(n.0),
361            RdfObject::BlankNode(b) => OxTerm::BlankNode(b.0),
362            RdfObject::Literal(l) => OxTerm::Literal(l.0),
363        }
364    }
365}
366
367/// RDF term (any RDF value)
368#[derive(Debug, Clone, PartialEq, Eq, Hash)]
369pub enum RdfTerm {
370    /// Named node (IRI)
371    NamedNode(NamedNode),
372    /// Blank node
373    BlankNode(BlankNode),
374    /// Literal value
375    Literal(Literal),
376}
377
378impl From<RdfSubject> for RdfTerm {
379    fn from(subject: RdfSubject) -> Self {
380        match subject {
381            RdfSubject::NamedNode(n) => RdfTerm::NamedNode(n),
382            RdfSubject::BlankNode(b) => RdfTerm::BlankNode(b),
383        }
384    }
385}
386
387impl From<RdfObject> for RdfTerm {
388    fn from(object: RdfObject) -> Self {
389        match object {
390            RdfObject::NamedNode(n) => RdfTerm::NamedNode(n),
391            RdfObject::BlankNode(b) => RdfTerm::BlankNode(b),
392            RdfObject::Literal(l) => RdfTerm::Literal(l),
393        }
394    }
395}
396
397/// RDF triple (subject-predicate-object)
398#[derive(Debug, Clone, PartialEq, Eq, Hash)]
399pub struct Triple {
400    /// Subject
401    pub subject: RdfSubject,
402    /// Predicate
403    pub predicate: RdfPredicate,
404    /// Object
405    pub object: RdfObject,
406}
407
408impl Triple {
409    /// Create a new triple
410    pub fn new(subject: RdfSubject, predicate: RdfPredicate, object: RdfObject) -> Self {
411        Self {
412            subject,
413            predicate,
414            object,
415        }
416    }
417
418    /// Convert to oxrdf Triple
419    pub fn to_oxrdf(&self) -> OxTriple {
420        let subject: OxSubject = self.subject.clone().into();
421        let predicate: OxNamedNode = self.predicate.clone().0.into();
422        let object: OxTerm = self.object.clone().into();
423
424        OxTriple::new(subject, predicate, object)
425    }
426}
427
428impl fmt::Display for Triple {
429    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430        write!(f, "{} {} {} .", self.subject, self.predicate, self.object)
431    }
432}
433
434impl From<OxTriple> for Triple {
435    fn from(triple: OxTriple) -> Self {
436        Self {
437            subject: triple.subject.into(),
438            predicate: RdfPredicate(triple.predicate.into()),
439            object: triple.object.into(),
440        }
441    }
442}
443
444/// RDF quad (triple + named graph)
445#[derive(Debug, Clone, PartialEq, Eq, Hash)]
446pub struct Quad {
447    /// Subject
448    pub subject: RdfSubject,
449    /// Predicate
450    pub predicate: RdfPredicate,
451    /// Object
452    pub object: RdfObject,
453    /// Named graph (None = default graph)
454    pub graph: Option<NamedNode>,
455}
456
457impl Quad {
458    /// Create a new quad
459    pub fn new(
460        subject: RdfSubject,
461        predicate: RdfPredicate,
462        object: RdfObject,
463        graph: Option<NamedNode>,
464    ) -> Self {
465        Self {
466            subject,
467            predicate,
468            object,
469            graph,
470        }
471    }
472
473    /// Create a quad from a triple (default graph)
474    pub fn from_triple(triple: Triple) -> Self {
475        Self {
476            subject: triple.subject,
477            predicate: triple.predicate,
478            object: triple.object,
479            graph: None,
480        }
481    }
482
483    /// Get the triple part (without graph)
484    pub fn as_triple(&self) -> Triple {
485        Triple {
486            subject: self.subject.clone(),
487            predicate: self.predicate.clone(),
488            object: self.object.clone(),
489        }
490    }
491}
492
493impl fmt::Display for Quad {
494    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
495        if let Some(graph) = &self.graph {
496            write!(
497                f,
498                "{} {} {} {} .",
499                self.subject, self.predicate, self.object, graph
500            )
501        } else {
502            write!(f, "{} {} {} .", self.subject, self.predicate, self.object)
503        }
504    }
505}
506
507/// Triple pattern for queries (with optional variables)
508#[derive(Debug, Clone, PartialEq, Eq)]
509pub struct TriplePattern {
510    /// Subject (None = variable)
511    pub subject: Option<RdfSubject>,
512    /// Predicate (None = variable)
513    pub predicate: Option<RdfPredicate>,
514    /// Object (None = variable)
515    pub object: Option<RdfObject>,
516}
517
518impl TriplePattern {
519    /// Create a new triple pattern
520    pub fn new(
521        subject: Option<RdfSubject>,
522        predicate: Option<RdfPredicate>,
523        object: Option<RdfObject>,
524    ) -> Self {
525        Self {
526            subject,
527            predicate,
528            object,
529        }
530    }
531
532    /// Check if a triple matches this pattern
533    pub fn matches(&self, triple: &Triple) -> bool {
534        if let Some(ref s) = self.subject {
535            if s != &triple.subject {
536                return false;
537            }
538        }
539        if let Some(ref p) = self.predicate {
540            if p != &triple.predicate {
541                return false;
542            }
543        }
544        if let Some(ref o) = self.object {
545            if o != &triple.object {
546                return false;
547            }
548        }
549        true
550    }
551}
552
553/// Quad pattern for queries (with optional variables)
554#[derive(Debug, Clone, PartialEq, Eq)]
555pub struct QuadPattern {
556    /// Subject (None = variable)
557    pub subject: Option<RdfSubject>,
558    /// Predicate (None = variable)
559    pub predicate: Option<RdfPredicate>,
560    /// Object (None = variable)
561    pub object: Option<RdfObject>,
562    /// Graph (None = variable, Some(None) = default graph)
563    pub graph: Option<Option<NamedNode>>,
564}
565
566impl QuadPattern {
567    /// Check if a quad matches this pattern
568    pub fn matches(&self, quad: &Quad) -> bool {
569        if let Some(ref s) = self.subject {
570            if s != &quad.subject {
571                return false;
572            }
573        }
574        if let Some(ref p) = self.predicate {
575            if p != &quad.predicate {
576                return false;
577            }
578        }
579        if let Some(ref o) = self.object {
580            if o != &quad.object {
581                return false;
582            }
583        }
584        if let Some(ref g) = self.graph {
585            if g != &quad.graph {
586                return false;
587            }
588        }
589        true
590    }
591}
592
593#[cfg(test)]
594mod tests {
595    use super::*;
596
597    #[test]
598    fn test_named_node() {
599        let node = NamedNode::new("http://example.org/alice").unwrap();
600        assert_eq!(node.as_str(), "http://example.org/alice");
601        assert_eq!(node.to_string(), "<http://example.org/alice>");
602    }
603
604    #[test]
605    fn test_blank_node() {
606        let node1 = BlankNode::new();
607        let node2 = BlankNode::new();
608        assert_ne!(node1, node2); // Should have unique identifiers
609    }
610
611    #[test]
612    fn test_literal() {
613        // Simple literal
614        let lit = Literal::new_simple_literal("Alice");
615        assert_eq!(lit.value(), "Alice");
616
617        // Language-tagged literal
618        let lit = Literal::new_language_tagged_literal("Alice", "en").unwrap();
619        assert_eq!(lit.value(), "Alice");
620        assert_eq!(lit.language(), Some("en"));
621    }
622
623    #[test]
624    fn test_triple() {
625        let subject = NamedNode::new("http://example.org/alice").unwrap();
626        let predicate = RdfPredicate::new("http://xmlns.com/foaf/0.1/name").unwrap();
627        let object = Literal::new_simple_literal("Alice");
628
629        let triple = Triple::new(subject.into(), predicate, object.into());
630
631        assert!(triple.subject.is_named_node());
632        assert!(triple.object.is_literal());
633    }
634
635    #[test]
636    fn test_triple_pattern_matching() {
637        let subject = NamedNode::new("http://example.org/alice").unwrap();
638        let predicate = RdfPredicate::new("http://xmlns.com/foaf/0.1/name").unwrap();
639        let object = Literal::new_simple_literal("Alice");
640
641        let triple = Triple::new(subject.clone().into(), predicate.clone(), object.into());
642
643        // Pattern with subject
644        let pattern = TriplePattern::new(Some(subject.into()), None, None);
645        assert!(pattern.matches(&triple));
646
647        // Pattern with wrong subject
648        let wrong_subject = NamedNode::new("http://example.org/bob").unwrap();
649        let pattern = TriplePattern::new(Some(wrong_subject.into()), None, None);
650        assert!(!pattern.matches(&triple));
651
652        // Pattern with all variables
653        let pattern = TriplePattern::new(None, None, None);
654        assert!(pattern.matches(&triple));
655    }
656
657    #[test]
658    fn test_quad() {
659        let subject = NamedNode::new("http://example.org/alice").unwrap();
660        let predicate = RdfPredicate::new("http://xmlns.com/foaf/0.1/name").unwrap();
661        let object = Literal::new_simple_literal("Alice");
662        let graph = NamedNode::new("http://example.org/graph/social").unwrap();
663
664        let quad = Quad::new(subject.into(), predicate, object.into(), Some(graph));
665
666        assert!(quad.graph.is_some());
667
668        let triple = quad.as_triple();
669        assert!(triple.subject.is_named_node());
670    }
671
672    // ========== Batch 6: Additional RDF Types Tests ==========
673
674    #[test]
675    fn test_named_node_inner() {
676        let node = NamedNode::new("http://example.org/foo").unwrap();
677        let inner = node.inner();
678        assert_eq!(inner.as_str(), "http://example.org/foo");
679    }
680
681    #[test]
682    fn test_blank_node_from_str() {
683        let bn = BlankNode::from_str("b1").unwrap();
684        assert_eq!(bn.as_str(), "b1");
685        let inner = bn.inner();
686        assert_eq!(inner.as_str(), "b1");
687    }
688
689    #[test]
690    fn test_literal_language_tagged() {
691        let lit = Literal::new_language_tagged_literal("hello", "en").unwrap();
692        assert_eq!(lit.value(), "hello");
693        assert_eq!(lit.language(), Some("en"));
694    }
695
696    #[test]
697    fn test_literal_typed() {
698        let dt = NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").unwrap();
699        let lit = Literal::new_typed_literal("42", dt.clone());
700        assert_eq!(lit.value(), "42");
701        let dt2 = lit.datatype();
702        assert_eq!(dt2.as_str(), dt.as_str());
703    }
704
705    #[test]
706    fn test_literal_inner() {
707        let lit = Literal::new_simple_literal("test");
708        let inner = lit.inner();
709        assert_eq!(inner.value(), "test");
710    }
711
712    #[test]
713    fn test_rdf_subject_type_checks() {
714        let named = NamedNode::new("http://example.org/s").unwrap();
715        let subj = RdfSubject::from(named);
716        assert!(subj.is_named_node());
717        assert!(!subj.is_blank_node());
718
719        let blank = BlankNode::new();
720        let subj2 = RdfSubject::from(blank);
721        assert!(!subj2.is_named_node());
722        assert!(subj2.is_blank_node());
723    }
724
725    #[test]
726    fn test_rdf_predicate() {
727        let pred = RdfPredicate::new("http://example.org/p").unwrap();
728        let nn = pred.as_named_node();
729        assert_eq!(nn.as_str(), "http://example.org/p");
730    }
731
732    #[test]
733    fn test_rdf_object_type_checks() {
734        let named = NamedNode::new("http://example.org/o").unwrap();
735        let obj = RdfObject::from(named);
736        assert!(obj.is_named_node());
737        assert!(!obj.is_blank_node());
738        assert!(!obj.is_literal());
739
740        let lit = Literal::new_simple_literal("text");
741        let obj2 = RdfObject::from(lit);
742        assert!(!obj2.is_named_node());
743        assert!(!obj2.is_blank_node());
744        assert!(obj2.is_literal());
745
746        let blank = BlankNode::new();
747        let obj3 = RdfObject::from(blank);
748        assert!(!obj3.is_named_node());
749        assert!(obj3.is_blank_node());
750        assert!(!obj3.is_literal());
751    }
752
753    #[test]
754    fn test_triple_to_oxrdf() {
755        let subj = RdfSubject::from(NamedNode::new("http://example.org/s").unwrap());
756        let pred = RdfPredicate::new("http://example.org/p").unwrap();
757        let obj = RdfObject::from(Literal::new_simple_literal("value"));
758        let triple = Triple::new(subj, pred, obj);
759
760        let ox = triple.to_oxrdf();
761        assert_eq!(ox.subject.to_string(), "<http://example.org/s>");
762    }
763
764    #[test]
765    fn test_quad_from_triple() {
766        let subj = RdfSubject::from(NamedNode::new("http://example.org/s").unwrap());
767        let pred = RdfPredicate::new("http://example.org/p").unwrap();
768        let obj = RdfObject::from(Literal::new_simple_literal("value"));
769        let triple = Triple::new(subj, pred, obj);
770
771        let quad = Quad::from_triple(triple.clone());
772        assert!(quad.graph.is_none());
773        let back = quad.as_triple();
774        assert!(back.subject.is_named_node());
775    }
776
777    #[test]
778    fn test_triple_pattern_new() {
779        let subj = Some(RdfSubject::from(
780            NamedNode::new("http://example.org/s").unwrap(),
781        ));
782        let pattern = TriplePattern::new(subj, None, None);
783        assert!(pattern.subject.is_some());
784        assert!(pattern.predicate.is_none());
785        assert!(pattern.object.is_none());
786    }
787
788    #[test]
789    fn test_quad_pattern_matches() {
790        let subj = RdfSubject::from(NamedNode::new("http://example.org/s").unwrap());
791        let pred = RdfPredicate::new("http://example.org/p").unwrap();
792        let obj = RdfObject::from(Literal::new_simple_literal("value"));
793        let triple = Triple::new(subj.clone(), pred.clone(), obj.clone());
794        let quad = Quad::from_triple(triple);
795
796        // Wildcard pattern matches everything
797        let pattern = QuadPattern {
798            subject: None,
799            predicate: None,
800            object: None,
801            graph: None,
802        };
803        assert!(pattern.matches(&quad));
804
805        // Specific subject match
806        let pattern2 = QuadPattern {
807            subject: Some(subj),
808            predicate: None,
809            object: None,
810            graph: None,
811        };
812        assert!(pattern2.matches(&quad));
813    }
814
815    #[test]
816    fn test_triple_display() {
817        let subj = RdfSubject::from(NamedNode::new("http://example.org/s").unwrap());
818        let pred = RdfPredicate::new("http://example.org/p").unwrap();
819        let obj = RdfObject::from(Literal::new_simple_literal("hello"));
820        let triple = Triple::new(subj, pred, obj);
821
822        let display = format!("{}", triple);
823        assert!(display.contains("http://example.org/s"));
824        assert!(display.contains("http://example.org/p"));
825    }
826
827    // ========== Mop-up: From/Display/conversion coverage ==========
828
829    #[test]
830    fn test_named_node_from_ox() {
831        let ox_node = OxNamedNode::new("http://example.org/test").unwrap();
832        let node: NamedNode = ox_node.into();
833        assert_eq!(node.as_str(), "http://example.org/test");
834    }
835
836    #[test]
837    fn test_named_node_into_ox() {
838        let node = NamedNode::new("http://example.org/test").unwrap();
839        let ox_node: OxNamedNode = node.into();
840        assert_eq!(ox_node.as_str(), "http://example.org/test");
841    }
842
843    #[test]
844    fn test_blank_node_default() {
845        let bn = BlankNode::default();
846        assert!(!bn.as_str().is_empty());
847    }
848
849    #[test]
850    fn test_blank_node_display() {
851        let bn = BlankNode::from_str("b42").unwrap();
852        let display = format!("{}", bn);
853        assert!(display.starts_with("_:"));
854        assert!(display.contains("b42"));
855    }
856
857    #[test]
858    fn test_blank_node_from_ox() {
859        let ox_bn = OxBlankNode::default();
860        let bn: BlankNode = ox_bn.into();
861        assert!(!bn.as_str().is_empty());
862    }
863
864    #[test]
865    fn test_blank_node_into_ox() {
866        let bn = BlankNode::from_str("b1").unwrap();
867        let ox_bn: OxBlankNode = bn.into();
868        assert_eq!(ox_bn.as_str(), "b1");
869    }
870
871    #[test]
872    fn test_literal_display_simple() {
873        let lit = Literal::new_simple_literal("hello");
874        let display = format!("{}", lit);
875        assert!(display.contains("hello"));
876    }
877
878    #[test]
879    fn test_literal_display_language_tagged() {
880        let lit = Literal::new_language_tagged_literal("hello", "en").unwrap();
881        let display = format!("{}", lit);
882        assert!(display.contains("hello"));
883        assert!(display.contains("@en"));
884    }
885
886    #[test]
887    fn test_literal_display_typed() {
888        let dt = NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").unwrap();
889        let lit = Literal::new_typed_literal("42", dt);
890        let display = format!("{}", lit);
891        assert!(display.contains("42"));
892    }
893
894    #[test]
895    fn test_literal_from_ox() {
896        let ox_lit = OxLiteral::new_simple_literal("test");
897        let lit: Literal = ox_lit.into();
898        assert_eq!(lit.value(), "test");
899    }
900
901    #[test]
902    fn test_literal_into_ox() {
903        let lit = Literal::new_simple_literal("test");
904        let ox_lit: OxLiteral = lit.into();
905        assert_eq!(ox_lit.value(), "test");
906    }
907
908    #[test]
909    fn test_rdf_subject_display() {
910        let named = NamedNode::new("http://example.org/s").unwrap();
911        let subj = RdfSubject::from(named);
912        let display = format!("{}", subj);
913        assert!(display.contains("http://example.org/s"));
914
915        let blank = BlankNode::from_str("b1").unwrap();
916        let subj2 = RdfSubject::from(blank);
917        let display2 = format!("{}", subj2);
918        assert!(display2.starts_with("_:"));
919    }
920
921    #[test]
922    fn test_rdf_subject_from_ox() {
923        let ox_nn = OxNamedNode::new("http://example.org/s").unwrap();
924        let ox_subj = OxSubject::NamedNode(ox_nn);
925        let subj: RdfSubject = ox_subj.into();
926        assert!(subj.is_named_node());
927
928        let ox_bn = OxBlankNode::default();
929        let ox_subj2 = OxSubject::BlankNode(ox_bn);
930        let subj2: RdfSubject = ox_subj2.into();
931        assert!(subj2.is_blank_node());
932    }
933
934    #[test]
935    fn test_rdf_predicate_from_named_node() {
936        let nn = NamedNode::new("http://example.org/p").unwrap();
937        let pred: RdfPredicate = nn.into();
938        assert_eq!(pred.as_named_node().as_str(), "http://example.org/p");
939    }
940
941    #[test]
942    fn test_rdf_predicate_into_named_node() {
943        let pred = RdfPredicate::new("http://example.org/p").unwrap();
944        let nn: NamedNode = pred.into();
945        assert_eq!(nn.as_str(), "http://example.org/p");
946    }
947
948    #[test]
949    fn test_rdf_predicate_display() {
950        let pred = RdfPredicate::new("http://example.org/p").unwrap();
951        let display = format!("{}", pred);
952        assert!(display.contains("http://example.org/p"));
953    }
954
955    #[test]
956    fn test_rdf_object_display() {
957        let named = NamedNode::new("http://example.org/o").unwrap();
958        let obj = RdfObject::from(named);
959        let display = format!("{}", obj);
960        assert!(display.contains("http://example.org/o"));
961
962        let blank = BlankNode::from_str("b2").unwrap();
963        let obj2 = RdfObject::from(blank);
964        let display2 = format!("{}", obj2);
965        assert!(display2.starts_with("_:"));
966
967        let lit = Literal::new_simple_literal("value");
968        let obj3 = RdfObject::from(lit);
969        let display3 = format!("{}", obj3);
970        assert!(display3.contains("value"));
971    }
972
973    #[test]
974    fn test_rdf_object_from_ox_term() {
975        let ox_nn = OxNamedNode::new("http://example.org/o").unwrap();
976        let term = OxTerm::NamedNode(ox_nn);
977        let obj: RdfObject = term.into();
978        assert!(obj.is_named_node());
979
980        let ox_bn = OxBlankNode::default();
981        let term2 = OxTerm::BlankNode(ox_bn);
982        let obj2: RdfObject = term2.into();
983        assert!(obj2.is_blank_node());
984
985        let ox_lit = OxLiteral::new_simple_literal("test");
986        let term3 = OxTerm::Literal(ox_lit);
987        let obj3: RdfObject = term3.into();
988        assert!(obj3.is_literal());
989    }
990
991    #[test]
992    fn test_rdf_error_display() {
993        let e1 = RdfError::InvalidIri("bad iri".to_string());
994        let s1 = format!("{}", e1);
995        assert!(s1.contains("Invalid IRI"));
996
997        let e2 = RdfError::InvalidBlankNode("bad bn".to_string());
998        let s2 = format!("{}", e2);
999        assert!(s2.contains("Invalid blank node"));
1000
1001        let e3 = RdfError::InvalidLiteral("bad lit".to_string());
1002        let s3 = format!("{}", e3);
1003        assert!(s3.contains("Invalid literal"));
1004    }
1005
1006    #[test]
1007    fn test_named_node_invalid_iri() {
1008        let result = NamedNode::new("not a valid iri");
1009        assert!(result.is_err());
1010    }
1011
1012    // ========== Coverage batch: additional RDF types tests ==========
1013
1014    #[test]
1015    fn test_blank_node_invalid_str() {
1016        // Blank node IDs with spaces are invalid
1017        let result = BlankNode::from_str("invalid blank node id!");
1018        assert!(result.is_err());
1019    }
1020
1021    #[test]
1022    fn test_literal_language_tag_invalid() {
1023        // Invalid language tag
1024        let result = Literal::new_language_tagged_literal("hello", "invalid tag with spaces");
1025        assert!(result.is_err());
1026    }
1027
1028    #[test]
1029    fn test_literal_simple_no_language() {
1030        let lit = Literal::new_simple_literal("test value");
1031        assert_eq!(lit.value(), "test value");
1032        assert!(lit.language().is_none());
1033    }
1034
1035    #[test]
1036    fn test_literal_datatype_for_simple() {
1037        let lit = Literal::new_simple_literal("hello");
1038        let dt = lit.datatype();
1039        // Simple literals have xsd:string datatype
1040        assert!(dt.as_str().contains("string"));
1041    }
1042
1043    #[test]
1044    fn test_rdf_subject_into_ox_named() {
1045        let nn = NamedNode::new("http://example.org/s").unwrap();
1046        let subj = RdfSubject::NamedNode(nn);
1047        let ox_subj: OxSubject = subj.into();
1048        match ox_subj {
1049            OxSubject::NamedNode(n) => assert_eq!(n.as_str(), "http://example.org/s"),
1050            _ => panic!("Expected NamedNode"),
1051        }
1052    }
1053
1054    #[test]
1055    fn test_rdf_subject_into_ox_blank() {
1056        let bn = BlankNode::from_str("b99").unwrap();
1057        let subj = RdfSubject::BlankNode(bn);
1058        let ox_subj: OxSubject = subj.into();
1059        match ox_subj {
1060            OxSubject::BlankNode(b) => assert_eq!(b.as_str(), "b99"),
1061            _ => panic!("Expected BlankNode"),
1062        }
1063    }
1064
1065    #[test]
1066    fn test_rdf_object_into_ox_term_named() {
1067        let nn = NamedNode::new("http://example.org/o").unwrap();
1068        let obj = RdfObject::NamedNode(nn);
1069        let term: OxTerm = obj.into();
1070        match term {
1071            OxTerm::NamedNode(n) => assert_eq!(n.as_str(), "http://example.org/o"),
1072            _ => panic!("Expected NamedNode"),
1073        }
1074    }
1075
1076    #[test]
1077    fn test_rdf_object_into_ox_term_blank() {
1078        let bn = BlankNode::from_str("b1").unwrap();
1079        let obj = RdfObject::BlankNode(bn);
1080        let term: OxTerm = obj.into();
1081        match term {
1082            OxTerm::BlankNode(b) => assert_eq!(b.as_str(), "b1"),
1083            _ => panic!("Expected BlankNode"),
1084        }
1085    }
1086
1087    #[test]
1088    fn test_rdf_object_into_ox_term_literal() {
1089        let lit = Literal::new_simple_literal("val");
1090        let obj = RdfObject::Literal(lit);
1091        let term: OxTerm = obj.into();
1092        match term {
1093            OxTerm::Literal(l) => assert_eq!(l.value(), "val"),
1094            _ => panic!("Expected Literal"),
1095        }
1096    }
1097
1098    #[test]
1099    fn test_rdf_term_from_subject_named() {
1100        let nn = NamedNode::new("http://example.org/t").unwrap();
1101        let subj = RdfSubject::NamedNode(nn);
1102        let term: RdfTerm = subj.into();
1103        assert!(matches!(term, RdfTerm::NamedNode(_)));
1104    }
1105
1106    #[test]
1107    fn test_rdf_term_from_subject_blank() {
1108        let bn = BlankNode::new();
1109        let subj = RdfSubject::BlankNode(bn);
1110        let term: RdfTerm = subj.into();
1111        assert!(matches!(term, RdfTerm::BlankNode(_)));
1112    }
1113
1114    #[test]
1115    fn test_rdf_term_from_object_named() {
1116        let nn = NamedNode::new("http://example.org/t2").unwrap();
1117        let obj = RdfObject::NamedNode(nn);
1118        let term: RdfTerm = obj.into();
1119        assert!(matches!(term, RdfTerm::NamedNode(_)));
1120    }
1121
1122    #[test]
1123    fn test_rdf_term_from_object_blank() {
1124        let bn = BlankNode::new();
1125        let obj = RdfObject::BlankNode(bn);
1126        let term: RdfTerm = obj.into();
1127        assert!(matches!(term, RdfTerm::BlankNode(_)));
1128    }
1129
1130    #[test]
1131    fn test_rdf_term_from_object_literal() {
1132        let lit = Literal::new_simple_literal("val");
1133        let obj = RdfObject::Literal(lit);
1134        let term: RdfTerm = obj.into();
1135        assert!(matches!(term, RdfTerm::Literal(_)));
1136    }
1137
1138    #[test]
1139    fn test_triple_from_ox_triple() {
1140        let ox_subj = OxSubject::NamedNode(OxNamedNode::new("http://example.org/s").unwrap());
1141        let ox_pred = OxNamedNode::new("http://example.org/p").unwrap();
1142        let ox_obj = OxTerm::Literal(OxLiteral::new_simple_literal("value"));
1143        let ox_triple = OxTriple::new(ox_subj, ox_pred, ox_obj);
1144
1145        let triple: Triple = ox_triple.into();
1146        assert!(triple.subject.is_named_node());
1147        assert!(triple.object.is_literal());
1148    }
1149
1150    #[test]
1151    fn test_triple_display_full() {
1152        let subj = RdfSubject::from(NamedNode::new("http://example.org/alice").unwrap());
1153        let pred = RdfPredicate::new("http://xmlns.com/foaf/0.1/name").unwrap();
1154        let obj = RdfObject::from(Literal::new_simple_literal("Alice"));
1155        let triple = Triple::new(subj, pred, obj);
1156        let display = format!("{}", triple);
1157        assert!(display.contains("<http://example.org/alice>"));
1158        assert!(display.contains("<http://xmlns.com/foaf/0.1/name>"));
1159        assert!(display.contains("Alice"));
1160        assert!(display.ends_with("."));
1161    }
1162
1163    #[test]
1164    fn test_quad_display_with_graph() {
1165        let subj = RdfSubject::from(NamedNode::new("http://example.org/s").unwrap());
1166        let pred = RdfPredicate::new("http://example.org/p").unwrap();
1167        let obj = RdfObject::from(Literal::new_simple_literal("v"));
1168        let graph = NamedNode::new("http://example.org/g").unwrap();
1169        let quad = Quad::new(subj, pred, obj, Some(graph));
1170        let display = format!("{}", quad);
1171        assert!(display.contains("<http://example.org/g>"));
1172        assert!(display.ends_with("."));
1173    }
1174
1175    #[test]
1176    fn test_quad_display_default_graph() {
1177        let subj = RdfSubject::from(NamedNode::new("http://example.org/s").unwrap());
1178        let pred = RdfPredicate::new("http://example.org/p").unwrap();
1179        let obj = RdfObject::from(Literal::new_simple_literal("v"));
1180        let quad = Quad::new(subj, pred, obj, None);
1181        let display = format!("{}", quad);
1182        assert!(display.ends_with("."));
1183        assert!(!display.contains("graph"));
1184    }
1185
1186    #[test]
1187    fn test_triple_pattern_predicate_match() {
1188        let subj = NamedNode::new("http://example.org/s").unwrap();
1189        let pred = RdfPredicate::new("http://example.org/p1").unwrap();
1190        let obj = Literal::new_simple_literal("val");
1191        let triple = Triple::new(subj.into(), pred.clone(), obj.into());
1192
1193        // Pattern matching on predicate
1194        let pattern = TriplePattern::new(None, Some(pred), None);
1195        assert!(pattern.matches(&triple));
1196
1197        // Non-matching predicate
1198        let other_pred = RdfPredicate::new("http://example.org/p2").unwrap();
1199        let pattern2 = TriplePattern::new(None, Some(other_pred), None);
1200        assert!(!pattern2.matches(&triple));
1201    }
1202
1203    #[test]
1204    fn test_triple_pattern_object_match() {
1205        let subj = NamedNode::new("http://example.org/s").unwrap();
1206        let pred = RdfPredicate::new("http://example.org/p").unwrap();
1207        let obj = Literal::new_simple_literal("target_value");
1208        let triple = Triple::new(subj.into(), pred, obj.clone().into());
1209
1210        // Pattern matching on object
1211        let pattern = TriplePattern::new(None, None, Some(obj.into()));
1212        assert!(pattern.matches(&triple));
1213
1214        // Non-matching object
1215        let other_obj = RdfObject::from(Literal::new_simple_literal("other_value"));
1216        let pattern2 = TriplePattern::new(None, None, Some(other_obj));
1217        assert!(!pattern2.matches(&triple));
1218    }
1219
1220    #[test]
1221    fn test_quad_pattern_graph_match() {
1222        let subj = RdfSubject::from(NamedNode::new("http://example.org/s").unwrap());
1223        let pred = RdfPredicate::new("http://example.org/p").unwrap();
1224        let obj = RdfObject::from(Literal::new_simple_literal("v"));
1225        let graph = NamedNode::new("http://example.org/g").unwrap();
1226        let quad = Quad::new(subj, pred, obj, Some(graph.clone()));
1227
1228        // Match specific graph
1229        let pattern = QuadPattern {
1230            subject: None,
1231            predicate: None,
1232            object: None,
1233            graph: Some(Some(graph)),
1234        };
1235        assert!(pattern.matches(&quad));
1236
1237        // Match default graph (None) against quad with named graph => no match
1238        let pattern_default = QuadPattern {
1239            subject: None,
1240            predicate: None,
1241            object: None,
1242            graph: Some(None),
1243        };
1244        assert!(!pattern_default.matches(&quad));
1245    }
1246
1247    #[test]
1248    fn test_quad_pattern_predicate_mismatch() {
1249        let subj = RdfSubject::from(NamedNode::new("http://example.org/s").unwrap());
1250        let pred = RdfPredicate::new("http://example.org/p1").unwrap();
1251        let obj = RdfObject::from(Literal::new_simple_literal("v"));
1252        let quad = Quad::from_triple(Triple::new(subj, pred, obj));
1253
1254        let other_pred = RdfPredicate::new("http://example.org/p2").unwrap();
1255        let pattern = QuadPattern {
1256            subject: None,
1257            predicate: Some(other_pred),
1258            object: None,
1259            graph: None,
1260        };
1261        assert!(!pattern.matches(&quad));
1262    }
1263
1264    #[test]
1265    fn test_quad_pattern_object_mismatch() {
1266        let subj = RdfSubject::from(NamedNode::new("http://example.org/s").unwrap());
1267        let pred = RdfPredicate::new("http://example.org/p").unwrap();
1268        let obj = RdfObject::from(Literal::new_simple_literal("val1"));
1269        let quad = Quad::from_triple(Triple::new(subj, pred, obj));
1270
1271        let other_obj = RdfObject::from(Literal::new_simple_literal("val2"));
1272        let pattern = QuadPattern {
1273            subject: None,
1274            predicate: None,
1275            object: Some(other_obj),
1276            graph: None,
1277        };
1278        assert!(!pattern.matches(&quad));
1279    }
1280
1281    #[test]
1282    fn test_named_node_equality() {
1283        let n1 = NamedNode::new("http://example.org/same").unwrap();
1284        let n2 = NamedNode::new("http://example.org/same").unwrap();
1285        let n3 = NamedNode::new("http://example.org/different").unwrap();
1286        assert_eq!(n1, n2);
1287        assert_ne!(n1, n3);
1288    }
1289
1290    #[test]
1291    fn test_blank_node_clone_and_hash() {
1292        let bn = BlankNode::from_str("b1").unwrap();
1293        let bn_clone = bn.clone();
1294        assert_eq!(bn, bn_clone);
1295
1296        // Hash test - same blank node should have same hash
1297        use std::collections::HashSet;
1298        let mut set = HashSet::new();
1299        set.insert(bn.clone());
1300        assert!(set.contains(&bn_clone));
1301    }
1302
1303    #[test]
1304    fn test_literal_clone_and_eq() {
1305        let lit1 = Literal::new_simple_literal("same");
1306        let lit2 = lit1.clone();
1307        assert_eq!(lit1, lit2);
1308    }
1309
1310    #[test]
1311    fn test_quad_as_triple_preserves_content() {
1312        let subj = RdfSubject::from(NamedNode::new("http://example.org/s").unwrap());
1313        let pred = RdfPredicate::new("http://example.org/p").unwrap();
1314        let obj = RdfObject::from(Literal::new_simple_literal("val"));
1315        let graph = NamedNode::new("http://example.org/graph").unwrap();
1316        let quad = Quad::new(subj.clone(), pred.clone(), obj.clone(), Some(graph));
1317
1318        let triple = quad.as_triple();
1319        assert_eq!(triple.subject, subj);
1320        assert_eq!(triple.predicate, pred);
1321        assert_eq!(triple.object, obj);
1322    }
1323}