1use crate::model::{
4 Literal, LiteralRef, NamedNode, NamedNodeRef, ObjectTerm, PredicateTerm, RdfTerm, SubjectTerm,
5};
6use crate::OxirsError;
7use lazy_static::lazy_static;
8use regex::Regex;
10use std::collections::HashSet;
12use std::fmt;
13use std::hash::Hash;
14use std::sync::atomic::{AtomicU64, Ordering};
15
16fn is_pure_hex_numeric_id(id: &str) -> bool {
21 !id.is_empty()
26 && id
27 .chars()
28 .all(|c| c.is_ascii_hexdigit() && (c.is_ascii_lowercase() || c.is_ascii_digit()))
29 && id.starts_with(|c: char| ('a'..='f').contains(&c))
30 && id.len() >= 3 }
32
33fn to_integer_id(id: &str) -> Option<u128> {
34 let digits = id.as_bytes();
35 let mut value: u128 = 0;
36 if let None | Some(b'0') = digits.first() {
37 return None; }
39 for digit in digits {
40 value = value.checked_mul(16)?.checked_add(
41 match *digit {
42 b'0'..=b'9' => digit - b'0',
43 b'a'..=b'f' => digit - b'a' + 10,
44 _ => return None,
45 }
46 .into(),
47 )?;
48 }
49 Some(value)
50}
51
52lazy_static! {
53 static ref BLANK_NODE_REGEX: Regex = Regex::new(
55 r"^[a-zA-Z_][a-zA-Z0-9_.-]*$"
56 ).expect("Blank node regex compilation failed");
57
58 static ref VARIABLE_REGEX: Regex = Regex::new(
60 r"^[a-zA-Z_][a-zA-Z0-9_]*$"
61 ).expect("Variable regex compilation failed");
62
63 static ref BLANK_NODE_COUNTER: AtomicU64 = AtomicU64::new(0);
65
66 static ref BLANK_NODE_IDS: std::sync::Mutex<HashSet<String>> = std::sync::Mutex::new(HashSet::new());
68}
69
70fn validate_blank_node_id(id: &str) -> Result<(), OxirsError> {
72 if id.is_empty() {
73 return Err(OxirsError::Parse(
74 "Blank node ID cannot be empty".to_string(),
75 ));
76 }
77
78 let clean_id = if let Some(stripped) = id.strip_prefix("_:") {
80 stripped
81 } else {
82 id
83 };
84
85 if clean_id.is_empty() {
86 return Err(OxirsError::Parse(
87 "Blank node ID cannot be just '_:'".to_string(),
88 ));
89 }
90
91 if !BLANK_NODE_REGEX.is_match(clean_id) {
92 return Err(OxirsError::Parse(format!(
93 "Invalid blank node ID format: '{clean_id}'. Must match [a-zA-Z0-9_][a-zA-Z0-9_.-]*"
94 )));
95 }
96
97 Ok(())
98}
99
100fn validate_variable_name(name: &str) -> Result<(), OxirsError> {
102 if name.is_empty() {
103 return Err(OxirsError::Parse(
104 "Variable name cannot be empty".to_string(),
105 ));
106 }
107
108 let clean_name = if name.starts_with('?') || name.starts_with('$') {
110 &name[1..]
111 } else {
112 name
113 };
114
115 if clean_name.is_empty() {
116 return Err(OxirsError::Parse(
117 "Variable name cannot be just '?' or '$'".to_string(),
118 ));
119 }
120
121 if !VARIABLE_REGEX.is_match(clean_name) {
122 return Err(OxirsError::Parse(format!(
123 "Invalid variable name format: '{clean_name}'. Must match [a-zA-Z_][a-zA-Z0-9_]*"
124 )));
125 }
126
127 match clean_name.to_lowercase().as_str() {
129 "select" | "where" | "from" | "order" | "group" | "having" | "limit" | "offset"
130 | "distinct" | "reduced" | "construct" | "describe" | "ask" | "union" | "optional"
131 | "filter" | "bind" | "values" | "graph" | "service" | "minus" | "exists" | "not" => {
132 return Err(OxirsError::Parse(format!(
133 "Variable name '{clean_name}' is a reserved SPARQL keyword"
134 )));
135 }
136 _ => {}
137 }
138
139 Ok(())
140}
141
142#[derive(
147 Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
148)]
149pub struct BlankNode {
150 content: BlankNodeContent,
151}
152
153#[derive(
154 Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
155)]
156enum BlankNodeContent {
157 Named(String),
158 Anonymous { id: u128, str: String },
159}
160
161impl Default for BlankNode {
162 fn default() -> Self {
165 loop {
166 let id: u128 = fastrand::u128(..);
167 let str = format!("{id:x}"); if matches!(str.as_bytes().first(), Some(b'a'..=b'f')) {
169 return Self {
170 content: BlankNodeContent::Anonymous { id, str },
171 };
172 }
173 }
174 }
175}
176
177impl BlankNode {
178 pub fn new(id: impl Into<String>) -> Result<Self, OxirsError> {
186 let id = id.into();
187 let clean_id = if let Some(stripped) = id.strip_prefix("_:") {
189 stripped
190 } else {
191 &id
192 };
193 validate_blank_node_id(clean_id)?;
194
195 if is_pure_hex_numeric_id(clean_id) {
198 if let Some(numerical_id) = to_integer_id(clean_id) {
199 Ok(BlankNode {
201 content: BlankNodeContent::Anonymous {
202 id: numerical_id,
203 str: clean_id.to_string(),
204 },
205 })
206 } else {
207 Ok(BlankNode {
208 content: BlankNodeContent::Named(id),
209 })
210 }
211 } else {
212 Ok(BlankNode {
213 content: BlankNodeContent::Named(id),
214 })
215 }
216 }
217
218 pub fn new_unchecked(id: impl Into<String>) -> Self {
223 let id = id.into();
224 let clean_id = if let Some(stripped) = id.strip_prefix("_:") {
226 stripped
227 } else {
228 &id
229 };
230 if let Some(numerical_id) = to_integer_id(clean_id) {
231 Self::new_from_unique_id(numerical_id)
232 } else {
233 BlankNode {
234 content: BlankNodeContent::Named(id),
235 }
236 }
237 }
238
239 pub fn new_from_unique_id(id: u128) -> Self {
243 Self {
244 content: BlankNodeContent::Anonymous {
245 id,
246 str: format!("{id:x}"), },
248 }
249 }
250
251 pub fn new_unique() -> Self {
255 Self::default()
256 }
257
258 pub fn new_unique_with_prefix(prefix: &str) -> Result<Self, OxirsError> {
260 if !BLANK_NODE_REGEX.is_match(prefix) {
262 return Err(OxirsError::Parse(format!(
263 "Invalid blank node prefix: '{prefix}'. Must match [a-zA-Z0-9_][a-zA-Z0-9_.-]*"
264 )));
265 }
266
267 let counter = BLANK_NODE_COUNTER.fetch_add(1, Ordering::SeqCst);
268 let id = format!("{prefix}_{counter}");
269 Ok(BlankNode {
270 content: BlankNodeContent::Named(id),
271 })
272 }
273
274 pub fn id(&self) -> &str {
276 match &self.content {
277 BlankNodeContent::Named(id) => id,
278 BlankNodeContent::Anonymous { str, .. } => str,
279 }
280 }
281
282 pub fn as_str(&self) -> &str {
284 self.id()
285 }
286
287 pub fn unique_id(&self) -> Option<u128> {
289 match &self.content {
290 BlankNodeContent::Named(_) => None,
291 BlankNodeContent::Anonymous { id, .. } => Some(*id),
292 }
293 }
294
295 pub fn local_id(&self) -> &str {
297 let id = self.id();
298 if let Some(stripped) = id.strip_prefix("_:") {
299 stripped
300 } else {
301 id
302 }
303 }
304
305 pub fn as_ref(&self) -> BlankNodeRef<'_> {
307 BlankNodeRef {
308 content: match &self.content {
309 BlankNodeContent::Named(id) => BlankNodeRefContent::Named(id.as_str()),
310 BlankNodeContent::Anonymous { id, str } => BlankNodeRefContent::Anonymous {
311 id: *id,
312 str: str.as_str(),
313 },
314 },
315 }
316 }
317}
318
319impl fmt::Display for BlankNode {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 write!(f, "_:{}", self.local_id())
322 }
323}
324
325impl RdfTerm for BlankNode {
326 fn as_str(&self) -> &str {
327 self.id()
328 }
329
330 fn is_blank_node(&self) -> bool {
331 true
332 }
333}
334
335impl SubjectTerm for BlankNode {}
336impl ObjectTerm for BlankNode {}
337
338#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
342pub struct BlankNodeRef<'a> {
343 content: BlankNodeRefContent<'a>,
344}
345
346#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
347enum BlankNodeRefContent<'a> {
348 Named(&'a str),
349 Anonymous { id: u128, str: &'a str },
350}
351
352impl<'a> BlankNodeRef<'a> {
353 pub fn new(id: &'a str) -> Result<Self, OxirsError> {
361 let clean_id = if let Some(stripped) = id.strip_prefix("_:") {
363 stripped
364 } else {
365 id
366 };
367 validate_blank_node_id(clean_id)?;
368
369 if let Some(numerical_id) = to_integer_id(clean_id) {
371 Ok(BlankNodeRef {
372 content: BlankNodeRefContent::Anonymous {
373 id: numerical_id,
374 str: clean_id,
375 },
376 })
377 } else {
378 Ok(BlankNodeRef {
379 content: BlankNodeRefContent::Named(id),
380 })
381 }
382 }
383
384 pub fn new_unchecked(id: &'a str) -> Self {
389 let clean_id = if let Some(stripped) = id.strip_prefix("_:") {
391 stripped
392 } else {
393 id
394 };
395 if let Some(numerical_id) = to_integer_id(clean_id) {
396 BlankNodeRef {
397 content: BlankNodeRefContent::Anonymous {
398 id: numerical_id,
399 str: clean_id,
400 },
401 }
402 } else {
403 BlankNodeRef {
404 content: BlankNodeRefContent::Named(id),
405 }
406 }
407 }
408
409 pub fn id(&self) -> &str {
411 match &self.content {
412 BlankNodeRefContent::Named(id) => id,
413 BlankNodeRefContent::Anonymous { str, .. } => str,
414 }
415 }
416
417 pub fn as_str(&self) -> &str {
419 self.id()
420 }
421
422 pub fn unique_id(&self) -> Option<u128> {
424 match &self.content {
425 BlankNodeRefContent::Named(_) => None,
426 BlankNodeRefContent::Anonymous { id, .. } => Some(*id),
427 }
428 }
429
430 pub fn local_id(&self) -> &str {
432 let id = self.id();
433 if let Some(stripped) = id.strip_prefix("_:") {
434 stripped
435 } else {
436 id
437 }
438 }
439
440 pub fn to_owned(&self) -> BlankNode {
442 BlankNode::new_unchecked(self.id().to_string())
443 }
444}
445
446impl<'a> fmt::Display for BlankNodeRef<'a> {
447 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448 write!(f, "_:{}", self.local_id())
449 }
450}
451
452impl<'a> RdfTerm for BlankNodeRef<'a> {
453 fn as_str(&self) -> &str {
454 self.id()
455 }
456
457 fn is_blank_node(&self) -> bool {
458 true
459 }
460}
461
462impl<'a> SubjectTerm for BlankNodeRef<'a> {}
463impl<'a> ObjectTerm for BlankNodeRef<'a> {}
464
465impl<'a> From<BlankNodeRef<'a>> for BlankNode {
466 fn from(node_ref: BlankNodeRef<'a>) -> Self {
467 node_ref.to_owned()
468 }
469}
470
471impl<'a> From<&'a BlankNode> for BlankNodeRef<'a> {
472 fn from(node: &'a BlankNode) -> Self {
473 node.as_ref()
474 }
475}
476
477#[derive(
481 Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
482)]
483pub struct Variable {
484 name: String,
485}
486
487impl Variable {
488 pub fn new(name: impl Into<String>) -> Result<Self, OxirsError> {
496 let name = name.into();
497 validate_variable_name(&name)?;
498
499 let clean_name = if let Some(stripped) = name.strip_prefix('?') {
501 stripped.to_string()
502 } else if let Some(stripped) = name.strip_prefix('$') {
503 stripped.to_string()
504 } else {
505 name
506 };
507
508 Ok(Variable { name: clean_name })
509 }
510
511 pub fn new_unchecked(name: impl Into<String>) -> Self {
516 Variable { name: name.into() }
517 }
518
519 pub fn name(&self) -> &str {
521 &self.name
522 }
523
524 pub fn as_str(&self) -> &str {
526 &self.name
527 }
528
529 pub fn with_prefix(&self) -> String {
531 format!("?{}", self.name)
532 }
533}
534
535impl fmt::Display for Variable {
536 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
537 write!(f, "?{}", self.name)
538 }
539}
540
541impl Variable {
542 pub fn fmt_sse(&self, f: &mut impl fmt::Write) -> fmt::Result {
544 write!(f, "?{}", self.name)
545 }
546}
547
548impl RdfTerm for Variable {
549 fn as_str(&self) -> &str {
550 &self.name
551 }
552
553 fn is_variable(&self) -> bool {
554 true
555 }
556}
557
558#[derive(
590 Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
591)]
592pub enum Term {
593 NamedNode(NamedNode),
598
599 BlankNode(BlankNode),
604
605 Literal(Literal),
610
611 Variable(Variable),
616
617 QuotedTriple(Box<crate::model::star::QuotedTriple>),
622}
623
624impl Term {
625 pub fn is_named_node(&self) -> bool {
639 matches!(self, Term::NamedNode(_))
640 }
641
642 pub fn is_blank_node(&self) -> bool {
656 matches!(self, Term::BlankNode(_))
657 }
658
659 pub fn is_literal(&self) -> bool {
673 matches!(self, Term::Literal(_))
674 }
675
676 pub fn is_variable(&self) -> bool {
690 matches!(self, Term::Variable(_))
691 }
692
693 pub fn is_quoted_triple(&self) -> bool {
706 matches!(self, Term::QuotedTriple(_))
707 }
708
709 pub fn as_named_node(&self) -> Option<&NamedNode> {
711 match self {
712 Term::NamedNode(n) => Some(n),
713 _ => None,
714 }
715 }
716
717 pub fn as_blank_node(&self) -> Option<&BlankNode> {
719 match self {
720 Term::BlankNode(b) => Some(b),
721 _ => None,
722 }
723 }
724
725 pub fn as_literal(&self) -> Option<&Literal> {
727 match self {
728 Term::Literal(l) => Some(l),
729 _ => None,
730 }
731 }
732
733 pub fn as_variable(&self) -> Option<&Variable> {
735 match self {
736 Term::Variable(v) => Some(v),
737 _ => None,
738 }
739 }
740
741 pub fn as_quoted_triple(&self) -> Option<&crate::model::star::QuotedTriple> {
743 match self {
744 Term::QuotedTriple(qt) => Some(qt),
745 _ => None,
746 }
747 }
748
749 pub fn from_subject(subject: &crate::model::Subject) -> Term {
751 match subject {
752 crate::model::Subject::NamedNode(n) => Term::NamedNode(n.clone()),
753 crate::model::Subject::BlankNode(b) => Term::BlankNode(b.clone()),
754 crate::model::Subject::Variable(v) => Term::Variable(v.clone()),
755 crate::model::Subject::QuotedTriple(qt) => Term::QuotedTriple(qt.clone()),
756 }
757 }
758
759 pub fn from_predicate(predicate: &crate::model::Predicate) -> Term {
761 match predicate {
762 crate::model::Predicate::NamedNode(n) => Term::NamedNode(n.clone()),
763 crate::model::Predicate::Variable(v) => Term::Variable(v.clone()),
764 }
765 }
766
767 pub fn from_object(object: &crate::model::Object) -> Term {
769 match object {
770 crate::model::Object::NamedNode(n) => Term::NamedNode(n.clone()),
771 crate::model::Object::BlankNode(b) => Term::BlankNode(b.clone()),
772 crate::model::Object::Literal(l) => Term::Literal(l.clone()),
773 crate::model::Object::Variable(v) => Term::Variable(v.clone()),
774 crate::model::Object::QuotedTriple(qt) => Term::QuotedTriple(qt.clone()),
775 }
776 }
777}
778
779impl fmt::Display for Term {
780 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
781 match self {
782 Term::NamedNode(n) => write!(f, "{n}"),
783 Term::BlankNode(b) => write!(f, "{b}"),
784 Term::Literal(l) => write!(f, "{l}"),
785 Term::Variable(v) => write!(f, "{v}"),
786 Term::QuotedTriple(qt) => write!(f, "{qt}"),
787 }
788 }
789}
790
791impl RdfTerm for Term {
792 fn as_str(&self) -> &str {
793 match self {
794 Term::NamedNode(n) => n.as_str(),
795 Term::BlankNode(b) => b.as_str(),
796 Term::Literal(l) => l.as_str(),
797 Term::Variable(v) => v.as_str(),
798 Term::QuotedTriple(_) => "<<quoted-triple>>",
799 }
800 }
801
802 fn is_named_node(&self) -> bool {
803 self.is_named_node()
804 }
805
806 fn is_blank_node(&self) -> bool {
807 self.is_blank_node()
808 }
809
810 fn is_literal(&self) -> bool {
811 self.is_literal()
812 }
813
814 fn is_variable(&self) -> bool {
815 self.is_variable()
816 }
817
818 fn is_quoted_triple(&self) -> bool {
819 self.is_quoted_triple()
820 }
821}
822
823impl From<NamedNode> for Term {
825 fn from(node: NamedNode) -> Self {
826 Term::NamedNode(node)
827 }
828}
829
830impl From<BlankNode> for Term {
831 fn from(node: BlankNode) -> Self {
832 Term::BlankNode(node)
833 }
834}
835
836impl From<Literal> for Term {
837 fn from(literal: Literal) -> Self {
838 Term::Literal(literal)
839 }
840}
841
842impl From<Variable> for Term {
843 fn from(variable: Variable) -> Self {
844 Term::Variable(variable)
845 }
846}
847
848impl From<Subject> for Term {
850 fn from(subject: Subject) -> Self {
851 match subject {
852 Subject::NamedNode(nn) => Term::NamedNode(nn),
853 Subject::BlankNode(bn) => Term::BlankNode(bn),
854 Subject::Variable(v) => Term::Variable(v),
855 Subject::QuotedTriple(qt) => Term::QuotedTriple(qt),
856 }
857 }
858}
859
860impl From<Predicate> for Term {
861 fn from(predicate: Predicate) -> Self {
862 match predicate {
863 Predicate::NamedNode(nn) => Term::NamedNode(nn),
864 Predicate::Variable(v) => Term::Variable(v),
865 }
866 }
867}
868
869impl From<Object> for Term {
870 fn from(object: Object) -> Self {
871 match object {
872 Object::NamedNode(nn) => Term::NamedNode(nn),
873 Object::BlankNode(bn) => Term::BlankNode(bn),
874 Object::Literal(l) => Term::Literal(l),
875 Object::Variable(v) => Term::Variable(v),
876 Object::QuotedTriple(qt) => Term::QuotedTriple(qt),
877 }
878 }
879}
880
881#[derive(
883 Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
884)]
885pub enum Subject {
886 NamedNode(NamedNode),
887 BlankNode(BlankNode),
888 Variable(Variable),
889 QuotedTriple(Box<crate::model::star::QuotedTriple>),
890}
891
892impl From<NamedNode> for Subject {
893 fn from(node: NamedNode) -> Self {
894 Subject::NamedNode(node)
895 }
896}
897
898impl From<BlankNode> for Subject {
899 fn from(node: BlankNode) -> Self {
900 Subject::BlankNode(node)
901 }
902}
903
904impl From<Variable> for Subject {
905 fn from(variable: Variable) -> Self {
906 Subject::Variable(variable)
907 }
908}
909
910impl RdfTerm for Subject {
911 fn as_str(&self) -> &str {
912 match self {
913 Subject::NamedNode(n) => n.as_str(),
914 Subject::BlankNode(b) => b.as_str(),
915 Subject::Variable(v) => v.as_str(),
916 Subject::QuotedTriple(_) => "<<quoted-triple>>",
917 }
918 }
919
920 fn is_named_node(&self) -> bool {
921 matches!(self, Subject::NamedNode(_))
922 }
923
924 fn is_blank_node(&self) -> bool {
925 matches!(self, Subject::BlankNode(_))
926 }
927
928 fn is_variable(&self) -> bool {
929 matches!(self, Subject::Variable(_))
930 }
931
932 fn is_quoted_triple(&self) -> bool {
933 matches!(self, Subject::QuotedTriple(_))
934 }
935}
936
937impl SubjectTerm for Subject {}
938
939#[derive(
941 Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
942)]
943pub enum Predicate {
944 NamedNode(NamedNode),
945 Variable(Variable),
946}
947
948impl From<NamedNode> for Predicate {
949 fn from(node: NamedNode) -> Self {
950 Predicate::NamedNode(node)
951 }
952}
953
954impl From<Variable> for Predicate {
955 fn from(variable: Variable) -> Self {
956 Predicate::Variable(variable)
957 }
958}
959
960impl RdfTerm for Predicate {
961 fn as_str(&self) -> &str {
962 match self {
963 Predicate::NamedNode(n) => n.as_str(),
964 Predicate::Variable(v) => v.as_str(),
965 }
966 }
967
968 fn is_named_node(&self) -> bool {
969 matches!(self, Predicate::NamedNode(_))
970 }
971
972 fn is_variable(&self) -> bool {
973 matches!(self, Predicate::Variable(_))
974 }
975}
976
977impl PredicateTerm for Predicate {}
978
979#[derive(
981 Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
982)]
983pub enum Object {
984 NamedNode(NamedNode),
985 BlankNode(BlankNode),
986 Literal(Literal),
987 Variable(Variable),
988 QuotedTriple(Box<crate::model::star::QuotedTriple>),
989}
990
991impl From<NamedNode> for Object {
992 fn from(node: NamedNode) -> Self {
993 Object::NamedNode(node)
994 }
995}
996
997impl From<BlankNode> for Object {
998 fn from(node: BlankNode) -> Self {
999 Object::BlankNode(node)
1000 }
1001}
1002
1003impl From<Literal> for Object {
1004 fn from(literal: Literal) -> Self {
1005 Object::Literal(literal)
1006 }
1007}
1008
1009impl From<Variable> for Object {
1010 fn from(variable: Variable) -> Self {
1011 Object::Variable(variable)
1012 }
1013}
1014
1015impl From<Subject> for Object {
1017 fn from(subject: Subject) -> Self {
1018 match subject {
1019 Subject::NamedNode(n) => Object::NamedNode(n),
1020 Subject::BlankNode(b) => Object::BlankNode(b),
1021 Subject::Variable(v) => Object::Variable(v),
1022 Subject::QuotedTriple(qt) => Object::QuotedTriple(qt),
1023 }
1024 }
1025}
1026
1027impl From<Predicate> for Object {
1029 fn from(predicate: Predicate) -> Self {
1030 match predicate {
1031 Predicate::NamedNode(n) => Object::NamedNode(n),
1032 Predicate::Variable(v) => Object::Variable(v),
1033 }
1034 }
1035}
1036
1037impl From<Term> for Object {
1039 fn from(term: Term) -> Self {
1040 match term {
1041 Term::NamedNode(n) => Object::NamedNode(n),
1042 Term::BlankNode(b) => Object::BlankNode(b),
1043 Term::Literal(l) => Object::Literal(l),
1044 Term::Variable(v) => Object::Variable(v),
1045 Term::QuotedTriple(qt) => Object::QuotedTriple(qt),
1046 }
1047 }
1048}
1049
1050impl RdfTerm for Object {
1051 fn as_str(&self) -> &str {
1052 match self {
1053 Object::NamedNode(n) => n.as_str(),
1054 Object::BlankNode(b) => b.as_str(),
1055 Object::Literal(l) => l.as_str(),
1056 Object::Variable(v) => v.as_str(),
1057 Object::QuotedTriple(_) => "<<quoted-triple>>",
1058 }
1059 }
1060
1061 fn is_named_node(&self) -> bool {
1062 matches!(self, Object::NamedNode(_))
1063 }
1064
1065 fn is_blank_node(&self) -> bool {
1066 matches!(self, Object::BlankNode(_))
1067 }
1068
1069 fn is_literal(&self) -> bool {
1070 matches!(self, Object::Literal(_))
1071 }
1072
1073 fn is_variable(&self) -> bool {
1074 matches!(self, Object::Variable(_))
1075 }
1076
1077 fn is_quoted_triple(&self) -> bool {
1078 matches!(self, Object::QuotedTriple(_))
1079 }
1080}
1081
1082impl ObjectTerm for Object {}
1083
1084impl TryFrom<Term> for Subject {
1086 type Error = OxirsError;
1087
1088 fn try_from(term: Term) -> Result<Self, Self::Error> {
1089 match term {
1090 Term::NamedNode(n) => Ok(Subject::NamedNode(n)),
1091 Term::BlankNode(b) => Ok(Subject::BlankNode(b)),
1092 Term::Variable(v) => Ok(Subject::Variable(v)),
1093 Term::QuotedTriple(qt) => Ok(Subject::QuotedTriple(qt)),
1094 Term::Literal(_) => Err(OxirsError::Parse(
1095 "Literals cannot be used as subjects".to_string(),
1096 )),
1097 }
1098 }
1099}
1100
1101impl TryFrom<Term> for Predicate {
1102 type Error = OxirsError;
1103
1104 fn try_from(term: Term) -> Result<Self, Self::Error> {
1105 match term {
1106 Term::NamedNode(n) => Ok(Predicate::NamedNode(n)),
1107 Term::Variable(v) => Ok(Predicate::Variable(v)),
1108 Term::BlankNode(_) => Err(OxirsError::Parse(
1109 "Blank nodes cannot be used as predicates".to_string(),
1110 )),
1111 Term::Literal(_) => Err(OxirsError::Parse(
1112 "Literals cannot be used as predicates".to_string(),
1113 )),
1114 Term::QuotedTriple(_) => Err(OxirsError::Parse(
1115 "Quoted triples cannot be used as predicates".to_string(),
1116 )),
1117 }
1118 }
1119}
1120
1121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1128pub enum TermRef<'a> {
1129 NamedNode(NamedNodeRef<'a>),
1131 BlankNode(BlankNodeRef<'a>),
1133 Literal(LiteralRef<'a>),
1135 Variable(&'a Variable),
1137 #[cfg(feature = "rdf-star")]
1139 Triple(&'a crate::model::star::QuotedTriple),
1140}
1141
1142impl<'a> TermRef<'a> {
1143 pub fn is_named_node(&self) -> bool {
1145 matches!(self, TermRef::NamedNode(_))
1146 }
1147
1148 pub fn is_blank_node(&self) -> bool {
1150 matches!(self, TermRef::BlankNode(_))
1151 }
1152
1153 pub fn is_literal(&self) -> bool {
1155 matches!(self, TermRef::Literal(_))
1156 }
1157
1158 pub fn is_variable(&self) -> bool {
1160 matches!(self, TermRef::Variable(_))
1161 }
1162
1163 #[cfg(feature = "rdf-star")]
1165 pub fn is_triple(&self) -> bool {
1166 matches!(self, TermRef::Triple(_))
1167 }
1168}
1169
1170impl<'a> From<&'a Term> for TermRef<'a> {
1171 fn from(term: &'a Term) -> Self {
1172 match term {
1173 Term::NamedNode(n) => TermRef::NamedNode(n.as_ref()),
1174 Term::BlankNode(b) => TermRef::BlankNode(b.as_ref()),
1175 Term::Literal(l) => TermRef::Literal(l.as_ref()),
1176 Term::Variable(v) => TermRef::Variable(v),
1177 #[allow(unused_variables)]
1178 Term::QuotedTriple(t) => {
1179 #[cfg(feature = "rdf-star")]
1180 {
1181 TermRef::Triple(t.as_ref())
1182 }
1183 #[cfg(not(feature = "rdf-star"))]
1184 {
1185 panic!("RDF-star feature not enabled")
1186 }
1187 }
1188 }
1189 }
1190}
1191
1192impl<'a> RdfTerm for TermRef<'a> {
1193 fn as_str(&self) -> &str {
1194 match self {
1195 TermRef::NamedNode(n) => n.as_str(),
1196 TermRef::BlankNode(b) => b.as_str(),
1197 TermRef::Literal(l) => l.value(),
1198 TermRef::Variable(v) => v.name(),
1199 #[cfg(feature = "rdf-star")]
1200 TermRef::Triple(_) => "<<quoted triple>>", }
1202 }
1203
1204 fn is_named_node(&self) -> bool {
1205 self.is_named_node()
1206 }
1207
1208 fn is_blank_node(&self) -> bool {
1209 self.is_blank_node()
1210 }
1211
1212 fn is_literal(&self) -> bool {
1213 self.is_literal()
1214 }
1215
1216 fn is_variable(&self) -> bool {
1217 self.is_variable()
1218 }
1219}
1220
1221#[cfg(test)]
1222mod tests {
1223 use super::*;
1224
1225 #[test]
1226 fn test_blank_node() {
1227 let blank = BlankNode::new("b1").unwrap();
1228 assert_eq!(blank.id(), "b1");
1229 assert_eq!(blank.local_id(), "b1");
1230 assert!(blank.is_blank_node());
1231 assert_eq!(format!("{blank}"), "_:b1");
1232 }
1233
1234 #[test]
1235 fn test_blank_node_with_prefix() {
1236 let blank = BlankNode::new("_:test").unwrap();
1237 assert_eq!(blank.id(), "_:test");
1238 assert_eq!(blank.local_id(), "test");
1239 }
1240
1241 #[test]
1242 fn test_blank_node_unique() {
1243 let blank1 = BlankNode::new_unique();
1244 let blank2 = BlankNode::new_unique();
1245 assert_ne!(blank1.id(), blank2.id());
1246 assert!(matches!(blank1.id().as_bytes().first(), Some(b'a'..=b'f')));
1248 assert!(matches!(blank2.id().as_bytes().first(), Some(b'a'..=b'f')));
1249 }
1250
1251 #[test]
1252 fn test_blank_node_unique_with_prefix() {
1253 let blank1 = BlankNode::new_unique_with_prefix("test").unwrap();
1254 let blank2 = BlankNode::new_unique_with_prefix("test").unwrap();
1255 assert_ne!(blank1.id(), blank2.id());
1256 assert!(blank1.id().starts_with("test_"));
1257 assert!(blank2.id().starts_with("test_"));
1258 }
1259
1260 #[test]
1261 fn test_blank_node_validation() {
1262 assert!(BlankNode::new("test123").is_ok());
1264 assert!(BlankNode::new("Test_Node").is_ok());
1265 assert!(BlankNode::new("node-1.2").is_ok());
1266
1267 assert!(BlankNode::new("").is_err());
1269 assert!(BlankNode::new("_:").is_err());
1270 assert!(BlankNode::new("123invalid").is_err()); assert!(BlankNode::new("invalid@char").is_err());
1272 assert!(BlankNode::new("invalid space").is_err());
1273 }
1274
1275 #[test]
1276 fn test_blank_node_serde() {
1277 let blank = BlankNode::new("serializable").unwrap();
1278 let json = serde_json::to_string(&blank).unwrap();
1279 let deserialized: BlankNode = serde_json::from_str(&json).unwrap();
1280 assert_eq!(blank, deserialized);
1281 }
1282
1283 #[test]
1284 fn test_variable() {
1285 let var = Variable::new("x").unwrap();
1286 assert_eq!(var.name(), "x");
1287 assert!(var.is_variable());
1288 assert_eq!(format!("{var}"), "?x");
1289 assert_eq!(var.with_prefix(), "?x");
1290 }
1291
1292 #[test]
1293 fn test_variable_with_prefix() {
1294 let var1 = Variable::new("?test").unwrap();
1295 let var2 = Variable::new("$test").unwrap();
1296 assert_eq!(var1.name(), "test");
1297 assert_eq!(var2.name(), "test");
1298 assert_eq!(var1, var2); }
1300
1301 #[test]
1302 fn test_variable_validation() {
1303 assert!(Variable::new("x").is_ok());
1305 assert!(Variable::new("test123").is_ok());
1306 assert!(Variable::new("_underscore").is_ok());
1307 assert!(Variable::new("?prefixed").is_ok());
1308 assert!(Variable::new("$prefixed").is_ok());
1309
1310 assert!(Variable::new("").is_err());
1312 assert!(Variable::new("?").is_err());
1313 assert!(Variable::new("$").is_err());
1314 assert!(Variable::new("123invalid").is_err()); assert!(Variable::new("invalid-char").is_err());
1316 assert!(Variable::new("invalid space").is_err());
1317
1318 assert!(Variable::new("select").is_err());
1320 assert!(Variable::new("WHERE").is_err()); assert!(Variable::new("?from").is_err());
1322 }
1323
1324 #[test]
1325 fn test_variable_serde() {
1326 let var = Variable::new("serializable").unwrap();
1327 let json = serde_json::to_string(&var).unwrap();
1328 let deserialized: Variable = serde_json::from_str(&json).unwrap();
1329 assert_eq!(var, deserialized);
1330 }
1331
1332 #[test]
1333 fn test_term_enum() {
1334 let term = Term::NamedNode(NamedNode::new("http://example.org").unwrap());
1335 assert!(term.is_named_node());
1336 assert!(term.as_named_node().is_some());
1337 assert!(term.as_blank_node().is_none());
1338 }
1339
1340 #[test]
1341 fn test_blank_node_numerical() {
1342 let blank1 = BlankNode::new("a100a").unwrap();
1344 assert_eq!(blank1.unique_id(), Some(0xa100a));
1345 assert_eq!(blank1.local_id(), "a100a");
1346
1347 let blank2 = BlankNode::new("a100A").unwrap(); assert_eq!(blank2.unique_id(), None);
1350
1351 let blank3 = BlankNode::new_from_unique_id(0x42);
1353 assert_eq!(blank3.unique_id(), Some(0x42));
1354 assert_eq!(blank3.local_id(), "42");
1355 }
1356
1357 #[test]
1358 fn test_blank_node_default() {
1359 let blank = BlankNode::default();
1360 assert!(blank.unique_id().is_some());
1361 assert!(matches!(blank.id().as_bytes().first(), Some(b'a'..=b'f')));
1363 }
1364}