Inheritance works only up to one level
BenediktHeinrichs opened this issue · 6 comments
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: <http://datashapes.org/dash#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix schema: <http://schema.org/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
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"
}
]
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: <http://datashapes.org/dash#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix schema: <http://schema.org/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
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: <http://schema.org/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
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="
}
}
}
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
.
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 ?
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.
Thanks Holger, that's very helpful. I didn't know about the "Implicit Class Targets" feature, which opens up a lot of possibilities :)
Fixed in #42