The GitHub API example
GitHub provides an extensive API. For instance, you can list the watchers of a repository with:
GET https://api.github.com/repos/:owner/:repo/subscribers
What response should you expect? According to the documentation, one example of possible response is:
Status: 200 OK
Link: <https://api.github.com/resource?page=2>; rel="next",
<https://api.github.com/resource?page=5>; rel="last"
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4999
[
{
"login": "octocat",
"id": 1,
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "somehexcode",
"url": "https://api.github.com/users/octocat"
}
]
What promises is this example-based documentation making to the API consumers?
If you had to parse the result based solely on one example, you would be filled with doubts like:
- will the status code always be
200
? - will the body always return a JSON
array
? - will the array always include only one element?
- will each element always have a
login
attribute with the value"octocat"
? - will the
id
attribute always have aninteger
value? - will the
avatar_url
attribute always have a value?
The power of RSpecApi to the rescue
RSpecApi provides a new, concise and readable way to document the promises of the endpoint above:
resource :watcher do
host 'https://api.github.com'
authorize_with 'YOUR-GITHUB-PERSONAL-API-TOKEN'
has_attribute :login, type: :string
has_attribute :id, type: :number
has_attribute :avatar_url, type: [:null, {string: :url}]
has_attribute :gravatar_id, type: [:null, :string]
has_attribute :url, type: {string: :url}
get '/repos/:owner/:repo/subscribers', collection: true do
respond_with :ok, owner: existing(:user), repo: existing(:repo)
respond_with :not_found, owner: unknown(:user), repo: unknown(:repo)
end
end
This snippet of code is a more comprehensive documentation than an example-based one.
Even though the returned data from the endpoint might differ from call to call, the consumer can always expect:
- a status code of
200
when given an existing owner/repo - a body of
JSON content-type
- the body to be an
array
of items, each representing a watcher - each watcher to have a
login
attribute with astring
value - each watcher to have an
id
attribute with annumeric
value - each watcher to have an
avatar_url
attribute with either aURL
or anil
value - each watcher to have a
gravatar_id
attribute with either astring
or anil
value - each watcher to have a
url
attribute with aURL
value - a status code of
404
when given an unknown owner/repo
The best part of RSpecApi is that you can actually verify that GitHub is keeping its API promises at any given time, by executing the snippet of code above through RSpec and ensuring everything is green:
$ rspec spec/features/remote/github/activity/watchers_spec.rb
Watchers
GET https://api.github.com/repos/:owner/:repo/subscribers
given an existing owner "rspec-api" and an existing repo "rspec-api"
responds with a status code that
should be 200
responds with headers that
should include 'Content-Type': 'application/json; charset=utf-8'
responds with a body that
should be a JSON Array
should include the attribute "login" with a string value
should include the attribute "id" with an integer value
should include the attribute "avatar_url" with a URL or nil value
should include the attribute "gravatar_id" with a string or nil value
should include the attribute "url" with a URL value
given an unknown owner "i-don-t-exist" and an unknown repo "not-a-repo"
responds with a status code that
should be 404
Now, go and run this code for yourself, then tell me what you think. All you need is a GitHub API key (instructions).