use super::types::{GraphTarget, GspError};
use oxirs_core::model::{GraphName, NamedNode, Quad, Triple};
use oxirs_core::Store;
use std::sync::Arc;
pub struct GraphAccess {
target: GraphTarget,
exists: bool,
}
impl GraphAccess {
pub fn new(target: GraphTarget, store: &dyn Store) -> Self {
let exists = Self::check_exists(&target, store);
Self { target, exists }
}
fn check_exists(target: &GraphTarget, store: &dyn Store) -> bool {
match target {
GraphTarget::Default => {
true
}
GraphTarget::Union => {
true
}
GraphTarget::Named(uri) => {
if let Ok(node) = NamedNode::new(uri) {
let graph_name = GraphName::NamedNode(node);
store
.find_quads(None, None, None, Some(&graph_name))
.map(|quads| !quads.is_empty())
.unwrap_or(false)
} else {
false
}
}
}
}
pub fn exists(&self) -> bool {
self.exists
}
pub fn target(&self) -> &GraphTarget {
&self.target
}
pub fn label(&self) -> String {
self.target.label()
}
pub fn is_writable(&self) -> bool {
self.target.is_writable()
}
pub fn get_triples(&self, store: &dyn Store) -> Result<Vec<Triple>, GspError> {
match &self.target {
GraphTarget::Default => {
let quads = store
.find_quads(None, None, None, Some(&GraphName::DefaultGraph))
.map_err(|e| GspError::StoreError(e.to_string()))?;
let triples: Vec<Triple> = quads
.into_iter()
.map(|quad| {
Triple::new(
quad.subject().clone(),
quad.predicate().clone(),
quad.object().clone(),
)
})
.collect();
Ok(triples)
}
GraphTarget::Union => {
let quads = store
.find_quads(None, None, None, None)
.map_err(|e| GspError::StoreError(e.to_string()))?;
let triples: Vec<Triple> = quads
.into_iter()
.map(|quad| {
Triple::new(
quad.subject().clone(),
quad.predicate().clone(),
quad.object().clone(),
)
})
.collect();
Ok(triples)
}
GraphTarget::Named(uri) => {
let node = NamedNode::new(uri)
.map_err(|e| GspError::BadRequest(format!("Invalid graph URI: {}", e)))?;
let graph_name = GraphName::NamedNode(node);
let quads = store
.find_quads(None, None, None, Some(&graph_name))
.map_err(|e| GspError::StoreError(e.to_string()))?;
let triples: Vec<Triple> = quads
.into_iter()
.map(|quad| {
Triple::new(
quad.subject().clone(),
quad.predicate().clone(),
quad.object().clone(),
)
})
.collect();
Ok(triples)
}
}
}
pub fn replace_triples(
&self,
store: &dyn Store,
triples: Vec<Triple>,
) -> Result<usize, GspError> {
if !self.is_writable() {
return Err(GspError::MethodNotAllowed(format!(
"Cannot write to {}",
self.label()
)));
}
match &self.target {
GraphTarget::Default => {
let existing = store
.find_quads(None, None, None, Some(&GraphName::DefaultGraph))
.map_err(|e| GspError::StoreError(e.to_string()))?;
for quad in existing {
store
.remove_quad(&quad)
.map_err(|e| GspError::StoreError(e.to_string()))?;
}
for triple in &triples {
let quad = Quad::new(
triple.subject().clone(),
triple.predicate().clone(),
triple.object().clone(),
GraphName::DefaultGraph,
);
store
.insert_quad(quad)
.map_err(|e| GspError::StoreError(e.to_string()))?;
}
Ok(triples.len())
}
GraphTarget::Named(uri) => {
let node = NamedNode::new(uri)
.map_err(|e| GspError::BadRequest(format!("Invalid graph URI: {}", e)))?;
let graph_name = GraphName::NamedNode(node);
let existing = store
.find_quads(None, None, None, Some(&graph_name))
.map_err(|e| GspError::StoreError(e.to_string()))?;
for quad in existing {
store
.remove_quad(&quad)
.map_err(|e| GspError::StoreError(e.to_string()))?;
}
for triple in &triples {
let quad = Quad::new(
triple.subject().clone(),
triple.predicate().clone(),
triple.object().clone(),
graph_name.clone(),
);
store
.insert_quad(quad)
.map_err(|e| GspError::StoreError(e.to_string()))?;
}
Ok(triples.len())
}
GraphTarget::Union => Err(GspError::MethodNotAllowed(
"Cannot write to union graph".to_string(),
)),
}
}
pub fn add_triples(&self, store: &dyn Store, triples: Vec<Triple>) -> Result<usize, GspError> {
if !self.is_writable() {
return Err(GspError::MethodNotAllowed(format!(
"Cannot write to {}",
self.label()
)));
}
let graph_name = match &self.target {
GraphTarget::Default => GraphName::DefaultGraph,
GraphTarget::Named(uri) => {
let node = NamedNode::new(uri)
.map_err(|e| GspError::BadRequest(format!("Invalid graph URI: {}", e)))?;
GraphName::NamedNode(node)
}
GraphTarget::Union => {
return Err(GspError::MethodNotAllowed(
"Cannot write to union graph".to_string(),
))
}
};
for triple in &triples {
let quad = Quad::new(
triple.subject().clone(),
triple.predicate().clone(),
triple.object().clone(),
graph_name.clone(),
);
store
.insert_quad(quad)
.map_err(|e| GspError::StoreError(e.to_string()))?;
}
Ok(triples.len())
}
pub fn delete_graph(&self, store: &dyn Store) -> Result<usize, GspError> {
if !self.is_writable() {
return Err(GspError::MethodNotAllowed(format!(
"Cannot delete {}",
self.label()
)));
}
let graph_name = match &self.target {
GraphTarget::Default => GraphName::DefaultGraph,
GraphTarget::Named(uri) => {
let node = NamedNode::new(uri)
.map_err(|e| GspError::BadRequest(format!("Invalid graph URI: {}", e)))?;
GraphName::NamedNode(node)
}
GraphTarget::Union => {
return Err(GspError::MethodNotAllowed(
"Cannot delete union graph".to_string(),
))
}
};
let existing = store
.find_quads(None, None, None, Some(&graph_name))
.map_err(|e| GspError::StoreError(e.to_string()))?;
let count = existing.len();
for quad in existing {
store
.remove_quad(&quad)
.map_err(|e| GspError::StoreError(e.to_string()))?;
}
Ok(count)
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxirs_core::model::Literal;
use oxirs_core::rdf_store::ConcreteStore;
fn setup_test_store() -> ConcreteStore {
let store = ConcreteStore::new().unwrap();
let s1 = NamedNode::new("http://example.org/s1").unwrap();
let p1 = NamedNode::new("http://example.org/p1").unwrap();
let o1 = Literal::new_simple_literal("value1");
let triple1 = Triple::new(s1, p1, o1);
store.insert_triple(triple1).unwrap();
store
}
#[test]
fn test_graph_access_default() {
let store = setup_test_store();
let target = GraphTarget::Default;
let access = GraphAccess::new(target, &store);
assert!(access.exists());
assert!(access.is_writable());
}
#[test]
fn test_graph_access_union() {
let store = setup_test_store();
let target = GraphTarget::Union;
let access = GraphAccess::new(target, &store);
assert!(access.exists());
assert!(!access.is_writable());
}
#[test]
fn test_get_triples_default_graph() {
let store = setup_test_store();
let target = GraphTarget::Default;
let access = GraphAccess::new(target, &store);
let triples = access.get_triples(&store).unwrap();
assert!(!triples.is_empty());
}
#[test]
fn test_replace_triples() {
let store = setup_test_store();
let target = GraphTarget::Default;
let access = GraphAccess::new(target, &store);
let s = NamedNode::new("http://example.org/new").unwrap();
let p = NamedNode::new("http://example.org/pred").unwrap();
let o = Literal::new_simple_literal("new value");
let triple = Triple::new(s, p, o);
let count = access.replace_triples(&store, vec![triple]).unwrap();
assert_eq!(count, 1);
let triples = access.get_triples(&store).unwrap();
assert_eq!(triples.len(), 1);
}
}