Using ADS with SHACL
Constraint Validation
While the SHACL Core language defines built-in constraint properties such as sh:minCount
, SHACL is also an extensible language, allowing extension languages such as SPARQL to declare constraints that go beyond the Core.
In this section we introduce how the Active Data Shapes framework can be used as one such extension mechanism, allowing SHACL users to declare constraints and also constraint components, using JavaScript. JavaScript offers significantly greater expressiveness than, for example, SPARQL, allowing the representation of very complex conditions and taking better control over the execution speed.
Script Constraints
The property dash:scriptConstraint
links a shape with instances of dash:ScriptConstraint
.
This is similar to how sh:sparql
links a shape with instances of sh:SPARQLConstraint
.
Each dash:ScriptConstraint
must have a value for dash:js
to represent the JavaScript expression that shall be evaluated.
It may also use sh:message
to declare which validation messages shall be produced.
The optional property dash:onAllValues
may be used to control what variables can be used by the script, as explained in the following examples.
Example: Validating an individual value
This example illustrates the most simple form of script-based constraints: a single JavaScript expression that gets executed for each individual value of a property.
Here we have a class ex:AdultPerson
with a property ex:age
that must not have values smaller than 18.
1ex:AdultPerson
2 a rdfs:Class, sh:NodeShape ;
3 sh:property ex:AdultPerson-age .
4
5ex:AdultPerson-age
6 a sh:PropertyShape ;
7 sh:path ex:age ;
8 sh:datatype xsd:integer ;
9 sh:maxCount 1 ;
10 dash:scriptConstraint [
11 a dash:ScriptConstraint ;
12 sh:message "Age must be >= 18" ;
13 dash:js "value >= 18" ;
14 ] .
Such constraints will be executed for each value node of the property.
In each script execution, the variable value will point at the actual property value.
In this case, assuming that the value(s) of ex:age
are xsd:integer
literals, the value will be a JavaScript number.
As usual with ADS, xsd:string
literals are represented as JavaScript strings, while xsd:boolean
literals become true or false in JavaScript.
Literals of other datatypes become instances of LiteralNode
, and NamedNode
is used to represent URIs or blank nodes.
The dash:js
script can also access the current focus node using the variable focusNode
as an instance of NamedNode
, if needed.
The dash:js
expression will be evaluated against the current data graph.
As usual in JavaScript, such expressions may consist of multiple lines, and the final result of the expression will be the result of its last line.
If the result is a string then the validation will produce a validation result with the given string as
sh:resultMessage
.If the result is true then the validation passes OK without violations.
If the result is false the validation will produce a validation result that uses the provided
sh:message
or a generic fallback message. The values ofsh:message
can access the values{$focusNode}
,{$value}
etc as template variables.If the result an object, a validation result will be produced using the value of the field
message
of the object as result message. If the fieldvalue
has a value then this will become thesh:value
in the violation.
Alternatively, the script may return an array then each array member will be mapped to one validation result, following the mapping rules above.
Example: Validating all values at once
In some scenarios, constraints need to look at all value nodes at once, for example to count the number of available values.
This mode is activated if dash:onAllValues
is true, and the JavaScript variable values
will then be an array of the value nodes.
In this example we verify that at least one of the values of the ex:supervision
property of a person is an adult.
1ex:Person
2 a rdfs:Class, sh:NodeShape ;
3 sh:property ex:Person-supervision .
4
5ex:Person-supervision
6 a sh:PropertyShape ;
7 sh:path ex:supervision ;
8 sh:class ex:Person ;
9 dash:scriptConstraint [
10 a dash:ScriptConstraint ;
11 sh:message "At least one of the supervisors must be an adult" ;
12 dash:onAllValues true ;
13 dash:js "values.some(value => value.instanceOf(ex.AdultPerson))" ;
14 ] .
Script Constraint Components
SHACL constraint components are reusable building blocks that declare property types that others may use. Similar to SPARQL-based Constraint Components, ADS makes it possible to declare new constraint components using JavaScript.
Create an instance of sh:ConstraintComponent
to get started, and declare the parameters as usual in SHACL.
Then declare a dash:ScriptValidator
as value of sh:validator
and store the ADS script that shall be executed using dash:js
at the validator.
Within the JavaScript code, the same variables may be used as for constraints, and dash:onAllValues
has the same meaning.
In addition, the parameters will be mapped to JavaScript variables that have the same name as the local name of the parameter.
This is similar to how SPARQL-based constraint components work.
This is best explained using an example.
This example declares a SHACL constraint component based on a parameter ex:isJSONArray
which can be used to validate that all values of a given property can be parsed into JSON arrays.
1ex:IsJSONArrayConstraintComponent
2 a sh:ConstraintComponent ;
3 rdfs:comment "As an example of a Script-based constraint component, this can be used to verify that all value nodes are valid JSON arrays (encoded as strings)." ;
4 rdfs:label "Is JSON array constraint component" ;
5 sh:message "Value must be a JSON array: {$value}" ;
6 sh:parameter ex:IsJSONArrayConstraintComponent-isJSONArray ;
7 sh:validator ex:IsJSONArrayScriptValidator ;
8.
9ex:IsJSONArrayConstraintComponent-isJSONArray
10 a sh:Parameter ;
11 sh:path ex:isJSONArray ;
12 sh:datatype xsd:boolean ;
13 sh:description "True to activate the check that all values must be JSON arrays." ;
14 sh:name "is JSONArray" ;
15.
16ex:IsJSONArrayScriptValidator
17 a dash:ScriptValidator ;
18 rdfs:label "IsJSONArray script validator" ;
19 dash:js """
20 if(isJSONArray) {
21 let array = JSON.parse(value);
22 Array.isArray(array);
23 }""" ;
24.
Note that the dash:js
script queries the value of isJSONArray
which will be a boolean that is automatically set to the correct value by the engine.
Once declared, the new property can be used as in the following example shapes graph.
Here we have set ex:isJSONArray
to true which will trigger the execution of the script validator, which will verify that the value is true before proceeding with the actual check.
1ex:TestShape
2 a rdfs:Class ;
3 a sh:NodeShape ;
4 rdfs:label "Test shape" ;
5 rdfs:subClassOf rdfs:Resource ;
6 sh:property ex:TestShape-property ;
7.
8ex:TestShape-property
9 a sh:PropertyShape ;
10 sh:path ex:property ;
11 ex:isJSONArray true ;
12 sh:datatype xsd:string ;
13 sh:name "property" ;
14.
Here is a valid instance from a data graph:
1ex:ValidInstance
2 a ex:TestShape ;
3 ex:property "[1, 2, 3]" ;
4 ex:property "[]" ;
5 rdfs:label "Valid instance" ;
6.
Inferring Values
The SHACL Advanced Features include a mechanism to dynamically compute (infer) values of certain properties, based on the sh:values
property.
Active Data Shapes technology introduces a new kind of SHACL Node Expression based on scripts that makes it possible to dynamically compute property values from JavaScript expressions.
These node expressions are blank nodes that have the script as value of dash:js
.
In the script, you can use the variable focusNode
to reference the current context node.
In the following example, we introduce a new derived property skos:narrowerConceptCount
which is an integer computed as the number of narrower concepts.
1skos:Concept-narrowerConceptCount
2 a sh:PropertyShape ;
3 sh:path skos:narrowerConceptCount ;
4 sh:datatype xsd:integer ;
5 sh:description "The number of narrower concepts of this." ;
6 sh:group skos:HierarchicalRelationships ;
7 sh:maxCount 1 ;
8 sh:name "narrower concept count" ;
9 sh:order "10"^^xsd:decimal ;
10 sh:values [
11 dash:js "focusNode.narrower.length" ;
12 ] .
13
14skos:Concept sh:property skos:Concept-narrowerConceptCount .
With the new property defined as above, forms of SKOS concepts will now show the count:
The dash:js
expression may also return an array, in which case multiple values would be inferred.
Warning
Make sure to use this (powerful) feature with care, as it might have a performance impact if the system needs to repeatedly fire up a Script Runtime for each individual value on a form. Performance should however, be good as long as you call your inference as part of some other script. In the above case, you could now query the new property like any other property of the instance:
As usual, inferred properties cannot be directly modified, i.e. you cannot assign them.
Change the underlying values (here: skos:broader
) instead.
Pro tip: if you want the Form panel and other parts of the UI update automatically after changes to the asserted properties annotate your node expression with dash:dependencyPredicate
.
1skos:Concept-narrowerConceptCount
2 ...
3 sh:values [
4 dash:js "focusNode.narrower.length" ;
5 dash:dependencyPredicate skos:broader ;
6 ] .
The above will mean that the counter will automatically update whenever some skos:broader
triple has been changed (we are using skos:broader
as the sh:inversePath
in the computation of the narrower property).
Current limitations: these script-based inferred values only work within graphs that are under teamwork control, i.e. you can only query them within EDG asset collections, not from other (file based) graphs in your installation.
Furthermore, make sure that the focus node only has a single type, so that it is able to pick the right JavaScript class for the focusNode
variable.