JSON Schema: Your API's Secret Weapon

Preview:

Citation preview

JSON Schema: Your API’s Secret Weapon

API Craft Boston / 2016-03-10 Pete Gamache / pete@appcues.com / @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