shacl_validation/shacl_processor.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
use clap::ValueEnum;
use prefixmap::PrefixMap;
use shacl_ast::compiled::schema::CompiledSchema;
use sparql_service::RdfData;
use srdf::RDFFormat;
use srdf::SRDFBasic;
use srdf::SRDFSparql;
use std::fmt::Debug;
use std::path::Path;
use crate::engine::native::NativeEngine;
use crate::engine::sparql::SparqlEngine;
use crate::engine::Engine;
use crate::shape::Validate;
use crate::store::graph::Graph;
use crate::store::sparql::Endpoint;
use crate::store::Store;
use crate::validate_error::ValidateError;
use crate::validation_report::report::ValidationReport;
#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Default)]
/// Backend used for the validation.
///
/// According to the SHACL Recommendation, there exists no concrete method for
/// implementing SHACL. Thus, by choosing your preferred SHACL Validation Mode,
/// the user can select which engine is used for the validation.
pub enum ShaclValidationMode {
/// We use a Rust native engine in an imperative manner (performance)
#[default]
Native,
/// We use a SPARQL-based engine, which is declarative
Sparql,
}
/// The basic operations of the SHACL Processor.
///
/// The ShaclProcessor trait is the one in charge of applying the SHACL
/// Validation algorithm. For this, first, the validation report is initiliazed
/// to empty, and, for each shape in the schema, the target nodes are
/// selected, and then, each validator for each constraint is applied.
pub trait ShaclProcessor<S: SRDFBasic + Debug> {
fn store(&self) -> &S;
fn runner(&self) -> &dyn Engine<S>;
/// Executes the Validation of the provided Graph, in any of the supported
/// formats, against the shapes graph passed as an argument. As a result,
/// the Validation Report generated from the validation process is returned.
///
/// # Arguments
///
/// * `shapes_graph` - A compiled SHACL shapes graph
fn validate(
&self,
shapes_graph: &CompiledSchema<S>,
) -> Result<ValidationReport, ValidateError> {
// we initialize the validation report to empty
let mut validation_results = Vec::new();
// for each shape in the schema
for (_, shape) in shapes_graph.iter() {
let results = shape.validate(self.store(), self.runner(), None)?;
validation_results.extend(results);
}
Ok(ValidationReport::new()
.with_results(validation_results)
.with_prefixmap(shapes_graph.prefix_map())) // return the possibly empty validation report
}
}
/// The In-Memory Graph Validation algorithm.
///
/// ```
/// use std::path::Path;
///
/// use shacl_validation::shacl_processor::GraphValidation;
/// use shacl_validation::shacl_processor::ShaclValidationMode;
/// use shacl_validation::shacl_processor::ShaclProcessor;
/// use shacl_validation::store::ShaclDataManager;
/// use srdf::RDFFormat;
///
/// let graph_validation = GraphValidation::new(
/// Path::new("../examples/book_conformant.ttl"), // example graph (refer to the examples folder)
/// RDFFormat::Turtle, // serialization format of the graph
/// None, // no base is defined
/// ShaclValidationMode::Native, // use the Native mode (performance)
/// )
/// .unwrap();
///
/// // the following schema should generate no errors when the conforming graph
/// // loaded in the previous declaration is used for validation
/// let schema = std::fs::read_to_string(Path::new("../examples/book.ttl")).unwrap();
/// let cursor = std::io::Cursor::new(schema);
/// let compiled_schema = ShaclDataManager::load(cursor, RDFFormat::Turtle, None).unwrap();
///
/// let report = graph_validation.validate(&compiled_schema).unwrap();
///
/// assert_eq!(report.results().len(), 0);
/// ```
pub struct GraphValidation {
store: Graph,
mode: ShaclValidationMode,
}
impl GraphValidation {
/// Returns an In-Memory Graph validation SHACL processor.
///
/// # Arguments
///
/// * `data` - A path to the graph's serialization file
/// * `data_format` - Any of the possible RDF serialization formats
/// * `base` - An optional String, the base URI
/// * `mode` - Any of the possible SHACL validation modes
///
/// # Examples
///
/// ```
/// use std::path::Path;
///
/// use shacl_validation::shacl_processor::GraphValidation;
/// use shacl_validation::shacl_processor::ShaclValidationMode;
/// use shacl_validation::shacl_processor::ShaclProcessor;
/// use srdf::RDFFormat;
///
/// let graph_validation = GraphValidation::new(
/// Path::new("../examples/book_conformant.ttl"), // example graph (refer to the examples folder)
/// RDFFormat::Turtle, // serialization format of the graph
/// None, // no base is defined
/// ShaclValidationMode::Native, // use the Native mode (performance)
/// );
/// ```
pub fn from_path(
data: &Path,
data_format: RDFFormat,
base: Option<&str>,
mode: ShaclValidationMode,
) -> Result<Self, ValidateError> {
Ok(GraphValidation {
store: Graph::from_path(data, data_format, base)?,
mode,
})
}
pub fn from_graph(graph: Graph, mode: ShaclValidationMode) -> GraphValidation {
GraphValidation { store: graph, mode }
}
}
impl ShaclProcessor<RdfData> for GraphValidation {
fn store(&self) -> &RdfData {
self.store.store()
}
fn runner(&self) -> &dyn Engine<RdfData> {
match self.mode {
ShaclValidationMode::Native => &NativeEngine,
ShaclValidationMode::Sparql => &SparqlEngine,
}
}
}
/// The Endpoint Graph Validation algorithm.
pub struct EndpointValidation {
store: Endpoint,
mode: ShaclValidationMode,
}
impl EndpointValidation {
pub fn new(
data: &str,
prefixmap: &PrefixMap,
mode: ShaclValidationMode,
) -> Result<Self, ValidateError> {
Ok(EndpointValidation {
store: Endpoint::new(data, prefixmap)?,
mode,
})
}
pub fn from_sparql(
sparql: SRDFSparql,
mode: ShaclValidationMode,
) -> Result<Self, ValidateError> {
Ok(EndpointValidation {
store: Endpoint::from_sparql(sparql),
mode,
})
}
}
impl ShaclProcessor<SRDFSparql> for EndpointValidation {
fn store(&self) -> &SRDFSparql {
self.store.store()
}
fn runner(&self) -> &dyn Engine<SRDFSparql> {
match self.mode {
ShaclValidationMode::Native => &NativeEngine,
ShaclValidationMode::Sparql => &SparqlEngine,
}
}
}