From 1c2916e0115c62799794717acee036ca6665e2ea Mon Sep 17 00:00:00 2001 From: Alex Nelson Date: Tue, 29 Jul 2025 10:29:40 -0400 Subject: [PATCH] #429: Have SHACL-SHACL warn about unsatisfiable usage of sh:in and sh:xone This patch revises part of the implementation in PR 417. * `in-minListLength` was not added to the Core document, but it was referenced in SHACL-SHACL. Verbiage is added to Core in line with the text drafted for Issue 429. * `xone-memberShape` was added as a reference in SHACL-SHACL, but doesn't exist in the Core document. `xone-members-node` is the anchor in the Core document. `xone-members-node` is now used. * The `PropertyShape` for `sh:in` set a minimum required length of 1, which after discussion in Issue 429 appears to be a backward- incompatible change. A shape paralleling the original suggestion for `sh:xone` is added to house that new restriction at the same warning- level severity. An implementation style point: The property shapes housing `sh:minListLength` are given IRIs after sketching a test (`.../in-003.ttl`) and assuming it would be more desirable to have an IRI-identified shape in the results graph, rather than a blank node. A testing style point: Rather than running SHACL-SHACL over the whole of a test, shapes from SHACL-SHACL are copied into some focused tests (`.../{in,xone}-003.ttl`). References: * https://github.com/w3c/data-shapes/issues/429 * https://github.com/w3c/data-shapes/pull/417 Signed-off-by: Alex Nelson --- shacl12-core/index.html | 2 + shacl12-test-suite/tests/core/node/in-002.ttl | 46 ++++++++++++++++ shacl12-test-suite/tests/core/node/in-003.ttl | 55 +++++++++++++++++++ .../tests/core/node/xone-002.ttl | 45 +++++++++++++++ .../tests/core/node/xone-003.ttl | 55 +++++++++++++++++++ shacl12-vocabularies/shacl-shacl.ttl | 32 ++++++++++- 6 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 shacl12-test-suite/tests/core/node/in-002.ttl create mode 100644 shacl12-test-suite/tests/core/node/in-003.ttl create mode 100644 shacl12-test-suite/tests/core/node/xone-002.ttl create mode 100644 shacl12-test-suite/tests/core/node/xone-003.ttl diff --git a/shacl12-core/index.html b/shacl12-core/index.html index 6aeb016e..91434c80 100644 --- a/shacl12-core/index.html +++ b/shacl12-core/index.html @@ -5899,6 +5899,7 @@

sh:xone

A SHACL list of shapes to validate the value nodes against. Each value of sh:xone in a shape is a SHACL list. Each member of such list must be a well-formed shape. + There should be at least one member of such list. @@ -7299,6 +7300,7 @@

sh:in

A SHACL list that has the allowed values as members. Each value of sh:in in a shape is a SHACL list. A shape has at most one value for sh:in. + There should be at least one member of such list. diff --git a/shacl12-test-suite/tests/core/node/in-002.ttl b/shacl12-test-suite/tests/core/node/in-002.ttl new file mode 100644 index 00000000..7b55364f --- /dev/null +++ b/shacl12-test-suite/tests/core/node/in-002.ttl @@ -0,0 +1,46 @@ +@prefix ex: . +@prefix mf: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix sht: . +@prefix xsd: . + +ex:Instance + rdf:type ex:TestShape ; +. +ex:TestInUnsatisfiableShape + rdf:type rdfs:Class ; + rdf:type sh:NodeShape ; + rdfs:comment "Unsatisfiable due to empty sh:in list. SHACL-SHACL would raise a warning about the empty list."@en ; + rdfs:label "Test shape" ; + sh:in () ; +. +<> + rdf:type mf:Manifest ; + mf:entries ( + + ) ; +. + + rdf:type sht:Validate ; + rdfs:label "Test of sh:in at node shape 002" ; + mf:action [ + sht:dataGraph <> ; + sht:shapesGraph <> ; + ] ; + mf:result [ + rdf:type sh:ValidationReport ; + sh:conforms "false"^^xsd:boolean ; + sh:result [ + rdf:type sh:ValidationResult ; + sh:focusNode ex:Instance ; + sh:resultSeverity sh:Violation ; + sh:sourceConstraintComponent sh:InConstraintComponent ; + sh:sourceShape ex:TestShape ; + sh:value ex:Instance ; + ] ; + ] ; + mf:status sht:approved ; +. diff --git a/shacl12-test-suite/tests/core/node/in-003.ttl b/shacl12-test-suite/tests/core/node/in-003.ttl new file mode 100644 index 00000000..0f9def74 --- /dev/null +++ b/shacl12-test-suite/tests/core/node/in-003.ttl @@ -0,0 +1,55 @@ +@prefix ex: . +@prefix mf: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix sht: . +@prefix xsd: . + +ex:TestInUnsatisfiableShape + rdf:type rdfs:Class ; + rdf:type sh:NodeShape ; + rdfs:label "Test shape" ; + sh:in rdf:nil ; +. +shsh:inSubjectsShape + a sh:NodeShape ; + rdfs:isDefinedBy shsh: ; + sh:property shsh:inSubjectsShapeXonePropertyShape ; + sh:targetSubjectsOf sh:in ; + . +shsh:inSubjectsShapeInPropertyShape + a sh:PropertyShape ; + rdfs:isDefinedBy shsh: ; + sh:path sh:in ; + sh:minListLength 1 ; + sh:severity sh:Warning ; + . +<> + rdf:type mf:Manifest ; + mf:entries ( + + ) ; +. + + rdf:type sht:Validate ; + rdfs:label "Test of sh:in at node shape 003" ; + mf:action [ + sht:dataGraph <> ; + sht:shapesGraph <> ; + ] ; + mf:result [ + rdf:type sh:ValidationReport ; + sh:conforms "false"^^xsd:boolean ; + sh:result [ + rdf:type sh:ValidationResult ; + sh:focusNode ex:TestInUnsatisfiableShape ; + sh:resultSeverity sh:Warning ; + sh:sourceConstraintComponent sh:MinListLengthConstraintComponent ; + sh:sourceShape shsh:inSubjectsShapeInPropertyShape ; + sh:value rdf:nil ; + ] ; + ] ; + mf:status sht:approved ; +. diff --git a/shacl12-test-suite/tests/core/node/xone-002.ttl b/shacl12-test-suite/tests/core/node/xone-002.ttl new file mode 100644 index 00000000..d17f8db1 --- /dev/null +++ b/shacl12-test-suite/tests/core/node/xone-002.ttl @@ -0,0 +1,45 @@ +@prefix ex: . +@prefix mf: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix sht: . +@prefix xsd: . + +ex:Bob + rdf:type ex:Person ; +. +ex:TestXoneUnsatisfiableShape + rdf:type sh:NodeShape ; + rdfs:comment "Unsatisfiable due to empty sh:xone list. SHACL-SHACL would raise a warning about the empty list."@en ; + sh:targetClass ex:Person ; + sh:xone () ; +. +<> + rdf:type mf:Manifest ; + mf:entries ( + + ) ; +. + + rdf:type sht:Validate ; + rdfs:label "Test of sh:xone at node shape 002" ; + mf:action [ + sht:dataGraph <> ; + sht:shapesGraph <> ; + ] ; + mf:result [ + rdf:type sh:ValidationReport ; + sh:conforms "false"^^xsd:boolean ; + sh:result [ + rdf:type sh:ValidationResult ; + sh:focusNode ex:Bob ; + sh:resultSeverity sh:Violation ; + sh:sourceConstraintComponent sh:XoneConstraintComponent ; + sh:sourceShape ex:TestXoneUnsatisfiableShape ; + sh:value ex:Bob ; + ] ; + ] ; + mf:status sht:approved ; +. diff --git a/shacl12-test-suite/tests/core/node/xone-003.ttl b/shacl12-test-suite/tests/core/node/xone-003.ttl new file mode 100644 index 00000000..f2932671 --- /dev/null +++ b/shacl12-test-suite/tests/core/node/xone-003.ttl @@ -0,0 +1,55 @@ +@prefix ex: . +@prefix mf: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix shsh: . +@prefix sht: . +@prefix xsd: . + +ex:TestXoneUnsatisfiableShape + rdf:type sh:NodeShape ; + sh:targetClass ex:Person ; + sh:xone rdf:nil ; +. +shsh:xoneSubjectsShape + a sh:NodeShape ; + rdfs:isDefinedBy shsh: ; + sh:property shsh:xoneSubjectsShapeXonePropertyShape ; + sh:targetSubjectsOf sh:xone ; + . +shsh:xoneSubjectsShapeXonePropertyShape + a sh:PropertyShape ; + rdfs:isDefinedBy shsh: ; + sh:path sh:xone ; + sh:minListLength 1 ; + sh:severity sh:Warning ; + . +<> + rdf:type mf:Manifest ; + mf:entries ( + + ) ; +. + + rdf:type sht:Validate ; + rdfs:label "Test of sh:xone at node shape 003" ; + mf:action [ + sht:dataGraph <> ; + sht:shapesGraph <> ; + ] ; + mf:result [ + rdf:type sh:ValidationReport ; + sh:conforms "false"^^xsd:boolean ; + sh:result [ + rdf:type sh:ValidationResult ; + sh:focusNode ex:TestXoneUnsatisfiableShape ; + sh:resultSeverity sh:Warning ; + sh:sourceConstraintComponent sh:MinListLengthConstraintComponent ; + sh:sourceShape shsh:xoneSubjectsShapeXonePropertyShape ; + sh:value rdf:nil ; + ] ; + ] ; + mf:status sht:approved ; +. diff --git a/shacl12-vocabularies/shacl-shacl.ttl b/shacl12-vocabularies/shacl-shacl.ttl index 31f7e7a5..882e56f2 100644 --- a/shacl12-vocabularies/shacl-shacl.ttl +++ b/shacl12-vocabularies/shacl-shacl.ttl @@ -164,7 +164,6 @@ shsh:ShapeShape sh:property [ sh:path sh:in ; sh:maxCount 1 ; # in-maxCount - sh:minListLength 1 ; # in-minListLength ] ; sh:property [ sh:path sh:languageIn ; @@ -294,7 +293,7 @@ shsh:ShapeShape ] ; sh:property [ sh:path sh:xone ; - sh:memberShape shsh:ShapeShape ; # xone-memberShape + sh:memberShape shsh:ShapeShape ; # xone-members-node ] . shsh:NodeShapeShape @@ -347,6 +346,35 @@ shsh:ShapesListShape sh:targetObjectsOf sh:xone ; # xone-members-node sh:memberShape shsh:ShapeShape . +# Values of sh:in and sh:xone should have length >= 1. +shsh:inSubjectsShape + a sh:NodeShape ; + rdfs:comment "A shape that raises warnings on unsatisfiable lists. If some use case requires a length-0 list on 'sh:in', this graph should be supplemented with 'shsh:inSubjectsShape sh:deactivated true .'."@en ; + sh:property shsh:inSubjectsShapeInPropertyShape ; + sh:targetSubjectsOf sh:in ; + . + +shsh:inSubjectsShapeInPropertyShape + a sh:PropertyShape ; + sh:path sh:in ; + sh:minListLength 1 ; # in-minListLength + sh:severity sh:Warning ; + . + +shsh:xoneSubjectsShape + a sh:NodeShape ; + rdfs:comment "A shape that raises warnings on unsatisfiable lists. If some use case requires a length-0 list on 'sh:xone', this graph should be supplemented with 'shsh:xoneSubjectsShape sh:deactivated true .'."@en ; + sh:property shsh:xoneSubjectsShapeXonePropertyShape ; + sh:targetSubjectsOf sh:xone ; + . + +shsh:xoneSubjectsShapeXonePropertyShape + a sh:PropertyShape ; + sh:path sh:xone ; + sh:minListLength 1 ; # xone-minListLength + sh:severity sh:Warning ; + . + # A path of blank node path syntax, used to simulate recursion _:PathPath sh:alternativePath (