New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

rdf-validate-shacl

Package Overview
Dependencies
Maintainers
1
Versions
28
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rdf-validate-shacl - npm Package Compare versions

Comparing version 0.1.1 to 0.1.2

src/dataset-utils.js

9

CHANGELOG.md

@@ -10,2 +10,11 @@

## 0.1.2 (2020-04-21)
* Include deep blank node structures in validation report
* Add official SHACL test suite
* Fix provided factory not being used to create all quads in the validation
report
* Performance improvements
## 0.1.1 (2020-04-07)

@@ -12,0 +21,0 @@

10

package.json
{
"name": "rdf-validate-shacl",
"version": "0.1.1",
"version": "0.1.2",
"description": "RDF SHACL validator",

@@ -23,7 +23,7 @@ "main": "index.js",

"@rdfjs/term-set": "~1.0.1",
"clownface": "^0.12.1",
"clownface": "^0.12.3",
"rdf-validate-datatype": "^0.1.1"
},
"devDependencies": {
"@rdfjs/parser-n3": "^1.1.3",
"@rdfjs/parser-n3": "^1.1.4",
"@zazuko/rdf-vocabularies": "^2020.3.23",

@@ -33,5 +33,5 @@ "debug": "^4.1.1",

"mocha": "^7.1.1",
"nyc": "^15.0.0",
"nyc": "^15.0.1",
"rdf-ext": "^1.3.0",
"rdf-utils-fs": "^2.1.0",
"rdf-utils-dataset": "1.1.0",
"standard": "^14.3.3"

@@ -38,0 +38,0 @@ },

# rdf-validate-shacl
RDF/JS SHACL validator
JavaScript SHACL validation ([RDF/JS](https://rdf.js.org/) compatible)

@@ -11,11 +11,29 @@ [![Build Status](https://travis-ci.org/zazuko/rdf-validate-shacl.svg?branch=master)](https://travis-ci.org/zazuko/rdf-validate-shacl)

Create a new SHACL validator and load data and shapes to trigger the validation.
The library only handles SHACL validation and not data loading/parsing.
The following example uses [rdf-utils-fs](https://github.com/rdf-ext/rdf-utils-fs)
for this purpose. For more information about handling RDF data in JavaScript,
check out [Get started with RDF in JavaScript](https://zazuko.com/get-started/developers/).
The validation function returns a `ValidationReport` object that can be used
to inspect conformance and results.
to inspect conformance and results. The `ValidationReport` also has a
`.dataset` property, which provides the report as RDF data.
```javascript
const validator = new SHACLValidator(shapesDataset)
const report = await validator.validate(dataDataset)
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`

@@ -34,2 +52,5 @@ console.log(report.conforms)

}
// Validation report as RDF dataset
console.log(report.dataset)
```

@@ -36,0 +57,0 @@

@@ -1,5 +0,70 @@

const NodeSet = require('./node-set')
const { rdf, sh } = require('./namespaces')
/**
* Extracts all the nodes of a property path from a graph and returns a
* property path object.
*
* @param {RDFLibGraph} graph
* @param {Term} pathNode - Start node of the path
* @return Property path object
*/
function extractPropertyPath (graph, pathNode) {
if (pathNode.termType === 'NamedNode') {
return pathNode
}
if (pathNode.termType === 'BlankNode') {
const pathCf = graph.cf.node(pathNode)
const first = pathCf.out(rdf.first).term
if (first) {
const paths = graph.rdfListToArray(pathNode)
return paths.map(path => extractPropertyPath(graph, path))
}
const alternativePath = pathCf.out(sh.alternativePath).term
if (alternativePath) {
const paths = graph.rdfListToArray(alternativePath)
return { or: paths.map(path => extractPropertyPath(graph, path)) }
}
const zeroOrMorePath = pathCf.out(sh.zeroOrMorePath).term
if (zeroOrMorePath) {
return { zeroOrMore: extractPropertyPath(graph, zeroOrMorePath) }
}
const oneOrMorePath = pathCf.out(sh.oneOrMorePath).term
if (oneOrMorePath) {
return { oneOrMore: extractPropertyPath(graph, oneOrMorePath) }
}
const zeroOrOnePath = pathCf.out(sh.zeroOrOnePath).term
if (zeroOrOnePath) {
return { zeroOrOne: extractPropertyPath(graph, zeroOrOnePath) }
}
const inversePath = pathCf.out(sh.inversePath).term
if (inversePath) {
return { inverse: extractPropertyPath(graph, inversePath) }
}
}
throw new Error(`Unsupported SHACL path: ${pathNode.value}`)
}
/**
* Follows a property path in a graph, starting from a given node, and returns
* all the nodes it points to.
*
* @param {RDFLibGraph} graph
* @param {Term} subject - Start node
* @param {object} path - Property path object
* @return {Term[]} - Nodes that are reachable through the property path
*/
function getPathObjects (graph, subject, path) {
return [...getPathObjectsSet(graph, subject, path)]
}
function getPathObjectsSet (graph, subject, path) {
if (path.termType === 'NamedNode') {

@@ -33,3 +98,3 @@ return getNamedNodePathObjects(graph, subject, path)

subjects = new NodeSet(flatMap(subjects, subjectItem =>
[...getPathObjects(graph, subjectItem, pathItem)]))
getPathObjects(graph, subjectItem, pathItem)))
}

@@ -40,3 +105,3 @@ return subjects

function getOrPathObjects (graph, subject, path) {
return new NodeSet(flatMap(path.or, pathItem => [...getPathObjects(graph, subject, pathItem)]))
return new NodeSet(flatMap(path.or, pathItem => getPathObjects(graph, subject, pathItem)))
}

@@ -53,3 +118,3 @@

function getZeroOrOnePathObjects (graph, subject, path) {
const pathObjects = getPathObjects(graph, subject, path.zeroOrOne)
const pathObjects = getPathObjectsSet(graph, subject, path.zeroOrOne)
pathObjects.add(subject)

@@ -74,3 +139,3 @@ return pathObjects

const pathValues = getPathObjects(graph, subject, path)
const pathValues = getPathObjectsSet(graph, subject, path)

@@ -94,3 +159,4 @@ const deeperValues = flatMap(pathValues, pathValue => {

module.exports = {
extractPropertyPath,
getPathObjects
}
const clownface = require('clownface')
const isMatch = require('@rdfjs/dataset/isMatch')
const { getPathObjects } = require('./property-path')
const NodeSet = require('./node-set')

@@ -17,18 +15,4 @@ const { rdf, rdfs } = require('./namespaces')

hasMatch (s, p, o) {
for (const quad of this.dataset) {
if (isMatch(quad, s, p, o)) {
return true
}
}
return false
}
getPathObjects (subject, path) {
return [...getPathObjects(this, subject, path)]
}
get cf () {
return clownface({ dataset: this.dataset })
return clownface({ dataset: this.dataset, factory: this.factory })
}

@@ -73,11 +57,5 @@

rdfListToArray ($rdfList) {
const items = []
while (!$rdfList.equals(rdf.nil)) {
const first = this.cf.node($rdfList).out(rdf.first).term
items.push(first)
const rest = this.cf.node($rdfList).out(rdf.rest).term
$rdfList = rest
}
return items
rdfListToArray (listNode) {
const iterator = this.cf.node(listNode).list()
return [...iterator].map(({ term }) => term)
}

@@ -84,0 +62,0 @@ }

@@ -22,3 +22,3 @@ // Design:

const validatorsRegistry = require('./validators-registry')
const { toRDFQueryPath } = require('./validators')
const { extractPropertyPath, getPathObjects } = require('./property-path')
const { rdfs, sh } = require('./namespaces')

@@ -85,7 +85,9 @@

$shapes.isInstanceOf(shapeNode, rdfs.Class) ||
$shapes.hasMatch(shapeNode, sh.targetClass, null) ||
$shapes.hasMatch(shapeNode, sh.targetNode, null) ||
$shapes.hasMatch(shapeNode, sh.targetSubjectsOf, null) ||
$shapes.hasMatch(shapeNode, sh.targetObjectsOf, null) ||
$shapes.hasMatch(shapeNode, sh.target, null)
$shapes.cf.node(shapeNode).out([
sh.targetClass,
sh.targetNode,
sh.targetSubjectsOf,
sh.targetObjectsOf,
sh.target
]).terms.length > 0
) {

@@ -137,3 +139,3 @@ this.targetShapes.push(this.getShape(shapeNode))

this.parameterNodes.push(parameter)
if (this.context.$shapes.hasMatch(parameter, sh.optional, this.factory.true)) {
if (this.context.$shapes.match(parameter, sh.optional, this.factory.true).size > 0) {
this.optionals[path.value] = true

@@ -192,3 +194,3 @@ } else {

this.isRequired(parameter.value) &&
!this.context.$shapes.hasMatch(shapeNode, parameter, null)
this.context.$shapes.match(shapeNode, parameter, null).size === 0
))

@@ -212,2 +214,3 @@ }

this.path = context.$shapes.cf.node(shapeNode).out(sh.path).term
this._pathObject = undefined
this.shapeNode = shapeNode

@@ -232,2 +235,13 @@ this.constraints = []

/**
* Property path object
*/
get pathObject () {
if (this._pathObject === undefined) {
this._pathObject = this.path ? extractPropertyPath(this.context.$shapes, this.path) : null
}
return this._pathObject
}
getTargetNodes (rdfDataGraph) {

@@ -268,6 +282,5 @@ const results = new NodeSet()

getValueNodes (focusNode, rdfDataGraph) {
getValueNodes (focusNode, dataGraph) {
if (this.path) {
const path = toRDFQueryPath(this.context.$shapes, this.path)
return rdfDataGraph.getPathObjects(focusNode, path)
return getPathObjects(dataGraph, focusNode, this.pathObject)
} else {

@@ -274,0 +287,0 @@ return [focusNode]

const ValidationReport = require('./validation-report')
const { extractStructure } = require('./dataset-utils')
const error = require('debug')('validation-enging::error')

@@ -32,6 +33,6 @@

this.addResultProperty(result, sh.sourceConstraintComponent, sourceConstraintComponent)
this.addResultProperty(result, sh.sourceShape, sourceShape)
this.addResultProperty(result, sh.focusNode, focusNode)
this.addResultPropertyDeep(result, sh.sourceShape, sourceShape)
this.addResultPropertyDeep(result, sh.focusNode, focusNode)
if (valueNode) {
this.addResultProperty(result, sh.value, valueNode)
this.addResultPropertyDeep(result, sh.value, valueNode)
}

@@ -41,2 +42,11 @@ return result

addResultPropertyDeep (result, predicate, node) {
this.addResultProperty(result, predicate, node)
const structureQuads = extractStructure(this.context.$shapes.dataset, node)
for (const quad of structureQuads) {
this.results.push(quad)
}
}
/**

@@ -58,3 +68,3 @@ * Creates all the validation result nodes and messages for the result of applying the validation logic

if (constraint.shape.isPropertyShape()) {
this.addResultProperty(result, sh.resultPath, constraint.shape.path) // TODO: Make deep copy
this.addResultPropertyDeep(result, sh.resultPath, constraint.shape.path, true)
}

@@ -69,3 +79,3 @@ this.createResultMessages(result, constraint)

if (constraint.shape.isPropertyShape()) {
this.addResultProperty(result, sh.resultPath, constraint.shape.path) // TODO: Make deep copy
this.addResultPropertyDeep(result, sh.resultPath, constraint.shape.path, true)
}

@@ -81,10 +91,10 @@ this.addResultProperty(result, sh.resultMessage, this.factory.literal(obj, xsd.string))

if (obj.path) {
this.addResultProperty(result, sh.resultPath, obj.path) // TODO: Make deep copy
this.addResultPropertyDeep(result, sh.resultPath, obj.path, true)
} else if (constraint.shape.isPropertyShape()) {
this.addResultProperty(result, sh.resultPath, constraint.shape.path) // TODO: Make deep copy
this.addResultPropertyDeep(result, sh.resultPath, constraint.shape.path, true)
}
if (obj.value) {
this.addResultProperty(result, sh.value, obj.value)
this.addResultPropertyDeep(result, sh.value, obj.value)
} else if (valueNode) {
this.addResultProperty(result, sh.value, valueNode)
this.addResultPropertyDeep(result, sh.value, valueNode)
}

@@ -91,0 +101,0 @@ if (obj.message) {

const { validateTerm } = require('rdf-validate-datatype')
const NodeSet = require('./node-set')
const { rdf, sh } = require('./namespaces')
const { getPathObjects } = require('./property-path')

@@ -60,13 +61,12 @@ function validateAnd (context, focusNode, valueNode, constraint) {

const disjointNode = constraint.getParameterValue(sh.disjoint)
return !context.$data.hasMatch(focusNode, disjointNode, valueNode)
return context.$data.match(focusNode, disjointNode, valueNode).size === 0
}
function validateEqualsProperty (context, focusNode, valueNode, constraint) {
const pathNode = constraint.shape.path
const path = constraint.shape.pathObject
const equalsNode = constraint.getParameterValue(sh.equals)
const results = []
const path = toRDFQueryPath(context.$shapes, pathNode)
context.$data.getPathObjects(focusNode, path).forEach(value => {
if (!context.$data.hasMatch(focusNode, equalsNode, value)) {
getPathObjects(context.$data, focusNode, path).forEach(value => {
if (context.$data.match(focusNode, equalsNode, value).size === 0) {
results.push({ value })

@@ -79,3 +79,3 @@ }

const value = object
if (!context.$data.getPathObjects(focusNode, path).some(pathValue => pathValue.equals(value))) {
if (!getPathObjects(context.$data, focusNode, path).some(pathValue => pathValue.equals(value))) {
results.push({ value })

@@ -93,3 +93,3 @@ }

let solutions = 0
context.$data.getPathObjects(focusNode, equalsNode).forEach(value => {
getPathObjects(context.$data, focusNode, equalsNode).forEach(value => {
solutions++

@@ -114,8 +114,6 @@ if (compareNodes(focusNode, value) !== 0) {

function validateHasValueProperty (context, focusNode, valueNode, constraint) {
const pathNode = constraint.shape.path
const path = toRDFQueryPath(context.$shapes, pathNode)
const path = constraint.shape.pathObject
const hasValueNode = constraint.getParameterValue(sh.hasValue)
return context.$data
.getPathObjects(focusNode, path)
return getPathObjects(context.$data, focusNode, path)
.some(value => value.equals(hasValueNode))

@@ -146,5 +144,4 @@ }

function validateLessThanProperty (context, focusNode, valueNode, constraint) {
const pathNode = constraint.shape.path
const valuePath = toRDFQueryPath(context.$shapes, pathNode)
const values = context.$data.getPathObjects(focusNode, valuePath)
const valuePath = constraint.shape.pathObject
const values = getPathObjects(context.$data, focusNode, valuePath)
const lessThanNode = constraint.getParameterValue(sh.lessThan)

@@ -166,5 +163,4 @@ const referenceValues = context.$data.cf.node(focusNode).out(lessThanNode).terms

function validateLessThanOrEqualsProperty (context, focusNode, valueNode, constraint) {
const pathNode = constraint.shape.path
const valuePath = toRDFQueryPath(context.$shapes, pathNode)
const values = context.$data.getPathObjects(focusNode, valuePath)
const valuePath = constraint.shape.pathObject
const values = getPathObjects(context.$data, focusNode, valuePath)
const lessThanOrEqualsNode = constraint.getParameterValue(sh.lessThanOrEquals)

@@ -186,5 +182,4 @@ const referenceValues = context.$data.cf.node(focusNode).out(lessThanOrEqualsNode).terms

function validateMaxCountProperty (context, focusNode, valueNode, constraint) {
const pathNode = constraint.shape.path
const path = toRDFQueryPath(context.$shapes, pathNode)
const count = context.$data.getPathObjects(focusNode, path).length
const path = constraint.shape.pathObject
const count = getPathObjects(context.$data, focusNode, path).length
const maxCountNode = constraint.getParameterValue(sh.maxCount)

@@ -215,5 +210,4 @@

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

@@ -324,6 +318,4 @@

const pathNode = constraint.shape.path
const path = toRDFQueryPath(context.$shapes, pathNode)
return context.$data
.getPathObjects(focusNode, path)
const path = constraint.shape.pathObject
return getPathObjects(context.$data, focusNode, path)
.filter(value =>

@@ -352,6 +344,5 @@ context.nodeConformsToShape(value, qualifiedValueShapeNode) &&

const pathNode = constraint.shape.path
const path = toRDFQueryPath(context.$shapes, pathNode)
const path = constraint.shape.pathObject
const map = {}
context.$data.getPathObjects(focusNode, path).forEach(value => {
getPathObjects(context.$data, focusNode, path).forEach(value => {
const lang = value.language

@@ -391,50 +382,2 @@ if (lang && lang !== '') {

// Utilities ------------------------------------------------------------------
function toRDFQueryPath ($shapes, shPath) {
if (shPath.termType === 'NamedNode') {
return shPath
}
if (shPath.termType === 'BlankNode') {
const shPathCf = $shapes.cf.node(shPath)
const first = shPathCf.out(rdf.first).term
if (first) {
const paths = $shapes.rdfListToArray(shPath)
return paths.map(path => toRDFQueryPath($shapes, path))
}
const alternativePath = shPathCf.out(sh.alternativePath).term
if (alternativePath) {
const paths = $shapes.rdfListToArray(alternativePath)
return { or: paths.map(path => toRDFQueryPath($shapes, path)) }
}
const zeroOrMorePath = shPathCf.out(sh.zeroOrMorePath).term
if (zeroOrMorePath) {
return { zeroOrMore: toRDFQueryPath($shapes, zeroOrMorePath) }
}
const oneOrMorePath = shPathCf.out(sh.oneOrMorePath).term
if (oneOrMorePath) {
return { oneOrMore: toRDFQueryPath($shapes, oneOrMorePath) }
}
const zeroOrOnePath = shPathCf.out(sh.zeroOrOnePath).term
if (zeroOrOnePath) {
return { zeroOrOne: toRDFQueryPath($shapes, zeroOrOnePath) }
}
const inversePath = shPathCf.out(sh.inversePath).term
if (inversePath) {
return { inverse: toRDFQueryPath($shapes, inversePath) }
}
}
throw new Error('Unsupported SHACL path ' + shPath)
// TODO: implement conforming to AbstractQuery.path syntax
// return shPath
}
// Private helper functions

@@ -487,3 +430,2 @@

module.exports = {
toRDFQueryPath,
validateAnd,

@@ -490,0 +432,0 @@ validateClass,

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc