dunglas / vulcain

🔨 Fast and idiomatic client-driven REST APIs.
https://vulcain.rocks
GNU Affero General Public License v3.0
3.51k stars 106 forks source link

Support setting accepted media-type on Preload & Fields pushes #46

Open andrerom opened 4 years ago

andrerom commented 4 years ago

I choose to view Vulcain as a possible REST maturity models level 4. So I'm wondering if it should be possible to specify accepted media-type on pushes / preloads via Preload and Fields in order to fit well with already mature REST API's that uses media type for content negotiation.

In all transparency we use that in eZ Platform, and it's a rather powerful concept as it allows integrators to extend certain api endpoints. But unlike graphQL where caller can ask for whatever, in this case someone would need to code alternative representations.

While it would have to be in a format that can work both in the header and query format, something like the following might be a way to do it:

GET /books/ HTTP/2
Preload: /member:application%2Fvnd.acme.externalmember+json/*/author
Fields: /author/familyName

Open questions:

dunglas commented 4 years ago

Hi @andrerom, Interesting topic!

First, let me explain the current rationale regarding content negotiation:

It means that content negotiation is possible, and actually works, but with one assumed constraint: the Accept header will be the same for all requests (the explicit and the implicit). However, the Content-Type of every response can differ (even if it's not usual I guess).

In most cases, using the priority mechanism of the Accept header should be good enough. Ex: I want XML for API responses, and PNG for related images: Accept: text/xml;q=0.9, image/png;q=0.8, */*;q=0.7.

So my first question is: isn't the current capabilities good enough? They are just what HTTP allows.

So about your proposal:

Separator to use to show start of media type, here shows as :, however that is legal in URI's so maybe it will have to be something else, did not find any MIME's with this tough

: is definitely not a possible option because it's the characters used in XML, JSON-LD and all related W3C formats to separate the namespace and the property in compact IRIs. For instance, {"hydra:member": [...]} is used everywhere in API Platform for instance, Schema.org's key are usually schema:prop (ex: schema:Person) regardless of the format etc.

What we can do however, is to add a new escape character in the extended JSON Pointer format as we've done for *: https://github.com/dunglas/vulcain/blob/master/spec/vulcain.md#extended-json-pointer (/ and ~ are already a special char in the original JSON Pointer spec). We could say for instance that - the special character we choose - must be escaped by ~2).

Finally, is there any example in the RFC corpus of marking a key with a MIME type like you propose? If yes, we should use the same character than the existing one (RFC tries to be consistent altogether). If no, maybe that we shouldn't add this in the main spec (The Vulcain spec is designed to be as minimal as possible), but could be provided as an extension (using the escaping trick I suggest just above)?

andrerom commented 4 years ago

Finally, is there any example in the RFC corpus of marking a key with a MIME type like you propose? If yes, we should use the same character than the existing one (RFC tries to be consistent altogether).

I have looked for it, I tried to look for examples on JSON API / Open REST / RFCs, but didn't quite find what I was looking for, so I think no.

As for Accept header, for some cases that could work. But it's a global header, and Preload and Fields are essentially loading other documents then the main request, which in our case might have different or conflicting* media types

* conflicting as in several of them might be same entity type, root and leaf might be same, but you might want different representation of them due to the data you need. I know this sounds very abstract, so if you want I can try to write a more concrete example on the use case, but TL;DR; both will be "Content" in our case.

dunglas commented 4 years ago

What do you think about an extension (that can be stored in this repo but that will not be proposed to IETF)?

andrerom commented 4 years ago

maybe

But I did kind of find a spec that allows this now, however it's not embedded in the URI itself. See Link header spec for type usage:

 Link           = "Link" ":" #link-value
  link-value     = "<" URI-Reference ">" *( ";" link-param )
  link-param     = ( ( "rel" "=" relation-types )
                 | ( "anchor" "=" <"> URI-Reference <"> )
                 | ( "rev" "=" relation-types )
                 | ( "hreflang" "=" Language-Tag )
                 | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) )
                 | ( "title" "=" quoted-string )
                 | ( "title*" "=" ext-value )
                 | ( "type" "=" ( media-type | quoted-mt ) )

Did not see concrete example in the spec, but seems like it should be something like this (however maybe with more relevant rel usage):

Link: </TheBook/chapter2>;
         rel="previous"; type="application/vnd.myBook.v3.abstract+json"; title*=UTF-8'de'letztes%20Kapitel,
         </TheBook/chapter4>;
         rel="next"; type="application/vnd.myBook.v3.firstPage+json"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel

This is also used for Server Push:

Link: </assets/font.woff2>; rel=preload; as=font; type='font/woff2'

So, would it be feasible to align with subset of Link spec here?

Example:

Preload: </member/*>; type="application/vnd.myBook.v3.abstract+json",
         </member/*/author>; type="application/vnd.myBook.v3.extendedAuthorBio+json"

Side: As shown in last example, media types can also be used for versioning the API gracefully over time on a per resource basis.

andrerom commented 4 years ago

This was not really solved in #54 as that is more about doing a media type selector feature, and not so much about content / media type negotiation aspect.

But that said, I guess we can rather solve our need by making sure to expose additional links in our responses which can be used by Preload or expanded using Fields.

So this can stay closed for now.

dunglas commented 4 years ago

Let’s reopen (it has been automatically closed by GitHub) until we describe a solution for all use cases.