Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: using property shapes in logical constraints #141

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/selfish-pants-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"rdf-validate-shacl": patch
---

Gracefully handle Property Shapes used inside logical constraints (fixes #140)
17 changes: 14 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,19 @@ class SHACLValidator {
}

// Exposed to be available from validation functions as `SHACL.nodeConformsToShape`
nodeConformsToShape(focusNode, shapeNode, engine = this.validationEngine.clone()) {
const shape = this.shapesGraph.getShape(shapeNode)
nodeConformsToShape(focusNode, shapeNode, propertyPathOrEngine) {
let engine
let shape = this.shapesGraph.getShape(shapeNode)

if (propertyPathOrEngine && 'termType' in propertyPathOrEngine) {
engine = this.validationEngine.clone({
propertyPath: propertyPathOrEngine,
recordErrorsLevel: this.validationEngine.recordErrorsLevel,
})
shape = shape.overridePath(propertyPathOrEngine)
} else {
engine = propertyPathOrEngine || this.validationEngine.clone()
}
try {
this.depth++
const foundViolations = engine.validateNodeAgainstShape(focusNode, shape, this.$data)
Expand All @@ -78,7 +89,7 @@ class SHACLValidator {
}
}

validateNodeAgainstShape (focusNode, shapeNode) {
validateNodeAgainstShape(focusNode, shapeNode) {
return this.nodeConformsToShape(focusNode, shapeNode, this.validationEngine)
}
}
Expand Down
15 changes: 14 additions & 1 deletion src/shapes-graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ class Constraint {
return this.paramValue || this.shapeNodePointer.out(param).term
}

get pathObject() {
return this.shape.pathObject
}

get validationFunction() {
return this.shape.isPropertyShape
? this.component.propertyValidationFunction
Expand Down Expand Up @@ -231,7 +235,6 @@ class Shape {
this.severity = this.shapeNodePointer.out(sh.severity).term || sh.Violation
this.deactivated = this.shapeNodePointer.out(sh.deactivated).value === 'true'
this.path = this.shapeNodePointer.out(sh.path).term
this.isPropertyShape = this.path != null
this._pathObject = undefined

this.constraints = []
Expand All @@ -251,6 +254,16 @@ class Shape {
})
}

get isPropertyShape() {
return this.path != null
}

overridePath(path) {
const shape = new Shape(this.context, this.shapeNode)
shape.path = path
return shape
}

/**
* Property path object
*/
Expand Down
14 changes: 10 additions & 4 deletions src/validation-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,21 @@ class ValidationEngine {
this.factory = context.factory
this.maxErrors = options.maxErrors
this.maxNodeChecks = options.maxNodeChecks === undefined ? defaultMaxNodeChecks : options.maxNodeChecks
this.propertyPath = options.propertyPath
this.initReport()
this.recordErrorsLevel = 0
this.recordErrorsLevel = options.recordErrorsLevel || 0
this.violationsCount = 0
this.validationError = null
this.nestedResults = {}
this.nestedResults = options.nestedResults || {}
}

clone() {
return new ValidationEngine(this.context, { maxErrors: this.maxErrors, maxNodeChecks: this.maxNodeChecks })
clone({ propertyPath, recordErrorsLevel } = {}) {
return new ValidationEngine(this.context, {
maxErrors: this.maxErrors,
maxNodeChecks: this.maxNodeChecks,
propertyPath,
recordErrorsLevel,
})
}

initReport() {
Expand Down
10 changes: 8 additions & 2 deletions src/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ function validateAnd(context, focusNode, valueNode, constraint) {
const andNode = constraint.getParameterValue(sh.and)
const shapes = rdfListToArray(context.$shapes.node(andNode))

return shapes.every((shape) => context.nodeConformsToShape(valueNode, shape))
return shapes.every((shape) => {
if (constraint.shape.isPropertyShape) {
return context.nodeConformsToShape(focusNode, shape, constraint.pathObject)
}

return context.nodeConformsToShape(valueNode, shape)
})
}

function validateClass(context, focusNode, valueNode, constraint) {
Expand Down Expand Up @@ -226,7 +232,7 @@ function validateMaxLength(context, focusNode, valueNode, constraint) {

function validateMinCountProperty(context, focusNode, valueNode, constraint) {
const { sh } = context.ns
const path = constraint.shape.pathObject
const path = constraint.pathObject
const count = getPathObjects(context.$data, focusNode, path).length
const minCountNode = constraint.getParameterValue(sh.minCount)

Expand Down
76 changes: 76 additions & 0 deletions test/data/data-shapes/custom/and-minCount.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
PREFIX schema: <http://schema.org/>
@prefix dash: <http://datashapes.org/dash#> .
@prefix ex: <http://datashapes.org/sh/tests/core/property/uniqueLang-001.test#> .
@prefix mf: <http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix sht: <http://www.w3.org/ns/shacl-test#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<>
rdf:type mf:Manifest ;
mf:entries
(
<and-minCount>
) ;
.
<and-minCount>
rdf:type sht:Validate ;
rdfs:label "Test of unexpected validation error as reported in issue zazuko/rdf-validate-shacl#140" ;
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:resultPath schema:age ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraintComponent sh:AndConstraintComponent ;
sh:sourceShape ex:age ;
sh:value 18 ;
],
[
rdf:type sh:ValidationResult ;
sh:focusNode ex:Instance ;
sh:resultPath schema:name ;
sh:resultSeverity sh:Violation ;
sh:sourceConstraintComponent sh:AndConstraintComponent ;
sh:sourceShape ex:name ;
sh:value "John" ;
] ;
] ;
mf:status sht:proposed ;
.
ex:Instance
schema:age 18 ;
schema:name "John" ;
a schema:Person ;
.
ex:PersonAddressShape
a sh:NodeShape ;
sh:targetClass schema:Person ;
sh:property ex:name, ex:age .

ex:ps1 a sh:PropertyShape ;
sh:minCount 2 ;
.

ex:name a sh:PropertyShape ;
sh:path schema:name ;
sh:and ( ex:ps1 ) ;
.

ex:age a sh:PropertyShape ;
sh:path schema:age ;
sh:and ( ex:ps1 ) ;
.
1 change: 1 addition & 0 deletions test/data/data-shapes/custom/manifest.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
mf:include <targetNodeDoesNotExist.ttl> ;
mf:include <lessThan-moreTypes.ttl> ;
mf:include <circularReferences.ttl> ;
mf:include <and-minCount.ttl> ;
.
Loading