mdesalvo / RDFSharp

Lightweight and friendly .NET library for realizing Semantic Web applications
Apache License 2.0
118 stars 26 forks source link

[BUG] Validating `sh:path` containing `sh:inversePath` not working. #302

Closed Pingviinituutti closed 1 year ago

Pingviinituutti commented 1 year ago

Describe the bug It seems that SHACL shapes with sh:inversePath are not working as they should be. A correctly set cardinality with sh:inversePath triggers an error with RDFSharp.

Expected behavior Link to SHACL Playground.

In this parent-child relationship, there is a requirement for a child to have at least one parent and each parent to have at least one child. The catch here is that the Parent.Child cardinality is constrained as sh:path [ sh:inversePath ex:Child.Parent ] ;. The expected behaviour is that only the ex:Parent instance parent1 should raise an error. However, both parent0 and parent1 instances trigger the error when running the minimal working example below. The SHACL Playground (linked above) shows correct behaviour.

Library Version RDFSharp version '3.5.0'

Additional context Minimal working example:

// shapedata-parent-child.xml
@prefix ex:     <http://example.com/ns#> .
@prefix rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix sh:      <http://www.w3.org/ns/shacl#> .

ex:Parent  rdf:type  sh:NodeShape ;
        sh:property     ex:Parent.Child-cardinality ;
        sh:targetClass  ex:Parent .

ex:Child  rdf:type  sh:NodeShape ;
        sh:property     ex:Child.Parent-cardinality ;
        sh:targetClass  ex:Child .

ex:Parent.Child-cardinality
        rdf:type        sh:PropertyShape ;
        sh:description  "This constraint validates the cardinality of the association at the inverse direction." ;
        sh:group        ex:CardinalityGroup ;
        sh:message      "Cardinality violation. Lower bound shall be 1. ex:Parent.Child-cardinality" ;
        sh:minCount     1 ;
        sh:name         "Parent.Child-cardinality" ;
        sh:order        0 ;
        sh:path         [ sh:inversePath  ex:Child.Parent ] ;
        sh:severity     sh:Violation .

ex:Child.Parent-cardinality
        rdf:type        sh:PropertyShape ;
        sh:description  "This constraint validates the cardinality of the association at the inverse direction." ;
        sh:group        ex:CardinalityGroup ;
        sh:message      "Cardinality violation. A child should have at least one parent. ex:Parent.Child-cardinality" ;
        sh:minCount     1 ;
        sh:name         "Parent.Child-cardinality" ;
        sh:order        0 ;
        sh:path         ex:Child.Parent ;
        sh:severity     sh:Violation .
<!-- shapedata-parent-child.xml -->
<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:ex="http://example.com/ns#"
    >

    <ex:Child rdf:about="http://example.com/child">
        <ex:Child.Parent rdf:resource="http://example.com/parent0" />
    </ex:Child>

    <ex:Parent rdf:about="http://example.com/parent0">
    </ex:Parent>

    <ex:Parent rdf:about="http://example.com/parent1" />
</rdf:RDF>
// See https://aka.ms/new-console-template for more information
using RDFSharp.Model;
using System;
using System.Text;

string shapesValidationFilePath = "SHACL_constraints_parent_child.ttl";
string dataFilePath = "shapedata-parent-child.xml";

try
{
    RDFGraph dataGraph = RDFGraph.FromFile(RDFModelEnums.RDFFormats.RdfXml, dataFilePath);

    RDFGraph shapesGraphObject = RDFGraph.FromFile(
        RDFModelEnums.RDFFormats.Turtle,
        shapesValidationFilePath
    );
    RDFShapesGraph shapesGraphValidation = RDFShapesGraph.FromRDFGraph(shapesGraphObject);
    //validate
    StringBuilder sb = new StringBuilder();
    RDFValidationReport validationResults = shapesGraphValidation.Validate(dataGraph);
    if (!validationResults.Conforms)
    {
        foreach (RDFValidationResult result in validationResults)
        {
            foreach (RDFLiteral resultMessage in result.ResultMessages)
            {
                sb.AppendLine(result.ResultPath.URI.Fragment + " " + resultMessage.ToString() + ". ID: " + result.FocusNode.ToString());
            }
        }
        Console.WriteLine("RDFSharp Validation errors:");
        string errors = sb.ToString();
        Console.WriteLine(errors);
    }
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

Result

RDFSharp Validation errors:
 Cardinality violation. Lower bound shall be 1. ex:Parent.Child-cardinality. ID: http://example.com/parent0
 Cardinality violation. Lower bound shall be 1. ex:Parent.Child-cardinality. ID: http://example.com/parent1
mdesalvo commented 1 year ago

Thanks Henrik for the very detailed and precise report.

I'll investigate about this and post updates here.

Regards, Marco

mdesalvo commented 1 year ago

Hi Henrik, I merged the work for proper detection and handling of "sh:path" declared with "sh:inversePath", which should allow you to successfully deal with this use case. It was my glitch to not know this kind of syntax.

Expect a new release containing the feature in the next days.

Thanks again and regards, Marco

Pingviinituutti commented 1 year ago

Wow that was quick! Thank you very much! Much appreciated!