Skip to content

Latest commit

 

History

History
166 lines (123 loc) · 4.2 KB

README.md

File metadata and controls

166 lines (123 loc) · 4.2 KB

Sinatra Schema

Gem version Build Status

Define a schema for your Sinatra application to get requests and responses validated. Dump it as a JSON Schema to aid client generation and more!

Usage

Register Sinatra::Schema to define resources, like:

class MyApi < Sinatra::Base
  register Sinatra::Schema

  resource("/account") do |res|
    res.property.text :email

    res.get do |link|
      link.action do
        # per definition above we need to serialize "email"
        MultiJson.encode(email: current_user.email)
      end
    end
  end
end

Params

Links can have properties too:

resource("/account") do |res|
  res.post do |link|
    link.property.ref  :email # reuse the property defined above
    link.property.text :role, optional: true
    link.property.bool :admin

    link.action do |data|
      user = User.new(email: data[:email])
      if data[:admin] # this is a boolean, params are casted accordingly!
        # ...
      end
    end
  end

Reference properties across resources

Reuse properties from other resources when appropriate:

resource("/artists") do |res|
  res.property.text :name, description: "Artist name"
end

resource("/albums") do |res|
  res.property.text :name, description: "Album name"
  res.property.ref :artist_name, to: "artists/name"
end

You can also customize attributes on the reference so that they're different from the original property. For instance:

resource("/artists") do |res|
  res.property.text :name, description: "Artist name"

  res.patch do |link|
    link.property.ref :name, optional: true
  end
end

In this case the artist name has to be serialized by every endpoint on the resource, but doesn't have to be informed by every request to PATCH the resource.

Nested properties

These are also casted and validated as you'd expect:

resource("/albums") do |res|
  res.property[:label].text :name
  res.property[:label].bool :active
end

JSON Schema

The extension will serve a JSON Schema dump at GET /schema for you.

You can also include the schema Rake task to print it out. To do so, add to your Rakefile:

require "./app" # load your app to have the endpoints defined
load "sinatra/schema/tasks/schema.rake"

Then dump it like:

$ rake schema
{
  "$schema":"http://json-schema.org/draft-04/hyper-schema",
  "definitions":{
    "account":{
      "title":"Account",
      "type":"object",
      "definitions":{
        "email":{
          "type":"string"
        }
      },
      "links":[
        {
          "href":"/account",
          "method":"GET"
        }
      ]
    }
  }
}

Error handling

By default it will raise a 400 on bad requests (eg: invalid JSON request) and a 422 on bad params (eg: missing mandatory param):

$ curl -d "" http://localhost:5000/account
{"error":"Missing expected params: email"}

Redefine the error handlers to render a different status or serialize errors differently:

class MyApi < Sinatra::Base
  register Sinatra::Schema

  error(Sinatra::Schema::BadParams) do |e|
    halt(422, MultiJson.encode(id: "bad_params", message: e.message))
  end
end

Context

There are lots of reasons why you should consider describe your API with a machine-readable format:

  • Describe what endpoints are available
  • Validate requests and responses
  • Embrace constraints for consistent API design
  • Generate documentation
  • Generate clients
  • Generate service stubs

Sinatra Schema is a thin layer on top of JSON Schema, trying to bring all of the benefits without any of the JSON.

If you need more flexibility, or if you think the schema should live by itself, then you should consider writing the schema yourself. Tools like prmd can really help you get started, and committee can help you get benefits out of that schema.

Meta

Created by Pedro Belo. MIT license.