json schema: your api's secret weapon
TRANSCRIPT
JSON Schema: Your API’s Secret Weapon
API Craft Boston / 2016-03-10 Pete Gamache / [email protected] / @gamache
JSON SchemaDescribes the structure of JSON data using a JSON-based language
Standards-track
Simple
Great at nested objects
Generally treated as documentation
Good library support, though
Example: Event
{ "name": "button_click", "timestamp": 1457437187, "attributes": { "button_id": 271828, "page": "/" }}
Example JSON Schema{ "$schema": "http://json-schema.org/draft-04/schema#", "title": "Example Schema", "definitions": { "event": { "type": "object", "required": ["name", "timestamp"], "properties": { "name": {"type": "string"}, "timestamp": {"type": "integer"}, "attributes": {"type": "object"} } }, // ...
Example: Event Collection{ "events": [ { "name": "button_click", "timestamp": 1457437187, "attributes": { "button_id": 271828, "page": "/" } }, // ... ]}
Example JSON Schema{ "$schema": "http://json-schema.org/draft-04/schema#", "title": "Example Schema", "definitions": { // ... "event_collection": { "required": ["events"], "properties": { "events": { "type": "array", "items": {"ref": "#/definitions/event"} } } }, // ...
It would be a shame for a lovely, machine-readable doc like that to be wasted on humans...
JSON Validation with Elixir and ExJsonSchemaiex> schema = File.read!("schema.json") |> Poison.decode! |> ExJsonSchema.Schema.resolveiex> event_schema = schema.schema["definitions"]["event"]iex> ExJsonSchema.Validator.validate(schema, event_schema, %{})[{"Required property name was not present.", []}, {"Required property timestamp was not present.", []}]iex> ExJsonSchema.Validator.validate(schema, event_schema, %{"name" => "hi", "timestamp" => 1})[]
JSON Validation with Elixir and ExJsonSchema, cont.iex> event_collection_schema = schema.schema["definitions"]["event_collection"]iex> ExJsonSchema.Validator.validate(schema, event_collection_schema,...> %{"events" => [...> %{"name" => "event 1", "attributes" => %{"awesome" => true}},...> %{"name" => "event 2", "timestamp" => "whenever"},...> %{"name" => "event 3", "timestamp" => 1234567890}...> ]})[{"Required property timestamp was not present.", ["events", 0]}, {"Expected \"whenever\" to be a valid ISO 8601 date-time.", ["events", 1, "timestamp"]}]
Use Case 1: Input Validation
Writing data validators is a pain, especially for anything complex
Not only do we have to validate input, we need to generate coherent error messages
Lots of opportunity to reinvent the wheel, but let's not
API Input Validation with Elixir and ExJsonSchemadefmodule MyApp.EventsController do use MyApp.Web, :controller plug :validate_params defp validate_params(conn, _params) do case JsonSchema.validate(conn.params, :event_collection) do [] -> conn |> assign(:event_collection, conn.params) errors -> json_errors = errors |> JsonSchema.errors_to_json conn |> put_status(422) |> json(%{errors: json_errors}) |> halt end end def save_events(conn, params) do event_collection = conn.assigns[:event_collection] # ... do something here conn |> put_status(202) |> json(%{ok: true}) endend
Use Case 2: Output Validation
Pointing to the JSON Schema in API docs is great for humans
Performing JSON Schema validation in API tests ensures your docs aren't lying*. This is also great for humans
* at least not about output data structure format
API Output Validation with Elixir and ExJsonSchemadefmodule MyApp.EventsControllerTest do use Plug.Test test "it returns well-formed event collection" do resp = conn(:post, "url goes here", %{params: ...}) |> MyApp.Router.call(MyApp.Router.init([])) resp_object = resp.resp_body |> Poison.decode! assert([] == JsonSchema.validate(resp_object, :event_collection)) end # ... more tests hereend
Referenceshttp://json-schema.org/
JSON Pointer -- https://tools.ietf.org/html/rfc6901
https://github.com/jonasschmidt/ex_json_schema
https://engineering.appcues.com/2016/01/20/ex-json-schema.html
http://www.slideshare.net/petegamache/jsonschema20160310
Questions?
Love APIs? Appcues is hiring!
APIs in Elixir and ES6/AWS Lambda/API Gateway
Frontend in ES6/Redux/React
http://tinyurl.com/appcues-full-stack