Skip to content

Commit

Permalink
Merge pull request #137 from s-tittel/master
Browse files Browse the repository at this point in the history
fixes #136
  • Loading branch information
tpluscode authored Jul 23, 2024
2 parents 1d43b2f + 6041552 commit b4d77c8
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/many-queens-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"rdf-validate-shacl": patch
---

Added a `maxNodeChecks` option to prevent `too much recursion` error caused by cyclic shape references (fixes #136)
17 changes: 16 additions & 1 deletion src/validation-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import ValidationReport from './validation-report.js'
import { extractStructure, extractSourceShapeStructure } from './dataset-utils.js'

const error = debug('validation-enging::error')
const defaultMaxNodeChecks = 50

class ValidationEngine {
constructor(context, options) {
this.context = context
this.factory = context.factory
this.maxErrors = options.maxErrors
this.maxNodeChecks = options.maxNodeChecks === undefined ? defaultMaxNodeChecks : options.maxNodeChecks
this.initReport()
this.recordErrorsLevel = 0
this.violationsCount = 0
Expand All @@ -18,11 +20,12 @@ class ValidationEngine {
}

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

initReport() {
const { rdf, sh } = this.context.ns
this.nodeCheckCounters = {}

this.reportPointer = clownface({
dataset: this.factory.dataset(),
Expand Down Expand Up @@ -67,6 +70,18 @@ class ValidationEngine {

if (shape.deactivated) return false

if (this.maxNodeChecks > 0) {
// check how many times we have already tested this focusNode against this shape
const id = JSON.stringify([focusNode, shape.shapeNode])
const nodeCheckCounter = this.nodeCheckCounters[id] === undefined ? 0 : this.nodeCheckCounters[id]
if (nodeCheckCounter > this.maxNodeChecks) {
// max node checks reached, so bail out
return false
}
// increment check counter for given focusNode/shape pair
this.nodeCheckCounters[id] = nodeCheckCounter + 1
}

const valueNodes = shape.getValueNodes(focusNode, dataGraph)
let errorFound = false
for (const constraint of shape.constraints) {
Expand Down
48 changes: 48 additions & 0 deletions test/data/data-shapes/custom/circularReferences.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
@prefix dash: <http://datashapes.org/dash#> .
@prefix ex: <http://example.org#> .
@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 (
<circularReferences>
) ;
.

<circularReferences>
rdf:type sht:Validate ;
rdfs:label "Test of circular references in data graph" ;
mf:action [
sht:dataGraph <> ;
sht:shapesGraph <> ;
] ;
mf:result [
rdf:type sh:ValidationReport ;
sh:conforms "true"^^xsd:boolean ;
] ;
.

ex:Thing
a sh:NodeShape,rdfs:Class ;
sh:property [
sh:path ex:references ;
sh:node ex:Thing
] ;
sh:targetClass <Thing>
.

<thing1>
a <Thing> ;
ex:references <thing2>
.

<thing2>
a <Thing> ;
ex:references <thing1> ;
.
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 @@ -11,4 +11,5 @@
mf:include <multiLevelInheritanceWithImplicitTarget.ttl> ;
mf:include <targetNodeDoesNotExist.ttl> ;
mf:include <lessThan-moreTypes.ttl> ;
mf:include <circularReferences.ttl> ;
.

0 comments on commit b4d77c8

Please sign in to comment.