shacl 0.3.11

A SHACL validator for RDF data, implemented in Rust.
Documentation
use crate::error::ValidationError;
use crate::ir::components::Closed;
use crate::ir::{IRComponent, IRSchema, IRShape};
use crate::validator::constraints::NativeValidator;
use crate::validator::engine::Engine;
use crate::validator::nodes::ValueNodes;
use crate::validator::report::ValidationResult;
#[cfg(feature = "sparql")]
use rudof_rdf::rdf_core::term::Term;
use rudof_rdf::rdf_core::term::{Object, Triple};
use rudof_rdf::rdf_core::{NeighsRDF, SHACLPath};
use std::fmt::Debug;

#[cfg(feature = "sparql")]
use crate::validator::constraints::BasicSparqlValidator;
#[cfg(feature = "sparql")]
use indoc::formatdoc;
#[cfg(feature = "sparql")]
use rudof_rdf::rdf_core::query::QueryRDF;

impl<S: NeighsRDF + Debug + 'static> NativeValidator<S> for Closed {
    fn validate_native(
        &self,
        component: &IRComponent,
        shape: &IRShape,
        store: &S,
        _: &mut dyn Engine<S>,
        value_nodes: &ValueNodes<S>,
        _: Option<&IRShape>,
        _: Option<&SHACLPath>,
        _: &IRSchema,
    ) -> Result<Vec<ValidationResult>, ValidationError> {
        if !self.is_closed() {
            return Ok(Vec::new());
        }

        let allowed_props = shape.allowed_properties();
        let component_obj = Object::iri(component.into());
        let mut results = Vec::new();

        for (fnode, _) in value_nodes.iter() {
            let subject = match S::term_as_subject(fnode) {
                Ok(subj) => subj,
                Err(_) => continue,
            };

            let triples = store
                .triples_with_subject(&subject)
                .map_err(ValidationError::new_graph_error::<S>)?;

            let focus_obj = S::term_as_object(fnode)?;

            for triple in triples {
                let (_, pred, obj) = triple.into_components();
                let pred_iri = pred.into();
                if !allowed_props.contains(&pred_iri) {
                    let value = S::term_as_object(&obj).ok();
                    let vr = ValidationResult::new(focus_obj.clone(), component_obj.clone(), shape.severity().clone())
                        .with_source(Some(shape.id().clone()))
                        .with_path(Some(SHACLPath::iri(pred_iri)))
                        .with_value(value);
                    results.push(vr);
                }
            }
        }

        Ok(results)
    }
}

#[cfg(feature = "sparql")]
impl<S: QueryRDF + NeighsRDF + Debug + 'static> BasicSparqlValidator<S> for Closed {
    fn validate_sparql(
        &self,
        component: &IRComponent,
        shape: &IRShape,
        store: &S,
        _: &mut dyn Engine<S>,
        value_nodes: &ValueNodes<S>,
        _: Option<&IRShape>,
        _: Option<&SHACLPath>,
        _: &IRSchema,
    ) -> Result<Vec<ValidationResult>, ValidationError> {
        if !self.is_closed() {
            return Ok(Vec::new());
        }

        let allowed_props = shape.allowed_properties();
        let component_obj = Object::iri(component.into());
        let mut results = Vec::new();

        let not_in_clause = if allowed_props.is_empty() {
            String::new()
        } else {
            let iris = allowed_props
                .iter()
                .map(|iri| format!("<{iri}>"))
                .collect::<Vec<_>>()
                .join(", ");
            format!(" FILTER (?p NOT IN ({iris}))")
        };

        for (fnode, _) in value_nodes.iter() {
            let focus_obj = S::term_as_object(fnode)?;

            let triples_iter: Box<dyn Iterator<Item = (Object, Option<Object>)>> = if fnode.is_blank_node() {
                let subject = match S::term_as_subject(fnode) {
                    Ok(s) => s,
                    Err(_) => continue,
                };
                let triples = store
                    .triples_with_subject(&subject)
                    .map_err(ValidationError::new_graph_error::<S>)?;
                Box::new(triples.into_iter().filter_map(|t| {
                    let (_, pred, obj) = t.into_components();
                    let pred_iri = pred.into();
                    if allowed_props.contains(&pred_iri) {
                        None
                    } else {
                        Some((Object::Iri(pred_iri), S::term_as_object(&obj).ok()))
                    }
                }))
            } else {
                let query = formatdoc! {"
                    SELECT ?p ?o WHERE {{ {} ?p ?o .{} }}
                ", fnode, not_in_clause};

                let solutions = store
                    .query_select(&query)
                    .map_err(ValidationError::select_query_error::<S>)?;

                let mut rows: Vec<(Object, Option<Object>)> = Vec::new();
                for sol in solutions.iter() {
                    let pred_term = match sol.find_solution("p") {
                        Some(t) => t,
                        None => continue,
                    };
                    let pred_obj = match S::term_as_object(pred_term) {
                        Ok(obj @ Object::Iri(_)) => obj,
                        _ => continue,
                    };
                    let value = sol.find_solution("o").and_then(|t| S::term_as_object(t).ok());
                    rows.push((pred_obj, value));
                }
                Box::new(rows.into_iter())
            };

            for (pred_obj, value) in triples_iter {
                let path = match &pred_obj {
                    Object::Iri(iri) => Some(SHACLPath::iri(iri.clone())),
                    _ => None,
                };
                let vr = ValidationResult::new(focus_obj.clone(), component_obj.clone(), shape.severity().clone())
                    .with_source(Some(shape.id().clone()))
                    .with_path(path)
                    .with_value(value);
                results.push(vr);
            }
        }

        Ok(results)
    }
}