diff --git a/ontology/uco/types/types.ttl b/ontology/uco/types/types.ttl
index 0b8ac9c7..2f646d9f 100644
--- a/ontology/uco/types/types.ttl
+++ b/ontology/uco/types/types.ttl
@@ -59,16 +59,47 @@ types:Dictionary
;
rdfs:subClassOf core:UcoInherentCharacterizationThing ;
rdfs:label "Dictionary"@en ;
- rdfs:comment "A dictionary is list of (term/key, value) pairs with each term/key existing no more than once."@en ;
+ rdfs:comment "A dictionary is list of (term/key, value) pairs with each term/key having an expectation to exist no more than once. types:Dictionary alone does not validate this expectation, but validation is available. For use cases where this expectation must be validated, the subclass types:ProperDictionary should be used instead of types:Dictionary. For instances where this expectation has been found to be violated, the subclass types:ImproperDictionary should be used instead of types:Dictionary."@en ;
sh:property [
sh:class types:DictionaryEntry ;
- sh:minCount "1"^^xsd:integer ;
sh:nodeKind sh:IRI ;
sh:path types:entry ;
] ;
sh:targetClass types:Dictionary ;
.
+types:Dictionary-keyUniqueness-shape
+ a sh:NodeShape ;
+ sh:description "This shape is separated from the types:Dictionary class-shape in order to associate a warning-severity SPARQL-based shape."@en ;
+ sh:severity sh:Warning ;
+ sh:sparql [
+ a sh:SPARQLConstraint ;
+ sh:message "A key in a dictionary should appear no more than once. The value literal does. Please consider using the types:ImproperDictionary class and types:repeatsKey property."@en ;
+ sh:select """
+ PREFIX types:
+ SELECT $this ?value
+ WHERE {
+ $this
+ types:entry/types:key ?value ;
+ .
+ FILTER NOT EXISTS {
+ $this
+ a types:ImproperDictionary ;
+ .
+ }
+ FILTER NOT EXISTS {
+ $this
+ a types:ProperDictionary ;
+ .
+ }
+ }
+ GROUP BY ?value
+ HAVING (COUNT(?value) > 1)
+ """ ;
+ ] ;
+ sh:targetClass types:Dictionary ;
+ .
+
types:DictionaryEntry
a
owl:Class ,
@@ -167,11 +198,64 @@ types:Identifier
rdfs:comment "An identifier is a string conformant to the specified UUID-based format for UCO object identifiers."@en ;
.
+types:ImproperDictionary
+ a
+ owl:Class ,
+ sh:NodeShape
+ ;
+ rdfs:subClassOf types:Dictionary ;
+ rdfs:label "ImproperDictionary"@en ;
+ owl:disjointWith types:ProperDictionary ;
+ sh:property [
+ sh:datatype xsd:string ;
+ sh:nodeKind sh:Literal ;
+ sh:path types:repeatsKey ;
+ ] ;
+ sh:targetClass types:ImproperDictionary ;
+ .
+
+types:ImproperDictionary-disjointWith-ProperDictionary-shape
+ a sh:NodeShape ;
+ sh:message "types:ImproperDictionary and types:ProperDictionary are disjoint classes."@en ;
+ sh:not [
+ a sh:NodeShape ;
+ sh:class types:ProperDictionary ;
+ ] ;
+ sh:targetClass types:ImproperDictionary ;
+ .
+
types:NativeFormatString
a rdfs:Datatype ;
rdfs:comment "Specifies data in its native format of some external language. The data may be encoded in Base64 per [RFC4648]. Data encoded in Base64 must be denoted as such using the encoded property."@en ;
.
+types:ProperDictionary
+ a
+ owl:Class ,
+ sh:NodeShape
+ ;
+ rdfs:subClassOf types:Dictionary ;
+ rdfs:label "ProperDictionary"@en ;
+ rdfs:comment "A proper dictionary is list of (term/key, value) pairs with each term/key existing no more than once."@en ;
+ owl:disjointWith types:ImproperDictionary ;
+ sh:sparql [
+ a sh:SPARQLConstraint ;
+ sh:message "A key in a proper dictionary can appear no more than once."@en ;
+ sh:select """
+ PREFIX types:
+ SELECT $this ?value
+ WHERE {
+ $this
+ types:entry/types:key ?value ;
+ .
+ }
+ GROUP BY ?value
+ HAVING (COUNT(?value) > 1)
+ """ ;
+ ] ;
+ sh:targetClass types:ProperDictionary ;
+ .
+
types:StructuredText
a rdfs:Datatype ;
rdfs:comment "Expresses string-based data in some information structuring format (e.g., HTML5)."@en ;
@@ -269,6 +353,20 @@ types:key
rdfs:range xsd:string ;
.
+types:repeatsKey
+ a owl:DatatypeProperty ;
+ rdfs:label "repeatsKey"@en ;
+ rdfs:comment "A key found to be repeated in multiple dictionary entries within one dictionary."@en ;
+ rdfs:domain types:ImproperDictionary ;
+ rdfs:range xsd:string ;
+ .
+
+types:repeatsKey-subjects-shape
+ a sh:NodeShape ;
+ sh:class types:ImproperDictionary ;
+ sh:targetSubjectsOf types:repeatsKey ;
+ .
+
types:threadNextItem
a owl:ObjectProperty ;
rdfs:subPropertyOf types:threadSuccessor ;
diff --git a/tests/examples/Makefile b/tests/examples/Makefile
index 7921bb00..66b33e05 100644
--- a/tests/examples/Makefile
+++ b/tests/examples/Makefile
@@ -28,6 +28,8 @@ all: \
configuration_setting_XFAIL_validation.ttl \
database_records_PASS_validation.ttl \
database_records_XFAIL_validation.ttl \
+ dictionary_PASS_validation.ttl \
+ dictionary_XFAIL_validation.ttl \
disjointedness_PASS_validation.ttl \
event_XFAIL_validation.ttl \
file_url_PASS_validation.ttl \
@@ -101,6 +103,8 @@ check: \
configuration_setting_XFAIL_validation.ttl \
database_records_PASS_validation.ttl \
database_records_XFAIL_validation.ttl \
+ dictionary_PASS_validation.ttl \
+ dictionary_XFAIL_validation.ttl \
disjointedness_PASS_validation.ttl \
event_XFAIL_validation.ttl \
file_url_PASS_validation.ttl \
diff --git a/tests/examples/README.md b/tests/examples/README.md
index 4b00de91..03abedd1 100644
--- a/tests/examples/README.md
+++ b/tests/examples/README.md
@@ -9,6 +9,32 @@ Two instance data files are currently in the directory:
SHACL validation results are stored in corresponding files named `..._validation.ttl`, to present the current state of validation conditions.
+## Design of the Dictionary tests
+
+The `Dictionary` objects in the `dictionary_*.json` files cover these combinations of asserted type (proper dictionary, improper dictionary, or the generic parent class), whether a dictionary entry key is repeated in the data, and whether the `repeatsKey` property is asserted. (P/X denotes whether the instance is a PASS or XFAIL test case.)
+
+| uuid | P/X | Dictionary type | Key repeats | repeatsKey asserted |
+| --- | --- | --- | --- | --- |
+| `3bb38b3e` | P | `Dictionary` | no | no |
+| `e6dc9c2e` | X | `Dictionary` | no | yes |
+| `e9adf6c1` | P | `Dictionary` | yes | no |
+| `34ac0c49` | X | `Dictionary` | yes | yes |
+| `cbc1c80d` | P | `ImproperDictionary` | no | no |
+| `7fa3ea45` | P | `ImproperDictionary` | no | yes |
+| `14e28425` | P | `ImproperDictionary` | yes | no |
+| `a8e5e8e1` | P | `ImproperDictionary` | yes | yes |
+| `eaded28e` | P | `ProperDictionary` | no | no |
+| `8114819f` | X | `ProperDictionary` | no | yes |
+| `b2baf8af` | X | `ProperDictionary` | yes | no |
+| `f5ae2e6a` | X | `ProperDictionary` | yes | yes |
+
+Other miscellaneous tests are added without full combinatoric review:
+
+* `kb:ProperDictionary-f5ae2e6a-9b10-46f3-8441-30aada36aa1b` also demonstrates an XFAIL case where a key-value *pair* is repeated.
+* `kb:ImproperDictionary-7fa3ea45-6426-4ad3-bb5f-7559e07adeb4` also demonstrates a PASS case where `repeatsKey`'s value is not in the supplied dictionary.
+* `kb:Dictionary-5bc55661-4808-48e6-9e02-80a153eee5d3` demonstrates an XFAIL case where the disjoint `Dictionary` subtypes are both asserted.
+
+
## Design of the Relationship tests
The `Relationship` objects in the `relationship_*.json` files include a numbering scheme in their identifiers, (object class)-(lexical value)-(datatype). These track the following matrix of test cases:
diff --git a/tests/examples/dictionary_PASS.json b/tests/examples/dictionary_PASS.json
new file mode 100644
index 00000000..31e1aab8
--- /dev/null
+++ b/tests/examples/dictionary_PASS.json
@@ -0,0 +1,125 @@
+{
+ "@context": {
+ "kb": "http://example.org/kb/",
+ "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
+ "types": "https://ontology.unifiedcyberontology.org/uco/types/"
+ },
+ "@graph": [
+ {
+ "@id": "kb:Dictionary-3bb38b3e-d47a-43c8-8a77-afc0e6655ce1",
+ "@type": "types:Dictionary",
+ "types:entry": [
+ {
+ "@id": "kb:DictionaryEntry-b8a01d49-53c1-440f-a2d5-618b58801d37",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "1"
+ },
+ {
+ "@id": "kb:DictionaryEntry-6cac6c2c-5d4e-45f5-b784-c029c9f9fb6d",
+ "@type": "types:DictionaryEntry",
+ "types:key": "y",
+ "types:value": "2"
+ }
+ ]
+ },
+ {
+ "@id": "kb:ProperDictionary-eaded28e-0bf8-4df1-aee8-84d22c09702c",
+ "@type": "types:ProperDictionary",
+ "types:entry": [
+ {
+ "@id": "kb:DictionaryEntry-314212eb-39c4-4bf3-be3a-f07c38f0eae8",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "1"
+ },
+ {
+ "@id": "kb:DictionaryEntry-9ec24a1a-7e99-41c9-ba7d-9d23f11babb4",
+ "@type": "types:DictionaryEntry",
+ "types:key": "y",
+ "types:value": "2"
+ }
+ ]
+ },
+ {
+ "@id": "kb:ImproperDictionary-a8e5e8e1-b3de-4ac4-99dd-e36f96beea4d",
+ "@type": "types:ImproperDictionary",
+ "types:repeatsKey": "x",
+ "types:entry": [
+ {
+ "@id": "kb:DictionaryEntry-55786f64-534d-4e8c-8a64-616f708ea4d3",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "1"
+ },
+ {
+ "@id": "kb:DictionaryEntry-d1a83c3d-cbe6-40b0-bb26-3527c47a01d8",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "2"
+ }
+ ]
+ },
+ {
+ "@id": "kb:Dictionary-e9adf6c1-0287-4290-95a9-c94a128d7ff6",
+ "@type": "types:Dictionary",
+ "rdfs:comment": "This dictionary, not being typed as a ProperDictionary, will not trigger a warning from having two entries keyed with value 'x'.",
+ "types:entry": [
+ {
+ "@id": "kb:DictionaryEntry-20431f00-64a3-4c0f-94a4-1eb09f8a6b6a",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "1"
+ },
+ {
+ "@id": "kb:DictionaryEntry-f187ee7f-12fb-4580-966d-47bf1afd4975",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "1"
+ }
+ ]
+ },
+ {
+ "@id": "kb:ImproperDictionary-7fa3ea45-6426-4ad3-bb5f-7559e07adeb4",
+ "@type": "types:ImproperDictionary",
+ "repeatsKey": "z"
+ },
+ {
+ "@id": "kb:ImproperDictionary-14e28425-00c1-4f11-b2ed-21390fc0749a",
+ "@type": "types:ImproperDictionary",
+ "types:entry": [
+ {
+ "@id": "kb:DictionaryEntry-09f23642-389b-4553-b5be-283a6160f534",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "1"
+ },
+ {
+ "@id": "kb:DictionaryEntry-7a84a0d6-d1cd-4291-afb4-c834d611898d",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "2"
+ }
+ ]
+ },
+ {
+ "@id": "kb:ImproperDictionary-cbc1c80d-1bad-4947-8459-c53ff61e8bfa",
+ "@type": "types:ImproperDictionary",
+ "rdfs:comment": "This improper dictionary has no repeated key or assertion of a repeated key. This should not trigger a data error, because the information in the graph could merely be incomplete.",
+ "types:entry": [
+ {
+ "@id": "kb:DictionaryEntry-ca1910ab-fa26-402a-86bb-229f490dd89a",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "1"
+ },
+ {
+ "@id": "kb:DictionaryEntry-2a13e674-5e95-4a7a-9fac-c90417dcd97c",
+ "@type": "types:DictionaryEntry",
+ "types:key": "y",
+ "types:value": "2"
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/examples/dictionary_PASS_validation.ttl b/tests/examples/dictionary_PASS_validation.ttl
new file mode 100644
index 00000000..f563abc0
--- /dev/null
+++ b/tests/examples/dictionary_PASS_validation.ttl
@@ -0,0 +1,46 @@
+@prefix owl: .
+@prefix rdf: .
+@prefix rdfs: .
+@prefix sh: .
+@prefix types: .
+@prefix xsd: .
+
+[]
+ a sh:ValidationReport ;
+ sh:conforms "true"^^xsd:boolean ;
+ sh:result [
+ a sh:ValidationResult ;
+ sh:focusNode ;
+ sh:resultMessage "A key in a dictionary should appear no more than once. The value literal does. Please consider using the types:ImproperDictionary class and types:repeatsKey property." ;
+ sh:resultSeverity sh:Warning ;
+ sh:sourceConstraint [
+ a sh:SPARQLConstraint ;
+ sh:message "A key in a dictionary should appear no more than once. The value literal does. Please consider using the types:ImproperDictionary class and types:repeatsKey property."@en ;
+ sh:select """
+ PREFIX types:
+ SELECT $this ?value
+ WHERE {
+ $this
+ types:entry/types:key ?value ;
+ .
+ FILTER NOT EXISTS {
+ $this
+ a types:ImproperDictionary ;
+ .
+ }
+ FILTER NOT EXISTS {
+ $this
+ a types:ProperDictionary ;
+ .
+ }
+ }
+ GROUP BY ?value
+ HAVING (COUNT(?value) > 1)
+ """ ;
+ ] ;
+ sh:sourceConstraintComponent sh:SPARQLConstraintComponent ;
+ sh:sourceShape types:Dictionary-keyUniqueness-shape ;
+ sh:value "x" ;
+ ] ;
+ .
+
diff --git a/tests/examples/dictionary_XFAIL.json b/tests/examples/dictionary_XFAIL.json
new file mode 100644
index 00000000..dd2a4567
--- /dev/null
+++ b/tests/examples/dictionary_XFAIL.json
@@ -0,0 +1,122 @@
+{
+ "@context": {
+ "kb": "http://example.org/kb/",
+ "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
+ "types": "https://ontology.unifiedcyberontology.org/uco/types/"
+ },
+ "@graph": [
+ {
+ "@id": "kb:Dictionary-5bc55661-4808-48e6-9e02-80a153eee5d3",
+ "@type": [
+ "types:ImproperDictionary",
+ "types:ProperDictionary"
+ ],
+ "rdfs:comment": "This dictionary will trigger an error from being typed as both disjoint subclasses of types:Dictionary.",
+ "types:entry": {
+ "@id": "kb:DictionaryEntry-fa139d6e-2b4d-49e5-8c7d-3cfc635d56e0",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "1"
+ }
+ },
+ {
+ "@id": "kb:Dictionary-34ac0c49-1042-49c0-8fd6-c42a810e58da",
+ "@type": "types:Dictionary",
+ "rdfs:comment": "This dictionary will trigger an error from using repeatsKey while not also typing itself as a types:ImproperDictionary.",
+ "types:repeatsKey": "x",
+ "types:entry": [
+ {
+ "@id": "kb:DictionaryEntry-322b718b-3869-48a3-a7bf-d97d5463563b",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "1"
+ },
+ {
+ "@id": "kb:DictionaryEntry-e51c7808-7fcb-423a-95e6-dcb431a3bade",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "2"
+ }
+ ]
+ },
+ {
+ "@id": "kb:ProperDictionary-b2baf8af-3d5d-4c4e-b442-49befefd147e",
+ "@type": "types:ProperDictionary",
+ "rdfs:comment": "This dictionary will trigger an error from having two entries keyed with value 'x'.",
+ "types:entry": [
+ {
+ "@id": "kb:DictionaryEntry-203a8596-1439-4065-a99f-daf4d530bed7",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "1"
+ },
+ {
+ "@id": "kb:DictionaryEntry-40b9d75d-6a11-4a8f-9951-e96e2c1fe683",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "2"
+ }
+ ]
+ },
+ {
+ "@id": "kb:ProperDictionary-8114819f-d3c8-4e29-9e31-295d771f9db2",
+ "@type": "types:ProperDictionary",
+ "rdfs:comment": "This proper dictionary will trigger an error from using repeatsKey while not being an ImproperDictionary.",
+ "types:repeatsKey": "x",
+ "types:entry": [
+ {
+ "@id": "kb:DictionaryEntry-1311a664-fce3-4174-ace1-539ac6d54a5f",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "1"
+ },
+ {
+ "@id": "kb:DictionaryEntry-8b149881-5adc-4020-b46f-2be1c60bab83",
+ "@type": "types:DictionaryEntry",
+ "types:key": "y",
+ "types:value": "2"
+ }
+ ]
+ },
+ {
+ "@id": "kb:ProperDictionary-f5ae2e6a-9b10-46f3-8441-30aada36aa1b",
+ "@type": "types:ProperDictionary",
+ "rdfs:comment": "This dictionary will trigger an error from having two entries keyed with value 'x'.",
+ "types:repeatsKey": "x",
+ "types:entry": [
+ {
+ "@id": "kb:DictionaryEntry-0274c19c-89b9-42b6-a87e-f671cbd2c731",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "1"
+ },
+ {
+ "@id": "kb:DictionaryEntry-de108ec2-8ddd-4201-8267-5a04035ba88e",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "1"
+ }
+ ]
+ },
+ {
+ "@id": "kb:Dictionary-e6dc9c2e-25bc-422f-8ae8-8457e29f5fde",
+ "@type": "types:Dictionary",
+ "rdfs:comment": "This dictionary will trigger an error from using repeatsKey while not also typing itself as a types:ImproperDictionary.",
+ "types:repeatsKey": "x",
+ "types:entry": [
+ {
+ "@id": "kb:DictionaryEntry-02edb446-1ad5-41ef-8877-fbee912189e7",
+ "@type": "types:DictionaryEntry",
+ "types:key": "x",
+ "types:value": "1"
+ },
+ {
+ "@id": "kb:DictionaryEntry-147908bb-ebba-42e8-854d-72352dc903a1",
+ "@type": "types:DictionaryEntry",
+ "types:key": "y",
+ "types:value": "2"
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/examples/dictionary_XFAIL_validation.ttl b/tests/examples/dictionary_XFAIL_validation.ttl
new file mode 100644
index 00000000..f15a7580
--- /dev/null
+++ b/tests/examples/dictionary_XFAIL_validation.ttl
@@ -0,0 +1,141 @@
+@prefix owl: .
+@prefix rdf: .
+@prefix rdfs: .
+@prefix sh: .
+@prefix types: .
+@prefix xsd: .
+
+[]
+ a sh:ValidationReport ;
+ sh:conforms "false"^^xsd:boolean ;
+ sh:result
+ [
+ a sh:ValidationResult ;
+ sh:focusNode ;
+ sh:resultMessage "A key in a dictionary should appear no more than once. The value literal does. Please consider using the types:ImproperDictionary class and types:repeatsKey property." ;
+ sh:resultSeverity sh:Warning ;
+ sh:sourceConstraint [
+ a sh:SPARQLConstraint ;
+ sh:message "A key in a dictionary should appear no more than once. The value literal does. Please consider using the types:ImproperDictionary class and types:repeatsKey property."@en ;
+ sh:select """
+ PREFIX types:
+ SELECT $this ?value
+ WHERE {
+ $this
+ types:entry/types:key ?value ;
+ .
+ FILTER NOT EXISTS {
+ $this
+ a types:ImproperDictionary ;
+ .
+ }
+ FILTER NOT EXISTS {
+ $this
+ a types:ProperDictionary ;
+ .
+ }
+ }
+ GROUP BY ?value
+ HAVING (COUNT(?value) > 1)
+ """ ;
+ ] ;
+ sh:sourceConstraintComponent sh:SPARQLConstraintComponent ;
+ sh:sourceShape types:Dictionary-keyUniqueness-shape ;
+ sh:value "x" ;
+ ] ,
+ [
+ a sh:ValidationResult ;
+ sh:focusNode ;
+ sh:resultMessage "Value does not have class types:ImproperDictionary" ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:ClassConstraintComponent ;
+ sh:sourceShape types:repeatsKey-subjects-shape ;
+ sh:value ;
+ ] ,
+ [
+ a sh:ValidationResult ;
+ sh:focusNode ;
+ sh:resultMessage "types:ImproperDictionary and types:ProperDictionary are disjoint classes."@en ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:NotConstraintComponent ;
+ sh:sourceShape types:ImproperDictionary-disjointWith-ProperDictionary-shape ;
+ sh:value ;
+ ] ,
+ [
+ a sh:ValidationResult ;
+ sh:focusNode ;
+ sh:resultMessage "Value does not have class types:ImproperDictionary" ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:ClassConstraintComponent ;
+ sh:sourceShape types:repeatsKey-subjects-shape ;
+ sh:value ;
+ ] ,
+ [
+ a sh:ValidationResult ;
+ sh:focusNode ;
+ sh:resultMessage "Value does not have class types:ImproperDictionary" ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:ClassConstraintComponent ;
+ sh:sourceShape types:repeatsKey-subjects-shape ;
+ sh:value ;
+ ] ,
+ [
+ a sh:ValidationResult ;
+ sh:focusNode ;
+ sh:resultMessage "A key in a proper dictionary can appear no more than once." ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraint [
+ a sh:SPARQLConstraint ;
+ sh:message "A key in a proper dictionary can appear no more than once."@en ;
+ sh:select """
+ PREFIX types:
+ SELECT $this ?value
+ WHERE {
+ $this
+ types:entry/types:key ?value ;
+ .
+ }
+ GROUP BY ?value
+ HAVING (COUNT(?value) > 1)
+ """ ;
+ ] ;
+ sh:sourceConstraintComponent sh:SPARQLConstraintComponent ;
+ sh:sourceShape types:ProperDictionary ;
+ sh:value "x" ;
+ ] ,
+ [
+ a sh:ValidationResult ;
+ sh:focusNode ;
+ sh:resultMessage "A key in a proper dictionary can appear no more than once." ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraint [
+ a sh:SPARQLConstraint ;
+ sh:message "A key in a proper dictionary can appear no more than once."@en ;
+ sh:select """
+ PREFIX types:
+ SELECT $this ?value
+ WHERE {
+ $this
+ types:entry/types:key ?value ;
+ .
+ }
+ GROUP BY ?value
+ HAVING (COUNT(?value) > 1)
+ """ ;
+ ] ;
+ sh:sourceConstraintComponent sh:SPARQLConstraintComponent ;
+ sh:sourceShape types:ProperDictionary ;
+ sh:value "x" ;
+ ] ,
+ [
+ a sh:ValidationResult ;
+ sh:focusNode ;
+ sh:resultMessage "Value does not have class types:ImproperDictionary" ;
+ sh:resultSeverity sh:Violation ;
+ sh:sourceConstraintComponent sh:ClassConstraintComponent ;
+ sh:sourceShape types:repeatsKey-subjects-shape ;
+ sh:value ;
+ ]
+ ;
+ .
+
diff --git a/tests/examples/test_validation.py b/tests/examples/test_validation.py
index d4f39e6a..6c4fc658 100644
--- a/tests/examples/test_validation.py
+++ b/tests/examples/test_validation.py
@@ -216,6 +216,30 @@ def test_database_records_XFAIL() -> None:
}
)
+def test_dictionary_PASS() -> None:
+ confirm_validation_results(
+ "dictionary_PASS_validation.ttl",
+ True,
+ expected_focus_node_severities={
+ ("http://example.org/kb/Dictionary-e9adf6c1-0287-4290-95a9-c94a128d7ff6", str(NS_SH.Warning)),
+ }
+ )
+
+def test_dictionary_XFAIL() -> None:
+ confirm_validation_results(
+ "dictionary_XFAIL_validation.ttl",
+ False,
+ expected_focus_node_severities={
+ ("http://example.org/kb/Dictionary-5bc55661-4808-48e6-9e02-80a153eee5d3", str(NS_SH.Violation)),
+ ("http://example.org/kb/Dictionary-e6dc9c2e-25bc-422f-8ae8-8457e29f5fde", str(NS_SH.Violation)),
+ ("http://example.org/kb/Dictionary-34ac0c49-1042-49c0-8fd6-c42a810e58da", str(NS_SH.Warning)),
+ ("http://example.org/kb/Dictionary-34ac0c49-1042-49c0-8fd6-c42a810e58da", str(NS_SH.Violation)),
+ ("http://example.org/kb/ProperDictionary-8114819f-d3c8-4e29-9e31-295d771f9db2", str(NS_SH.Violation)),
+ ("http://example.org/kb/ProperDictionary-b2baf8af-3d5d-4c4e-b442-49befefd147e", str(NS_SH.Violation)),
+ ("http://example.org/kb/ProperDictionary-f5ae2e6a-9b10-46f3-8441-30aada36aa1b", str(NS_SH.Violation)),
+ }
+ )
+
def test_disjointedness_PASS() -> None:
confirm_validation_results(
"disjointedness_PASS_validation.ttl",