Skip to content

Another take on non-RDF payloads (aka file upload) #199

@tpluscode

Description

@tpluscode

Describe the requirement

We've approached the problem of non-RDF media types a few times already. Unfortunately it seems that each time it was not focused enough. Either mixed with collections (#187) or lacking broader context (#186).

Looking back at both, I think they are on track, but need a little more refinement.

For this issues, I'd like to focus on expects only and not returned representations

Hydra-agnostic example

I would distinguish 3 kinds of requests coming to a Hydra API:

  1. RDF payloads - such that are currently described by expects and Class.

  2. Non RDF-payloads

    Directly uploading an image instead of RDF:

    POST /movie/123/poster-image HTTP/2
    Content-Type: image/png
    
    ...File bytes...
  3. multipart/form-data

    Submitting multiple images and RDF data:

    PUT /movie/123/image-gallery HTTP/2
    Content-Type: multipart/form-data; boundary=----hydra-content
    
    ----hydra-content
    Content-Disposition: form-data; filename="poster.png"
    Content-Type: image/png
    
    ...Poster image...
    ----hydra-content
    Content-Disposition: form-data; filename="cast.jpeg"
    Content-Type: image/jpeg
    
    ...Image of actors...
    ----hydra-content
    Content-Type: application/ld+json
    
    {
      "@type": "mov:Gallery",
      "description": {
        "@value": "Pictures for movie /movie/123"
      }
    }
    ----hydra-content

Hydra should allow describing operations which expect both kinds of file uploads.

Proposed solutions

It is important to keep support for the current expect semantics.

I propose that we extend the existing structure with a media-type description. Unfortunately it is not possible to have it both ways without revolutionising the structure, so the vocab will have to remove rdfs:range from expects and use schema:rangeIncludes instead.

{
  "@id": "hydra:expects",
-  "range": "hydra:Class",
+  "schema:rangeIncludes": [
+    "hydra:Class",
+    "hydra:RequestSpecification"
+  ]
},

Example of hydra:Class usage

{
  "@type": "Operation",
  "expects": {
    "@type": "RequestSpecification",
    "content": {
      "@type": "SupportedClassContent",
      "class": "mov:Movie"
    }
  }
}

This would be equivalent to "expects": "mov:Movie" and both should be supported at least for a while.

Example of non-RDF payload

{
  "@type": "Operation",
  "expects": {
    "@type": "RequestSpecification",
    "content": {
      "@type": "RawContent",
      "supportedContentType": [ "image/png", "image/jpeg" ]
    }
  }
}
  • supportedContentType could also use a more elaborate structure though I'm not conviced it's necessary.

Example of multipart

{
  "@type": "Operation",
  "expects": {
    "@type": "RequestSpecification",
    "content": {
      "@type": "MultipartContent",
      "allowedParts": [
        {
          "supportedContentType": [ "image/png", "image/jpeg" ],
          "maxCount": 2
        },
        {
          "@type": "SupportedClassContent",
          "class": "mov:Movie",
          "minCount": 1,
          "maxCount": 1
        }
      ]
    }
  }
}

Above interpreted as:

  • allowing 0-2 image parts
  • requiring one RDF-part with mov:Movie
  • allowedParts has same domain as content, extended with multipart-specific bit such as the min/max count

MultipartContent would have to become part of the core vocabulary.

Implications

The consequences of such design are far reaching:

  1. By introducing RequestSpecification we can directly describe HTTP requests (such as by using expectHeader)

  2. The content predicat can be an extension point we've talked about, allowing 3rd party vocab to describe bodies using SHACL. Something like

    ShaclContentSpecification subclassOf ContentSpecification

  3. It will even be possible to define operations which expect markdown, plain text or any other textual format

Alternative solutions

Here's how Open API does that for file uploads and multipart requests. For example

requestBody:
  content: 
    multipart/form-data: # Media type
      schema:            # Request payload
        type: object
        properties:      # Request parts
          id:            # Part 1 (string value)
            type: string
            format: uuid
          address:       # Part2 (object)
            type: object
            properties:
              street:
                type: string
              city:
                type: string
          profileImage:  # Part 3 (an image)
            type: string
            format: binary

Note that id , address and profileImage will be separate request parts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions