Skip to content

Instantly share code, notes, and snippets.

@samuelgoto
Last active October 16, 2017 17:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samuelgoto/12044bf39245c6f3206a1725b940c2fb to your computer and use it in GitHub Desktop.
Save samuelgoto/12044bf39245c6f3206a1725b940c2fb to your computer and use it in GitHub Desktop.
A hypermedia API file type

Microforms

microforms is a file type (application/microforms) designed to expose REST endpoints.

The syntax, inspired by kotlin, is designed to intermingle data (text) with control (hypertext) more ergonomically (compared to HTML or JSON).

Like HTML, it defines a set of affordances (e.g. links, forms, imports, etc, designed to be isomorphic to HTML) and is typically used with a client side library that follows instructions sent by the server.

{
  // Ah, interesting, kindaof looks like JSON ...
  foo: "foo",
  hello: "world",
  
  // This is where things get interesting: introducing, form {}.
  action: form {
    method: "POST",
    input { name: "content", required: true }
  }
}

Example

Microforms are designed to describe REST APIs. Here is a longer example:

{
  type: "Blog",
  blogPost: [{
    type: "BlogPosting",
    id: 1,
    title: "hello world",
    get: a { href: "/posts/1"}
  }, {
    type: "BlogPosting",
    id: 2,
    title: "foo bar",
    get: a { href: "/posts/2"}
  }],
  create: form {
    method: "POST",
    action: "/create",
    input { name: "title", type: "text", required: true }
    input { name: "content", type: "text", required: true }
    input { name: "author", type: "text", required: true }
  }
}

And here is how a client (in this specific example, a javascript client) would interact:

// No out-of-band information needed to construct a request to create a post.
// Everything is embedded into the message.
// The client knowns how to follow links (a) and submit forms (form).
let post = await fetch("/blog.micro").then(posts => posts.create({
  title: "hello",
  content: "world",
  author: "goto@google.com"
}));

Which returns:

{
  type: "BlogPosting",
  id: 4,
  title: "hello",
  content: "world",
  author: "goto@google.com",
  delete: form {
    action: "/posts/4"
    method: "DELETE",
  },
  update: form {
    method: "POST",
    input { name: "content", type: "text", required: true }
  }
}

With the magic of HATEOAS, submitting a microform gets you another microform, with new exciting data and affordances!

// For example, say you disliked it, you can update it ...
post.update({
  content: "Actually, nevermind."
});

// ... or delete it ...
post.delete();
// ...

Shared Semantics

Like HTML, microforms isn't opinionated about JSON conventions nor schemas / ontologies. As a layered data format, it is designed to enable innovation to happen in that space without recompilation of the parsers / binaries. Here is an example of what it could look like if you are into JSON-LD and schema.org.

{
  @context: "http://schema.org",
  @type: "Blog",
  @id: "/",
  title: "Sam's blog",
  blogPost: [{
    @type: "BlogPosting",
    title: "Welcome",
    content: "This is my first post!"
  }],
  potentialAction: form {
    @type: "CreateAction",
    input { name: "title", required: true }
    input { name: "content", required: true }
  }
}

Affordances

It comes with some common control structures, borrowed from HTML for ease of use. For example:

  • a
  • link
  • form
  • input
  • select
  • option
  • meta
  • fieldset

As well as some additional ones to enable contemporary APIs to be written:

  • group
  • data loading
  • validation

A lot of control structures are also available outside of the context of the file format, which gives clients of microforms more instructions on how to proceed. These are:

  • Accepts-Vocab: for vocabulary negotiation
  • Bearer tokens: for key management
  • Throttling: for quota management

Extension mechanism

We are still exploring some options, but it is clear that we need to support some sort of extension mechanism. Something along the lines of:

{
  namespace { id: "oauth", url: "http://oauth.org" }
  type: "Error",
  messsage: "Oops, you have to be signed-in to access this resource",
  retry: [oauth:signin] {
    [oauth:authorization]: "http://example.com",
    [oauth:scopes]: {
      "write.calendar": "modify your calendar events",
      "write.calendar": "access your calendar events",
    }
  }
}

Grammar

TODO(goto): write this down. Looks something like JSON + {} nodes + comments.

Related Work

  • hydra
  • atom
  • swagger
  • alps
  • siren
  • swagger
  • json-home
  • rsdl
  • wadl
  • blueprint
  • restdesc
  • wsdl
  • hal

Acknowledgements

  • TODO(goto): fill this up
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment