use crate::files::load_to_graph; use crate::vocab::*; use anyhow::{anyhow, Result}; use oxigraph::model::vocab::*; use oxigraph::model::*; use std::collections::VecDeque; use std::fmt; pub struct Test { pub id: NamedNode, pub kind: NamedNode, pub name: Option, pub comment: Option, pub action: Option, pub query: Option, pub update: Option, pub data: Option, pub graph_data: Vec<(NamedNode, String)>, pub service_data: Vec<(String, String)>, pub result: Option, pub result_graph_data: Vec<(NamedNode, String)>, } impl fmt::Display for Test { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.kind)?; for name in &self.name { write!(f, " named \"{name}\"")?; } for comment in &self.comment { write!(f, " with comment \"{comment}\"")?; } if let Some(action) = &self.action { write!(f, " on file \"{action}\"")?; } if let Some(query) = &self.query { write!(f, " on query {}", &query)?; } for data in &self.data { write!(f, " with data {data}")?; } for (_, data) in &self.graph_data { write!(f, " and graph data {data}")?; } for result in &self.result { write!(f, " and expected result {result}")?; } Ok(()) } } pub struct TestManifest { graph: Graph, tests_to_do: VecDeque, manifests_to_do: VecDeque, } impl TestManifest { pub fn new(manifest_urls: impl IntoIterator) -> Self { Self { graph: Graph::new(), tests_to_do: VecDeque::new(), manifests_to_do: manifest_urls .into_iter() .map(|url| url.to_string()) .collect(), } } } impl Iterator for TestManifest { type Item = Result; fn next(&mut self) -> Option> { loop { if let Some(next) = self.next_test() { return Some(next); } if let Err(e) = self.load_next_manifest()? { return Some(Err(e)); } } } } impl TestManifest { fn next_test(&mut self) -> Option> { let test_node = self.tests_to_do.pop_front()?; let test_node = match test_node { Term::NamedNode(n) => n, _ => { return Some(Err(anyhow!("Invalid test identifier. Got {test_node}"))); } }; let name = match self .graph .object_for_subject_predicate(&test_node, mf::NAME) { Some(TermRef::Literal(c)) => Some(c.value().to_string()), _ => None, }; let kind = match self .graph .object_for_subject_predicate(&test_node, rdf::TYPE) { Some(TermRef::NamedNode(c)) => c.into_owned(), _ => { return Some(Err(anyhow!( "The test {test_node} named {} has no rdf:type", name.as_deref().unwrap_or("") ))); } }; let comment = match self .graph .object_for_subject_predicate(&test_node, rdfs::COMMENT) { Some(TermRef::Literal(c)) => Some(c.value().to_string()), _ => None, }; let (action, query, update, data, graph_data, service_data) = match self .graph .object_for_subject_predicate(&test_node, mf::ACTION) { Some(TermRef::NamedNode(n)) => ( Some(n.as_str().to_owned()), None, None, None, vec![], vec![], ), Some(TermRef::BlankNode(n)) => { let query = match self.graph.object_for_subject_predicate(n, qt::QUERY) { Some(TermRef::NamedNode(q)) => Some(q.as_str().to_owned()), _ => None, }; let update = match self.graph.object_for_subject_predicate(n, ut::REQUEST) { Some(TermRef::NamedNode(q)) => Some(q.as_str().to_owned()), _ => None, }; let data = match self .graph .object_for_subject_predicate(n, qt::DATA) .or_else(|| self.graph.object_for_subject_predicate(n, ut::DATA)) { Some(TermRef::NamedNode(q)) => Some(q.as_str().to_owned()), _ => None, }; let graph_data = self .graph .objects_for_subject_predicate(n, qt::GRAPH_DATA) .chain(self.graph.objects_for_subject_predicate(n, ut::GRAPH_DATA)) .filter_map(|g| match g { TermRef::NamedNode(q) => Some((q.into_owned(), q.as_str().to_owned())), TermRef::BlankNode(node) => { if let Some(TermRef::NamedNode(graph)) = self.graph.object_for_subject_predicate(node, ut::GRAPH) { if let Some(TermRef::Literal(name)) = self.graph.object_for_subject_predicate(node, rdfs::LABEL) { Some(( NamedNode::new(name.value()).unwrap(), graph.as_str().to_owned(), )) } else { Some((graph.into_owned(), graph.as_str().to_owned())) } } else { None } } _ => None, }) .collect(); let service_data = self .graph .objects_for_subject_predicate(n, qt::SERVICE_DATA) .filter_map(|g| match g { TermRef::NamedNode(g) => Some(g.into()), TermRef::BlankNode(g) => Some(g.into()), _ => None, }) .filter_map(|g: SubjectRef<'_>| { if let ( Some(TermRef::NamedNode(endpoint)), Some(TermRef::NamedNode(data)), ) = ( self.graph.object_for_subject_predicate(g, qt::ENDPOINT), self.graph.object_for_subject_predicate(g, qt::DATA), ) { Some((endpoint.as_str().to_owned(), data.as_str().to_owned())) } else { None } }) .collect(); (None, query, update, data, graph_data, service_data) } Some(_) => return Some(Err(anyhow!("invalid action"))), None => { return Some(Err(anyhow!("action not found for test {test_node}"))); } }; let (result, result_graph_data) = match self .graph .object_for_subject_predicate(&test_node, mf::RESULT) { Some(TermRef::NamedNode(n)) => (Some(n.as_str().to_owned()), Vec::new()), Some(TermRef::BlankNode(n)) => ( if let Some(TermRef::NamedNode(result)) = self.graph.object_for_subject_predicate(n, ut::DATA) { Some(result.as_str().to_owned()) } else { None }, self.graph .objects_for_subject_predicate(n, ut::GRAPH_DATA) .filter_map(|g| match g { TermRef::NamedNode(q) => Some((q.into_owned(), q.as_str().to_owned())), TermRef::BlankNode(node) => { if let Some(TermRef::NamedNode(graph)) = self.graph.object_for_subject_predicate(node, ut::GRAPH) { if let Some(TermRef::Literal(name)) = self.graph.object_for_subject_predicate(node, rdfs::LABEL) { Some(( NamedNode::new(name.value()).unwrap(), graph.as_str().to_owned(), )) } else { Some((graph.into_owned(), graph.as_str().to_owned())) } } else { None } } _ => None, }) .collect(), ), Some(_) => return Some(Err(anyhow!("invalid result"))), None => (None, Vec::new()), }; Some(Ok(Test { id: test_node, kind, name, comment, action, query, update, data, graph_data, service_data, result, result_graph_data, })) } fn load_next_manifest(&mut self) -> Option> { let url = self.manifests_to_do.pop_front()?; self.graph.clear(); if let Err(error) = load_to_graph(&url, &mut self.graph) { return Some(Err(error)); } let manifests = self .graph .subjects_for_predicate_object(rdf::TYPE, mf::MANIFEST) .collect::>(); if manifests.len() != 1 { return Some(Err(anyhow!( "The file {url} should contain a single manifest" ))); } for manifest in manifests { match self .graph .object_for_subject_predicate(manifest, mf::INCLUDE) { Some(TermRef::BlankNode(list)) => { self.manifests_to_do.extend( RdfListIterator::iter(&self.graph, list.into()).filter_map(|m| match m { Term::NamedNode(nm) => Some(nm.into_string()), _ => None, }), ); } Some(_) => return Some(Err(anyhow!("invalid tests list"))), None => (), } // New tests match self .graph .object_for_subject_predicate(manifest, mf::ENTRIES) { Some(TermRef::BlankNode(list)) => { self.tests_to_do .extend(RdfListIterator::iter(&self.graph, list.into())); } Some(term) => { return Some(Err(anyhow!("Invalid tests list. Got term {term}"))); } None => (), } } Some(Ok(())) } } struct RdfListIterator<'a> { graph: &'a Graph, current_node: Option>, } impl<'a> RdfListIterator<'a> { fn iter(graph: &'a Graph, root: SubjectRef<'a>) -> RdfListIterator<'a> { RdfListIterator { graph, current_node: Some(root), } } } impl<'a> Iterator for RdfListIterator<'a> { type Item = Term; fn next(&mut self) -> Option { match self.current_node { Some(current) => { let result = self .graph .object_for_subject_predicate(current, rdf::FIRST) .map(|v| v.into_owned()); self.current_node = match self.graph.object_for_subject_predicate(current, rdf::REST) { Some(TermRef::NamedNode(n)) if n == rdf::NIL => None, Some(TermRef::NamedNode(n)) => Some(n.into()), Some(TermRef::BlankNode(n)) => Some(n.into()), _ => None, }; result } None => None, } } }