← every query · class 09 · examples/07-shacl
Check what comes in
Conforms: False Results (8) data/shapes.ttl over data/domain-bad.ttl
A product with no offer, a manufacturer written as text, a negative price and a currency in words, an order with two customers and a date written as text. Each row says which node, which path and what is expected — a report the producer of the data can act on. In the pipeline, this is exit code 1.
| Focus node | Path | Value | Message |
|---|---|---|---|
| [] (the schema:offers of product:62) | schema:price | "-3.00"^^xsd:decimal | Price required, decimal and not negative. MinInclusiveConstraintComponent |
| [] (the schema:offers of product:62) | schema:priceCurrency | "euros" | Currency required, as a three-letter ISO code. PatternConstraintComponent |
| order:1003 | onto:hasCustomer | customer:5 | The order has exactly one customer. This is integrity, not inference: in OWL, two customers would give owl:sameAs. ClassConstraintComponent |
| order:1003 | onto:date | "12 October" | The date of the order is required and is an xsd:date. DatatypeConstraintComponent |
| order:1003 | onto:hasCustomer | The order has exactly one customer. This is integrity, not inference: in OWL, two customers would give owl:sameAs. MaxCountConstraintComponent | |
| product:41 | schema:offers | Every product has at least one offer, with a price and a currency. MinCountConstraintComponent | |
| product:58 | onto:manufacturedBy | "Acme Lda" | The manufacturer is an entity of class Manufacturer, not text. ClassConstraintComponent |
| product:62 | schema:offers | [] (the schema:offers of product:62) | Every product has at least one offer, with a price and a currency. NodeConstraintComponent |
The same data under the OWL 2 RL reasoner: 0 inconsistencies. OWL infers; SHACL validates — they are complementary, not alternatives.
The files
data/shapes.ttl
# shapes.ttl — the SHACL shapes of the catalogue/orders domain.
#
# OWL infers; SHACL is what says "this is wrong". The messages are part of the
# shape: a report without readable messages is not actionable by whoever
# produces the data. They are in English, like the rest of the domain; the
# @en tag lets a second language be added next to them later.
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix schema: <https://schema.org/> .
@prefix onto: <https://data.csoares.me/onto#> .
@prefix shapes: <https://data.csoares.me/shapes#> .
shapes:ProductShape a sh:NodeShape ;
sh:targetClass onto:Product ;
sh:property [ sh:path schema:name ;
sh:minCount 1 ;
sh:datatype rdf:langString ; # name with a language tag
sh:uniqueLang true ; # one per language, not just one
sh:minLength 2 ;
sh:message "Every product has at least one name, one per language."@en ] ;
sh:property [ sh:path schema:sku ;
sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:string ;
sh:pattern "^[A-Z]{2}-[0-9]{4}$" ;
sh:message "The SKU is required and has the shape XX-0000."@en ] ;
sh:property [ sh:path schema:offers ; # the price lives in the offer
sh:minCount 1 ; sh:node shapes:OfferShape ;
sh:message "Every product has at least one offer, with a price and a currency."@en ] ;
sh:property [ sh:path onto:manufacturedBy ;
sh:minCount 1 ; sh:class onto:Manufacturer ;
sh:message "The manufacturer is an entity of class Manufacturer, not text."@en ] .
shapes:OfferShape a sh:NodeShape ;
sh:property [ sh:path schema:price ; sh:minCount 1 ; sh:maxCount 1 ;
sh:datatype xsd:decimal ; sh:minInclusive 0 ;
sh:message "Price required, decimal and not negative."@en ] ;
sh:property [ sh:path schema:priceCurrency ;
sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[A-Z]{3}$" ;
sh:message "Currency required, as a three-letter ISO code."@en ] .
shapes:ManufacturerShape a sh:NodeShape ;
sh:targetClass onto:Manufacturer ;
sh:property [
sh:path schema:name ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:message "The manufacturer has exactly one name."@en ] ;
sh:property [
sh:path onto:country ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:class onto:Country ;
sh:message "The country is required and is an entity, not text."@en ] .
shapes:OrderShape a sh:NodeShape ;
sh:targetClass onto:Order ;
sh:property [
sh:path onto:hasCustomer ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:class onto:Customer ;
sh:message "The order has exactly one customer. This is integrity, not inference: in OWL, two customers would give owl:sameAs."@en ] ;
sh:property [
sh:path onto:date ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:datatype xsd:date ;
sh:message "The date of the order is required and is an xsd:date."@en ] ;
sh:property [
sh:path onto:containsProduct ;
sh:minCount 1 ;
sh:class onto:Product ;
sh:message "The order has at least one product."@en ] ;
sh:property [
sh:path onto:totalCents ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:datatype xsd:integer ;
sh:minInclusive 0 ;
sh:message "The total in cents is required, an integer and not negative."@en ] .
shapes:SupplyShape a sh:NodeShape ;
sh:targetClass onto:Supply ;
sh:property [
sh:path onto:supplier ;
sh:minCount 1 ; sh:maxCount 1 ;
sh:class onto:Manufacturer ;
sh:message "The supply has exactly one supplier."@en ] ;
sh:property [
sh:path onto:quantity ;
sh:minCount 1 ;
sh:datatype xsd:integer ;
sh:minExclusive 0 ;
sh:message "The quantity supplied is a positive integer."@en ] .
data/domain-bad.ttl
# domain-bad.ttl — the data carrying the errors from the slide "The validation
# report". It is what is loaded to show validation failing, and what the
# pipeline in example 08 rejects.
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix schema: <https://schema.org/> .
@prefix onto: <https://data.csoares.me/onto#> .
@prefix product: <https://data.csoares.me/product/> .
@prefix manufacturer: <https://data.csoares.me/manufacturer/> .
@prefix customer: <https://data.csoares.me/customer/> .
@prefix order: <https://data.csoares.me/order/> .
@prefix country: <https://data.csoares.me/country/> .
manufacturer:9 a onto:Manufacturer ; schema:name "Acme Lda" ; onto:country country:PT .
country:PT a onto:Country .
customer:4 a onto:Customer ; schema:name "Papelaria Central" .
# error 1: a product with no price at all (not even an offer)
product:41 a onto:Product ;
schema:name "Monitor stand"@en ;
schema:sku "SM-0041" ;
onto:manufacturedBy manufacturer:9 .
# error 2: the classic one — manufacturer written as text instead of an entity
product:58 a onto:Product ;
schema:name "Keyboard base"@en ;
schema:sku "BT-0058" ;
schema:offers [ a schema:Offer ;
schema:price "9.90"^^xsd:decimal ;
schema:priceCurrency "EUR" ] ;
onto:manufacturedBy "Acme Lda" .
# error 3: a negative price and a currency outside the format
product:62 a onto:Product ;
schema:name "Mouse mat"@en ;
schema:sku "TR-0062" ;
schema:offers [ a schema:Offer ;
schema:price "-3.00"^^xsd:decimal ;
schema:priceCurrency "euros" ] ;
onto:manufacturedBy manufacturer:9 .
# error 4: an order with two customers and a date written as text
order:1003 a onto:Order ;
onto:hasCustomer customer:4 , customer:5 ;
onto:date "12 October" ;
onto:containsProduct product:41 ;
onto:totalCents "3900"^^xsd:integer .