Slide 1

Slide 1 text

MY API CLIENT MY API CLIENT FFTT #400 Jul 03, 2020

Slide 2

Slide 2 text

RYOSUKE SATO RYOSUKE SATO @ryosuke_sato

Slide 3

Slide 3 text

MY API CLIENT MY API CLIENT

Slide 4

Slide 4 text

WHAT IS WHAT IS ? ? A framework of Web API Client. Provides features error handling, retrying, pagination and so on. https://github.com/ryz310/my_api_client

Slide 5

Slide 5 text

BASIC USAGE BASIC USAGE

Slide 6

Slide 6 text

class ExampleApiClient < MyApiClient::Base endpoint 'https://example.com/common' # GET https://example.com/common/path/to/resource?key=value def get_resource query = { key: 'value' } get 'path/to/resource', headers: headers, query: query end private def headers { 'Content-Type': 'application/json;charset=UTF-8' } end end api_client = ExampleApiClient.new response = api_client.get_resource # => { message: 'Hello world!' }

Slide 7

Slide 7 text

SUPPORTED HTTP METHODS SUPPORTED HTTP METHODS get post patch (alias: put) delete

Slide 8

Slide 8 text

TESTING WITH RSPEC TESTING WITH RSPEC RSpec.describe ExampleApiClient, type: :api_client do let(:api_client) { described_class.new } let(:headers) do { 'Content-Type': 'application/json;charset=UTF-8' } end describe '#get_resource' do subject(:api_request) { api_client.get_resource } it do expect { api_request } .to request_to(:get, 'https://example.com/common/path/to/ .with(headers: headers, query: { key: 'value' }) end end ExampleApiClient #get_resource is expected to request to "GET https://example.com/common/pat

Slide 9

Slide 9 text

ERROR HANDLING ERROR HANDLING

Slide 10

Slide 10 text

HANDLE ERROR BY STATUS CODE HANDLE ERROR BY STATUS CODE class ExampleApiClient < MyApiClient::Base endpoint 'https://example.com/common' error_handling status_code: 404, raise: MyErrors::NotFound # GET https://example.com/common/path/to/resource?key=value def get_resource query = { key: 'value' } get 'path/to/resource', headers: headers, query: query end # ... end api_client = ExampleApiClient.new begin response = api_client.get_resource rescue MyErrors::NotFound puts 'The resource was not found.' end

Slide 11

Slide 11 text

TESTING WITH RSPEC TESTING WITH RSPEC RSpec.describe ExampleApiClient, type: :api_client do let(:api_client) { described_class.new } let(:headers) do { 'Content-Type': 'application/json;charset=UTF-8' } end describe '#get_resource' do subject(:api_request) { api_client.get_resource } context 'when received status code is 404' do it do expect { api_request } .to be_handled_as_an_error(MyErrors::NotFound) .when_receive(status_code: 404) end ExampleApiClient #get_resource when received status code is 404 is expected to be handled as MyErrors::NotFound

Slide 12

Slide 12 text

HANDLE ERROR BY RESPONSE BODY HANDLE ERROR BY RESPONSE BODY error_handling json: { '$.error.message': /You requested error co raise: MyErrors::ErrorCodeOther error_handling json: { '$.error.code': :zero? }, raise: MyErrors::ErrorCode00 error_handling json: { '$.error.code': 10 }, raise: MyErrors::ErrorCode10 error_handling json: { '$.error.code': 20..29 }, raise: MyErrors::ErrorCode2x error_handling json: { '$.error.code': 30 }, status_code: 400, raise: MyErrors::ErrorCode30 { "error": { "code": 10, "message": "You requested error code: 10" } }

Slide 13

Slide 13 text

TESTING WITH RSPEC TESTING WITH RSPEC RSpec.describe ExampleApiClient, type: :api_client do let(:api_client) { described_class.new } let(:headers) do { 'Content-Type': 'application/json;charset=UTF-8' } end describe '#get_resource' do subject(:api_request) { api_client.get_resource } context 'when received error code is 10' do let(:response_body) do { error: { code: 10, message: "You requested error code: 10" ExampleApiClient #get_resource when received error code is 10 is expected to be handled as MyErrors::ErrorCode10

Slide 14

Slide 14 text

TEST STUB TEST STUB

Slide 15

Slide 15 text

Returns arbitrary response without real HTTP connection. api_client = stub_api_client( ExampleApiClient, get_resource: { message: 'Hello world!' } ) response = api_client.get_resource # => { message: 'Hello world!' }

Slide 16

Slide 16 text

stub_api_client_all( ExampleApiClient, get_resource: { message: 'Hello world!' } ) api_client = ExampleApiClient.new # => It returns a mock instance. response = api_client.get_resource # => { message: 'Hello world!' }

Slide 17

Slide 17 text

WITH LAMBDA EXPRESSION WITH LAMBDA EXPRESSION api_client = stub_api_client( ExampleApiClient, get_resource: ->(name) { { message: "Hello #{name}!" } } ) response = api_client.get_resource('John') # => { message: 'Hello John!' }

Slide 18

Slide 18 text

TO RAISE ARBITRARY ERROR TO RAISE ARBITRARY ERROR api_client = stub_api_client( ExampleApiClient, get_resource: { raise: MyErrors::NotFound } ) begin response = api_client.get_resource rescue MyErrors::NotFound # You can raise arbitrary error. end

Slide 19

Slide 19 text

PAGINATION PAGINATION

Slide 20

Slide 20 text

DEMO DEMO $ curl GET $MY_API_ENDPOINT/pagination | jq . { "links": { "next": "https://{...}/pagination?page=2" }, "page": 1 } $ bin/console irb(main):001:0> api_client = MyPaginationApiClient.new irb(main):002:0> api_client.pagination irb(main):003:0> api_client.pagination.first irb(main):004:0> api_client.pagination.take(3).eager.map(&:itself

Slide 21

Slide 21 text

api_client = MyPaginationApiClient.new api_client.pagination # => # { # :links=>{ # :next=>"https://{...}/dev/pagination?page=2" # }, # :page=>1 # } api_client.pagination.take(3).eager.map(&:itself) # => [ # {

Slide 22

Slide 22 text

CONCLUSION CONCLUSION

Slide 23

Slide 23 text

IS… IS… A framework of Web API Client. Provides features: error handling test stub retrying pagination and so on…

Slide 24

Slide 24 text

API DOCUMENTATION (JAPANESE) API DOCUMENTATION (JAPANESE) https://github.com/ryz310/my_api_client/blob/master/RE

Slide 25

Slide 25 text

THANK YOU THANK YOU