shacl_validation/
shacl_processor.rs

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