use super::ToolResult;
use oxirs_arq::{SparqlUpdate, SparqlUpdateParser};
use std::io::Read;
fn read_update_from_file(path: &str) -> ToolResult<String> {
let mut file =
std::fs::File::open(path).map_err(|e| format!("cannot open file {path:?}: {e}"))?;
let mut content = String::new();
file.read_to_string(&mut content)
.map_err(|e| format!("cannot read file {path:?}: {e}"))?;
Ok(content)
}
fn describe_update(update: &SparqlUpdate) -> String {
format!("{update:#?}")
}
pub async fn run(_update: String, _file: bool, _print_ast: bool) -> ToolResult {
let update_text = if _file {
read_update_from_file(&_update)?
} else {
_update
};
match SparqlUpdateParser::parse(&update_text) {
Ok(updates) => {
let count = updates.len();
println!(
"Successfully parsed {} SPARQL Update operation{}.",
count,
if count == 1 { "" } else { "s" }
);
if _print_ast {
for (idx, update) in updates.iter().enumerate() {
println!("\n--- Operation {} ---", idx + 1);
println!("{}", describe_update(update));
}
}
}
Err(parse_err) => {
let msg = format!("SPARQL Update parse error: {parse_err}");
println!("{msg}");
return Err(msg.into());
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
const INSERT_DATA: &str =
"INSERT DATA { <http://example.org/s> <http://example.org/p> <http://example.org/o> }";
const DELETE_DATA: &str =
"DELETE DATA { <http://example.org/s> <http://example.org/p> <http://example.org/o> }";
#[test]
fn test_describe_insert_data() {
let updates = SparqlUpdateParser::parse(INSERT_DATA).expect("should parse");
assert_eq!(updates.len(), 1);
let desc = describe_update(&updates[0]);
assert!(desc.contains("InsertData") || desc.contains("Insert"));
}
#[tokio::test]
async fn test_run_insert_data_no_ast() {
run(INSERT_DATA.to_string(), false, false)
.await
.expect("should parse INSERT DATA");
}
#[tokio::test]
async fn test_run_delete_data_with_ast() {
run(DELETE_DATA.to_string(), false, true)
.await
.expect("should parse DELETE DATA with AST");
}
#[tokio::test]
async fn test_run_invalid_update_returns_error() {
let result = run("NOT A VALID UPDATE".to_string(), false, false).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_run_from_file() {
let dir = std::env::temp_dir();
let path = dir.join("oxirs_uparse_test.rq");
{
let mut f = std::fs::File::create(&path).expect("create temp file");
f.write_all(INSERT_DATA.as_bytes()).expect("write");
}
let path_str = path.to_string_lossy().into_owned();
run(path_str, true, true)
.await
.expect("should read and parse from file");
std::fs::remove_file(&path).ok();
}
#[tokio::test]
async fn test_run_missing_file_returns_error() {
let result = run(
"/tmp/oxirs_uparse_nonexistent_xyz.rq".to_string(),
true,
false,
)
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_run_multiple_operations() {
let multi = format!("{INSERT_DATA} ; {DELETE_DATA}");
run(multi, false, true)
.await
.expect("should parse multiple operations");
}
}