@brandur
heroku create
$ curl -X POST https://api.heroku.com/login \
-d "email=..." -d "password=good"
HTTP/1.1 200 OK
Content-Type: application/json
{ "email": "..." }
$ curl -X POST https://api.heroku.com/login \
-d "email=..." -d "password=bad"
HTTP/1.1 404 Not Found
Content-Type: text/plain
""
$ curl -X POST https://api.heroku.com/login \
-d "login_user[email]=..."
$ curl -X POST https://api.heroku.com/login \
-d "username=..."
$ curl -X POST https://api.heroku.com/login \
-d "email=..."
There are two hard problems in computer science ...
Principles derived from designing the public API.
201
or 202
on create; 200
elsewhere.
Range: id ..; max=1
2012-01-01T12:00:00Z
Hyphens in paths (e.g. ssl-endpoints
); underscores in params (e.g. created_at
)
If-None-Match: ...
Include RateLimit-Remaining
header
{
"type": "string"
}
✓ "foo"
✗ 42
{
"pattern": "^[a-z][a-z0-9-]{3,30}$",
"type": "string"
}
✓ "my-app"
✗ "my_app"
{
"properties": {
"name": {
"pattern": "^[a-z][a-z0-9-]{3,30}$",
"type": "string"
}
},
"required": ["name"],
"type": "object"
}
✓ { "name": "my-app" }
✗ {}
{
"definitions": {
"name": {
"pattern": "^[a-z][a-z0-9-]{3,30}$",
"type": "string"
}
},
"properties": {
"name": {
"$ref": "#/definitions/name"
}
},
"required": ["name"],
"type": "object"
}
`$ref` under "properties" is a JSON Reference to "definitions"
{
"definitions": {
"domain": {
"properties": {
"name": {
"format": "hostname",
"type": "string"
}
}
},
"app": {
"properties": {
"domains": {
"items": {
"$ref": "#/definitions/domain"
},
"type": "array"
}
}
}
}
...
}
{
"name": "my-app",
"domains": [
{ "name": "example.com },
{ "name": "heroku.com" }
]
}
{
"links": [
{
"description": "Create a new app.",
"href": "/apps",
"method": "POST",
"rel": "create"
},
{
"description": "List apps.",
"href": "/apps",
"method": "GET",
"rel": "instances"
}
],
"properties": {
"name": {
...
}
},
"required": ["name"],
"type": "object"
}
{
"description": "Create a new app.",
"href": "/apps",
"method": "POST",
"rel": "create",
"schema": {
"properties": {
"name": {
"$ref": "#/definitions/app/definitions/name"
}
},
"required": ["name"],
"type": "object"
},
"title": "Create"
}
$ curl -X POST http://example.com/apps \
-H "Content-Type: application/json" \
-d '{"name":"my-app"}'
{
"description": "List apps.",
"href": "/apps",
"method": "GET",
"rel": "instances",
"targetSchema": {
"items": {
"$ref": "#/definitions/app"
},
"type": "array"
},
"title": "List"
}
Note the reference to the link's parent
$ curl -X GET http://example.com/apps
[
{
"name": "my-app",
"domains": [
{ "name": "example.com },
{ "name": "heroku.com" }
]
}
]
$ curl -H "Accept: application/vnd.heroku+json; version=3" \
https://api.heroku.com/schema -o schema.json
{
"definitions": {
"domain": {
links: [
...
]
...
},
"app": {
links: [
...
]
...
}
}
...
}
{
"definitions": {
"resource": {
"properties": {
"links": {
"items": {
"$ref": "#/definitions/link"
},
"type": "array"
}
}
},
"link": {
"required": [ "method", "targetSchema" ],
"type": "object"
}
},
...
}
Requires links to have `method` and `targetSchema`
{
"resource": {
"properties": {
...,
"properties": {
"additionalProperties": false,
"patternProperties": {
"^[a-z][a-z_]+[a-z]$": {}
}
}
}
}
}
Requires params to use only `[a-z]` and underscores
✓ parameter_name
✗ parameter-name
$ curl https://interagent.github.io/interagent-hyper-schema -o meta.json
$ heroics-generate schema.json > client.rb
> heroku.app.create
=> { ... }
$ schematic schema.json > client.go
> heroku.app.createapp, err := h.AppCreate()
if err != nil {
panic(err)
}
fmt.Println(app.Name)
github.com/interagent/committee
$ committee-stub -p 3000 schema.json
$ curl http://localhost:3000/apps/my-app
{
"created_at": "2012-01-01T12:00:00Z",
"git_url": "git@heroku.com:example.git",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"name": "example"
}
help_stub: bundle exec committee-stub -p 3000 help/schema.json
logplex_stub: bundle exec committee-stub -p 3001 logplex/schema.json
maestro_stub: bundle exec committee-stub -p 3002 maestro/schema.json
psmgr_stub: bundle exec committee-stub -p 3003 psmgr/schema.json
vault_stub: bundle exec committee-stub -p 3004 vault/schema.json
def schema_path
"./schema.json"
end
it "conforms to schema" do
@app = App.create! name: "my-app"
get "/apps/my-app"
assert_equal 200, last_response.status
assert_schema_conform
end