Open lambdakris opened 2 years ago
Indeed, clownface doe not have a feature to get edges in or out of a node (but watch https://github.com/zazuko/clownface/pull/62)
Alcaeus also doe not have a built-in feature for this I'm afraid. You could access the underlying data as raw triples but there may be 🐉
For the time being, a little bit more hackish way could be to turn your resource into a JS object by calling response.representation.root.toJSON()
and iterate the property names natively
Ah ok, so here is a broader question then. I'm trying to figure out how to build what one might call an ontology driven app. What I'm thinking is that I would retrieve an api resource, it's linked api documentation, and it's referenced ontologies, and sort of iterate through the metadata to basically build up the UI. Is that crazy talk or does that sound like a legitimate semantic technology scenario and would you have anything you could point me to for reference?
Ah, now we're talking! I have been trying to answer similar questions myself and so far I have concluded that Hydra maybe not the right medium for data models. I would like to see the hydra:supportedClass
and hydra:supportedProperty
predicates only used for actual hypermedia.
There are existing vocabularies for building ontologies and data models: OWL, SHACL, ShEx. I would suggest you build a UI based on those instead. This is what I have been doing lately, namely with SHACL.
This gives you additional options. First, any given resource can be represented in multiple models schemes. I typically have a top-level hydra:collection
which lets the app discover the shapes of a resource fetched from the API
# Entrypoint resource
</> hydra:collection </api/shapes> .
# Shapes collection
</api/shapes>
hydra:memberAssertion
[
hydra:property rdf:type ;
hydra:object sh:NodeShape ;
] ;
] ;
hydra:search
[
hydra:template "{?resource}" ;
hydra:mapping
[
hydra:variable "resource" ;
hydra:property sh:targetNode ;
] ;
] ;
.
So, when the app want to display a UI for resource /foo/bar/baz
, it would find the collection and then fetch /api/shapes?resource=/foo/bar/baz
. Discovery is done with Alcaeus
import type { Resource } from 'alcaeus'
import { rdf, sh } from '@tpluscode/rdf-ns-builders/strict'
let entrypoint: Resource
const [shapesCollection] = entrypoint.getCollections({
predicate: rdf.type,
object: sh.NodeShape,
}) || []
cons shapes = await shapesCollection.load()
Of course, you could could adapt this approach to specific needs:
It's not perfect yet. In fact, inspired by your question I figured a way to slightly improve what I have been doing.
Here's the code which does the discovery of shapes in my latest development: https://github.com/wikibus/wikibus/blob/master/apps/www/src/lib/shapeLoader/dereferenced.ts
And here's the actual collection resource: https://github.com/wikibus/wikibus/blob/master/apps/api/resources/api/shapes.ttl
Notice that the filterByTargetNode
function not only filters by sh:targetNode
but will also match on the resource's types and sh:targetClass/dash:applicableToClass
. The filter is not relying on the hydra:property sh:targetNode
annotation verbatim
Very interesting! Still being fairly new to this, I got a couple of follow up questions. In your first example, where/when does the "resource" variable replacement occur for the template? In your second example, is that a sort of remote code execution? More generally, would something like the shapes collection be necessary if using OWL or would one be able to simply dereference the types of the resource to get its different representations?
In your first example, where/when does the "resource" variable replacement occur for the template?
This is still a bit in flux. I just pushed my latest update which simplifies that process. This uses the IRITemplate
feature of alcaeus. In this line I set the sh:targetNode
property to an in-memory representation of the template params. It gets fed into the template according to the hydra:mapping
.
In your second example, is that a sort of remote code execution?
Depending how you want to view that. In essence, I simple dereference a resource. Yes, it does execute code remotely, although that fact is irrelevant to a REST client. The important part is that the application does not have too much hard-coded. By using the hydra:collection
, a client only knows that it should look for
such a collection whose members are shapes ([ hydra:property rdf:type ; hydra:object sh:NodeShape ]
)
As I mentioned above, SHACL is one option. OWL could totally be another.
And yes, to dereference the resource types could totally work too. The reason I prefer shapes though, is that external vocabulary terms are often not dereferencabe or at least unstable. They are also outside of your control, which may or may not be a good thing. On the other hand, shapes are purpose-built. And I like them that way. For example, you might have different views, of varying level of detail. Each described with its own shape. Or shapes to display a collection of items in multiple ways: table, grid, map? This goes way beyond simply defining domain models. Domain models, which have a different purpose.
Ok, but I realise I digress now. Coming back to your original question
would one be able to simply dereference the types of the resource to get its different representations?
I think we're looking at a very similar thing. However, given a resource such as <> a schema:Person
you cannot dereference at all. You might introduce a new class and make it <> a schema:Person, </api/Person>
. The latter will now dereference. But how do you define client behaviour?
I find it easier to codify and replicate that a client always does the same thing:
hydra:collection
with member assertion of rdf:type+sh:NodeShape
(or OWL, or whatever)GET
request.This removes the necessity for exposing the shapes/model up-front and does not burden the client with unnecessary decision making about what and when dereference (or worst of all, try and fail)
Hey @tpluscode , is there a way to iterate over the properties on a resource if they are not listed as supported in the api doc? I did try resorting to using clownface, but I could only manage to get the property values and not the property names.
This is essentially what I tried with clownface
And just in case...
Here is the resource I'm trying to consume
Here is the documentation