Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 55 additions & 6 deletions docs/reference/administration/ontologies.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,11 @@ WHERE {}</pre>
</div>
<div>
<h3 id="constraints">Constraints</h3>
<p>Constraints are SPARQL queries or <a href="https://spinrdf.org/spin.html#spin-templates" target="_blank">SPIN command templates</a> that validate submitted
RDF data during document creation and editing. A constraint applies to instances of the class it is declared on and reports violations —
<p>Constraints validate a document as a write leaves it. Two kinds are supported, and both are checked on every write:
<a href="https://spinrdf.org/spin.html#spin-constraints" target="_blank">SPIN constraints</a> — SPARQL queries or
<a href="https://spinrdf.org/spin.html#spin-templates" target="_blank">SPIN command templates</a> attached to a class with
<code>spin:constraint</code> — and <a href="https://www.w3.org/TR/shacl/" target="_blank">SHACL</a> shapes. A SPIN constraint applies to
instances of the class it is declared on and reports violations —
missing mandatory properties, malformed values and so on. For example, an instance of
<code>dh:Item</code> without <code>dct:title</code> will fail validation because titles are mandatory for LinkedDataHub documents.</p>
<p>The most common constraint shape is the missing-property check. The Northwind demo requires every Product to have a name:</p>
Expand All @@ -168,17 +171,26 @@ WHERE {}</pre>
rdfs:label "Missing schema:name" ;
spin:violationRoot &lt;#this&gt; ;
spin:violationPath schema:name ] .</pre>
<p>LinkedDataHub reuses <a href="http://spinrdf.org/spin.html#spin-constraints" target="_blank">SPIN constraints</a>. Classes inherit constraints from superclasses.</p>
<p><a href="https://www.w3.org/TR/shacl/" target="_blank">SHACL</a> constraint validation is supported as well: SHACL shapes declared in the ontology
(or in any ontology of its imports closure) are validated with <a href="https://jena.apache.org/documentation/shacl/" target="_blank">Jena SHACL</a>
<p>Classes inherit SPIN constraints from superclasses. SHACL shapes declared in the ontology (or in any ontology of its imports closure) are validated with <a href="https://jena.apache.org/documentation/shacl/" target="_blank">Jena SHACL</a>
alongside the SPIN constraints, and a violation is likewise rejected with <samp>422 Unprocessable Entity</samp>.</p>
<p>What is validated is the document as it will be written, not the request body alone. A <code>PUT</code> carries the whole document, a
<code>POST</code> is checked merged with the triples the document already holds, and a <code>PATCH</code> is checked on the graph its update
produces. A <code>PATCH</code> that adds a <code>schema:description</code> to a Product therefore passes without repeating its name, and one that
deletes the name is rejected. A <code>PATCH</code> cannot remove a resource's <code>rdf:type</code> either, since that would take the resource
out of its class's constraints. <a href="../../imports/">Imports</a> write through the same methods and are held to the same check.</p>
<p>A write is rejected whenever the resulting document violates a constraint, including a violation that was there before the write. A document
left invalid by a later change to the ontology accepts only a write that repairs every violation it has. The <samp>422</samp> body describes
the violating resources alone, not the whole document, so it names what has to be repaired.</p>
<p>Constraints are evaluated over the written document's own graph. A constraint cannot look into other documents, and writing another document
never re-validates this one.</p>
</div>
</div>
<div>
<h2 id="properties">Properties</h2>
<p>You can define new properties. Like classes, properties are declared in the dataspace's namespace ontology.
The expected values of a property on a class are described either with a SPIN <a href="#constraints">property constraint</a> (a mandatory-property check)
or with an OWL <a href="#restrictions">restriction</a>, both of which the creation and editing forms enforce.</p>
or with an OWL <a href="#restrictions">restriction</a>, both of which the creation and editing forms enforce. A property can also carry a
<a href="#views">view</a> that lists the resources it relates.</p>
<div>
<h3 id="restrictions">Restrictions</h3>
<p>An <code>owl:Restriction</code> pins the cardinality of a property. The Northwind model, for example, declares its properties with
Expand All @@ -196,6 +208,43 @@ WHERE {}</pre>
rdfs:isDefinedBy : .</pre>
<p class="exhibit-links">Source: <a href="https://github.com/AtomGraph/LinkedDataHub-Apps/blob/master/demo/northwind-traders/admin/model/ns.ttl" target="_blank">admin/model/ns.ttl</a></p>
</div>
<div>
<h3 id="views">Views</h3>
<p>A property can carry a <a href="../../data-model/resources/#views">view</a> that lists the resources it relates: <code>ldh:view</code> attaches
the view in the property's forward direction, <code>ldh:inverseView</code> in its inverse. The view is stored in no document. LinkedDataHub
derives it whenever it renders a resource, and appends it to that resource's row.</p>
<p>Which resources get the view is decided by the property's declaration, not by the data. An <code>ldh:view</code> renders on every resource
typed with the property's <code>rdfs:domain</code>, an <code>ldh:inverseView</code> on every resource typed with its <code>rdfs:range</code>,
and a domain or range inherited through <code>rdfs:subPropertyOf</code> counts as well. The resource does not have to carry the property, and a
property with no domain or range never renders its view. In the view's query, <code>$about</code> is bound to the resource the view is
rendered on.</p>
<p>The Northwind model lists a customer's orders on the customer, through the inverse of <code>schema:customer</code>, whose range is
<code>schema:Corporation</code>:</p>
<pre>schema:customer a owl:ObjectProperty ;
rdfs:domain schema:Order ;
rdfs:range schema:Corporation .

schema:customer ldh:inverseView :OrdersFromCustomer .

:OrdersFromCustomer a ldh:View ;
dct:title "Orders from this customer" ;
spin:query :SelectOrdersFromCustomer ;
ac:mode ac:TableMode ;
ldh:showWhenEmpty false .

:SelectOrdersFromCustomer a sp:Select ;
sp:text \"\"\"
SELECT DISTINCT ?order
WHERE { GRAPH ?graph { ?order schema:customer $about ; schema:orderDate ?orderDate } }
ORDER BY DESC(?orderDate)\"\"\" .</pre>
<p class="exhibit-links">Source: <a href="https://github.com/AtomGraph/LinkedDataHub-Apps/blob/master/demo/northwind-traders/admin/model/ns.ttl" target="_blank">admin/model/ns.ttl</a></p>
<p>Since the view renders on every customer, <code>ldh:showWhenEmpty false</code> hides it on a customer that has placed no orders. The view's
<code>ac:mode</code> fixes its layout mode, and the view renders a <span class="ac-btn in-primary ap-solid sz-md">Create</span> button for
<a href="../../data-model/resources/#inline-creation">inline creation</a> of the class at the property's other end.</p>
<p>The attachments are read from the namespace ontology and its imports closure, so a view declared in <samp>ns.ttl</samp> and one declared in
a <a href="../packages/#property-views">package ontology</a> behave the same. A derived view has no edit or drag controls: it is changed by
editing its declaration in the ontology.</p>
</div>
</div>
<div>
<h2 id="importing-vocabularies">Importing external vocabularies</h2>
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/administration/packages.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ ns:SelectNarrowerConcepts a sp:Select ;
sp:text \"\"\"SELECT DISTINCT ?narrower
WHERE { GRAPH ?graph { $about skos:narrower ?narrower } }
ORDER BY ?narrower\"\"\" .</pre>
<p>Views are rendered when displaying resources that have the specified property. Use <code>ldh:view</code> for forward relationships (the resource carries the property) or <code>ldh:inverseView</code> for inverse relationships (other resources point at this resource through the property).</p>
<p>A view attached with <code>ldh:view</code> is rendered on every resource typed with the property's <code>rdfs:domain</code>, one attached with <code>ldh:inverseView</code> on every resource typed with its <code>rdfs:range</code> — whether or not the resource has the property, so a property without a domain or range renders no view. <code>$about</code> in the query is the resource the view is rendered on. A package's views work like the ones a dataspace declares in its own ontology — see <a href="../ontologies/#views">Views</a> in the ontologies reference.</p>
</div>
<div>
<h2 id="package-stylesheet">Package stylesheet</h2>
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/data-model/resources.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ WHERE {
</div>
<div>
<h3 id="inline-creation">Inline creation</h3>
<p>A view attached to a property in the <a href="../../administration/packages/#property-views">dataspace's ontology</a> renders a <span class="ac-btn in-primary ap-solid sz-md">Create</span> button in its header, so a view can double as an entry point for the data it lists. The class to construct is inferred from the attaching property: its <code>rdfs:range</code> for a forward <code>ldh:view</code>, its <code>rdfs:domain</code> for an inverse <code>ldh:inverseView</code>. A property whose range is not a named class — an <code>owl:unionOf</code> list, say — gets no button, because there is nothing to construct.</p>
<p>A view <a href="../../administration/ontologies/#views">attached to a property</a> in the dataspace's ontology renders a <span class="ac-btn in-primary ap-solid sz-md">Create</span> button in its header, so a view can double as an entry point for the data it lists. The class to construct is inferred from the attaching property: its <code>rdfs:range</code> for a forward <code>ldh:view</code>, its <code>rdfs:domain</code> for an inverse <code>ldh:inverseView</code>. A property whose range is not a named class — an <code>owl:unionOf</code> list, say — gets no button, because there is nothing to construct.</p>
<p>Where the new instance is stored is not declared anywhere. LinkedDataHub works it out from the view's own results: a second query, derived from the view's <code>SELECT</code> pattern with <code>LIMIT</code>, <code>OFFSET</code> and <code>ORDER BY</code> removed, asks which containers hold the documents that describe the existing solutions. A view is a projection and its rows are its solutions, so creating a row means making a new solution appear in that projection — and the existing solutions already say where their descriptions live.</p>
<ul>
<li><em>Exactly one container</em> — the button renders and targets it.</li>
Expand Down
23 changes: 15 additions & 8 deletions docs/reference/http-api.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ ETag: "1e635b4-ntriples"
</tr>
<tr>
<td><code>422 Unprocessable Entity</code></td>
<td><a href="../administration/ontologies/#constraints">Constraint</a> violation</td>
<td><a href="../administration/ontologies/#constraints">Constraint</a> violation in the resulting document</td>
</tr>
<tr>
<th rowspan="5" scope="row"><code id="ld-put">PUT</code></th>
Expand All @@ -184,7 +184,7 @@ ETag: "1e635b4-ntriples"
</tr>
<tr>
<td><code>422 Unprocessable Entity</code></td>
<td><a href="../administration/ontologies/#constraints">Constraint</a> violation</td>
<td><a href="../administration/ontologies/#constraints">Constraint</a> violation in the resulting document</td>
</tr>
<tr>
<th rowspan="2" scope="row"><code id="ld-delete">DELETE</code></th>
Expand All @@ -198,11 +198,14 @@ ETag: "1e635b4-ntriples"
<td>Deleting root, owner, or secretary documents is not allowed</td>
</tr>
<tr>
<th scope="row"><code id="ld-patch">PATCH</code></th>
<td>Modifies a document using SPARQL Update</td>
<td><code>204 No Content</code></td>
<td><code>422 Unprocessable Entity</code></td>
<td>SPARQL update string violates syntax constraints</td>
<th rowspan="2" scope="row"><code id="ld-patch">PATCH</code></th>
<td rowspan="2">Modifies a document using SPARQL Update</td>
<td rowspan="2"><code>204 No Content</code></td>
<td rowspan="2"><code>422 Unprocessable Entity</code></td>
<td>SPARQL update is not a single graph-scoped <code>INSERT</code>/<code>DELETE</code> operation, or removes an <code>rdf:type</code></td>
</tr>
<tr>
<td><a href="../administration/ontologies/#constraints">Constraint</a> violation in the updated document</td>
</tr>
</tbody>
</table>
Expand Down Expand Up @@ -293,7 +296,11 @@ WHERE {}</pre>
<li>It is not possible to modify or delete the documents of the owner agent and the secretary agent (returns <code>405 Method Not Allowed</code>)</li>
<li>A document can only be created with a URL relative to an existing container (i.e. resolving <samp>..</samp> against the new document's URL must identify an existing container)</li>
</ul>
<p>The built-in constraints are similar to, but separate from the <a href="../administration/ontologies/#constraints">ontology constraints</a>.</p>
<p>The built-in constraints are similar to, but separate from the <a href="../administration/ontologies/#constraints">ontology constraints</a> —
<a href="https://spinrdf.org/spin.html#spin-constraints" target="_blank">SPIN constraints</a> and
<a href="https://www.w3.org/TR/shacl/" target="_blank">SHACL</a> shapes declared in the dataspace's ontology. Those are checked against the
document as the write leaves it, not the request body alone, so a <code>POST</code> or <code>PATCH</code> need not repeat the properties the
document already holds, and one that leaves the document invalid is answered <code>422 Unprocessable Entity</code>.</p>
</div>
</div>
<div>
Expand Down
4 changes: 4 additions & 0 deletions docs/reference/migrating-from-5x.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@
<p>A client that wrote unconditionally now reads the tag first, as the <a href="../command-line-interface/"><samp>ldh</samp> CLI</a> does for you. An entity tag
identifies a negotiated variant, so the read supplying the validator must send the same <code>Accept</code> as the write quoting it, or the write is refused
<code>412</code>. The <a href="../http-api/">HTTP API</a> page has the request shapes.</p>
<p>Every write is validated against the <a href="../administration/ontologies/#constraints">constraints</a> as the document it leaves, which
<code>PATCH</code> already was in 5.10. A <code>POST</code> used to be checked on its body alone and is now checked merged with the document, so
appending to a document that violates a constraint is refused <code>422</code> until the violation is repaired. A <code>PUT</code> whose body does
not type the document is checked as the <code>dh:Item</code> the server makes it, so it needs a <code>dct:title</code>.</p>
<p>The access control query fails closed on a typeless resource, so a request for a URL that does not exist is answered <code>403</code> rather than
<code>404</code> unless the agent is the owner.</p>
<p><code>ldh:container</code> leaves the ontology, the <code>ldh:ViewConstructor</code> form and the view query. A view's <dfn>Create</dfn> button determines the
Expand Down
Loading