zazuko / rdf-validate-shacl

Validate RDF data purely in JavaScript. An implementation of the W3C SHACL specification on top of the RDFJS stack.
MIT License
98 stars 13 forks source link

Inheritance works only up to one level #41

Closed BenediktHeinrichs closed 3 years ago

BenediktHeinrichs commented 3 years ago

Recently, I have been trying to create shapes which inherit properties from other shapes.

Using this library, the validation went fairly well until I hit the second inherited level.

This means that having a construct of Shape C > Shape B and Shape B > Shape A, the properties of Shape B will be validated, the properties of Shape A however won't.

I tried this construct in the https://shacl.org/playground/ and this supports the structure, so I assume there is something not working correctly.

In the following I provide an example shape graph and data graph which validates correctly that the field "schema:name" is missing in the playground, however does not report that issue using this library.

Shapes Graph: ``` @prefix dash: . @prefix rdf: . @prefix rdfs: . @prefix schema: . @prefix sh: . @prefix xsd: . @prefix dcterms: . schema:ToolShape a sh:NodeShape , rdfs:Class ; sh:targetClass schema:ToolShape ; sh:property [ sh:path schema:name ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Name"@en, "Name"@de ; ] . schema:SoftwareShape a sh:NodeShape , rdfs:Class ; rdfs:subClassOf schema:ToolShape ; sh:targetClass schema:SoftwareShape ; sh:property [ sh:path schema:version ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Version"@en, "Version"@de ; ] ; sh:property [ sh:path schema:creator ; sh:datatype xsd:string ; sh:minCount 1 ; sh:name "Creator"@en, "Ersteller"@de ; ] . schema:AnalysisSoftwareShape a sh:NodeShape , rdfs:Class ; rdfs:subClassOf schema:SoftwareShape ; sh:targetClass schema:AnalysisSoftwareShape . ```
Data Graph: ``` [ { "@type":"http://schema.org/AnalysisSoftwareShape", "http://schema.org/version":{ "@value":"ü+", "@type":"http://www.w3.org/2001/XMLSchema#string" }, "http://schema.org/creator":{ "@value":"pop", "@type":"http://www.w3.org/2001/XMLSchema#string" } }, { "@id":"http://schema.org/SoftwareShape", "http://www.w3.org/2000/01/rdf-schema#subClassOf":[ { "@id":"http://schema.org/ToolShape" } ] }, { "@id":"http://schema.org/AnalysisSoftwareShape", "http://www.w3.org/2000/01/rdf-schema#subClassOf":[ { "@id":"http://schema.org/SoftwareShape" } ] }, { "@id":"http://schema.org/ToolShape" } ] ```
BenediktHeinrichs commented 3 years ago

I created a minimal code example for this issue, which can test this problem:

index.js ``` async function main() { const fs = require('fs'); const factory = require('rdf-ext'); const ParserN3 = require('@rdfjs/parser-n3'); const SHACLValidator = require('rdf-validate-shacl'); async function loadDataset (filePath) { const stream = fs.createReadStream(filePath); const parser = new ParserN3({ factory }); return factory.dataset().import(parser.import(stream)); } const shapes = await loadDataset('my-shapes.ttl'); const data = await loadDataset('my-data.ttl'); const validator = new SHACLValidator(shapes, { factory }); const report = await validator.validate(data); // Check conformance: `true` or `false` console.log(report.conforms); for (const result of report.results) { // See https://www.w3.org/TR/shacl/#results-validation-result for details // about each property console.log(result.message); console.log(result.path); console.log(result.focusNode); console.log(result.severity); console.log(result.sourceConstraintComponent); console.log(result.sourceShape); } // Validation report as RDF dataset console.log(report.dataset); } main(); ```
my-shapes.ttl ``` @prefix dash: . @prefix rdf: . @prefix rdfs: . @prefix schema: . @prefix sh: . @prefix xsd: . @prefix dcterms: . schema:ToolShape a sh:NodeShape , rdfs:Class ; sh:targetClass schema:ToolShape ; sh:property [ sh:path schema:name ; sh:datatype xsd:string ; sh:minCount 1 ; sh:maxCount 1 ; sh:name "Name"@en, "Name"@de ; ] . schema:SoftwareShape a sh:NodeShape , rdfs:Class ; rdfs:subClassOf schema:ToolShape ; sh:targetClass schema:SoftwareShape ; sh:property [ sh:path schema:version ; sh:datatype xsd:string ; sh:maxCount 1 ; sh:name "Version"@en, "Version"@de ; ] ; sh:property [ sh:path schema:creator ; sh:datatype xsd:string ; sh:minCount 1 ; sh:name "Creator"@en, "Ersteller"@de ; ] . schema:AnalysisSoftwareShape a sh:NodeShape , rdfs:Class ; rdfs:subClassOf schema:SoftwareShape ; sh:targetClass schema:AnalysisSoftwareShape . ```
my-data.ttl ``` @prefix schema: . @prefix rdfs: . @prefix xsd: . schema:SoftwareShape rdfs:subClassOf schema:ToolShape . schema:AnalysisSoftwareShape rdfs:subClassOf schema:SoftwareShape . [] a schema:AnalysisSoftwareShape ; schema:creator "pop"^^xsd:string ; schema:version "ü+"^^xsd:string . ```
package-lock.json ``` { "requires": true, "lockfileVersion": 1, "dependencies": { "@rdfjs/data-model": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@rdfjs/data-model/-/data-model-1.2.0.tgz", "integrity": "sha512-6ITWcu2sr9zJqXUPDm1XJ8DRpea7PotWBIkTzuO1MCSruLOWH2ICoQOAtlJy30cT+GqH9oAQKPR+CHXejsdizA==", "requires": { "@types/rdf-js": "*" } }, "@rdfjs/dataset": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@rdfjs/dataset/-/dataset-1.0.1.tgz", "integrity": "sha512-k/c6g4K881QX7LE3eskg6t1j31zDe+CKwTEiKkSCFk6M25gUJ/BReT/FrLdKmPjhXp+YOgHj97AtEphzTeKVeA==", "requires": { "@rdfjs/data-model": "^1.1.1" } }, "@rdfjs/namespace": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rdfjs/namespace/-/namespace-1.1.0.tgz", "integrity": "sha512-utO5rtaOKxk8B90qzaQ0N+J5WrCI28DtfAY/zExCmXE7cOfC5uRI/oMKbLaVEPj2P7uArekt/T4IPATtj7Tjug==", "requires": { "@rdfjs/data-model": "^1.1.0" } }, "@rdfjs/parser-n3": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@rdfjs/parser-n3/-/parser-n3-1.1.4.tgz", "integrity": "sha512-PUKSNlfD2Zq3GcQZuOF2ndfrLbc+N96FUe2gNIzARlR2er0BcOHBHEFUJvVGg1Xmsg3hVKwfg0nwn1JZ7qKKMw==", "requires": { "@rdfjs/data-model": "^1.0.1", "@rdfjs/sink": "^1.0.2", "n3": "^1.3.5", "readable-stream": "^3.6.0", "readable-to-readable": "^0.1.0" } }, "@rdfjs/sink": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@rdfjs/sink/-/sink-1.0.3.tgz", "integrity": "sha512-2KfYa8mAnptRNeogxhQqkWNXqfYVWO04jQThtXKepySrIwYmz83+WlevQtA4VDLFe+kFd2TwgL29ekPe5XVUfA==" }, "@rdfjs/term-set": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@rdfjs/term-set/-/term-set-1.0.1.tgz", "integrity": "sha512-7+pSdm8R1nhSyP1xEqcQYFTARD/2iLr98tiAsSUfykCg0T0LrZ6j/3EEKmcKTWLMazMA9deRfmjPvzqMFwLBig==", "requires": { "@rdfjs/to-ntriples": "^1.0.2" } }, "@rdfjs/to-ntriples": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@rdfjs/to-ntriples/-/to-ntriples-1.0.2.tgz", "integrity": "sha512-ngw5XAaGHjgGiwWWBPGlfdCclHftonmbje5lMys4G2j4NvfExraPIuRZgjSnd5lg4dnulRVUll8tRbgKO+7EDA==" }, "@tpluscode/rdf-ns-builders": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@tpluscode/rdf-ns-builders/-/rdf-ns-builders-0.1.0.tgz", "integrity": "sha512-HxcNaUSZZOIBS8pdYiyRodFejLCcrleCMc/b8qdHA8bw830u5JOomSBZKRgylEAaps69G7PQPWtwvAEFSPRcKQ==", "requires": { "@rdfjs/namespace": "^1.1.0", "@types/rdf-js": "^2.0.11", "@types/rdfjs__namespace": "^1.1.1" }, "dependencies": { "@types/rdf-js": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/@types/rdf-js/-/rdf-js-2.0.12.tgz", "integrity": "sha512-NBzHFHp2vHOJkPlSqzsOrkEsD9grKn+PdFjZieIw59pc0FlRG6WEQAjQZvHzFxJlYzC6ZDCFyTA1wBvUnnzUQw==", "requires": { "@types/node": "*" } } } }, "@types/node": { "version": "14.14.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.13.tgz", "integrity": "sha512-vbxr0VZ8exFMMAjCW8rJwaya0dMCDyYW2ZRdTyjtrCvJoENMpdUHOT/eTzvgyA5ZnqRZ/sI0NwqAxNHKYokLJQ==" }, "@types/rdf-js": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/rdf-js/-/rdf-js-4.0.0.tgz", "integrity": "sha512-2uaR7ks0380MqzUWGOPOOk9yZIr/6MOaCcaj3ntKgd2PqNocgi8j5kSHIJTDe+5ABtTHqKMSE0v0UqrsT8ibgQ==", "requires": { "@types/node": "*" } }, "@types/rdfjs__namespace": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@types/rdfjs__namespace/-/rdfjs__namespace-1.1.3.tgz", "integrity": "sha512-seCWK6z0TqF40ioY7lMhR624I9ovo7xhnhvhAvbWlxfXYtsoQ4QEoddaWFmJDqfat2M1Xv6ThRSN2JGm2OtTIw==", "requires": { "@types/rdf-js": "*" } }, "clownface": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/clownface/-/clownface-1.2.0.tgz", "integrity": "sha512-01iluBG+GPzFFFvFPhZxuWf8gU8mQVdm6gkvI71xl4fJyJSX7VpX3US4LbMC+0D6+KK75C+Q9AFtYpzv3fKvmg==", "requires": { "@rdfjs/data-model": "^1.1.0", "@rdfjs/namespace": "^1.0.0" } }, "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "requires": { "ms": "2.1.2" } }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "n3": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/n3/-/n3-1.6.4.tgz", "integrity": "sha512-qiiBhW2nJ59cfQzi0nvZs5tSXkXgDXedIy3zNNuKjTwE8Bcvv95DTFJpOY9geg6of5T7z6cg+ZWcaHIij3svrA==", "requires": { "queue-microtask": "^1.1.2", "readable-stream": "^3.6.0" } }, "queue-microtask": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.2.tgz", "integrity": "sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg==" }, "rdf-dataset-indexed": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/rdf-dataset-indexed/-/rdf-dataset-indexed-0.4.0.tgz", "integrity": "sha512-xIZ3PGwBHh1DVax9FTq/zhJcrdJTkCgRT2xolF5ZhKj0EOFoT6wdITyfxKSmnD6YNyGyng5F5o2SR62TYask3Q==", "requires": { "@rdfjs/data-model": "^1.1.1", "readable-stream": "^3.3.0" } }, "rdf-ext": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/rdf-ext/-/rdf-ext-1.3.0.tgz", "integrity": "sha512-bzkJpINH1uZgAeSIlE7K1nLF6Y82vGi9U9sBRuiz864pNk/31ba3dw+yLoIkcvQsPcVwnKBVLvGCR3dvD9xM+Q==", "requires": { "@rdfjs/data-model": "^1.1.0", "@rdfjs/to-ntriples": "^1.0.1", "rdf-dataset-indexed": "^0.4.0", "rdf-normalize": "^1.0.0" } }, "rdf-normalize": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/rdf-normalize/-/rdf-normalize-1.0.0.tgz", "integrity": "sha1-U0lrrzYszp2fyh8iFsbDAAf5nMo=" }, "rdf-validate-datatype": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/rdf-validate-datatype/-/rdf-validate-datatype-0.1.1.tgz", "integrity": "sha512-Gjwmc37cCj57Si2MXJSUZ2FKQPlMg8G9HOyvrcomQcq8Z3beZ3X9IGjmsz1kSa3+qiQsk5WsAESIFAnEXL8vBw==", "requires": { "@rdfjs/to-ntriples": "1.0.2", "@tpluscode/rdf-ns-builders": "0.1.0" } }, "rdf-validate-shacl": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/rdf-validate-shacl/-/rdf-validate-shacl-0.2.4.tgz", "integrity": "sha512-Z/GDqf1/v0BiVIHOQKq6hSYj0lMxeA5oV2fDtjzO5PoSuT+8BLmqNCZCAf+DfhnGyI8GS+e6V4zym8M7P7NYwA==", "requires": { "@rdfjs/dataset": "^1.0.0", "@rdfjs/namespace": "^1.0.0", "@rdfjs/term-set": "^1.0.0", "clownface": "^1.0.0", "debug": "^4.0.0", "rdf-validate-datatype": "^0.1.1" } }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "readable-to-readable": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/readable-to-readable/-/readable-to-readable-0.1.3.tgz", "integrity": "sha512-G+0kz01xJM/uTuItKcqC73cifW8S6CZ7tp77NLN87lE5mrSU+GC8geoSAlfmp0NocmXckQ7W8s8ns73HYsIA3w==", "requires": { "readable-stream": "^3.6.0" } }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "requires": { "safe-buffer": "~5.2.0" } }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" } } } ```
BenediktHeinrichs commented 3 years ago

It seems to be linked to the function defined here: https://github.com/zazuko/rdf-validate-shacl/blob/master/src/rdflib-graph.js#L19

It finds in the example SoftwareShape for ToolShape but not AnalysisSoftwareShape.

martinmaillard commented 3 years ago

Hey there, Thanks for the report.

I'm not really sure if inheritance between shapes is defined by SHACL. Do you know anything about that @bergos @tpluscode ?

HolgerKnublauch commented 3 years ago

The example uses Implicit Class Targets, see https://www.w3.org/TR/shacl/#implicit-targetClass - the instance of AnalysisSoftwareShape will be validated against the AnalysisSoftwareShape class as shape. The rather cryptic terms "SHACL shape" and "SHACL instance" mean that rdfs:subClassOf triples must be walked up implicitly. Consider any instance of AnalysisSoftwareShape also an instance of SoftwareShape, and recursively of ToolShape. Given that any instance of a subclass is also an instance of the superclass(es), they are also targeted by the SHACL definitions of those superclasses.

So yes, it needs to walk up to the root class and apply all constraints.

BTW: the sh:targetClass statements in the example are unnecessary. The usual design pattern for that would be to have

ex:ToolShape
    a sh:NodeShape ;
    sh:targetClass ex:Tool .

ex:Tool
    a rdfs:Class .

ex:MyTool
    a ex:Tool .

in which case the ToolShape will apply to MyTool. The other main pattern is

ex:Tool
    a rdfs:Class, sh:NodeShape .

ex:MyTool
    a ex:Tool .

assuming all constraints are directly attached to ex:Tool.

martinmaillard commented 3 years ago

Thanks Holger, that's very helpful. I didn't know about the "Implicit Class Targets" feature, which opens up a lot of possibilities :)

martinmaillard commented 3 years ago

Fixed in #42