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