Phil Sturgeon
@philsturgeon
Iterate, improve, and agree up on your contracts before you write any code!
Documentation without tests is like code without tests; You're just hoping it works well enough that you don't f**k everything up.
Broken documentation should fail the build.
$ npm install -g dredd
$ dredd init
$ dredd
info: Beginning Dredd testing...
fail: GET /products/1 duration: 17ms
fail: body: At '/data/attributes/volume' Missing required property: volume
complete: 4 passing, 1 failing, 0 errors, 10 skipped, 15 total
complete: Tests took 274ms
$ npm install -g drakov
$ drakov -f apiary.apib
$ ngrok http 3000
$ http GET http://xxxxxx.ngrok.io/products --json
Don't confuse REST for a metric of quality
Making an API 100% RESTful is hard
Do you actually need REST?
Maybe RPC would be more appropriate?
RPC = Commands
REST = Resources
Using commands to interact with resources is awful
And vice versa
RPC: GET /listCheeseburgers
REST: GET /cheeseburgers
RPC: POST /createCheeseburger
REST: POST /cheeseburgers
RPC: POST /updateCheeseburger
REST: PATCH /cheeseburgers/1
RPC: POST /deleteCheeseburger
REST: DELETE /cheeseburgers/1
RPC: POST /consumeCheeseburger
REST: urm...?
Actions that trigger state changes could be RPC...
... but maybe you could PATCH on fields with a state machine?
module Api
module States
class Trip
include Statesman::Machine
state :locating, initial: true
state :in_progress
state :complete
state :canceled
transition from: :locating, to: [:in_progress, :canceled]
transition from: :in_progress, to: [:complete, :canceled]
after_transition(from: :locating, to: :in_progress) do |trip|
trigger_some_action(trip)
end
# ...
end
end
end
There is a reason Slack use RPC...
So probably RPC For commands then?
Maybe you don't need HATEOAS
Hypermedia helps your API outlive your startup
TDD is the easiest way to build a complex HTTP API
Describe-it syntax helps you write complex tests easily
RSpec.describe 'Avatars' do
let(:user) { create(:user) }
describe 'POST /avatars' do
let(:png_path) { 'spec/fixtures/files/avatar.png' }
let(:png_file) { File.read(Rails.root.join(png_path)) }
context 'when the user already had an avatar' do
let!(:old_avatar) { create(:avatar, user: user ) }
it 'deletes the old avatar for the user. No soft delete.' do
payload = {
avatars: {
image_url: 'http://fake-ride/example.png'
}
}
post '/avatars', payload
expect(user.reload.avatar).not_to eql(old_avatar)
end
end
end
end
RSpec.describe 'Avatars' do
describe 'POST /avatars' do
context 'direct image uploads' do
it 'will fail if content-type is invalid' do
headers = oauth_headers(user_token).merge({
'CONTENT_TYPE' => 'some/crap'
})
post '/avatars', nil, headers
expected_response = {
'errors' => [{
'code' => 11_506,
'title' => "The root key is missing from the payload.",
'status' => 400,
'details' => 'avatars'
}]
}
expect(parsed_response).to eq(expected_response)
expect(response).to have_http_status(:bad_request)
end
end
end
end
Don't let clients hit staging for dev
?include=literally,eve
rything,in,the,goddam,database,what,is,happening,so,slow,help,me,database,server,is,on,fire
apisyouwonthate.com