HydraCG / Specifications

Specifications created by the Hydra W3C Community Group
Other
138 stars 26 forks source link

Relative Template URIs #208

Closed tpluscode closed 3 years ago

tpluscode commented 4 years ago

Description

I would also clarify the intended behavior around relative IRI Templates.

Especially in the context of #207 it might be a neat trick to be able to reuse same template for multiple links.

Consider two collection resources:

GET /people/

<http://example.com/people/>
  a hydra:Collection ;
  api:newPerson api:SubResourceTemplate .
GET /events/

<http://example.com/events/>
  a hydra:Collection ;
  api:newEvent api:SubResourceTemplate .

And an API Documentation

api:SubResourceTemplate
  a hydra:IriTemplate ;
  hydra:template "{name}" ;
  hydra:mapping [
    a hydra: IriTemplateMapping ;
    hydra:variable "name" ;
    hydra:property schema:name ;
    hydra:required true
  ] .

What's missing

Notice that the template is reused for two completely unrelated properties. To make this work the client would construct the template and apply it to a base URI. I expect that URI to be the parent's identifier.

In the example above that would effectively mean http://example.com/people/{name} and http://example.com/events/{name} respectively

Does this make sense?

Caveat

The above will not work as expected if the collection is identified like http://example.com/people (no trailing slash). Will result in

-http://example.com/people/foo
+http://example.com/foo

which might be unexpected...

alien-mcl commented 4 years ago

Ok, I gave it some more thoughs. In general, everything looks promising, but as usual, I've tried to crash-test the idea and here are some of my findings.

Consider example:

GET /api/people?search=term

</api/people> a hydra:Collection ;
  api:newPerson api:SubResourceTemplate .

What is a parent here?

Another example, a bit more ephemeral:

GET /api/people/john/knows

some:resource some:relation </api/people/john/knows> .

In this case, parent resource does not exist as a subject of the relation (i.e. RDF store does not contain any members of this collection), still it is in a relation with some resource and server decided to push that statement.

tpluscode commented 4 years ago

What is a parent here?

In both It has to be subject of the relation. Otherwise I see your concern about ambiguity. "Parent" was poor wording here

tpluscode commented 4 years ago

To illustrate my point, consider an entrypoint resource which offers shortcut links to collections I mention in the issue description

GET /home

</home> hydra:collection </people/>, </events/> .

</people/>
  a hydra:Collection ;
  api:newPerson api:SubResourceTemplate .

</events/>
  a hydra:Collection ;
  api:newEvent api:SubResourceTemplate .

The two collection resources "arrived" with the representation of /home and their templates will be resolved against their respective identifiers. The subjects become the base URI

alien-mcl commented 4 years ago

Consider these:

GET /home

</home> hydra:collection </people>.
</people>
  a hydra:Collection;
  api:newPerson [hydra:template "./new/{slug}"].
GET /home

</home> hydra:collection </people>.
</people>
  a hydra:Collection;
  api:newPerson <./new/somebody>.

The former would get resolved to /people/new/somebody (with {slug} replaced with somebody), the latter would get resolved to /home/somebody.

I'd expect that resolving an Iri template should lead to same result as "flattened" link.

tpluscode commented 4 years ago

Please, do fix the formatting

alien-mcl commented 4 years ago

I gave it lot of thoughts. Here is how Heracles.ts and Heracles.net are doing it:

I think it is more likely to be an expected behavior. I also find it easier to implement as it does not require any RDF traversing as the subject may be a blank node (in which case you would need to climb through the graph to find a first named node which still may lead to rare circumstances when it is not found).

Still, let's consider some alternatives. Let's try to figure it out on how the expansion algorithm could look like as having it bound-able to i.e. collections is tempting:

In case of an relative IRI template, client SHOULD consider these steps to obtain a _base_ Iri for expansion:
- Iri of the first subject being a named node that is NOT of type hydra:IriTemplate
- _base_ provided within the document carrying an Iri template
- Url of the document used to obtain a payload carrying an Iri template

We could also consider some additional hints to the client on which algorithm to use (leaving current behavior as default):

Please feel free to deliberate more. I just don't want to use any unnecessary quick moves to claim low hanging fruits while breaking something we can't see now.

tpluscode commented 4 years ago

Please feel free to deliberate more. I just don't want to use any unnecessary quick moves to claim low hanging fruits while breaking something we can't see now.

Okay, let's take step back. Please refer to the section 5.1 of the URI RFC3986. It defines four sources of a Base URI:

5.1.1. Base URI Embedded in Content 5.1.2. Base URI from the Encapsulating Entity 5.1.3. URI used to retrieve the entity
5.1.4. Default Base URI (application-dependent)

I propose to follow this as it is an established standard that we can refer to for guidance and disambiguation. It appears to also be a fair match for your proposal.

5.1.1 is out of scope, because Hydra client is already operating on the parsed RDF graph so any media-type annotations (@base in turtle or JSON-LD) are out of reach (unless... read to the end).

Here's my take

Given a triple :Resource :Link :IriTemplate, when :Link is a hydra:TemplatedLink and its hydra:template is a relative reference, then the client MUST establish a Base URI according to the section 5.1 of the RFC 3986:

1. if the :Resource is a named resource, its identifier serves as the Base URI
2. if not then the URI of the requested resource is the Base URI according to section 5.1.3 of the aforementioned RFC 3986
3. if the requested resource URI is not known, then an application specific, default Base URI is used as defined in section 5.1.4 of RFC 3986 

With the Base URI established, the client SHOULD resolve the URI using the method described in section 5.2 of RFC before sending a server request.

Two things to notice:

  1. I am back to only considering the direct link relation between :Resource and :IriTemplate above. If :Resource is a blank node then the next step should be taken
  2. In the last step I used SHOULD because for example in the case of browser's fetch the browser will automatically apply the default Base URI when a request is made using a relative reference. Otherwise the client library such as Alcaeus or Heracles.X would need a Base URI setting.

We could also consider some additional hints to the client on which algorithm to use (leaving current behavior as default)

I would not. Please let's stick to the saying that "Everything should be made as simple as possible, but not simpler"

alien-mcl commented 4 years ago

5.1.1. Base URI Embedded in Content

I agree. At first I thought about custom predicate for denoting a local base but the RFC mentions media types and their syntax.

5.1.2. Base URI from the Encapsulating Entity

This seems it doesn't fit to the intermediate parent (as you named it in your commit to your client). The RFC mentions a situation when a document is enclosed within another being, (i.e. attachment to the email, IFrame within the HTML page). I think this is not the case of a resource being in relation with another one within the same document.

5.1.3. URI used to retrieve the entity 5.1.4. Default Base URI (application-dependent)

These are already in use. No questions about it.

It clearly looks like whatever we may want to add to make it happen will be outside of the mentioned RFC which may result in unexpected behavior.

I feel like we may need something extra added to the vocabulary/spec that servers will need to use explicitly in order to amend client's behavior in that matter.

BTW. It is somehow unfortunate to have implementation of the feature in discussion without resolving it. Whatever reason you may want to use it will look like you want to support what you've already implemented.

tpluscode commented 4 years ago

This seems it doesn't fit to the intermediate parent

Yes, I know you're right and I admit taking a little liberty in interpreting the nature of this "embedding". Unfortunately, the HTTP spec is rather closely related to HTML hence the spec is not as universal as it could. I do find the notion of graph subject/object relation a close enough approximation. Especially easy to accept when you look at the graph as a JSON-LD tree

It is somehow unfortunate to have implementation of the feature in discussion without resolving it

Chicken and egg? How can you standardise anything without implementing first but at the same time hold off any implementation before you standardise? 🤔

Treat it as a proof of concept. And I have only implemented the "Encapsulating Entity" way for now

<collection/> hydra:search [
  hydra:template "{?name,tag}
] .

Which results in <collection/?name=john&tag=friend>

I feel like we may need something extra added to the vocabulary/spec that servers will need to use explicitly in order to amend client's behavior in that matter.

Very on point, only that I'm a little tired of only trying to following "feelings". Only by actually implementing something, even if not yet agreed as "standard", will we be able to decide whet works and what doesn't.

And granted, this is not the first and not the last code in Alcaeus which fills in gaps in the specification to get a useful result of Hydra

tpluscode commented 4 years ago

And I have only implemented the "Encapsulating Entity" way for now

Oh, lol, I actually forgot that I've added a client-wide baseUri setting on the 0.10 version.

alien-mcl commented 4 years ago

I do find the notion of graph subject/object relation a close enough approximation. Especially easy to >accept when you look at the graph as a JSON-LD tree

I disagree. It's like treating nested HTML tags as separate documents. Spec is clear and says about documents. While you could imagine a situation when another document is embedded within RDF (i.e. as a raw base64 string inside of some value), but part of the document's graph should not be treated as separate documents.

Chicken and egg? How can you standardise anything without implementing first but at the same >time hold off any implementation before you standardise?

Don't get me wrong - I have no issues with prototyping. It just looks like the commit is on a release candidate branch and I just don't want us to take any design decisions under pressure. Whole process should be clean and transparent from W3C point of view.

And granted, this is not the first and not the last code in Alcaeus which fills in gaps in the >specification to get a useful result of Hydra

I bet! I hope to lower number of those gaps.

Back to the topic - is there any agreement to adding new predicates for IriTemplate that would mark it for separate template expansion algorithm, i.e. hydra:expansionAlgorithm accepting values from which the default would point to the RFC mentioned?

tpluscode commented 4 years ago

No pressure. Maybe it's a good thing that Alceaus is not the reference client. Gives some freedom to do what I find useful for the consumer :). It is a release candidate because I'm finalising the internal architecture. If the spec breaks something for me I will just go to v2, v3 etc.

It's like treating nested HTML tags as separate documents. Spec is clear and says about documents.

Shoot, you're right, I'm wrong.

Which does not change the fact that RDF is not HTML and the hydra:template is just a string. It has not semantic meaning for RDF itself. It has only meaning for a Hydra client and it is up to us to define those semantics.

Back to the topic - is there any agreement to adding new predicates for IriTemplate that would mark it for separate template expansion algorithm

Well, there really is not an "expansion algorithm". There is but the choice of Base URI to resolve a relative URI (the algorithm explained in section 5.2 of RFC3986).

There only two good options IMO:

Thus, to address your proposal, I would add hydra:resolveRelativeTo, which would optionally point to named individuals to indicate the Base URI to use:

<> 
  a hydra:ApiDocumentation ;
  hydra:apiBaseUri "http://example.com/" .

_:relativeToContext a hydra:IriTemplate ;
  hydra:resolveRelativeTo hydra:LinkContext .

_:relativeToBase a hydra:IriTemplate ;
  hydra:resolveRelativeTo hydra:ApiBaseUri .

The question is what we'd want to treat as default for hydra:resolveRelativeTo and what if a template wants ApiBaseUri but it's not set to the ApiDocumentation

alien-mcl commented 4 years ago

You meant "Alceaus is not the reference client". Please, make a correction immediately and be fair with your doings as it feels less unfortunate and more intended.

alien-mcl commented 4 years ago

Well, there really is not an "expansion algorithm".

Bad wording, but we all know what it is all about.

the link context

It sounds promising - has roots in one RFC and doesn't sound like it's against another RFC

a shared base, communicated by the ApiDocumentation

I wouldn't go in that direction. I think it is against the RFC3986 and it mimics whatever comes in that matter with media types (i.e. built in base mechanism in RDF serializations).

tpluscode commented 4 years ago

I wouldn't go in that direction. I think it is against the RFC3986 and it mimics whatever comes in that matter with media types

We've been over this. hydra:template is a string and has nothing to do with the RDF serialisation.

And how is it against RFC3986?

alien-mcl commented 4 years ago

RFC 3986 section 5.1.1 says:

The appropriate syntax, when available, is described by the data format specification associated with each media type.

It clearly mentions media type syntax. I believe custom base predicates used within RDF are not syntax related. Terms like @base in turtle and other RDF serialization equivalents fits here.

tpluscode commented 4 years ago

No it does not fit

@base <http://example.com/>

[
  hydra:template "/{foo}/{bar}"
]
@base <http://tempuri.org/>

[
  hydra:template "/{foo}/{bar}"
]

The above graphs are isomorphic. The template is a literal and no base declaration of any RDF syntax will have any effect on it.

alien-mcl commented 4 years ago

But after template expansion you will get a relative IRI that needs to be resolved to an absolute one. That IRI/URI somehow becomes part of the graph as IRI template is an algorithm of how a client can create IRI's of reasources that normally would be provided within the graph. Having that in mind that very IRI/URI should fit all the procedures applicable to an ordinary Uri.

alien-mcl commented 4 years ago

Regardless our reasons, mentioned explicit mechanism sounds like a consensus (like the example you provided, [] hydra:resolveRelativeTo hydra:LinkContext). Am I correct?

tpluscode commented 4 years ago

But after template expansion you will get a relative IRI that needs to be resolved to an absolute one.

Yes, and I propose two base Uris to choose from: link context or one provided by the API

That IRI/URI somehow becomes part of the graph IRI template is an algorithm of how a client can create IRI's of reasources that normally would be provided within the graph.

I do not see how it becomes "part of a graph". You have no reason to ever add it as a triple. You only need to construct the absolute URL and use it to perform a HTTP request.

Having that in mind that very IRI/URI should fit all the procedures applicable to an ordinary Uri.

I have no clue what "procedures" you refer to :)

mentioned explicit mechanism sounds like a consensus

hydra:LinkContext would be the option I am looking for right now. What about any alternatives? A property of the ApiDocumentation? And the default behaviour in case of no value specified?

alien-mcl commented 4 years ago

You have no reason to ever add it as a triple.

I disagree. This mere URL is something you can work with depending on the protocol and methods in force (for HTTP you can GET it, PUT it, whatever the operation/link says). It is/will be part of the graph. Without IRI templates you would need to provide all those reasources somehow. With IRI template you can learn a client on how to do it all by itself. IRI template is a simple tool here and you're still dealing with RDF graph.

I have no clue what "procedures" you refer to :)

Then this whole conversation is useless. I meant all those RFC's mentioned.

Yes, and I propose two base Uris to choose from: link context or one provided by the API

I'm somehow against the one provided by the API - we should stick to RFCs and document URLs'

tpluscode commented 4 years ago

I'm sorry but you are just rambling now. Of course the template is part of the graph. We've had that for ages. But the resolved URL (the actual link target) is not. It is purely runtime artifact. Why? Because same template can be used in multiple contexts (subjects) and the resulting URLs might be different.

I'm somehow against the one provided by the API - we should stick to RFCs and document URLs'

Right, so a second, default (?) option would be to resolve against the effective URI of the request, correct?

I have in mind a scenario in which an API is hosted under a virtual dir, say, /tenant/api/. All relative templates would be resolved against that. Even if the base changes, the API will not have to change all the templates.

</tenant/api> a hydra:ApiDocumentation; hydra:baseUri </tenant/api/> .

</person> some:link [
  a hydra:IriTemplate ;
  hydra:template "person{?name}" # for example </tenant/api/person?name=foo>
] .

This would in fact be similar to @base at the serialisation level.

alien-mcl commented 4 years ago

I'm sorry but you are just rambling now

For me the logic behind the Iri template is as follows (using a collection example):

"Dear client, here is a collection and all (some) of it's members. You can GET and PUT them. Buthey, if you want, here is also a formula on how you can point directly to each member without searching it through my members. You can use this formula to operate on those members, but don't forget, these are still my members"

The iri template does not change the fact that the each member is in relation with that collection. Resource created at runtime using the formulat provided is not an exception here and it is correct to state (still following the example) that the example collection hydra:member some:iriTemplateCraftedIri is true.

Right, so a second, default (?) option would be to resolve against the effective URI of the request, correct?

Yes as it fits the RFC3986. What if there are multiple API documentations available? It is not forbidden and the server can provide multipe API documentation links (i.e. parts, incremental upgrades, whatever reason was behind such a behavior).

asbjornu commented 4 years ago

I view the URI template as a black box. How it looks like, and how it's constructed is opaque to a consumer, besides which parameters it accepts. Once the required parameters are assembled, they are passed to the black box and out comes a fully constructed (possibly relative) URI.

Since the purpose of the constructed URI is to perform an HTTP request and not to reason about the relationship between two resources, I agree with @tpluscode that the "black box" (opaque string) URI template belongs to the graph, while the constructed URI does not.

If we view the URI template as a function, the function itself is a part of the graph. But the function's return value is a runtime concern that in theory can change on every invocation of the function and thus isn't something that can exist as a part of the graph or API description.

If the constructed URI is relative, it's up to the Hydra client to resolve it according to processing rules that are outside of both scope and control of the serialization format used to communicate the template.

alien-mcl commented 4 years ago

But the function's return value is a runtime concern that in theory can change on every invocation >of the function

No questions about that

and thus isn't something that can exist as a part of the graph or API description.

Why? Why a function can be a part of the graph but runtime results of that very function shouldn't be treated as like? I find a reasoning process similar to that situation. There are statements produced in the process and in order to correctly interprete all the data within the graph the produced statements should be taken into account, even when these additional statements are not explicitely declared (but are products of an input data and algorithm). Am I wrong?

If the constructed URI is relative, it's up to the Hydra client to resolve it according to processing rules that are outside of both scope and control of the serialization format used to communicate the template.

Not sure I understand this correctly. Indeed, RDF serialization has no IRI template matters within it's scope. But hydra clearly states that hydra:template is rooted in RFC 6570 (if not stated otherwise) and that template should be handled according to this RFC's rules. Product of is an URI that should also be handled according to RFCs related to Uris

alien-mcl commented 4 years ago

Anyway, I still find this part of the discussion leading nowhere. If we agreed to come with an explicit term telling a client that it should resolve a relative IRI template different way that RFC 3986 instructs, it will not matter whether it's product is part of the graph or not.

alien-mcl commented 3 years ago

PR #213 should resolve this issue - closing