shex_validation/
validator.rs

1use crate::atom;
2use crate::validator_error::*;
3use crate::validator_runner::Engine;
4use crate::Reason;
5use crate::ValidatorConfig;
6use either::Either;
7use prefixmap::IriRef;
8use prefixmap::PrefixMap;
9use serde_json::Value;
10use shapemap::query_shape_map::QueryShapeMap;
11use shapemap::ResultShapeMap;
12use shapemap::ValidationStatus;
13use shex_ast::ir::schema_ir::SchemaIR;
14use shex_ast::ir::shape_expr::ShapeExpr;
15use shex_ast::ir::shape_label::ShapeLabel;
16use shex_ast::object_value::ObjectValue;
17use shex_ast::Node;
18use shex_ast::ShapeExprLabel;
19use shex_ast::ShapeLabelIdx;
20use srdf::Query;
21use tracing::debug;
22
23type Result<T> = std::result::Result<T, ValidatorError>;
24type Atom = atom::Atom<(Node, ShapeLabelIdx)>;
25
26#[derive(Debug)]
27pub struct Validator {
28    schema: SchemaIR,
29    config: ValidatorConfig,
30}
31
32impl Validator {
33    pub fn new(schema: SchemaIR, config: &ValidatorConfig) -> Result<Validator> {
34        if config.check_negation_requirement.unwrap_or(true) && schema.has_neg_cycle() {
35            let neg_cycles = schema.neg_cycles();
36            let mut neg_cycles_displayed = Vec::new();
37            for cycle in neg_cycles.iter() {
38                let mut cycle_displayed = Vec::new();
39                for (source, target, shapes) in cycle.iter() {
40                    let source_str = if let Some(label) = schema.shape_label_from_idx(source) {
41                        schema.show_label(label)
42                    } else {
43                        format!("internal_{source}")
44                    };
45                    let target_str = if let Some(label) = schema.shape_label_from_idx(target) {
46                        schema.show_label(label)
47                    } else {
48                        format!("internal_{target}")
49                    };
50                    let mut shapes_str = Vec::new();
51                    for shape in shapes.iter() {
52                        let shape_str = if let Some(label) = schema.shape_label_from_idx(shape) {
53                            schema.show_label(label)
54                        } else {
55                            format!("internal_{shape}")
56                        };
57                        shapes_str.push(shape_str);
58                    }
59                    cycle_displayed.push((source_str, target_str, shapes_str));
60                }
61                neg_cycles_displayed.push(cycle_displayed);
62            }
63            return Err(ValidatorError::NegCycleError {
64                neg_cycles: neg_cycles_displayed,
65            });
66        }
67        // let engine = Engine::new(config);
68        Ok(Validator {
69            schema,
70            config: config.clone(),
71        })
72    }
73
74    /*pub fn reset_result_map(&mut self) {
75        self.runner.reset()
76    }*/
77
78    pub fn schema(&self) -> &SchemaIR {
79        &self.schema
80    }
81
82    /// validate a node against a shape label
83    pub fn validate_node_shape<S>(
84        &mut self,
85        node: &Node,
86        shape: &ShapeLabel,
87        rdf: &S,
88        schema: &SchemaIR,
89        maybe_nodes_prefixmap: &Option<PrefixMap>,
90        maybe_shapes_prefixmap: &Option<PrefixMap>,
91    ) -> Result<ResultShapeMap>
92    where
93        S: Query,
94    {
95        let idx = self.get_idx(shape)?;
96        let mut engine = Engine::new(&self.config);
97        engine.add_pending(node.clone(), idx);
98        debug!("Before while loop: ${}@{}", node, idx);
99        self.loop_validating(&mut engine, rdf, schema)?;
100        let result = self.result_map(&mut engine, maybe_nodes_prefixmap, maybe_shapes_prefixmap)?;
101        Ok(result)
102    }
103
104    fn get_shape_expr_label(
105        &self,
106        label: &ShapeExprLabel,
107        schema: &SchemaIR,
108    ) -> Result<ShapeLabelIdx> {
109        schema
110            .find_ref(label)
111            .map_err(|error| ValidatorError::ShapeLabelNotFoundError {
112                shape_label: label.clone(),
113                error: format!("{error}"),
114            })
115    }
116
117    pub fn validate_shapemap<S>(
118        &self,
119        shapemap: &QueryShapeMap,
120        rdf: &S,
121        schema: &SchemaIR,
122        maybe_nodes_prefixmap: &Option<PrefixMap>,
123        maybe_shapes_prefixmap: &Option<PrefixMap>,
124    ) -> Result<ResultShapeMap>
125    where
126        S: Query,
127    {
128        let mut engine = Engine::new(&self.config);
129        self.fill_pending(&mut engine, shapemap, rdf, schema)?;
130        self.loop_validating(&mut engine, rdf, schema)?;
131        let result = self.result_map(&mut engine, maybe_nodes_prefixmap, maybe_shapes_prefixmap)?;
132        Ok(result)
133    }
134
135    pub fn validate_shapemap2<S>(
136        &self,
137        shapemap: &QueryShapeMap,
138        rdf: &S,
139        schema: &SchemaIR,
140        maybe_nodes_prefixmap: &Option<PrefixMap>,
141        maybe_shapes_prefixmap: &Option<PrefixMap>,
142    ) -> Result<ResultShapeMap>
143    where
144        S: Query,
145    {
146        let mut engine = Engine::new(&self.config);
147        self.fill_pending(&mut engine, shapemap, rdf, schema)?;
148        engine.validate_pending(rdf, schema)?;
149        let result = self.result_map(&mut engine, maybe_nodes_prefixmap, maybe_shapes_prefixmap)?;
150        Ok(result)
151    }
152
153    fn fill_pending<S>(
154        &self,
155        engine: &mut Engine,
156        shapemap: &QueryShapeMap,
157        rdf: &S,
158        schema: &SchemaIR,
159    ) -> Result<()>
160    where
161        S: Query,
162    {
163        for (node_value, label) in shapemap.iter_node_shape(rdf) {
164            let idx = self.get_shape_expr_label(label, schema)?;
165            let node = self.node_from_object_value(node_value, rdf)?;
166            engine.add_pending(node.clone(), idx);
167        }
168        Ok(())
169    }
170
171    fn node_from_object_value<S>(&self, value: &ObjectValue, rdf: &S) -> Result<Node>
172    where
173        S: Query,
174    {
175        match value {
176            ObjectValue::IriRef(IriRef::Iri(iri)) => Ok(Node::iri(iri.clone())),
177            ObjectValue::IriRef(IriRef::Prefixed { prefix, local }) => {
178                let iri = rdf.resolve_prefix_local(prefix, local)?;
179                Ok(Node::iri(iri.clone()))
180            }
181            ObjectValue::Literal(lit) => Ok(Node::literal(lit.clone())),
182        }
183    }
184
185    fn loop_validating<S>(&self, engine: &mut Engine, rdf: &S, schema: &SchemaIR) -> Result<()>
186    where
187        S: Query,
188    {
189        while engine.no_end_steps() && engine.more_pending() {
190            engine.new_step();
191            let atom = engine.pop_pending().unwrap();
192            debug!("Processing atom: {}", show(&atom));
193            engine.add_processing(&atom);
194            let passed = self.check_node_atom(engine, &atom, rdf, schema)?;
195            engine.remove_processing(&atom);
196            match passed {
197                Either::Right(reasons) => {
198                    engine.add_checked_pos(atom, reasons);
199                }
200                Either::Left(errors) => {
201                    engine.add_checked_neg(atom.negated(), errors);
202                }
203            }
204        }
205        Ok(())
206    }
207
208    pub fn check_node_atom<S>(
209        &self,
210        engine: &mut Engine,
211        atom: &Atom,
212        rdf: &S,
213        schema: &SchemaIR,
214    ) -> Result<Either<Vec<ValidatorError>, Vec<Reason>>>
215    where
216        S: Query,
217    {
218        let (node, idx) = atom.get_value();
219        let se = find_shape_idx(idx, &self.schema);
220        match atom {
221            Atom::Pos { .. } => engine.check_node_shape_expr_old(node, se, rdf, schema),
222            Atom::Neg { .. } => {
223                // Check if a node doesn't conform to a shape expr
224                todo!()
225            }
226        }
227    }
228
229    fn get_idx(&self, shape: &ShapeLabel) -> Result<ShapeLabelIdx> {
230        match self.schema.find_label(shape) {
231            Some((idx, _se)) => Ok(*idx),
232            None => Err(ValidatorError::NotFoundShapeLabel {
233                shape: (*shape).clone(),
234            }),
235        }
236    }
237
238    fn get_shape_label(&self, idx: &ShapeLabelIdx) -> Result<&ShapeLabel> {
239        let (label, _se) = self.schema.find_shape_idx(idx).unwrap();
240        match label {
241            Some(label) => Ok(label),
242            None => Err(ValidatorError::NotFoundShapeLabelWithIndex { idx: *idx }),
243        }
244    }
245
246    pub fn result_map(
247        &self,
248        engine: &mut Engine,
249        maybe_nodes_prefixmap: &Option<PrefixMap>,
250        maybe_shapes_prefixmap: &Option<PrefixMap>,
251    ) -> Result<ResultShapeMap> {
252        let mut result = match (maybe_nodes_prefixmap, maybe_shapes_prefixmap) {
253            (None, None) => ResultShapeMap::new(),
254            (Some(npm), None) => ResultShapeMap::new().with_nodes_prefixmap(npm),
255            (None, Some(spm)) => ResultShapeMap::new().with_shapes_prefixmap(spm),
256            (Some(npm), Some(spm)) => ResultShapeMap::new()
257                .with_nodes_prefixmap(npm)
258                .with_shapes_prefixmap(spm),
259        };
260        for atom in &engine.checked() {
261            let (node, idx) = atom.get_value();
262            let label = self.get_shape_label(idx)?;
263            match atom {
264                Atom::Pos(pa) => {
265                    let reasons = engine.find_reasons(pa);
266                    let status = ValidationStatus::conformant(
267                        show_reasons(&reasons),
268                        json_reasons(&reasons),
269                    );
270                    // result.add_ok()
271                    result
272                        .add_result((*node).clone(), label.clone(), status)
273                        .map_err(|e| ValidatorError::AddingConformantError {
274                            node: node.to_string(),
275                            label: label.to_string(),
276                            error: format!("{e}"),
277                        })?;
278                }
279                Atom::Neg(na) => {
280                    let errors = engine.find_errors(na);
281                    let status = ValidationStatus::non_conformant(
282                        show_errors(&errors),
283                        json_errors(&errors),
284                    );
285                    result
286                        .add_result((*node).clone(), label.clone(), status)
287                        .map_err(|e| ValidatorError::AddingNonConformantError {
288                            node: node.to_string(),
289                            label: label.to_string(),
290                            error: format!("{e}"),
291                        })?;
292                }
293            }
294        }
295        for atom in &engine.pending() {
296            let (node, idx) = atom.get_value();
297            let label = self.get_shape_label(idx)?;
298            let status = ValidationStatus::pending();
299            result
300                .add_result((*node).clone(), label.clone(), status)
301                .map_err(|e| ValidatorError::AddingPendingError {
302                    node: node.to_string(),
303                    label: label.to_string(),
304                    error: format!("{e}"),
305                })?;
306        }
307        Ok(result)
308    }
309
310    pub fn shapes_prefixmap(&self) -> PrefixMap {
311        self.schema.prefixmap()
312    }
313}
314
315fn find_shape_idx<'a>(idx: &'a ShapeLabelIdx, schema: &'a SchemaIR) -> &'a ShapeExpr {
316    let (_label, se) = schema.find_shape_idx(idx).unwrap();
317    se
318}
319
320fn show_errors(errors: &[ValidatorError]) -> String {
321    let mut result = String::new();
322    for (err, idx) in errors.iter().enumerate() {
323        result.push_str(format!("Error #{idx}: {err}\n").as_str());
324    }
325    result
326}
327
328fn json_errors(_errors: &[ValidatorError]) -> Value {
329    let vs = vec!["todo", "errors"];
330    vs.into()
331}
332
333fn json_reasons(reasons: &[Reason]) -> Value {
334    let value = Value::Array(reasons.iter().map(|reason| reason.as_json()).collect());
335    value
336}
337
338fn show_reasons(reasons: &[Reason]) -> String {
339    let mut result = String::new();
340    for (reason, idx) in reasons.iter().enumerate() {
341        result.push_str(format!("Reason #{idx}: {reason}\n").as_str());
342    }
343    result
344}
345
346fn show(atom: &Atom) -> String {
347    match atom {
348        Atom::Pos((node, idx)) => format!("+({node},{idx})"),
349        Atom::Neg((node, idx)) => format!("!({node},{idx})"),
350    }
351}
352
353#[cfg(test)]
354mod tests {}