use crate::{NodeId, Predicate, Value};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TripleId(pub [u8; 32]);
impl TripleId {
pub fn new(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub fn from_triple(triple: &Triple) -> Self {
let mut hasher = blake3::Hasher::new();
hasher.update(&triple.subject.to_bytes());
hasher.update(&triple.predicate.to_bytes());
hasher.update(&triple.object.to_bytes());
Self(*hasher.finalize().as_bytes())
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn to_hex(&self) -> String {
self.0.iter().map(|b| format!("{:02x}", b)).collect()
}
pub fn from_hex(hex: &str) -> Option<Self> {
if hex.len() != 64 {
return None;
}
let mut bytes = [0u8; 32];
for (i, chunk) in hex.as_bytes().chunks(2).enumerate() {
let s = std::str::from_utf8(chunk).ok()?;
bytes[i] = u8::from_str_radix(s, 16).ok()?;
}
Some(Self(bytes))
}
}
impl fmt::Display for TripleId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.to_hex()[..16])
}
}
impl From<[u8; 32]> for TripleId {
fn from(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TripleMeta {
pub created_at: DateTime<Utc>,
pub author: Option<NodeId>,
pub signature: Option<Vec<u8>>,
pub source: Option<String>,
pub confidence: Option<f64>,
pub validated: bool,
pub properties: std::collections::HashMap<String, String>,
}
impl Default for TripleMeta {
fn default() -> Self {
Self {
created_at: Utc::now(),
author: None,
signature: None,
source: None,
confidence: None,
validated: false,
properties: std::collections::HashMap::new(),
}
}
}
impl TripleMeta {
pub fn new() -> Self {
Self::default()
}
pub fn with_author(mut self, author: NodeId) -> Self {
self.author = Some(author);
self
}
pub fn with_source(mut self, source: impl Into<String>) -> Self {
self.source = Some(source.into());
self
}
pub fn with_confidence(mut self, confidence: f64) -> Self {
self.confidence = Some(confidence.clamp(0.0, 1.0));
self
}
pub fn validated(mut self) -> Self {
self.validated = true;
self
}
pub fn with_property(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.properties.insert(key.into(), value.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Triple {
pub subject: NodeId,
pub predicate: Predicate,
pub object: Value,
pub meta: TripleMeta,
}
impl Triple {
pub fn new(subject: NodeId, predicate: Predicate, object: Value) -> Self {
Self {
subject,
predicate,
object,
meta: TripleMeta::default(),
}
}
pub fn with_meta(
subject: NodeId,
predicate: Predicate,
object: Value,
meta: TripleMeta,
) -> Self {
Self {
subject,
predicate,
object,
meta,
}
}
pub fn literal(
subject: impl Into<NodeId>,
predicate: impl Into<Predicate>,
value: impl Into<String>,
) -> Self
where
NodeId: From<String>,
{
Self::new(subject.into(), predicate.into(), Value::literal(value))
}
pub fn link(
subject: impl Into<NodeId>,
predicate: impl Into<Predicate>,
object: impl Into<NodeId>,
) -> Self {
Self::new(subject.into(), predicate.into(), Value::Node(object.into()))
}
pub fn id(&self) -> TripleId {
TripleId::from_triple(self)
}
pub fn is_link(&self) -> bool {
self.object.is_node()
}
pub fn is_literal(&self) -> bool {
self.object.is_literal()
}
pub fn object_node(&self) -> Option<&NodeId> {
self.object.as_node()
}
pub fn object_string(&self) -> Option<&str> {
self.object.as_string()
}
pub fn to_ntriples(&self) -> String {
format!("{} {} {} .", self.subject, self.predicate, self.object)
}
pub fn to_bytes(&self) -> Vec<u8> {
bincode::serde::encode_to_vec(self, bincode::config::standard()).unwrap_or_default()
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
bincode::serde::decode_from_slice(bytes, bincode::config::standard())
.map(|(v, _)| v)
.ok()
}
pub fn index_keys(&self) -> TripleIndexKeys {
let s = self.subject.to_bytes();
let p = self.predicate.to_bytes();
let o = self.object.sort_key();
let mut spo = Vec::with_capacity(s.len() + p.len() + o.len() + 2);
spo.extend(&s);
spo.push(0);
spo.extend(&p);
spo.push(0);
spo.extend(&o);
let mut pos = Vec::with_capacity(s.len() + p.len() + o.len() + 2);
pos.extend(&p);
pos.push(0);
pos.extend(&o);
pos.push(0);
pos.extend(&s);
let mut osp = Vec::with_capacity(s.len() + p.len() + o.len() + 2);
osp.extend(&o);
osp.push(0);
osp.extend(&s);
osp.push(0);
osp.extend(&p);
TripleIndexKeys { spo, pos, osp }
}
}
pub struct TripleIndexKeys {
pub spo: Vec<u8>,
pub pos: Vec<u8>,
pub osp: Vec<u8>,
}
impl fmt::Display for Triple {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {}", self.subject, self.predicate, self.object)
}
}
pub struct TripleBuilder {
subject: Option<NodeId>,
predicate: Option<Predicate>,
object: Option<Value>,
meta: TripleMeta,
}
impl TripleBuilder {
pub fn new() -> Self {
Self {
subject: None,
predicate: None,
object: None,
meta: TripleMeta::default(),
}
}
pub fn subject(mut self, subject: impl Into<NodeId>) -> Self {
self.subject = Some(subject.into());
self
}
pub fn predicate(mut self, predicate: impl Into<Predicate>) -> Self {
self.predicate = Some(predicate.into());
self
}
pub fn object(mut self, object: impl Into<Value>) -> Self {
self.object = Some(object.into());
self
}
pub fn object_node(mut self, node: impl Into<NodeId>) -> Self {
self.object = Some(Value::Node(node.into()));
self
}
pub fn author(mut self, author: impl Into<NodeId>) -> Self {
self.meta.author = Some(author.into());
self
}
pub fn source(mut self, source: impl Into<String>) -> Self {
self.meta.source = Some(source.into());
self
}
pub fn build(self) -> Option<Triple> {
Some(Triple {
subject: self.subject?,
predicate: self.predicate?,
object: self.object?,
meta: self.meta,
})
}
}
impl Default for TripleBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_triple() {
let triple = Triple::new(
NodeId::named("user:alice"),
Predicate::named("has_name"),
Value::literal("Alice"),
);
assert_eq!(triple.subject.as_name(), Some("user:alice"));
assert_eq!(triple.predicate.as_str(), "has_name");
assert_eq!(triple.object.as_string(), Some("Alice"));
}
#[test]
fn test_triple_id() {
let t1 = Triple::new(
NodeId::named("a"),
Predicate::named("b"),
Value::literal("c"),
);
let t2 = Triple::new(
NodeId::named("a"),
Predicate::named("b"),
Value::literal("c"),
);
let t3 = Triple::new(
NodeId::named("a"),
Predicate::named("b"),
Value::literal("d"),
);
assert_eq!(t1.id(), t2.id()); assert_ne!(t1.id(), t3.id()); }
#[test]
fn test_triple_link() {
let triple = Triple::link(
NodeId::named("user:alice"),
Predicate::named("knows"),
NodeId::named("user:bob"),
);
assert!(triple.is_link());
assert!(!triple.is_literal());
assert_eq!(triple.object_node(), Some(&NodeId::named("user:bob")));
}
#[test]
fn test_triple_builder() {
let triple = TripleBuilder::new()
.subject("user:alice")
.predicate("has_age")
.object(Value::integer(30))
.author("system")
.source("import")
.build()
.unwrap();
assert_eq!(triple.object.as_integer(), Some(30));
assert_eq!(triple.meta.source, Some("import".to_string()));
}
#[test]
fn test_to_ntriples() {
let triple = Triple::new(
NodeId::named("user:alice"),
Predicate::named("has_name"),
Value::literal("Alice"),
);
let nt = triple.to_ntriples();
assert!(nt.contains("user:alice"));
assert!(nt.contains("has_name"));
assert!(nt.contains("Alice"));
}
#[test]
fn test_index_keys() {
let triple = Triple::new(
NodeId::named("s"),
Predicate::named("p"),
Value::literal("o"),
);
let keys = triple.index_keys();
assert!(!keys.spo.is_empty());
assert!(!keys.pos.is_empty());
assert!(!keys.osp.is_empty());
assert_ne!(keys.spo, keys.pos);
assert_ne!(keys.pos, keys.osp);
}
#[test]
fn test_serialization() {
let triple = Triple::new(
NodeId::named("test:s"),
Predicate::named("test:p"),
Value::literal("test:o"),
);
let bytes = triple.to_bytes();
let restored = Triple::from_bytes(&bytes).unwrap();
assert_eq!(triple.subject, restored.subject);
assert_eq!(triple.predicate, restored.predicate);
assert_eq!(triple.object, restored.object);
}
#[test]
fn test_triple_id_hex() {
let triple = Triple::new(
NodeId::named("a"),
Predicate::named("b"),
Value::literal("c"),
);
let id = triple.id();
let hex = id.to_hex();
let restored = TripleId::from_hex(&hex).unwrap();
assert_eq!(id, restored);
}
}