use crate::model::{
Literal, LiteralRef, NamedNode, NamedNodeRef, ObjectTerm, PredicateTerm, RdfTerm, SubjectTerm,
};
use crate::OxirsError;
use lazy_static::lazy_static;
use regex::Regex;
use std::collections::HashSet;
use std::fmt;
use std::hash::Hash;
use std::sync::atomic::{AtomicU64, Ordering};
fn is_pure_hex_numeric_id(id: &str) -> bool {
!id.is_empty()
&& id
.chars()
.all(|c| c.is_ascii_hexdigit() && (c.is_ascii_lowercase() || c.is_ascii_digit()))
&& id.starts_with(|c: char| ('a'..='f').contains(&c))
&& id.len() >= 3 }
fn to_integer_id(id: &str) -> Option<u128> {
let digits = id.as_bytes();
let mut value: u128 = 0;
if let None | Some(b'0') = digits.first() {
return None; }
for digit in digits {
value = value.checked_mul(16)?.checked_add(
match *digit {
b'0'..=b'9' => digit - b'0',
b'a'..=b'f' => digit - b'a' + 10,
_ => return None,
}
.into(),
)?;
}
Some(value)
}
lazy_static! {
static ref BLANK_NODE_REGEX: Regex = Regex::new(
r"^[a-zA-Z_][a-zA-Z0-9_.-]*$"
).expect("Blank node regex compilation failed");
static ref VARIABLE_REGEX: Regex = Regex::new(
r"^[a-zA-Z_][a-zA-Z0-9_]*$"
).expect("Variable regex compilation failed");
static ref BLANK_NODE_COUNTER: AtomicU64 = AtomicU64::new(0);
static ref BLANK_NODE_IDS: std::sync::Mutex<HashSet<String>> = std::sync::Mutex::new(HashSet::new());
}
fn validate_blank_node_id(id: &str) -> Result<(), OxirsError> {
if id.is_empty() {
return Err(OxirsError::Parse(
"Blank node ID cannot be empty".to_string(),
));
}
let clean_id = if let Some(stripped) = id.strip_prefix("_:") {
stripped
} else {
id
};
if clean_id.is_empty() {
return Err(OxirsError::Parse(
"Blank node ID cannot be just '_:'".to_string(),
));
}
if !BLANK_NODE_REGEX.is_match(clean_id) {
return Err(OxirsError::Parse(format!(
"Invalid blank node ID format: '{clean_id}'. Must match [a-zA-Z0-9_][a-zA-Z0-9_.-]*"
)));
}
Ok(())
}
fn validate_variable_name(name: &str) -> Result<(), OxirsError> {
if name.is_empty() {
return Err(OxirsError::Parse(
"Variable name cannot be empty".to_string(),
));
}
let clean_name = if name.starts_with('?') || name.starts_with('$') {
&name[1..]
} else {
name
};
if clean_name.is_empty() {
return Err(OxirsError::Parse(
"Variable name cannot be just '?' or '$'".to_string(),
));
}
if !VARIABLE_REGEX.is_match(clean_name) {
return Err(OxirsError::Parse(format!(
"Invalid variable name format: '{clean_name}'. Must match [a-zA-Z_][a-zA-Z0-9_]*"
)));
}
match clean_name.to_lowercase().as_str() {
"select" | "where" | "from" | "order" | "group" | "having" | "limit" | "offset"
| "distinct" | "reduced" | "construct" | "describe" | "ask" | "union" | "optional"
| "filter" | "bind" | "values" | "graph" | "service" | "minus" | "exists" | "not" => {
return Err(OxirsError::Parse(format!(
"Variable name '{clean_name}' is a reserved SPARQL keyword"
)));
}
_ => {}
}
Ok(())
}
#[derive(
Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub struct BlankNode {
content: BlankNodeContent,
}
#[derive(
Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
enum BlankNodeContent {
Named(String),
Anonymous { id: u128, str: String },
}
impl Default for BlankNode {
fn default() -> Self {
loop {
let id: u128 = fastrand::u128(..);
let str = format!("{id:x}"); if matches!(str.as_bytes().first(), Some(b'a'..=b'f')) {
return Self {
content: BlankNodeContent::Anonymous { id, str },
};
}
}
}
}
impl BlankNode {
pub fn new(id: impl Into<String>) -> Result<Self, OxirsError> {
let id = id.into();
let clean_id = if let Some(stripped) = id.strip_prefix("_:") {
stripped
} else {
&id
};
validate_blank_node_id(clean_id)?;
if is_pure_hex_numeric_id(clean_id) {
if let Some(numerical_id) = to_integer_id(clean_id) {
Ok(BlankNode {
content: BlankNodeContent::Anonymous {
id: numerical_id,
str: clean_id.to_string(),
},
})
} else {
Ok(BlankNode {
content: BlankNodeContent::Named(id),
})
}
} else {
Ok(BlankNode {
content: BlankNodeContent::Named(id),
})
}
}
pub fn new_unchecked(id: impl Into<String>) -> Self {
let id = id.into();
let clean_id = if let Some(stripped) = id.strip_prefix("_:") {
stripped
} else {
&id
};
if let Some(numerical_id) = to_integer_id(clean_id) {
Self::new_from_unique_id(numerical_id)
} else {
BlankNode {
content: BlankNodeContent::Named(id),
}
}
}
pub fn new_from_unique_id(id: u128) -> Self {
Self {
content: BlankNodeContent::Anonymous {
id,
str: format!("{id:x}"), },
}
}
pub fn new_unique() -> Self {
Self::default()
}
pub fn new_unique_with_prefix(prefix: &str) -> Result<Self, OxirsError> {
if !BLANK_NODE_REGEX.is_match(prefix) {
return Err(OxirsError::Parse(format!(
"Invalid blank node prefix: '{prefix}'. Must match [a-zA-Z0-9_][a-zA-Z0-9_.-]*"
)));
}
let counter = BLANK_NODE_COUNTER.fetch_add(1, Ordering::SeqCst);
let id = format!("{prefix}_{counter}");
Ok(BlankNode {
content: BlankNodeContent::Named(id),
})
}
pub fn id(&self) -> &str {
match &self.content {
BlankNodeContent::Named(id) => id,
BlankNodeContent::Anonymous { str, .. } => str,
}
}
pub fn as_str(&self) -> &str {
self.id()
}
pub fn unique_id(&self) -> Option<u128> {
match &self.content {
BlankNodeContent::Named(_) => None,
BlankNodeContent::Anonymous { id, .. } => Some(*id),
}
}
pub fn local_id(&self) -> &str {
let id = self.id();
if let Some(stripped) = id.strip_prefix("_:") {
stripped
} else {
id
}
}
pub fn as_ref(&self) -> BlankNodeRef<'_> {
BlankNodeRef {
content: match &self.content {
BlankNodeContent::Named(id) => BlankNodeRefContent::Named(id.as_str()),
BlankNodeContent::Anonymous { id, str } => BlankNodeRefContent::Anonymous {
id: *id,
str: str.as_str(),
},
},
}
}
}
impl fmt::Display for BlankNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "_:{}", self.local_id())
}
}
impl RdfTerm for BlankNode {
fn as_str(&self) -> &str {
self.id()
}
fn is_blank_node(&self) -> bool {
true
}
}
impl SubjectTerm for BlankNode {}
impl ObjectTerm for BlankNode {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct BlankNodeRef<'a> {
content: BlankNodeRefContent<'a>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
enum BlankNodeRefContent<'a> {
Named(&'a str),
Anonymous { id: u128, str: &'a str },
}
impl<'a> BlankNodeRef<'a> {
pub fn new(id: &'a str) -> Result<Self, OxirsError> {
let clean_id = if let Some(stripped) = id.strip_prefix("_:") {
stripped
} else {
id
};
validate_blank_node_id(clean_id)?;
if let Some(numerical_id) = to_integer_id(clean_id) {
Ok(BlankNodeRef {
content: BlankNodeRefContent::Anonymous {
id: numerical_id,
str: clean_id,
},
})
} else {
Ok(BlankNodeRef {
content: BlankNodeRefContent::Named(id),
})
}
}
pub fn new_unchecked(id: &'a str) -> Self {
let clean_id = if let Some(stripped) = id.strip_prefix("_:") {
stripped
} else {
id
};
if let Some(numerical_id) = to_integer_id(clean_id) {
BlankNodeRef {
content: BlankNodeRefContent::Anonymous {
id: numerical_id,
str: clean_id,
},
}
} else {
BlankNodeRef {
content: BlankNodeRefContent::Named(id),
}
}
}
pub fn id(&self) -> &str {
match &self.content {
BlankNodeRefContent::Named(id) => id,
BlankNodeRefContent::Anonymous { str, .. } => str,
}
}
pub fn as_str(&self) -> &str {
self.id()
}
pub fn unique_id(&self) -> Option<u128> {
match &self.content {
BlankNodeRefContent::Named(_) => None,
BlankNodeRefContent::Anonymous { id, .. } => Some(*id),
}
}
pub fn local_id(&self) -> &str {
let id = self.id();
if let Some(stripped) = id.strip_prefix("_:") {
stripped
} else {
id
}
}
pub fn to_owned(&self) -> BlankNode {
BlankNode::new_unchecked(self.id().to_string())
}
}
impl<'a> fmt::Display for BlankNodeRef<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "_:{}", self.local_id())
}
}
impl<'a> RdfTerm for BlankNodeRef<'a> {
fn as_str(&self) -> &str {
self.id()
}
fn is_blank_node(&self) -> bool {
true
}
}
impl<'a> SubjectTerm for BlankNodeRef<'a> {}
impl<'a> ObjectTerm for BlankNodeRef<'a> {}
impl<'a> From<BlankNodeRef<'a>> for BlankNode {
fn from(node_ref: BlankNodeRef<'a>) -> Self {
node_ref.to_owned()
}
}
impl<'a> From<&'a BlankNode> for BlankNodeRef<'a> {
fn from(node: &'a BlankNode) -> Self {
node.as_ref()
}
}
#[derive(
Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub struct Variable {
name: String,
}
impl Variable {
pub fn new(name: impl Into<String>) -> Result<Self, OxirsError> {
let name = name.into();
validate_variable_name(&name)?;
let clean_name = if let Some(stripped) = name.strip_prefix('?') {
stripped.to_string()
} else if let Some(stripped) = name.strip_prefix('$') {
stripped.to_string()
} else {
name
};
Ok(Variable { name: clean_name })
}
pub fn new_unchecked(name: impl Into<String>) -> Self {
Variable { name: name.into() }
}
pub fn name(&self) -> &str {
&self.name
}
pub fn as_str(&self) -> &str {
&self.name
}
pub fn with_prefix(&self) -> String {
format!("?{}", self.name)
}
}
impl fmt::Display for Variable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "?{}", self.name)
}
}
impl Variable {
pub fn fmt_sse(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, "?{}", self.name)
}
}
impl RdfTerm for Variable {
fn as_str(&self) -> &str {
&self.name
}
fn is_variable(&self) -> bool {
true
}
}
#[derive(
Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub enum Term {
NamedNode(NamedNode),
BlankNode(BlankNode),
Literal(Literal),
Variable(Variable),
QuotedTriple(Box<crate::model::star::QuotedTriple>),
}
impl Term {
pub fn is_named_node(&self) -> bool {
matches!(self, Term::NamedNode(_))
}
pub fn is_blank_node(&self) -> bool {
matches!(self, Term::BlankNode(_))
}
pub fn is_literal(&self) -> bool {
matches!(self, Term::Literal(_))
}
pub fn is_variable(&self) -> bool {
matches!(self, Term::Variable(_))
}
pub fn is_quoted_triple(&self) -> bool {
matches!(self, Term::QuotedTriple(_))
}
pub fn as_named_node(&self) -> Option<&NamedNode> {
match self {
Term::NamedNode(n) => Some(n),
_ => None,
}
}
pub fn as_blank_node(&self) -> Option<&BlankNode> {
match self {
Term::BlankNode(b) => Some(b),
_ => None,
}
}
pub fn as_literal(&self) -> Option<&Literal> {
match self {
Term::Literal(l) => Some(l),
_ => None,
}
}
pub fn as_variable(&self) -> Option<&Variable> {
match self {
Term::Variable(v) => Some(v),
_ => None,
}
}
pub fn as_quoted_triple(&self) -> Option<&crate::model::star::QuotedTriple> {
match self {
Term::QuotedTriple(qt) => Some(qt),
_ => None,
}
}
pub fn from_subject(subject: &crate::model::Subject) -> Term {
match subject {
crate::model::Subject::NamedNode(n) => Term::NamedNode(n.clone()),
crate::model::Subject::BlankNode(b) => Term::BlankNode(b.clone()),
crate::model::Subject::Variable(v) => Term::Variable(v.clone()),
crate::model::Subject::QuotedTriple(qt) => Term::QuotedTriple(qt.clone()),
}
}
pub fn from_predicate(predicate: &crate::model::Predicate) -> Term {
match predicate {
crate::model::Predicate::NamedNode(n) => Term::NamedNode(n.clone()),
crate::model::Predicate::Variable(v) => Term::Variable(v.clone()),
}
}
pub fn from_object(object: &crate::model::Object) -> Term {
match object {
crate::model::Object::NamedNode(n) => Term::NamedNode(n.clone()),
crate::model::Object::BlankNode(b) => Term::BlankNode(b.clone()),
crate::model::Object::Literal(l) => Term::Literal(l.clone()),
crate::model::Object::Variable(v) => Term::Variable(v.clone()),
crate::model::Object::QuotedTriple(qt) => Term::QuotedTriple(qt.clone()),
}
}
}
impl fmt::Display for Term {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Term::NamedNode(n) => write!(f, "{n}"),
Term::BlankNode(b) => write!(f, "{b}"),
Term::Literal(l) => write!(f, "{l}"),
Term::Variable(v) => write!(f, "{v}"),
Term::QuotedTriple(qt) => write!(f, "{qt}"),
}
}
}
impl RdfTerm for Term {
fn as_str(&self) -> &str {
match self {
Term::NamedNode(n) => n.as_str(),
Term::BlankNode(b) => b.as_str(),
Term::Literal(l) => l.as_str(),
Term::Variable(v) => v.as_str(),
Term::QuotedTriple(_) => "<<quoted-triple>>",
}
}
fn is_named_node(&self) -> bool {
self.is_named_node()
}
fn is_blank_node(&self) -> bool {
self.is_blank_node()
}
fn is_literal(&self) -> bool {
self.is_literal()
}
fn is_variable(&self) -> bool {
self.is_variable()
}
fn is_quoted_triple(&self) -> bool {
self.is_quoted_triple()
}
}
impl From<NamedNode> for Term {
fn from(node: NamedNode) -> Self {
Term::NamedNode(node)
}
}
impl From<BlankNode> for Term {
fn from(node: BlankNode) -> Self {
Term::BlankNode(node)
}
}
impl From<Literal> for Term {
fn from(literal: Literal) -> Self {
Term::Literal(literal)
}
}
impl From<Variable> for Term {
fn from(variable: Variable) -> Self {
Term::Variable(variable)
}
}
impl From<Subject> for Term {
fn from(subject: Subject) -> Self {
match subject {
Subject::NamedNode(nn) => Term::NamedNode(nn),
Subject::BlankNode(bn) => Term::BlankNode(bn),
Subject::Variable(v) => Term::Variable(v),
Subject::QuotedTriple(qt) => Term::QuotedTriple(qt),
}
}
}
impl From<Predicate> for Term {
fn from(predicate: Predicate) -> Self {
match predicate {
Predicate::NamedNode(nn) => Term::NamedNode(nn),
Predicate::Variable(v) => Term::Variable(v),
}
}
}
impl From<Object> for Term {
fn from(object: Object) -> Self {
match object {
Object::NamedNode(nn) => Term::NamedNode(nn),
Object::BlankNode(bn) => Term::BlankNode(bn),
Object::Literal(l) => Term::Literal(l),
Object::Variable(v) => Term::Variable(v),
Object::QuotedTriple(qt) => Term::QuotedTriple(qt),
}
}
}
#[derive(
Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub enum Subject {
NamedNode(NamedNode),
BlankNode(BlankNode),
Variable(Variable),
QuotedTriple(Box<crate::model::star::QuotedTriple>),
}
impl From<NamedNode> for Subject {
fn from(node: NamedNode) -> Self {
Subject::NamedNode(node)
}
}
impl From<BlankNode> for Subject {
fn from(node: BlankNode) -> Self {
Subject::BlankNode(node)
}
}
impl From<Variable> for Subject {
fn from(variable: Variable) -> Self {
Subject::Variable(variable)
}
}
impl RdfTerm for Subject {
fn as_str(&self) -> &str {
match self {
Subject::NamedNode(n) => n.as_str(),
Subject::BlankNode(b) => b.as_str(),
Subject::Variable(v) => v.as_str(),
Subject::QuotedTriple(_) => "<<quoted-triple>>",
}
}
fn is_named_node(&self) -> bool {
matches!(self, Subject::NamedNode(_))
}
fn is_blank_node(&self) -> bool {
matches!(self, Subject::BlankNode(_))
}
fn is_variable(&self) -> bool {
matches!(self, Subject::Variable(_))
}
fn is_quoted_triple(&self) -> bool {
matches!(self, Subject::QuotedTriple(_))
}
}
impl SubjectTerm for Subject {}
#[derive(
Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub enum Predicate {
NamedNode(NamedNode),
Variable(Variable),
}
impl From<NamedNode> for Predicate {
fn from(node: NamedNode) -> Self {
Predicate::NamedNode(node)
}
}
impl From<Variable> for Predicate {
fn from(variable: Variable) -> Self {
Predicate::Variable(variable)
}
}
impl RdfTerm for Predicate {
fn as_str(&self) -> &str {
match self {
Predicate::NamedNode(n) => n.as_str(),
Predicate::Variable(v) => v.as_str(),
}
}
fn is_named_node(&self) -> bool {
matches!(self, Predicate::NamedNode(_))
}
fn is_variable(&self) -> bool {
matches!(self, Predicate::Variable(_))
}
}
impl PredicateTerm for Predicate {}
#[derive(
Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub enum Object {
NamedNode(NamedNode),
BlankNode(BlankNode),
Literal(Literal),
Variable(Variable),
QuotedTriple(Box<crate::model::star::QuotedTriple>),
}
impl From<NamedNode> for Object {
fn from(node: NamedNode) -> Self {
Object::NamedNode(node)
}
}
impl From<BlankNode> for Object {
fn from(node: BlankNode) -> Self {
Object::BlankNode(node)
}
}
impl From<Literal> for Object {
fn from(literal: Literal) -> Self {
Object::Literal(literal)
}
}
impl From<Variable> for Object {
fn from(variable: Variable) -> Self {
Object::Variable(variable)
}
}
impl From<Subject> for Object {
fn from(subject: Subject) -> Self {
match subject {
Subject::NamedNode(n) => Object::NamedNode(n),
Subject::BlankNode(b) => Object::BlankNode(b),
Subject::Variable(v) => Object::Variable(v),
Subject::QuotedTriple(qt) => Object::QuotedTriple(qt),
}
}
}
impl From<Predicate> for Object {
fn from(predicate: Predicate) -> Self {
match predicate {
Predicate::NamedNode(n) => Object::NamedNode(n),
Predicate::Variable(v) => Object::Variable(v),
}
}
}
impl From<Term> for Object {
fn from(term: Term) -> Self {
match term {
Term::NamedNode(n) => Object::NamedNode(n),
Term::BlankNode(b) => Object::BlankNode(b),
Term::Literal(l) => Object::Literal(l),
Term::Variable(v) => Object::Variable(v),
Term::QuotedTriple(qt) => Object::QuotedTriple(qt),
}
}
}
impl RdfTerm for Object {
fn as_str(&self) -> &str {
match self {
Object::NamedNode(n) => n.as_str(),
Object::BlankNode(b) => b.as_str(),
Object::Literal(l) => l.as_str(),
Object::Variable(v) => v.as_str(),
Object::QuotedTriple(_) => "<<quoted-triple>>",
}
}
fn is_named_node(&self) -> bool {
matches!(self, Object::NamedNode(_))
}
fn is_blank_node(&self) -> bool {
matches!(self, Object::BlankNode(_))
}
fn is_literal(&self) -> bool {
matches!(self, Object::Literal(_))
}
fn is_variable(&self) -> bool {
matches!(self, Object::Variable(_))
}
fn is_quoted_triple(&self) -> bool {
matches!(self, Object::QuotedTriple(_))
}
}
impl ObjectTerm for Object {}
impl TryFrom<Term> for Subject {
type Error = OxirsError;
fn try_from(term: Term) -> Result<Self, Self::Error> {
match term {
Term::NamedNode(n) => Ok(Subject::NamedNode(n)),
Term::BlankNode(b) => Ok(Subject::BlankNode(b)),
Term::Variable(v) => Ok(Subject::Variable(v)),
Term::QuotedTriple(qt) => Ok(Subject::QuotedTriple(qt)),
Term::Literal(_) => Err(OxirsError::Parse(
"Literals cannot be used as subjects".to_string(),
)),
}
}
}
impl TryFrom<Term> for Predicate {
type Error = OxirsError;
fn try_from(term: Term) -> Result<Self, Self::Error> {
match term {
Term::NamedNode(n) => Ok(Predicate::NamedNode(n)),
Term::Variable(v) => Ok(Predicate::Variable(v)),
Term::BlankNode(_) => Err(OxirsError::Parse(
"Blank nodes cannot be used as predicates".to_string(),
)),
Term::Literal(_) => Err(OxirsError::Parse(
"Literals cannot be used as predicates".to_string(),
)),
Term::QuotedTriple(_) => Err(OxirsError::Parse(
"Quoted triples cannot be used as predicates".to_string(),
)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TermRef<'a> {
NamedNode(NamedNodeRef<'a>),
BlankNode(BlankNodeRef<'a>),
Literal(LiteralRef<'a>),
Variable(&'a Variable),
#[cfg(feature = "rdf-star")]
Triple(&'a crate::model::star::QuotedTriple),
}
impl<'a> TermRef<'a> {
pub fn is_named_node(&self) -> bool {
matches!(self, TermRef::NamedNode(_))
}
pub fn is_blank_node(&self) -> bool {
matches!(self, TermRef::BlankNode(_))
}
pub fn is_literal(&self) -> bool {
matches!(self, TermRef::Literal(_))
}
pub fn is_variable(&self) -> bool {
matches!(self, TermRef::Variable(_))
}
#[cfg(feature = "rdf-star")]
pub fn is_triple(&self) -> bool {
matches!(self, TermRef::Triple(_))
}
}
impl<'a> From<&'a Term> for TermRef<'a> {
fn from(term: &'a Term) -> Self {
match term {
Term::NamedNode(n) => TermRef::NamedNode(n.as_ref()),
Term::BlankNode(b) => TermRef::BlankNode(b.as_ref()),
Term::Literal(l) => TermRef::Literal(l.as_ref()),
Term::Variable(v) => TermRef::Variable(v),
#[allow(unused_variables)]
Term::QuotedTriple(t) => {
#[cfg(feature = "rdf-star")]
{
TermRef::Triple(t.as_ref())
}
#[cfg(not(feature = "rdf-star"))]
{
panic!("RDF-star feature not enabled")
}
}
}
}
}
impl<'a> RdfTerm for TermRef<'a> {
fn as_str(&self) -> &str {
match self {
TermRef::NamedNode(n) => n.as_str(),
TermRef::BlankNode(b) => b.as_str(),
TermRef::Literal(l) => l.value(),
TermRef::Variable(v) => v.name(),
#[cfg(feature = "rdf-star")]
TermRef::Triple(_) => "<<quoted triple>>", }
}
fn is_named_node(&self) -> bool {
self.is_named_node()
}
fn is_blank_node(&self) -> bool {
self.is_blank_node()
}
fn is_literal(&self) -> bool {
self.is_literal()
}
fn is_variable(&self) -> bool {
self.is_variable()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blank_node() {
let blank = BlankNode::new("b1").expect("valid blank node id");
assert_eq!(blank.id(), "b1");
assert_eq!(blank.local_id(), "b1");
assert!(blank.is_blank_node());
assert_eq!(format!("{blank}"), "_:b1");
}
#[test]
fn test_blank_node_with_prefix() {
let blank = BlankNode::new("_:test").expect("valid blank node id");
assert_eq!(blank.id(), "_:test");
assert_eq!(blank.local_id(), "test");
}
#[test]
fn test_blank_node_unique() {
let blank1 = BlankNode::new_unique();
let blank2 = BlankNode::new_unique();
assert_ne!(blank1.id(), blank2.id());
assert!(matches!(blank1.id().as_bytes().first(), Some(b'a'..=b'f')));
assert!(matches!(blank2.id().as_bytes().first(), Some(b'a'..=b'f')));
}
#[test]
fn test_blank_node_unique_with_prefix() {
let blank1 =
BlankNode::new_unique_with_prefix("test").expect("construction should succeed");
let blank2 =
BlankNode::new_unique_with_prefix("test").expect("construction should succeed");
assert_ne!(blank1.id(), blank2.id());
assert!(blank1.id().starts_with("test_"));
assert!(blank2.id().starts_with("test_"));
}
#[test]
fn test_blank_node_validation() {
assert!(BlankNode::new("test123").is_ok());
assert!(BlankNode::new("Test_Node").is_ok());
assert!(BlankNode::new("node-1.2").is_ok());
assert!(BlankNode::new("").is_err());
assert!(BlankNode::new("_:").is_err());
assert!(BlankNode::new("123invalid").is_err()); assert!(BlankNode::new("invalid@char").is_err());
assert!(BlankNode::new("invalid space").is_err());
}
#[test]
fn test_blank_node_serde() {
let blank = BlankNode::new("serializable").expect("valid blank node id");
let json = serde_json::to_string(&blank).expect("construction should succeed");
let deserialized: BlankNode =
serde_json::from_str(&json).expect("construction should succeed");
assert_eq!(blank, deserialized);
}
#[test]
fn test_variable() {
let var = Variable::new("x").expect("valid variable name");
assert_eq!(var.name(), "x");
assert!(var.is_variable());
assert_eq!(format!("{var}"), "?x");
assert_eq!(var.with_prefix(), "?x");
}
#[test]
fn test_variable_with_prefix() {
let var1 = Variable::new("?test").expect("valid variable name");
let var2 = Variable::new("$test").expect("valid variable name");
assert_eq!(var1.name(), "test");
assert_eq!(var2.name(), "test");
assert_eq!(var1, var2); }
#[test]
fn test_variable_validation() {
assert!(Variable::new("x").is_ok());
assert!(Variable::new("test123").is_ok());
assert!(Variable::new("_underscore").is_ok());
assert!(Variable::new("?prefixed").is_ok());
assert!(Variable::new("$prefixed").is_ok());
assert!(Variable::new("").is_err());
assert!(Variable::new("?").is_err());
assert!(Variable::new("$").is_err());
assert!(Variable::new("123invalid").is_err()); assert!(Variable::new("invalid-char").is_err());
assert!(Variable::new("invalid space").is_err());
assert!(Variable::new("select").is_err());
assert!(Variable::new("WHERE").is_err()); assert!(Variable::new("?from").is_err());
}
#[test]
fn test_variable_serde() {
let var = Variable::new("serializable").expect("valid variable name");
let json = serde_json::to_string(&var).expect("construction should succeed");
let deserialized: Variable =
serde_json::from_str(&json).expect("construction should succeed");
assert_eq!(var, deserialized);
}
#[test]
fn test_term_enum() {
let term = Term::NamedNode(NamedNode::new("http://example.org").expect("valid IRI"));
assert!(term.is_named_node());
assert!(term.as_named_node().is_some());
assert!(term.as_blank_node().is_none());
}
#[test]
fn test_blank_node_numerical() {
let blank1 = BlankNode::new("a100a").expect("valid blank node id");
assert_eq!(blank1.unique_id(), Some(0xa100a));
assert_eq!(blank1.local_id(), "a100a");
let blank2 = BlankNode::new("a100A").expect("valid blank node id"); assert_eq!(blank2.unique_id(), None);
let blank3 = BlankNode::new_from_unique_id(0x42);
assert_eq!(blank3.unique_id(), Some(0x42));
assert_eq!(blank3.local_id(), "42");
}
#[test]
fn test_blank_node_default() {
let blank = BlankNode::default();
assert!(blank.unique_id().is_some());
assert!(matches!(blank.id().as_bytes().first(), Some(b'a'..=b'f')));
}
}