Skip to content

Commit d133a06

Browse files
committed
describing operations and templates with SHACL
1 parent 62137c7 commit d133a06

File tree

4 files changed

+183
-10
lines changed

4 files changed

+183
-10
lines changed

api-documentation/shacl.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Using SHACL to describe data structures
2+
3+
## Story
4+
5+
As an API publisher
6+
7+
I want to use SHACL to describe data structures
8+
9+
So that consumers can use existing tooling to consume the API
10+
11+
## Details
12+
13+
SHACL, which stands for Shapes Constraint Language is an RDF vocabulary, defined in a [W3C recommendation][shacl], which is used to describe graph shapes.
14+
15+
It is very expressive, more rich than `hydra:Class` et.al., and out of the box provides multiple features to provide an accurate description of request payloads.
16+
17+
[Many uses for SHACL have been proposed][ucr] such as, but not limited to, validation and building user interfaces dynamically.
18+
19+
This page shows how Shapes can be used to replace or extend Hydra Core vocabulary terms within the API Documentation and resource representations.
20+
21+
[shacl]: https://www.w3.org/TR/shacl/
22+
[ucr]: https://www.w3.org/TR/shacl-ucr/
23+
24+
### Announce SHACL in API Documentation
25+
26+
An API which chooses to use SHACL in addition to the built-in `hydra:Class` `MUST` announce that fact in its API Documentation resource
27+
28+
```json
29+
{
30+
"@context": {
31+
"hydra": "http://www.w3.org/ns/hydra/core#",
32+
"requires": { "@id": "hydra:requires", "@type": "@id" }
33+
},
34+
"@id": "/api",
35+
"@type": "hydra:ApiDocumentation",
36+
"requires": "http://www.w3.org/ns/hydra/shacl"
37+
}
38+
```
39+
40+
By adding the `requires` property with the `hydra/shacl` identifier the client will be prepared to find SHACL terms within the payloads coming from this API.
41+
42+
### Describing request bodies
43+
44+
The most obvious use for Shapes is to use them to annotate supported operations' payloads. They will simply replace instances of `hydra:Class` used as objects of `hydra:expects`.
45+
46+
```json
47+
{
48+
"@context": "/api/context.jsonld",
49+
"@id": "/api",
50+
"@type": "hydra:ApiDocumentation",
51+
"supportedClass": [{
52+
"@id": "mov:Movies",
53+
"supportedOperation": {
54+
"@type": "schema:CreateAction",
55+
"method": "POST",
56+
"expects": ["mov:Movie", "mov:NewMovieShape"]
57+
}
58+
}, {
59+
"@id": "mov:Movie",
60+
"@type": "Class",
61+
"supportedProperty": [{
62+
"property": "schema:name"
63+
}, {
64+
"property": "mov:director"
65+
}, {
66+
"property": "schema:genre"
67+
}]
68+
}]
69+
}
70+
```
71+
72+
By adding a second value to the `expects` annotation, the client will discover that the payload of this operation is expected to be not only a representation of a `mov:Movie` but also that the request body has to conform to the given SHACL Shape.
73+
74+
{% hint style="info" %}
75+
Client which do not understand SHACL will ignore it and proceed with only the `hydra:Class` description.
76+
{% endhint %}
77+
78+
For clarity the `mov:NewMovieShape` is presented separately as it would appear in the API Documentation resource or as its own individual resource.
79+
80+
```json
81+
{
82+
"@id": "mov:NewMovieShape",
83+
"@type": "sh:NodeShape",
84+
"sh:targetClass": "mov:Movie",
85+
"sh:property": [{
86+
"sh:path": "schema:name",
87+
"sh:or": [{
88+
"sh:datatype": "xsd:string"
89+
}, {
90+
"sh:datatype": "rdf:langString"
91+
}],
92+
"sh:name": "Title",
93+
"sh:description": "Movie's title",
94+
"sh:minCount": 1
95+
}, {
96+
"sh:path": "mov:director",
97+
"sh:class": "schema:Person",
98+
"sh:nodeKind": "sh:IRI",
99+
"sh:maxCount": 1
100+
}, {
101+
"sh:path": "schema:genre",
102+
"sh:in": [
103+
"Comedy",
104+
"Drama",
105+
"Documentary"
106+
]
107+
}]
108+
}
109+
```
110+
111+
While the Shape repeats some of the information typically found on the supported class `mov:Movie` it is clear that it can also provide much more information about the expected payload of the request to create a movie resource, which would be otherwise impossible with just Hydra Core terms:
112+
113+
1. About `schema:name` property:
114+
* At least one value is required
115+
* There can be multiple values
116+
* Values can be a plain string of string with language tag
117+
2. About `mov:director` property:
118+
* Must be a resource identifier of a `schema:Person` resource
119+
* There can be at most one value
120+
3. About `schema:genre` property:
121+
* Values should be one of those provided by the `sh:in` enumeration
122+
123+
{% hint style='info' %}
124+
The only gray area is the director property, where it is up to the client to figure out the available instances of `schema:Person`
125+
{% endhint %}
126+
127+
### Describing IRI templates
128+
129+
Another way for a client to create HTTP requests is by filling an IRI template with string values and dereferencing the resulting identifier. This works well for example for filtering a collection with query strings.
130+
131+
An instance of tht `mov:Movies` class above could provide such a search template to filter movies by genre.
132+
133+
```json
134+
{
135+
"@id": "movies",
136+
"@type": ["Collection", "mov:Movies"],
137+
"search": {
138+
"@type": "IriTemplate",
139+
"template": "movies{?genre}",
140+
"mapping": [{
141+
"@type": "IriTemplateMapping",
142+
"variable": "slug",
143+
"property": "schema:genre"
144+
}]
145+
}
146+
}
147+
```
148+
149+
While this is enough to match an existing resource which has the `schema:genre` property, it is too little information to gather user input, such as with a form on a web page.
150+
151+
To enrich the template definition, a SHACL shape can be added to further specify the template mappings.
152+
153+
```diff
154+
{
155+
"@id": "movies",
156+
"@type": ["Collection", "mov:Movies"],
157+
"search": {
158+
"@type": "IriTemplate",
159+
"template": "movies{?genre}",
160+
"mapping": [{
161+
"@type": "IriTemplateMapping",
162+
"variable": "slug",
163+
"property": "schema:genre"
164+
- }]
165+
+ }],
166+
+ "hashi:shape": {
167+
+ "@type": "sh:Shape",
168+
+ "rdfs:label": "Search movies collection",
169+
+ "sh:property": [{
170+
+ "sh:path": "schema:genre",
171+
+ "sh:name": "Genre",
172+
+ "sh:in": ["Comedy", "Drama", "Documentary"]
173+
+ }]
174+
+ }
175+
}
176+
}
177+
```
178+
179+
Just like in the shape to create a new movie, the values of `schema:genre` property would be described as an enumeration of string values to present in a select box.
180+
181+
<img alt="select example" src="/images/search-select.png" width="400px">

images/search-select.png

81.8 KB
Loading

summary.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
* API Documentation
2+
* [SHACL](api-documentation/shacl.md)
13
* Examples
24
* [Movies](movies/index.md)
35
* [1 API entry point](movies/entry-point.md)

yarn.lock

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -961,16 +961,6 @@ gitbook-cli@^2.3.2:
961961
tmp "0.0.31"
962962
user-home "2.0.0"
963963

964-
gitbook-plugin-forkmegithub@^2.2.0:
965-
version "2.2.0"
966-
resolved "https://registry.yarnpkg.com/gitbook-plugin-forkmegithub/-/gitbook-plugin-forkmegithub-2.2.0.tgz#b64b62a1402b099b1b48c396a9d5b0aba967c04a"
967-
integrity sha1-tktioUArCZsbSMOWqdWwq6lnwEo=
968-
969-
gitbook-plugin-hints@^1.0.2:
970-
version "1.0.2"
971-
resolved "https://registry.yarnpkg.com/gitbook-plugin-hints/-/gitbook-plugin-hints-1.0.2.tgz#e6589983e3ea84749f16dc631e282fafd45ec39b"
972-
integrity sha1-5liZg+PqhHSfFtxjHigvr9Rew5s=
973-
974964
github-url-from-git@~1.4.0:
975965
version "1.4.0"
976966
resolved "https://registry.yarnpkg.com/github-url-from-git/-/github-url-from-git-1.4.0.tgz#285e6b520819001bde128674704379e4ff03e0de"

0 commit comments

Comments
 (0)