diff --git a/ontology/uco/action/action.ttl b/ontology/uco/action/action.ttl index c2da040a..2b4c7cf9 100644 --- a/ontology/uco/action/action.ttl +++ b/ontology/uco/action/action.ttl @@ -37,7 +37,10 @@ action:Action owl:Class , sh:NodeShape ; - rdfs:subClassOf core:UcoObject ; + rdfs:subClassOf + core:NeverInformationResource , + core:UcoObject + ; rdfs:label "Action"@en ; rdfs:comment "An action is something that may be done or performed."@en ; owl:disjointWith core:Event ; diff --git a/ontology/uco/core/core.ttl b/ontology/uco/core/core.ttl index 6293d3a7..bba04d4b 100644 --- a/ontology/uco/core/core.ttl +++ b/ontology/uco/core/core.ttl @@ -181,7 +181,10 @@ core:Event owl:Class , sh:NodeShape ; - rdfs:subClassOf core:UcoObject ; + rdfs:subClassOf + core:NeverInformationResource , + core:UcoObject + ; rdfs:label "Event"@en ; rdfs:comment "An Event is a noteworthy occurrence (something that happens or might happen)."@en ; owl:disjointWith action:Action ; @@ -284,6 +287,26 @@ core:IdentityAbstraction sh:targetClass core:IdentityAbstraction ; . +core:InformationResource + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf core:UcoThing ; + owl:disjointWith core:NeverInformationResource ; + sh:targetClass core:InformationResource ; + . + +core:InformationResource-disjointWith-NeverInformationResource-shape + a sh:NodeShape ; + sh:message "core:InformationResource and core:NeverInformationResource are disjoint classes."@en ; + sh:not [ + a sh:NodeShape ; + sh:class core:NeverInformationResource ; + ] ; + sh:targetClass core:InformationResource ; + . + core:Item a owl:Class , @@ -317,6 +340,24 @@ core:ModusOperandi sh:targetClass core:ModusOperandi ; . +core:NeverInformationResource + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf core:NonInformationResource ; + sh:targetClass core:NeverInformationResource ; + . + +core:NonInformationResource + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf core:UcoThing ; + sh:targetClass core:NonInformationResource ; + . + core:Relationship a owl:Class , @@ -371,7 +412,7 @@ core:UcoInherentCharacterizationThing owl:Class , sh:NodeShape ; - rdfs:subClassOf core:UcoThing ; + rdfs:subClassOf core:NeverInformationResource ; rdfs:label "UcoInherentCharacterizationThing"@en ; rdfs:comment "A UCO inherent characterization thing is a grouping of characteristics unique to a particular inherent aspect of a UCO domain object."@en ; sh:targetClass core:UcoInherentCharacterizationThing ; diff --git a/ontology/uco/identity/identity.ttl b/ontology/uco/identity/identity.ttl index 93c20b6f..06df8e18 100644 --- a/ontology/uco/identity/identity.ttl +++ b/ontology/uco/identity/identity.ttl @@ -160,7 +160,10 @@ identity:Organization owl:Class , sh:NodeShape ; - rdfs:subClassOf identity:Identity ; + rdfs:subClassOf + core:NeverInformationResource , + identity:Identity + ; rdfs:label "Organization"@en ; rdfs:comment "An organization is a grouping of identifying characteristics unique to a group of people who work together in an organized way for a shared purpose. [based on https://dictionary.cambridge.org/us/dictionary/english/organization]"@en ; sh:targetClass identity:Organization ; @@ -182,7 +185,10 @@ identity:Person owl:Class , sh:NodeShape ; - rdfs:subClassOf identity:Identity ; + rdfs:subClassOf + core:NeverInformationResource , + identity:Identity + ; rdfs:label "Person"@en ; rdfs:comment "A person is a grouping of identifying characteristics unique to a human being regarded as an individual. [based on https://www.lexico.com/en/definition/person]"@en ; sh:targetClass identity:Person ; diff --git a/ontology/uco/observable/observable.ttl b/ontology/uco/observable/observable.ttl index e29bc164..3cf51e98 100644 --- a/ontology/uco/observable/observable.ttl +++ b/ontology/uco/observable/observable.ttl @@ -2214,7 +2214,10 @@ observable:Device owl:Class , sh:NodeShape ; - rdfs:subClassOf observable:ObservableObject ; + rdfs:subClassOf + core:NeverInformationResource , + observable:ObservableObject + ; rdfs:label "Device"@en ; rdfs:comment "A device is a piece of equipment or a mechanism designed to serve a special purpose or perform a special function. [based on https://www.merriam-webster.com/dictionary/device]"@en ; sh:targetClass observable:Device ; @@ -6894,7 +6897,10 @@ observable:URL owl:Class , sh:NodeShape ; - rdfs:subClassOf observable:ObservableObject ; + rdfs:subClassOf + core:NeverInformationResource , + observable:ObservableObject + ; rdfs:label "URL"@en ; rdfs:comment "A URL is a uniform resource locator (URL) acting as a resolvable address to a particular WWW (World Wide Web) accessible resource."@en ; sh:targetClass observable:URL ; @@ -7350,12 +7356,26 @@ observable:WebPage owl:Class , sh:NodeShape ; - rdfs:subClassOf observable:ObservableObject ; + rdfs:subClassOf + core:InformationResource , + observable:WebResource + ; rdfs:label "WebPage"@en ; rdfs:comment "A web page is a specific collection of information provided by a website and displayed to a user in a web browser. A website typically consists of many web pages linked together in a coherent fashion. [based on https://en.wikipedia.org/wiki/Web_page]"@en ; sh:targetClass observable:WebPage ; . +observable:WebResource + a + owl:Class , + sh:NodeShape + ; + rdfs:subClassOf observable:ObservableObject ; + rdfs:label "WebResource"@en ; + rdfs:seeAlso ; + sh:targetClass observable:WebResource ; + . + observable:WhoIs a owl:Class , diff --git a/ontology/uco/types/types.ttl b/ontology/uco/types/types.ttl index 9153e742..917b8093 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 ; @@ -267,6 +351,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 55e5c298..ffe8ddd0 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_XFAIL_validation.ttl \ event_XFAIL_validation.ttl \ file_url_XFAIL_validation.ttl \ @@ -35,6 +37,8 @@ all: \ has_facet_inverse_functional_XFAIL_validation.ttl \ hash_PASS_validation.ttl \ hash_XFAIL_validation.ttl \ + information_resource_PASS_validation.ttl \ + information_resource_XFAIL_validation.ttl \ location_PASS_validation.ttl \ location_XFAIL_validation.ttl \ message_thread_PASS_validation.ttl \ @@ -101,6 +105,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_XFAIL_validation.ttl \ event_XFAIL_validation.ttl \ file_url_XFAIL_validation.ttl \ @@ -108,6 +114,8 @@ check: \ has_facet_inverse_functional_XFAIL_validation.ttl \ hash_PASS_validation.ttl \ hash_XFAIL_validation.ttl \ + information_resource_PASS_validation.ttl \ + information_resource_XFAIL_validation.ttl \ location_PASS_validation.ttl \ location_XFAIL_validation.ttl \ message_thread_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/information_resource_PASS.json b/tests/examples/information_resource_PASS.json new file mode 100644 index 00000000..ecdd397e --- /dev/null +++ b/tests/examples/information_resource_PASS.json @@ -0,0 +1,71 @@ +{ + "@context": { + "core": "https://ontology.unifiedcyberontology.org/uco/core/", + "kb": "http://example.org/kb/", + "identity": "https://ontology.unifiedcyberontology.org/uco/identity/", + "observable": "https://ontology.unifiedcyberontology.org/uco/observable/", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#" + }, + "@graph": [ + { + "@id": "http://example.org/~bob", + "@type": "observable:WebResource", + "core:name": "Bob", + "core:description": "Bob's company home page.", + "rdfs:seeAlso": [ + { + "@id": "kb:Person-a3d3af3d-ea1d-47f6-bc02-ac334ded6549" + }, + { + "@id": "kb:WebPage-1c05c378-124e-4d3c-898a-fb5a8d178cf8" + } + ] + }, + + { + "@id": "kb:Person-a3d3af3d-ea1d-47f6-bc02-ac334ded6549", + "@type": "identity:Person", + "core:name": "Bob", + "rdfs:seeAlso": { + "@id": "http://example.org/~bob" + } + }, + { + "@id": "kb:WebPage-1c05c378-124e-4d3c-898a-fb5a8d178cf8", + "@type": "observable:WebPage", + "core:description": "Bob's company home page.", + "rdfs:seeAlso": { + "@id": "http://example.org/~bob" + } + }, + { + "@id": "http://example.org/~chris", + "@type": "observable:WebResource", + "rdfs:comment": "This node will trigger an info-level result from not designating itself a InformationResource, and not ending with a UUID." + }, + { + "@id": "https://mc.example.co.jp/", + "@type": "observable:WebPage", + "rdfs:comment": [ + "This node should trigger no errors, even when incorporating the annotations on the same identifier from another JSON dict.", + "This JSON dict was provided by a market analyst in Japan." + ] + }, + { + "@id": "https://mc.example.co.jp/", + "@type": ["core:NonInformationResource", "observable:WebResource"], + "rdfs:comment": [ + "This node should trigger no errors, even when incorporating the annotations on the same identifier from another JSON dict.", + "This JSON dict was provided by a market analyst in France." + ] + }, + { + "@id": "https://mc.example.co.jp/lang-fr/", + "@type": "observable:WebPage", + "rdfs:comment": [ + "This node should trigger no errors.", + "This JSON dict was provided by a market analyst in France." + ] + } + ] +} diff --git a/tests/examples/information_resource_PASS_validation.ttl b/tests/examples/information_resource_PASS_validation.ttl new file mode 100644 index 00000000..95482e49 --- /dev/null +++ b/tests/examples/information_resource_PASS_validation.ttl @@ -0,0 +1,130 @@ +@prefix core: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . + +[] + a sh:ValidationReport ; + sh:conforms "true"^^xsd:boolean ; + sh:result + [ + a sh:ValidationResult ; + sh:focusNode ; + sh:resultMessage "UcoThings are suggested to end with a UUID." ; + sh:resultSeverity sh:Info ; + sh:sourceConstraint [ + a sh:SPARQLConstraint ; + rdfs:seeAlso ; + sh:message "UcoThings are suggested to end with a UUID."@en ; + sh:select ''' + PREFIX rdfs: + PREFIX core: + SELECT $this + WHERE { + $this a/rdfs:subClassOf* core:UcoThing . + FILTER ( + ! REGEX ( + STR($this), + "[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$", + "i" + ) + ) + } + ''' ; + ] ; + sh:sourceConstraintComponent sh:SPARQLConstraintComponent ; + sh:sourceShape core:UcoThing-identifier-regex-shape ; + sh:value ; + ] , + [ + a sh:ValidationResult ; + sh:focusNode ; + sh:resultMessage "UcoThings are suggested to end with a UUID." ; + sh:resultSeverity sh:Info ; + sh:sourceConstraint [ + a sh:SPARQLConstraint ; + rdfs:seeAlso ; + sh:message "UcoThings are suggested to end with a UUID."@en ; + sh:select ''' + PREFIX rdfs: + PREFIX core: + SELECT $this + WHERE { + $this a/rdfs:subClassOf* core:UcoThing . + FILTER ( + ! REGEX ( + STR($this), + "[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$", + "i" + ) + ) + } + ''' ; + ] ; + sh:sourceConstraintComponent sh:SPARQLConstraintComponent ; + sh:sourceShape core:UcoThing-identifier-regex-shape ; + sh:value ; + ] , + [ + a sh:ValidationResult ; + sh:focusNode ; + sh:resultMessage "UcoThings are suggested to end with a UUID." ; + sh:resultSeverity sh:Info ; + sh:sourceConstraint [ + a sh:SPARQLConstraint ; + rdfs:seeAlso ; + sh:message "UcoThings are suggested to end with a UUID."@en ; + sh:select ''' + PREFIX rdfs: + PREFIX core: + SELECT $this + WHERE { + $this a/rdfs:subClassOf* core:UcoThing . + FILTER ( + ! REGEX ( + STR($this), + "[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$", + "i" + ) + ) + } + ''' ; + ] ; + sh:sourceConstraintComponent sh:SPARQLConstraintComponent ; + sh:sourceShape core:UcoThing-identifier-regex-shape ; + sh:value ; + ] , + [ + a sh:ValidationResult ; + sh:focusNode ; + sh:resultMessage "UcoThings are suggested to end with a UUID." ; + sh:resultSeverity sh:Info ; + sh:sourceConstraint [ + a sh:SPARQLConstraint ; + rdfs:seeAlso ; + sh:message "UcoThings are suggested to end with a UUID."@en ; + sh:select ''' + PREFIX rdfs: + PREFIX core: + SELECT $this + WHERE { + $this a/rdfs:subClassOf* core:UcoThing . + FILTER ( + ! REGEX ( + STR($this), + "[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$", + "i" + ) + ) + } + ''' ; + ] ; + sh:sourceConstraintComponent sh:SPARQLConstraintComponent ; + sh:sourceShape core:UcoThing-identifier-regex-shape ; + sh:value ; + ] + ; + . + diff --git a/tests/examples/information_resource_XFAIL.json b/tests/examples/information_resource_XFAIL.json new file mode 100644 index 00000000..b1f7f080 --- /dev/null +++ b/tests/examples/information_resource_XFAIL.json @@ -0,0 +1,29 @@ +{ + "@context": { + "core": "https://ontology.unifiedcyberontology.org/uco/core/", + "kb": "http://example.org/kb/", + "identity": "https://ontology.unifiedcyberontology.org/uco/identity/", + "observable": "https://ontology.unifiedcyberontology.org/uco/observable/", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#" + }, + "@graph": [ + { + "@id": "http://example.org/~bob", + "@type": [ + "identity:Person", + "observable:WebPage" + ], + "core:name": "Bob", + "core:description": "Bob's company home page.", + "rdfs:comment": "This node will trigger an error from conflating a node as both a person and the person's home page.", + "rdfs:seeAlso": [ + { + "@id": "kb:Person-a3d3af3d-ea1d-47f6-bc02-ac334ded6549" + }, + { + "@id": "kb:WebPage-1c05c378-124e-4d3c-898a-fb5a8d178cf8" + } + ] + } + ] +} diff --git a/tests/examples/information_resource_XFAIL_validation.ttl b/tests/examples/information_resource_XFAIL_validation.ttl new file mode 100644 index 00000000..3d28a2bd --- /dev/null +++ b/tests/examples/information_resource_XFAIL_validation.ttl @@ -0,0 +1,52 @@ +@prefix core: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . + +[] + a sh:ValidationReport ; + sh:conforms "false"^^xsd:boolean ; + sh:result + [ + a sh:ValidationResult ; + sh:focusNode ; + sh:resultMessage "UcoThings are suggested to end with a UUID." ; + sh:resultSeverity sh:Info ; + sh:sourceConstraint [ + a sh:SPARQLConstraint ; + rdfs:seeAlso ; + sh:message "UcoThings are suggested to end with a UUID."@en ; + sh:select ''' + PREFIX rdfs: + PREFIX core: + SELECT $this + WHERE { + $this a/rdfs:subClassOf* core:UcoThing . + FILTER ( + ! REGEX ( + STR($this), + "[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$", + "i" + ) + ) + } + ''' ; + ] ; + sh:sourceConstraintComponent sh:SPARQLConstraintComponent ; + sh:sourceShape core:UcoThing-identifier-regex-shape ; + sh:value ; + ] , + [ + a sh:ValidationResult ; + sh:focusNode ; + sh:resultMessage "core:InformationResource and core:NeverInformationResource are disjoint classes."@en ; + sh:resultSeverity sh:Violation ; + sh:sourceConstraintComponent sh:NotConstraintComponent ; + sh:sourceShape core:InformationResource-disjointWith-NeverInformationResource-shape ; + sh:value ; + ] + ; + . + diff --git a/tests/examples/test_validation.py b/tests/examples/test_validation.py index 9fa6dae1..2edb1633 100644 --- a/tests/examples/test_validation.py +++ b/tests/examples/test_validation.py @@ -213,6 +213,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_XFAIL() -> None: confirm_validation_results( "disjointedness_XFAIL_validation.ttl", @@ -269,6 +293,28 @@ def test_hash_XFAIL() -> None: } ) +def test_information_resource_PASS_validation() -> None: + confirm_validation_results( + "information_resource_PASS_validation.ttl", + True, + expected_focus_node_severities={ + ("http://example.org/~bob", str(NS_SH.Info)), + ("http://example.org/~chris", str(NS_SH.Info)), + ("https://mc.example.co.jp/", str(NS_SH.Info)), + ("https://mc.example.co.jp/lang-fr/", str(NS_SH.Info)), + } + ) + +def test_information_resource_XFAIL_validation() -> None: + confirm_validation_results( + "information_resource_XFAIL_validation.ttl", + False, + expected_focus_node_severities={ + ("http://example.org/~bob", str(NS_SH.Info)), + ("http://example.org/~bob", str(NS_SH.Violation)), + } + ) + def test_co_PASS_validation() -> None: confirm_validation_results("co_PASS_validation.ttl", True)