use crate::writer::{RdfTerm, TermType};
use std::collections::{BTreeMap, HashSet};
use std::fmt;
#[derive(Debug, Clone)]
pub struct PatchError {
pub line: usize,
pub message: String,
}
impl PatchError {
pub(crate) fn new(line: usize, message: impl Into<String>) -> Self {
Self {
line,
message: message.into(),
}
}
pub(crate) fn at(line: usize, msg: impl fmt::Display) -> Self {
Self::new(line, msg.to_string())
}
}
impl fmt::Display for PatchError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "patch error at line {}: {}", self.line, self.message)
}
}
impl std::error::Error for PatchError {}
pub type PatchResult<T> = Result<T, PatchError>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PatchHeader {
Version(String),
Previous(String),
Id(String),
Unknown {
key: String,
value: String,
},
}
impl PatchHeader {
pub fn key(&self) -> &str {
match self {
PatchHeader::Version(_) => "version",
PatchHeader::Previous(_) => "prev",
PatchHeader::Id(_) => "id",
PatchHeader::Unknown { key, .. } => key.as_str(),
}
}
pub fn value(&self) -> &str {
match self {
PatchHeader::Version(v) | PatchHeader::Previous(v) | PatchHeader::Id(v) => v.as_str(),
PatchHeader::Unknown { value, .. } => value.as_str(),
}
}
}
impl fmt::Display for PatchHeader {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "H {} {}", self.key(), self.value())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PatchTerm(pub RdfTerm);
impl PatchTerm {
pub fn iri(iri: impl Into<String>) -> Self {
Self(RdfTerm::iri(iri))
}
pub fn blank_node(id: impl Into<String>) -> Self {
Self(RdfTerm::blank_node(id))
}
pub fn literal(value: impl Into<String>) -> Self {
Self(RdfTerm::simple_literal(value))
}
pub fn lang_literal(value: impl Into<String>, lang: impl Into<String>) -> Self {
Self(RdfTerm::lang_literal(value, lang))
}
pub fn typed_literal(value: impl Into<String>, datatype: impl Into<String>) -> Self {
Self(RdfTerm::typed_literal(value, datatype))
}
pub fn term(&self) -> &RdfTerm {
&self.0
}
pub fn is_iri(&self) -> bool {
self.0.term_type == TermType::Iri
}
pub fn is_blank_node(&self) -> bool {
self.0.term_type == TermType::BlankNode
}
pub fn value(&self) -> &str {
&self.0.value
}
}
impl fmt::Display for PatchTerm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0.term_type {
TermType::Iri => write!(f, "<{}>", self.0.value),
TermType::BlankNode => write!(f, "_:{}", self.0.value),
TermType::Literal { datatype, lang } => {
let escaped = self.0.value.replace('\\', "\\\\").replace('"', "\\\"");
write!(f, "\"{escaped}\"")?;
if let Some(l) = lang {
write!(f, "@{l}")?;
} else if let Some(dt) = datatype {
write!(f, "^^<{dt}>")?;
}
Ok(())
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PatchTriple {
pub subject: PatchTerm,
pub predicate: PatchTerm,
pub object: PatchTerm,
}
impl PatchTriple {
pub fn new(subject: PatchTerm, predicate: PatchTerm, object: PatchTerm) -> Self {
Self {
subject,
predicate,
object,
}
}
}
impl fmt::Display for PatchTriple {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {} .", self.subject, self.predicate, self.object)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PatchQuad {
pub subject: PatchTerm,
pub predicate: PatchTerm,
pub object: PatchTerm,
pub graph: PatchTerm,
}
impl PatchQuad {
pub fn new(
subject: PatchTerm,
predicate: PatchTerm,
object: PatchTerm,
graph: PatchTerm,
) -> Self {
Self {
subject,
predicate,
object,
graph,
}
}
}
impl fmt::Display for PatchQuad {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {} {} {} .",
self.subject, self.predicate, self.object, self.graph
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PatchChange {
AddPrefix {
prefix: String,
iri: String,
},
DeletePrefix {
prefix: String,
iri: String,
},
AddTriple(PatchTriple),
DeleteTriple(PatchTriple),
AddQuad(PatchQuad),
DeleteQuad(PatchQuad),
TransactionBegin,
TransactionCommit,
TransactionAbort,
}
impl PatchChange {
pub fn line_prefix(&self) -> &'static str {
match self {
PatchChange::AddPrefix { .. } => "PA",
PatchChange::DeletePrefix { .. } => "PD",
PatchChange::AddTriple(_) => "A",
PatchChange::DeleteTriple(_) => "D",
PatchChange::AddQuad(_) => "A",
PatchChange::DeleteQuad(_) => "D",
PatchChange::TransactionBegin => "TX",
PatchChange::TransactionCommit => "TC",
PatchChange::TransactionAbort => "TA",
}
}
pub fn is_add(&self) -> bool {
matches!(
self,
PatchChange::AddTriple(_) | PatchChange::AddQuad(_) | PatchChange::AddPrefix { .. }
)
}
pub fn is_delete(&self) -> bool {
matches!(
self,
PatchChange::DeleteTriple(_)
| PatchChange::DeleteQuad(_)
| PatchChange::DeletePrefix { .. }
)
}
pub fn is_transaction_control(&self) -> bool {
matches!(
self,
PatchChange::TransactionBegin
| PatchChange::TransactionCommit
| PatchChange::TransactionAbort
)
}
}
impl fmt::Display for PatchChange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PatchChange::AddPrefix { prefix, iri } => {
write!(f, "PA {prefix} <{iri}>")
}
PatchChange::DeletePrefix { prefix, iri } => {
write!(f, "PD {prefix} <{iri}>")
}
PatchChange::AddTriple(t) => write!(f, "A {t}"),
PatchChange::DeleteTriple(t) => write!(f, "D {t}"),
PatchChange::AddQuad(q) => write!(f, "A {q}"),
PatchChange::DeleteQuad(q) => write!(f, "D {q}"),
PatchChange::TransactionBegin => write!(f, "TX"),
PatchChange::TransactionCommit => write!(f, "TC"),
PatchChange::TransactionAbort => write!(f, "TA"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct RdfPatch {
pub headers: Vec<PatchHeader>,
pub changes: Vec<PatchChange>,
}
impl RdfPatch {
pub fn new() -> Self {
Self::default()
}
pub fn with_changes(headers: Vec<PatchHeader>, changes: Vec<PatchChange>) -> Self {
Self { headers, changes }
}
pub fn id(&self) -> Option<&str> {
self.headers.iter().find_map(|h| {
if let PatchHeader::Id(v) = h {
Some(v.as_str())
} else {
None
}
})
}
pub fn previous(&self) -> Option<&str> {
self.headers.iter().find_map(|h| {
if let PatchHeader::Previous(v) = h {
Some(v.as_str())
} else {
None
}
})
}
pub fn add_count(&self) -> usize {
self.changes
.iter()
.filter(|c| matches!(c, PatchChange::AddTriple(_) | PatchChange::AddQuad(_)))
.count()
}
pub fn delete_count(&self) -> usize {
self.changes
.iter()
.filter(|c| matches!(c, PatchChange::DeleteTriple(_) | PatchChange::DeleteQuad(_)))
.count()
}
pub fn is_empty(&self) -> bool {
self.headers.is_empty() && self.changes.is_empty()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct PatchStats {
pub triples_added: usize,
pub triples_deleted: usize,
pub prefixes_added: usize,
pub prefixes_deleted: usize,
pub transactions: usize,
pub aborts: usize,
}
#[derive(Debug, Clone, Default)]
pub struct Graph {
pub triples: HashSet<String>,
pub prefixes: BTreeMap<String, String>,
triple_objects: Vec<PatchTriple>,
}
impl Graph {
pub fn new() -> Self {
Self::default()
}
pub fn add_triple(&mut self, triple: PatchTriple) -> bool {
let key = Self::triple_key(&triple);
if self.triples.insert(key) {
self.triple_objects.push(triple);
true
} else {
false
}
}
pub fn remove_triple(&mut self, triple: &PatchTriple) -> bool {
let key = Self::triple_key(triple);
if self.triples.remove(&key) {
self.triple_objects.retain(|t| Self::triple_key(t) != key);
true
} else {
false
}
}
pub fn contains(&self, triple: &PatchTriple) -> bool {
self.triples.contains(&Self::triple_key(triple))
}
pub fn len(&self) -> usize {
self.triples.len()
}
pub fn is_empty(&self) -> bool {
self.triples.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &PatchTriple> {
self.triple_objects.iter()
}
pub(crate) fn triple_key(t: &PatchTriple) -> String {
format!("{}\x00{}\x00{}", t.subject, t.predicate, t.object)
}
}