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!
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
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
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.
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
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"
}
]
}
}
}
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
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.
Created by Pedro Belo. MIT license.