← 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 nodePathValueMessage
[] (the schema:offers of product:62)schema:price"-3.00"^^xsd:decimalPrice 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:1003onto:hasCustomercustomer:5The order has exactly one customer. This is integrity, not inference: in OWL, two customers would give owl:sameAs.
ClassConstraintComponent
order:1003onto:date"12 October"The date of the order is required and is an xsd:date.
DatatypeConstraintComponent
order:1003onto:hasCustomerThe order has exactly one customer. This is integrity, not inference: in OWL, two customers would give owl:sameAs.
MaxCountConstraintComponent
product:41schema:offersEvery product has at least one offer, with a price and a currency.
MinCountConstraintComponent
product:58onto:manufacturedBy"Acme Lda"The manufacturer is an entity of class Manufacturer, not text.
ClassConstraintComponent
product:62schema: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 .