my_api_client 1.3.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.dockerignore +8 -0
- data/.envrc.skeleton +0 -1
- data/.github/dependabot.yml +82 -7
- data/.github/workflows/ci.yml +334 -0
- data/.github/workflows/dependabot-auto-merge.yml +96 -0
- data/.github/workflows/release.yml +33 -0
- data/.github/workflows/rubocop_challenge.yml +33 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +1 -1
- data/.ruby-version +1 -1
- data/AGENTS.md +128 -0
- data/CHANGELOG.md +54 -2
- data/CLAUDE.md +6 -0
- data/Dockerfile +22 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +67 -59
- data/README.md +398 -62
- data/docker-compose.yml +29 -0
- data/docs/runbooks/dependabot_pr_auto_merge.md +113 -0
- data/gemfiles/rails_7.2.gemfile +1 -1
- data/gemfiles/{rails_7.1.gemfile → rails_8.0.gemfile} +2 -2
- data/gemfiles/{rails_6.1.gemfile → rails_8.1.gemfile} +2 -2
- data/lib/my_api_client/default_error_handlers.rb +1 -1
- data/lib/my_api_client/errors/client_error.rb +5 -1
- data/lib/my_api_client/integrations/bugsnag.rb +1 -1
- data/lib/my_api_client/version.rb +1 -1
- data/lib/my_api_client.rb +1 -1
- data/my_api/.ruby-version +1 -1
- data/my_api/AGENTS.md +35 -0
- data/my_api/Dockerfile +25 -0
- data/my_api/Gemfile +5 -16
- data/my_api/Gemfile.lock +263 -228
- data/my_api/README.md +36 -25
- data/my_api/Rakefile +3 -2
- data/my_api/app/controllers/application_controller.rb +1 -2
- data/my_api/app/controllers/error_controller.rb +2 -2
- data/my_api/app/controllers/header_controller.rb +6 -3
- data/my_api/app/controllers/pagination_controller.rb +5 -15
- data/my_api/app/controllers/rest_controller.rb +12 -16
- data/my_api/app/controllers/status_controller.rb +3 -4
- data/my_api/config/application.rb +12 -67
- data/{rails_app/rails_6.1 → my_api}/config/boot.rb +1 -1
- data/{rails_app/rails_7.1 → my_api}/config/environment.rb +0 -2
- data/my_api/config/environments/development.rb +6 -6
- data/my_api/config/environments/production.rb +5 -8
- data/my_api/config/environments/test.rb +5 -6
- data/my_api/config/routes.rb +2 -10
- data/my_api/config.ru +2 -4
- data/my_api/public/index.html +10 -90
- data/{rails_app/rails_7.0 → my_api}/spec/rails_helper.rb +3 -4
- data/my_api/spec/requests/error_controller_spec.rb +18 -0
- data/my_api/spec/requests/pagination_controller_spec.rb +22 -0
- data/my_api/spec/requests/rest_controller_spec.rb +22 -0
- data/my_api/spec/requests/status_controller_spec.rb +20 -0
- data/my_api/spec/spec_helper.rb +15 -23
- data/my_api_client.gemspec +2 -2
- data/rails_app/AGENTS.md +13 -0
- data/rails_app/rails_7.2/Dockerfile +1 -1
- data/rails_app/rails_7.2/Gemfile +2 -0
- data/rails_app/rails_7.2/Gemfile.lock +143 -122
- data/rails_app/{rails_7.1 → rails_8.0}/.dockerignore +16 -2
- data/rails_app/{rails_6.1 → rails_8.0}/.gitattributes +2 -1
- data/rails_app/rails_8.0/.github/workflows/ci.yml +39 -0
- data/rails_app/{rails_6.1 → rails_8.0}/.gitignore +11 -7
- data/rails_app/rails_8.0/.rubocop.yml +8 -0
- data/rails_app/rails_8.0/.ruby-version +1 -0
- data/rails_app/{rails_7.1 → rails_8.0}/Dockerfile +28 -18
- data/rails_app/rails_8.0/Gemfile +15 -0
- data/rails_app/rails_8.0/Gemfile.lock +286 -0
- data/rails_app/{rails_6.1 → rails_8.0}/Rakefile +1 -3
- data/rails_app/{rails_7.1 → rails_8.0}/app/controllers/application_controller.rb +0 -2
- data/rails_app/rails_8.0/app/jobs/application_job.rb +7 -0
- data/rails_app/rails_8.0/app/mailers/application_mailer.rb +4 -0
- data/rails_app/{rails_7.0 → rails_8.0}/app/models/application_record.rb +0 -2
- data/rails_app/rails_8.0/app/views/layouts/mailer.html.erb +13 -0
- data/rails_app/rails_8.0/app/views/layouts/mailer.text.erb +1 -0
- data/rails_app/rails_8.0/bin/brakeman +7 -0
- data/rails_app/rails_8.0/bin/dev +2 -0
- data/rails_app/rails_8.0/bin/docker-entrypoint +14 -0
- data/rails_app/rails_8.0/bin/rails +4 -0
- data/rails_app/rails_8.0/bin/rake +4 -0
- data/rails_app/rails_8.0/bin/rubocop +8 -0
- data/rails_app/{rails_7.1 → rails_8.0}/bin/setup +11 -12
- data/rails_app/rails_8.0/bin/thrust +5 -0
- data/rails_app/{rails_7.1 → rails_8.0}/config/application.rb +14 -16
- data/rails_app/rails_8.0/config/boot.rb +3 -0
- data/rails_app/rails_8.0/config/cable.yml +10 -0
- data/rails_app/rails_8.0/config/credentials.yml.enc +1 -0
- data/rails_app/{rails_7.1 → rails_8.0}/config/database.yml +18 -2
- data/rails_app/{rails_6.1 → rails_8.0}/config/environment.rb +1 -3
- data/rails_app/rails_8.0/config/environments/development.rb +70 -0
- data/rails_app/rails_8.0/config/environments/production.rb +86 -0
- data/rails_app/{rails_7.1 → rails_8.0}/config/environments/test.rb +16 -19
- data/rails_app/{rails_7.1 → rails_8.0}/config/initializers/cors.rb +0 -1
- data/rails_app/{rails_7.1 → rails_8.0}/config/initializers/filter_parameter_logging.rb +2 -4
- data/rails_app/{rails_7.0 → rails_8.0}/config/initializers/inflections.rb +0 -1
- data/rails_app/rails_8.0/config/puma.rb +41 -0
- data/rails_app/{rails_7.1 → rails_8.0}/config/routes.rb +1 -3
- data/rails_app/rails_8.0/config/storage.yml +34 -0
- data/rails_app/{rails_7.1 → rails_8.0}/config.ru +1 -3
- data/rails_app/{rails_7.1 → rails_8.0}/db/seeds.rb +0 -1
- data/rails_app/{rails_7.1 → rails_8.0}/spec/rails_helper.rb +1 -1
- data/rails_app/rails_8.1/.dockerignore +45 -0
- data/rails_app/rails_8.1/.gitattributes +9 -0
- data/rails_app/rails_8.1/.github/workflows/ci.yml +52 -0
- data/rails_app/rails_8.1/.gitignore +33 -0
- data/rails_app/rails_8.1/.rspec +1 -0
- data/rails_app/rails_8.1/.rubocop.yml +8 -0
- data/rails_app/rails_8.1/.ruby-version +1 -0
- data/rails_app/rails_8.1/Dockerfile +68 -0
- data/rails_app/rails_8.1/Gemfile +15 -0
- data/rails_app/rails_8.1/Gemfile.lock +289 -0
- data/rails_app/{rails_7.0 → rails_8.1}/Rakefile +1 -3
- data/rails_app/{rails_6.1 → rails_8.1}/app/controllers/application_controller.rb +0 -2
- data/rails_app/rails_8.1/app/jobs/application_job.rb +7 -0
- data/rails_app/rails_8.1/app/mailers/application_mailer.rb +4 -0
- data/rails_app/{rails_7.1 → rails_8.1}/app/models/application_record.rb +0 -2
- data/rails_app/rails_8.1/app/views/layouts/mailer.html.erb +13 -0
- data/rails_app/rails_8.1/app/views/layouts/mailer.text.erb +1 -0
- data/rails_app/rails_8.1/bin/brakeman +7 -0
- data/rails_app/rails_8.1/bin/bundler-audit +6 -0
- data/rails_app/rails_8.1/bin/ci +6 -0
- data/rails_app/rails_8.1/bin/dev +2 -0
- data/rails_app/{rails_7.1 → rails_8.1}/bin/docker-entrypoint +1 -1
- data/rails_app/rails_8.1/bin/rails +4 -0
- data/rails_app/rails_8.1/bin/rake +4 -0
- data/rails_app/rails_8.1/bin/rubocop +8 -0
- data/rails_app/{rails_7.0 → rails_8.1}/bin/setup +13 -13
- data/rails_app/rails_8.1/bin/thrust +5 -0
- data/rails_app/{rails_6.1 → rails_8.1}/config/application.rb +19 -17
- data/rails_app/rails_8.1/config/boot.rb +3 -0
- data/rails_app/rails_8.1/config/bundler-audit.yml +5 -0
- data/rails_app/rails_8.1/config/cable.yml +10 -0
- data/rails_app/rails_8.1/config/ci.rb +19 -0
- data/rails_app/rails_8.1/config/credentials.yml.enc +1 -0
- data/rails_app/rails_8.1/config/database.yml +40 -0
- data/rails_app/{rails_7.0 → rails_8.1}/config/environment.rb +1 -3
- data/rails_app/rails_8.1/config/environments/development.rb +73 -0
- data/rails_app/rails_8.1/config/environments/production.rb +86 -0
- data/rails_app/rails_8.1/config/environments/test.rb +53 -0
- data/rails_app/{rails_7.0 → rails_8.1}/config/initializers/cors.rb +1 -2
- data/rails_app/rails_8.1/config/initializers/filter_parameter_logging.rb +8 -0
- data/rails_app/{rails_7.1 → rails_8.1}/config/initializers/inflections.rb +0 -1
- data/rails_app/rails_8.1/config/locales/en.yml +31 -0
- data/rails_app/rails_8.1/config/puma.rb +42 -0
- data/rails_app/rails_8.1/config/routes.rb +10 -0
- data/rails_app/rails_8.1/config/storage.yml +27 -0
- data/rails_app/{rails_7.0 → rails_8.1}/config.ru +1 -3
- data/rails_app/rails_8.1/db/seeds.rb +9 -0
- data/rails_app/{rails_6.1 → rails_8.1}/spec/rails_helper.rb +1 -2
- metadata +139 -136
- data/.circleci/config.yml +0 -344
- data/README.jp.md +0 -785
- data/gemfiles/rails_7.0.gemfile +0 -15
- data/my_api/.envrc.skeleton +0 -3
- data/my_api/.jetskeep +0 -1
- data/my_api/Procfile +0 -7
- data/my_api/app/helpers/application_helper.rb +0 -5
- data/my_api/app/jobs/application_job.rb +0 -7
- data/my_api/app/models/application_item.rb +0 -5
- data/my_api/config/dynamodb.yml +0 -22
- data/my_api/spec/controllers/error_controller_spec.rb +0 -43
- data/my_api/spec/controllers/header_controller_spec.rb +0 -33
- data/my_api/spec/controllers/pagination_controller_spec.rb +0 -73
- data/my_api/spec/controllers/rest_controller_spec.rb +0 -99
- data/my_api/spec/controllers/status_controller_spec.rb +0 -47
- data/my_api/spec/fixtures/payloads/posts-index.json +0 -51
- data/my_api/spec/fixtures/payloads/posts-show.json +0 -53
- data/rails_app/rails_6.1/.rspec +0 -3
- data/rails_app/rails_6.1/Gemfile +0 -17
- data/rails_app/rails_6.1/Gemfile.lock +0 -195
- data/rails_app/rails_6.1/app/models/application_record.rb +0 -5
- data/rails_app/rails_6.1/bin/bundle +0 -122
- data/rails_app/rails_6.1/bin/rails +0 -6
- data/rails_app/rails_6.1/bin/rake +0 -6
- data/rails_app/rails_6.1/bin/setup +0 -35
- data/rails_app/rails_6.1/config/credentials.yml.enc +0 -1
- data/rails_app/rails_6.1/config/database.yml +0 -25
- data/rails_app/rails_6.1/config/environments/development.rb +0 -59
- data/rails_app/rails_6.1/config/environments/production.rb +0 -97
- data/rails_app/rails_6.1/config/environments/test.rb +0 -51
- data/rails_app/rails_6.1/config/initializers/application_controller_renderer.rb +0 -9
- data/rails_app/rails_6.1/config/initializers/backtrace_silencers.rb +0 -10
- data/rails_app/rails_6.1/config/initializers/cors.rb +0 -17
- data/rails_app/rails_6.1/config/initializers/filter_parameter_logging.rb +0 -8
- data/rails_app/rails_6.1/config/initializers/inflections.rb +0 -17
- data/rails_app/rails_6.1/config/initializers/mime_types.rb +0 -5
- data/rails_app/rails_6.1/config/initializers/wrap_parameters.rb +0 -16
- data/rails_app/rails_6.1/config/locales/en.yml +0 -33
- data/rails_app/rails_6.1/config/routes.rb +0 -5
- data/rails_app/rails_6.1/config.ru +0 -8
- data/rails_app/rails_6.1/db/seeds.rb +0 -8
- data/rails_app/rails_7.0/Gemfile +0 -13
- data/rails_app/rails_7.0/Gemfile.lock +0 -212
- data/rails_app/rails_7.0/app/controllers/application_controller.rb +0 -4
- data/rails_app/rails_7.0/bin/bundle +0 -122
- data/rails_app/rails_7.0/bin/rails +0 -6
- data/rails_app/rails_7.0/bin/rake +0 -6
- data/rails_app/rails_7.0/config/application.rb +0 -41
- data/rails_app/rails_7.0/config/boot.rb +0 -5
- data/rails_app/rails_7.0/config/credentials.yml.enc +0 -1
- data/rails_app/rails_7.0/config/database.yml +0 -25
- data/rails_app/rails_7.0/config/environments/development.rb +0 -58
- data/rails_app/rails_7.0/config/environments/production.rb +0 -70
- data/rails_app/rails_7.0/config/environments/test.rb +0 -52
- data/rails_app/rails_7.0/config/initializers/filter_parameter_logging.rb +0 -8
- data/rails_app/rails_7.0/config/locales/en.yml +0 -33
- data/rails_app/rails_7.0/config/routes.rb +0 -8
- data/rails_app/rails_7.0/db/seeds.rb +0 -8
- data/rails_app/rails_7.1/Gemfile +0 -16
- data/rails_app/rails_7.1/Gemfile.lock +0 -241
- data/rails_app/rails_7.1/README.md +0 -24
- data/rails_app/rails_7.1/Rakefile +0 -8
- data/rails_app/rails_7.1/bin/bundle +0 -122
- data/rails_app/rails_7.1/bin/rails +0 -6
- data/rails_app/rails_7.1/bin/rake +0 -6
- data/rails_app/rails_7.1/config/boot.rb +0 -5
- data/rails_app/rails_7.1/config/credentials.yml.enc +0 -1
- data/rails_app/rails_7.1/config/environments/development.rb +0 -58
- data/rails_app/rails_7.1/config/environments/production.rb +0 -74
- data/rails_app/rails_7.1/config/puma.rb +0 -46
- data/rails_app/rails_7.1/public/robots.txt +0 -2
- data/rails_app/rails_7.1/spec/spec_helper.rb +0 -13
- data/rails_app/rails_7.1/vendor/.keep +0 -0
- data/rails_app/rails_7.2/.github/dependabot.yml +0 -12
- /data/rails_app/{rails_7.1 → rails_8.0}/.rspec +0 -0
- /data/rails_app/{rails_6.1 → rails_8.0}/README.md +0 -0
- /data/rails_app/{rails_6.1 → rails_8.0}/app/controllers/concerns/.keep +0 -0
- /data/rails_app/{rails_6.1/app/javascript → rails_8.0/app/models/concerns}/.keep +0 -0
- /data/rails_app/{rails_7.1 → rails_8.0}/config/locales/en.yml +0 -0
- /data/rails_app/{rails_6.1/app/models/concerns → rails_8.0/lib/tasks}/.keep +0 -0
- /data/rails_app/{rails_6.1/lib/tasks → rails_8.0/log}/.keep +0 -0
- /data/rails_app/{rails_6.1 → rails_8.0}/public/robots.txt +0 -0
- /data/rails_app/{rails_6.1/tmp → rails_8.0/script}/.keep +0 -0
- /data/rails_app/{rails_6.1 → rails_8.0}/spec/spec_helper.rb +0 -0
- /data/rails_app/{rails_6.1/tmp/pids → rails_8.0/storage}/.keep +0 -0
- /data/rails_app/{rails_6.1 → rails_8.0}/vendor/.keep +0 -0
- /data/rails_app/{rails_7.0 → rails_8.1}/README.md +0 -0
- /data/rails_app/{rails_7.1 → rails_8.1}/app/controllers/concerns/.keep +0 -0
- /data/rails_app/{rails_7.1 → rails_8.1}/app/models/concerns/.keep +0 -0
- /data/rails_app/{rails_7.1 → rails_8.1}/lib/tasks/.keep +0 -0
- /data/rails_app/{rails_7.1/storage → rails_8.1/log}/.keep +0 -0
- /data/rails_app/{rails_7.0 → rails_8.1}/public/robots.txt +0 -0
- /data/rails_app/{rails_7.1/tmp → rails_8.1/script}/.keep +0 -0
- /data/rails_app/{rails_7.0 → rails_8.1}/spec/spec_helper.rb +0 -0
- /data/rails_app/{rails_7.1/tmp/pids → rails_8.1/storage}/.keep +0 -0
- /data/rails_app/{rails_7.1/tmp/storage → rails_8.1/vendor}/.keep +0 -0
data/README.jp.md
DELETED
|
@@ -1,785 +0,0 @@
|
|
|
1
|
-
[](https://circleci.com/gh/ryz310/my_api_client) [](https://badge.fury.io/rb/my_api_client) [](https://codeclimate.com/github/ryz310/my_api_client/maintainability) [](https://codeclimate.com/github/ryz310/my_api_client/test_coverage) [](https://dependabot.com)
|
|
2
|
-
|
|
3
|
-
# MyApiClient
|
|
4
|
-
|
|
5
|
-
MyApiClient は API リクエストクラスを作成するための汎用的な機能を提供します。[Sawyer](https://github.com/lostisland/sawyer) や [Faraday](https://github.com/lostisland/faraday) をベースにエラーハンドリングの機能を強化した構造になっています。
|
|
6
|
-
|
|
7
|
-
ただし、 Sawyer はダミーデータの作成が難しかったり、他の gem で競合することがよくあるので、将来的には依存しないように変更していくかもしれません。
|
|
8
|
-
|
|
9
|
-
また、 Ruby on Rails で利用することを想定してますが、それ以外の環境でも動作するように作っています。不具合などあれば Issue ページからご報告下さい。
|
|
10
|
-
|
|
11
|
-
[toc]
|
|
12
|
-
|
|
13
|
-
## Supported Versions
|
|
14
|
-
|
|
15
|
-
- Ruby 3.1, 3.2, 3.3
|
|
16
|
-
- Rails 6.1, 7.0, 7.1, 7.2
|
|
17
|
-
|
|
18
|
-
## Installation
|
|
19
|
-
|
|
20
|
-
`my_api_client` を Gemfile に追加して下さい:
|
|
21
|
-
|
|
22
|
-
```ruby
|
|
23
|
-
gem 'my_api_client'
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Ruby on Rails を利用している場合は `generator` 機能を利用できます。
|
|
27
|
-
|
|
28
|
-
```sh
|
|
29
|
-
$ rails g api_client path/to/resource get:path/to/resource --endpoint https://example.com
|
|
30
|
-
|
|
31
|
-
create app/api_clients/application_api_client.rb
|
|
32
|
-
create app/api_clients/path/to/resource_api_client.rb
|
|
33
|
-
invoke rspec
|
|
34
|
-
create spec/api_clients/path/to/resource_api_client_spec.rb
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## Usage
|
|
38
|
-
|
|
39
|
-
### Basic
|
|
40
|
-
|
|
41
|
-
最もシンプルな利用例を以下に示します。
|
|
42
|
-
|
|
43
|
-
```ruby
|
|
44
|
-
class ExampleApiClient < MyApiClient::Base
|
|
45
|
-
endpoint 'https://example.com/v1'
|
|
46
|
-
|
|
47
|
-
attr_reader :access_token
|
|
48
|
-
|
|
49
|
-
def initialize(access_token:)
|
|
50
|
-
@access_token = access_token
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# GET https://example.com/v1/users
|
|
54
|
-
#
|
|
55
|
-
# @return [Sawyer::Resource] HTTP resource parameter
|
|
56
|
-
def get_users
|
|
57
|
-
get 'users', headers: headers, query: { key: 'value' }
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# POST https://example.com/v1/users
|
|
61
|
-
#
|
|
62
|
-
# @param name [String] Username which want to create
|
|
63
|
-
# @return [Sawyer::Resource] HTTP resource parameter
|
|
64
|
-
def post_user(name:)
|
|
65
|
-
post 'users', headers: headers, body: { name: name }
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
private
|
|
69
|
-
|
|
70
|
-
def headers
|
|
71
|
-
{
|
|
72
|
-
'Content-Type': 'application/json;charset=UTF-8',
|
|
73
|
-
'Authorization': "Bearer #{access_token}",
|
|
74
|
-
}
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
api_clinet = ExampleApiClient.new(access_token: 'access_token')
|
|
79
|
-
api_clinet.get_users #=> #<Sawyer::Resource>
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
クラス定義の最初に記述される `endpoint` にはリクエスト URL の共通部分を定義します。後述の各メソッドで後続の path を定義しますが、上記の例だと `get 'users'` と定義すると、 `GET https://example.com/v1/users` というリクエストが実行されます。
|
|
83
|
-
|
|
84
|
-
次に、 `#initialize` を定義します。上記の例のように Access Token や API Key などを設定することを想定します。必要なければ定義の省略も可能です。
|
|
85
|
-
|
|
86
|
-
続いて、 `#get_users` や `#post_user` を定義します。メソッド名には API のタイトルを付けると良いと思います。メソッド内部で `#get` や `#post` を呼び出していますが、これがリクエスト時の HTTP Method になります。他にも `#patch` `#put` `#delete` が利用可能です。
|
|
87
|
-
|
|
88
|
-
### Pagination
|
|
89
|
-
|
|
90
|
-
API の中にはレスポンスに結果の続きを取得するための URL を含んでいるものがあります。
|
|
91
|
-
|
|
92
|
-
MyApiClient では、このような API を enumerable に扱うための `#pageable_get` というメソッドを用意しています。以下に例を示します。
|
|
93
|
-
|
|
94
|
-
```ruby
|
|
95
|
-
class MyPaginationApiClient < ApplicationApiClient
|
|
96
|
-
endpoint 'https://example.com/v1'
|
|
97
|
-
|
|
98
|
-
# GET pagination?page=1
|
|
99
|
-
def pagination
|
|
100
|
-
pageable_get 'pagination', paging: '$.links.next', headers: headers, query: { page: 1 }
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
private
|
|
104
|
-
|
|
105
|
-
def headers
|
|
106
|
-
{ 'Content-Type': 'application/json;charset=UTF-8' }
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
上記の例の場合、最初に `GET https://example.com/v1/pagination?page=1` に対してリクエストが実行され、続けてレスポンス JSON の `$.link.next` に含まれる URL に対して enumerable にリクエストを実行します。
|
|
112
|
-
|
|
113
|
-
例えば以下のようなレスポンスであれば、`$.link.next` は `"https://example.com/pagination?page=3"` を示します。
|
|
114
|
-
|
|
115
|
-
```json
|
|
116
|
-
{
|
|
117
|
-
"links": {
|
|
118
|
-
"next": "https://example.com/pagination?page=3",
|
|
119
|
-
"previous": "https://example.com/pagination?page=1"
|
|
120
|
-
},
|
|
121
|
-
"page": 2
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
そして `#pageable_get` は [Enumerator::Lazy](https://docs.ruby-lang.org/ja/latest/class/Enumerator=3a=3aLazy.html) を返すので、 `#each` や `#next` を実行することで次の結果を取得できます。
|
|
126
|
-
|
|
127
|
-
```ruby
|
|
128
|
-
api_clinet = MyPaginationApiClient.new
|
|
129
|
-
api_clinet.pagination.each do |response|
|
|
130
|
-
# Do something.
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
result = api_clinet.pagination
|
|
134
|
-
result.next # => 1st page result
|
|
135
|
-
result.next # => 2nd page result
|
|
136
|
-
result.next # => 3rd page result
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
なお、`#each` はレスポンスに含まれる `paging` の値が `nil` になるまで繰り返されるのでご注意ください。例えば `#take` と組み合わせることでページネーションの上限を設定できます。
|
|
140
|
-
|
|
141
|
-
`#pageable_get` の alias として `#pget` も利用可能です。
|
|
142
|
-
|
|
143
|
-
```ruby
|
|
144
|
-
# GET pagination?page=1
|
|
145
|
-
def pagination
|
|
146
|
-
pget 'pagination', paging: '$.links.next', headers: headers, query: { page: 1 }
|
|
147
|
-
end
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### Error handling
|
|
151
|
-
|
|
152
|
-
`my_api_client` ではレスポンスの内容によって例外を発生させるエラーハンドリングを定義できます。ここでは例として前述のコードにエラーハンドリングを定義しています。
|
|
153
|
-
|
|
154
|
-
```ruby
|
|
155
|
-
class ExampleApiClient < MyApiClient::Base
|
|
156
|
-
endpoint 'https://example.com'
|
|
157
|
-
|
|
158
|
-
error_handling status_code: 400..499,
|
|
159
|
-
raise: MyApiClient::ClientError
|
|
160
|
-
|
|
161
|
-
error_handling status_code: 500..599, raise: MyApiClient::ServerError do |_params, logger|
|
|
162
|
-
logger.warn 'Server error occurred.'
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
error_handling json: { '$.errors.code': 10..19 },
|
|
166
|
-
raise: MyApiClient::ClientError,
|
|
167
|
-
with: :my_error_handling
|
|
168
|
-
|
|
169
|
-
# Omission...
|
|
170
|
-
|
|
171
|
-
private
|
|
172
|
-
|
|
173
|
-
# @param params [MyApiClient::Params::Params] HTTP reqest and response params
|
|
174
|
-
# @param logger [MyApiClient::Request::Logger] Logger for a request processing
|
|
175
|
-
def my_error_handling(params, logger)
|
|
176
|
-
logger.warn "Response Body: #{params.response.body.inspect}"
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
一つずつ解説していきます。まず、以下のように `status_code` を指定するものについて。
|
|
182
|
-
|
|
183
|
-
```ruby
|
|
184
|
-
error_handling status_code: 400..499, raise: MyApiClient::ClientError
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
これは `ExampleApiClient` からのリクエスト全てにおいて、レスポンスのステータスコードが `400..499` であった場合に `MyApiClient::ClientError` が例外として発生するようになります。 `ExampleApiClient` を継承したクラスにもエラーハンドリングは適用されます。ステータスコードのエラーハンドリングは親クラスで定義すると良いと思います。
|
|
188
|
-
|
|
189
|
-
なお、 `status_code` には `Integer` `Range` `Regexp` が指定可能です。
|
|
190
|
-
|
|
191
|
-
`raise` には `MyApiClient::Error` を継承したクラスが指定可能です。`my_api_client` で標準で定義しているエラークラスについては以下のソースコードをご確認下さい。 `raise` を省略した場合は `MyApiClient::Error` を発生するようになります。
|
|
192
|
-
|
|
193
|
-
https://github.com/ryz310/my_api_client/blob/master/lib/my_api_client/errors
|
|
194
|
-
|
|
195
|
-
次に、 `block` を指定する場合について。
|
|
196
|
-
|
|
197
|
-
```ruby
|
|
198
|
-
error_handling status_code: 500..599, raise: MyApiClient::ServerError do |_params, logger|
|
|
199
|
-
logger.warn 'Server error occurred.'
|
|
200
|
-
end
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
上記の例であれば、ステータスコードが `500..599` の場合に `MyApiClient::ServerError` を発生させる前に `block` の内容が実行れます。引数の `params` にはリクエスト情報とレスポンス情報が含まれています。`logger` はログ出力用インスタンスですが、このインスタンスを使ってログ出力すると、以下のようにリクエスト情報がログ出力に含まれるようになり、デバッグの際に便利です。
|
|
204
|
-
|
|
205
|
-
```text
|
|
206
|
-
API request `GET https://example.com/path/to/resouce`: "Server error occurred."
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
`json` には `Hash` の Key に [JSONPath](https://goessner.net/articles/JsonPath/) を指定して、レスポンス JSON から任意の値を取得し、 Value とマッチするかどうかでエラーハンドリングできます。Value には `String` `Integer` `Range` `Regexp` が指定可能です。上記の場合であれば、以下のような JSON にマッチします。
|
|
210
|
-
|
|
211
|
-
```ruby
|
|
212
|
-
error_handling json: { '$.errors.code': 10..19 }, with: :my_error_handling
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
```json
|
|
216
|
-
{
|
|
217
|
-
"erros": {
|
|
218
|
-
"code": 10,
|
|
219
|
-
"message": "Some error has occurred."
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
`headers` には `Hash` の Key に レスポンスのヘッダーキーを指定して、 Value とマッチするかどうかでエラーハンドリングできます。Value には `String` `Regexp` が指定可能です。
|
|
225
|
-
|
|
226
|
-
```ruby
|
|
227
|
-
error_handling headers: { 'www-authenticate': /invalid token/ }, with: :my_error_handling
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
上記の場合であれば、以下のような レスポンスヘッダー にマッチします。
|
|
231
|
-
|
|
232
|
-
```text
|
|
233
|
-
cache-control: no-cache, no-store, max-age=0, must-revalidate
|
|
234
|
-
content-type: application/json
|
|
235
|
-
www-authenticate: Bearer error="invalid_token", error_description="invalid token"
|
|
236
|
-
content-length: 104
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
`with` にはインスタンスメソッド名を指定することで、エラーを検出した際、例外を発生させる前に任意のメソッドを実行させることができます。メソッドに渡される引数は `block` 定義の場合と同じく `params` と `logger` です。なお、 `block` と `with` は同時には利用できません。
|
|
240
|
-
|
|
241
|
-
```ruby
|
|
242
|
-
# @param params [MyApiClient::Params::Params] HTTP req and res params
|
|
243
|
-
# @param logger [MyApiClient::Request::Logger] Logger for a request processing
|
|
244
|
-
def my_error_handling(params, logger)
|
|
245
|
-
logger.warn "Response Body: #{params.response.body.inspect}"
|
|
246
|
-
end
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
#### Default error handling
|
|
250
|
-
|
|
251
|
-
`my_api_client` では、標準でステータスコード 400 ~ 500 番台のレスポンスを例外として処理するようにしています。ステータスコードが 400 番台場合は `MyApiClient::ClientError`、 500 番台の場合は `MyApiClient::ServerError` を継承した例外クラスが raise されます。
|
|
252
|
-
|
|
253
|
-
また、 `MyApiClient::NetworkError` に対しても標準で `retry_on` が定義されています。
|
|
254
|
-
|
|
255
|
-
いずれも override 可能ですので、必要に応じて `error_handling` を定義して下さい。
|
|
256
|
-
|
|
257
|
-
以下のファイルで定義しています。
|
|
258
|
-
|
|
259
|
-
https://github.com/ryz310/my_api_client/blob/master/lib/my_api_client/default_error_handlers.rb
|
|
260
|
-
|
|
261
|
-
#### Symbol を利用する
|
|
262
|
-
|
|
263
|
-
```ruby
|
|
264
|
-
error_handling json: { '$.errors.code': :negative? }
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
実験的な機能ですが、`status` や `json` の Value に `Symbol` を指定することで、結果値に対してメソッド呼び出しを行い、結果を判定させる事ができます。上記の場合、以下のような JSON にマッチします。なお、対象 Object に `#negative?` が存在しない場合はメソッドは呼び出されません。
|
|
268
|
-
|
|
269
|
-
```json
|
|
270
|
-
{
|
|
271
|
-
"erros": {
|
|
272
|
-
"code": -1,
|
|
273
|
-
"message": "Some error has occurred."
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
#### forbid_nil
|
|
279
|
-
|
|
280
|
-
```ruby
|
|
281
|
-
error_handling status_code: 200, json: :forbid_nil
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
一部のサービスではサーバーから何らかの Response Body が返ってくる事を期待しているにも関わらず、空の結果が結果が返ってくるというケースがあるようです。こちらも実験的な機能ですが、そういったケースを検出するために `json: :forbid_nil` オプションを用意しました。通常の場合、Response Body が空の場合はエラー判定をしませんが、このオプションを指定するとエラーとして検知する様になります。正常応答が空となる API も存在するので、誤検知にご注意下さい。
|
|
285
|
-
|
|
286
|
-
#### MyApiClient::Params::Params
|
|
287
|
-
|
|
288
|
-
WIP
|
|
289
|
-
|
|
290
|
-
#### MyApiClient::Error
|
|
291
|
-
|
|
292
|
-
API リクエストのレスポンスが `error_handling` で定義した matcher に合致した場合、 `raise` で指定した例外処理が発生します。この例外クラスは `MyApiClient::Error` を継承している必要があります。
|
|
293
|
-
|
|
294
|
-
この例外クラスには `#params` というメソッドが存在し、リクエストやレスポンスのパラメータを参照することが出来ます。
|
|
295
|
-
|
|
296
|
-
```ruby
|
|
297
|
-
begin
|
|
298
|
-
api_client.request
|
|
299
|
-
rescue MyApiClient::Error => e
|
|
300
|
-
e.params.inspect
|
|
301
|
-
# => {
|
|
302
|
-
# :request=>"#<MyApiClient::Params::Request#inspect>",
|
|
303
|
-
# :response=>"#<Sawyer::Response#inspect>",
|
|
304
|
-
# }
|
|
305
|
-
end
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
#### Bugsnag breadcrumbs
|
|
309
|
-
|
|
310
|
-
[Bugsnag-Ruby v6.11.0](https://github.com/bugsnag/bugsnag-ruby/releases/tag/v6.11.0) 以降を利用している場合は [breadcrumbs 機能](https://docs.bugsnag.com/platforms/ruby/other/#logging-breadcrumbs) が自動的にサポートされます。この機能によって `MyApiClient::Error` 発生時に内部で `Bugsnag.leave_breadcrumb` が呼び出され、 Bugsnag のコンソールからエラー発生時のリクエスト情報、レスポンス情報などが確認できるようになります。
|
|
311
|
-
|
|
312
|
-
### Retry
|
|
313
|
-
|
|
314
|
-
次に `MyApiClient` が提供するリトライ機能についてご紹介致します。
|
|
315
|
-
|
|
316
|
-
```ruby
|
|
317
|
-
class ExampleApiClient < MyApiClient::Base
|
|
318
|
-
endpoint 'https://example.com'
|
|
319
|
-
|
|
320
|
-
retry_on MyApiClient::NetworkError, wait: 0.1.seconds, attempts: 3
|
|
321
|
-
retry_on MyApiClient::ApiLimitError, wait: 30.seconds, attempts: 3
|
|
322
|
-
|
|
323
|
-
error_handling json: { '$.errors.code': 20 }, raise: MyApiClient::ApiLimitError
|
|
324
|
-
end
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
API リクエストを何度も実行していると回線の不調などによりネットワークエラーが発生する事があります。長時間ネットワークが使えなくなるケースもありますが、瞬間的なエラーであるケースも多々あります。 `MyApiClient` ではネットワーク系の例外はまとめて `MyApiClient::NetworkError` として `raise` されます。この例外の詳細は後述しますが、 `retry_on` を利用する事で、 `ActiveJob` のように任意の例外処理を補足して、一定回数、一定の期間を空けて API リクエストをリトライさせる事ができます。
|
|
328
|
-
|
|
329
|
-
なお、 `retry_on MyApiClient::NetworkError` は標準実装されているため、特別に定義せずとも自動的に適用されます。 `wait` や `attempts` に任意の値を設定したい場合のみ定義してご利用ください。
|
|
330
|
-
|
|
331
|
-
ただし、 `ActiveJob` とは異なり同期処理でリトライするため、ネットワークの瞬断に備えたリトライ以外ではあまり使う機会はないのではないかと思います。上記の例のように API Rate Limit に備えてリトライするケースもあるかと思いますが、こちらは `ActiveJob` で対応した方が良いかもしれません。
|
|
332
|
-
|
|
333
|
-
ちなみに一応 `discard_on` も実装していますが、作者自身が有効な用途を見出せていないので、詳細は割愛します。良い利用方法があれば教えてください。
|
|
334
|
-
|
|
335
|
-
#### 便利な使い方
|
|
336
|
-
|
|
337
|
-
`error_handling` に `retry` オプションを付与する事で `retry_on` の定義を省略できます。
|
|
338
|
-
例えば以下の 2 つのコードは同じ意味になります。
|
|
339
|
-
|
|
340
|
-
```ruby
|
|
341
|
-
retry_on MyApiClient::ApiLimitError, wait: 30.seconds, attempts: 3
|
|
342
|
-
error_handling json: { '$.errors.code': 20 },
|
|
343
|
-
raise: MyApiClient::ApiLimitError
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
```ruby
|
|
347
|
-
error_handling json: { '$.errors.code': 20 },
|
|
348
|
-
raise: MyApiClient::ApiLimitError,
|
|
349
|
-
retry: { wait: 30.seconds, attempts: 3 }
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
`retry_on` で `wait` や `attempts` を指定する必要がない場合は `retry: true` という記述で動作します。
|
|
353
|
-
|
|
354
|
-
```ruby
|
|
355
|
-
error_handling json: { '$.errors.code': 20 },
|
|
356
|
-
raise: MyApiClient::ApiLimitError,
|
|
357
|
-
retry: true
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
`retry` オプションを使用する際は以下の点に注意が必要です。
|
|
361
|
-
|
|
362
|
-
- `error_handling` に `raise` オプションの指定が必須となります。
|
|
363
|
-
- Block を使った `error_handling` の定義は禁止されます。
|
|
364
|
-
|
|
365
|
-
#### MyApiClient::NetworkError
|
|
366
|
-
|
|
367
|
-
前述の通りですが、 `MyApiClient` ではネットワーク系の例外はまとめて `MyApiClient::NetworkError` として `raise` されます。他の例外と同じく `MyApiClient::Error` を親クラスとしています。 `MyApiClient::NetworkError` として扱われる例外クラスの一覧は `MyApiClient::NETWORK_ERRORS` で参照できます。また、元となった例外は `#original_error` で参照できます。
|
|
368
|
-
|
|
369
|
-
```ruby
|
|
370
|
-
begin
|
|
371
|
-
api_client.request
|
|
372
|
-
rescue MyApiClient::NetworkError => e
|
|
373
|
-
e.original_error # => #<Net::OpenTimeout>
|
|
374
|
-
e.params.response # => nil
|
|
375
|
-
end
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
なお、通常の例外はリクエストの結果によって発生しますが、この例外はリクエスト中に発生するため、例外インスタンスにレスポンスパラメータは含まれません。
|
|
379
|
-
|
|
380
|
-
### Timeout
|
|
381
|
-
|
|
382
|
-
WIP
|
|
383
|
-
|
|
384
|
-
### Logger
|
|
385
|
-
|
|
386
|
-
WIP
|
|
387
|
-
|
|
388
|
-
## One request for one class
|
|
389
|
-
|
|
390
|
-
多くの場合、同一ホストの API は リクエストヘッダーやエラー情報が同じ構造になっているため、上記のように一つのクラス内に複数の API を定義する設計が理にかなっていますが、 API 毎に個別に定義したい場合は、以下のように 1 つのクラスに 1 の API という構造で設計することも可能です。
|
|
391
|
-
|
|
392
|
-
```ruby
|
|
393
|
-
class ExampleApiClient < MyApiClient::Base
|
|
394
|
-
endpoint 'https://example.com'
|
|
395
|
-
|
|
396
|
-
error_handling status_code: 400..599
|
|
397
|
-
|
|
398
|
-
attr_reader :access_token
|
|
399
|
-
|
|
400
|
-
def initialize(access_token:)
|
|
401
|
-
@access_token = access_token
|
|
402
|
-
end
|
|
403
|
-
|
|
404
|
-
private
|
|
405
|
-
|
|
406
|
-
def headers
|
|
407
|
-
{
|
|
408
|
-
'Content-Type': 'application/json;charset=UTF-8',
|
|
409
|
-
'Authorization': "Bearer #{access_token}",
|
|
410
|
-
}
|
|
411
|
-
end
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
class GetUsersApiClient < ExampleApiClient
|
|
415
|
-
error_handling json: { '$.errors.code': 10 }, raise: MyApiClient::ClientError
|
|
416
|
-
|
|
417
|
-
# GET https://example.com/users
|
|
418
|
-
#
|
|
419
|
-
# @return [Sawyer::Resource] HTTP resource parameter
|
|
420
|
-
def request
|
|
421
|
-
get 'users', query: { key: 'value' }, headers: headers
|
|
422
|
-
end
|
|
423
|
-
end
|
|
424
|
-
|
|
425
|
-
class PostUserApiClient < ExampleApiClient
|
|
426
|
-
error_handling json: { '$.errors.code': 10 }, raise: MyApiClient::ApiLimitError
|
|
427
|
-
|
|
428
|
-
# POST https://example.com/users
|
|
429
|
-
#
|
|
430
|
-
# @param name [String] Username which want to create
|
|
431
|
-
# @return [Sawyer::Resource] HTTP resource parameter
|
|
432
|
-
def request(name:)
|
|
433
|
-
post 'users', headers: headers, body: { name: name }
|
|
434
|
-
end
|
|
435
|
-
end
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
## RSpec
|
|
439
|
-
|
|
440
|
-
### Setup
|
|
441
|
-
|
|
442
|
-
RSpec を使ったテストをサポートしています。
|
|
443
|
-
以下のコードを `spec/spec_helper.rb` (または `spec/rails_helper.rb`) に追記して下さい。
|
|
444
|
-
|
|
445
|
-
```ruby
|
|
446
|
-
require 'my_api_client/rspec'
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
### Testing
|
|
450
|
-
|
|
451
|
-
以下のような `ApiClient` を定義しているとします。
|
|
452
|
-
|
|
453
|
-
```ruby
|
|
454
|
-
class ExampleApiClient < MyApiClient::Base
|
|
455
|
-
endpoint 'https://example.com/v1'
|
|
456
|
-
|
|
457
|
-
error_handling status_code: 200, json: { '$.errors.code': 10 },
|
|
458
|
-
raise: MyApiClient::ClientError
|
|
459
|
-
|
|
460
|
-
attr_reader :access_token
|
|
461
|
-
|
|
462
|
-
def initialize(access_token:)
|
|
463
|
-
@access_token = access_token
|
|
464
|
-
end
|
|
465
|
-
|
|
466
|
-
# GET https://example.com/v1/users
|
|
467
|
-
def get_users(condition:)
|
|
468
|
-
get 'users', headers: headers, query: { search: condition }
|
|
469
|
-
end
|
|
470
|
-
|
|
471
|
-
private
|
|
472
|
-
|
|
473
|
-
def headers
|
|
474
|
-
{
|
|
475
|
-
'Content-Type': 'application/json;charset=UTF-8',
|
|
476
|
-
'Authorization': "Bearer #{access_token}",
|
|
477
|
-
}
|
|
478
|
-
end
|
|
479
|
-
end
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
通常の場合 `ApiClient` を新たに定義した際にテストすべき項目が 2 つあります。
|
|
483
|
-
|
|
484
|
-
1. 指定したエンドポイントに対して、任意のパラメータを使ったリクエストが実行されること
|
|
485
|
-
2. 特定のレスポンスに対して適切にエラーハンドリングが実行されること
|
|
486
|
-
|
|
487
|
-
`my_api_client` ではこれらをテストするための Custom Matcher を用意しています。
|
|
488
|
-
|
|
489
|
-
#### 1. 指定したエンドポイントに対して、任意のパラメータを使ったリクエストが実行されること
|
|
490
|
-
|
|
491
|
-
例えば上述の `#get_users` の内部では、入力引数を用いて検索クエリが組み立てられていたり、 Header に `access_token` を利用したりしています。これらの値が正しくリクエストに用いられているかどうかのテストが必要となります。
|
|
492
|
-
|
|
493
|
-
この場合 `request_to` と `with` という Custom Matcher を利用することで簡単にテストを記述することが出来ます。 `expect` にはブロック `{}` を指定する必要がある点にご注意ください。他にも `with` には `body` というキーワード引数も指定できます。
|
|
494
|
-
|
|
495
|
-
```ruby
|
|
496
|
-
RSpec.describe ExampleApiClient, type: :api_client do
|
|
497
|
-
let(:api_client) { described_class.new(access_token: 'access token') }
|
|
498
|
-
let(:headers) do
|
|
499
|
-
{
|
|
500
|
-
'Content-Type': 'application/json;charset=UTF-8',
|
|
501
|
-
'Authorization': 'Bearer access token',
|
|
502
|
-
}
|
|
503
|
-
end
|
|
504
|
-
|
|
505
|
-
describe '#get_users' do
|
|
506
|
-
it do
|
|
507
|
-
expect { api_client.get_users(condition: 'condition') }
|
|
508
|
-
.to request_to(:get, 'https://example.com/v1/users')
|
|
509
|
-
.with(headers: headers, query: { condition: 'condition' })
|
|
510
|
-
end
|
|
511
|
-
end
|
|
512
|
-
end
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
#### 2. 特定のレスポンスに対して適切にエラーハンドリングが実行されること
|
|
516
|
-
|
|
517
|
-
次に `error_handling` についてのテストも記述していきます。ここではレスポンスのステータスコードが `200` かつ Body に `'$.errors.code': 10` という値が含まれていた場合は `MyApiClient::ClientError` を `raise` する、というエラーハンドリングが定義されています。
|
|
518
|
-
|
|
519
|
-
ここでは `be_handled_as_an_error` と `when_receive` という Custom Matcher を利用します。ここでも `expect` にはブロック `{}` を指定する必要がある点にご注意ください。
|
|
520
|
-
|
|
521
|
-
`be_handled_as_an_error` の引数には期待する例外クラスを指定します。 `when_receive` にはリクエスト結果としてどのような値が返ってきたのかを指定します。
|
|
522
|
-
|
|
523
|
-
なお、 `error_handling` で例外を発生させないケースは現在想定していないため、これ以外の Custom Matcher は定義されていません。何かユースケースがあれば教えて下さい。
|
|
524
|
-
|
|
525
|
-
```ruby
|
|
526
|
-
it do
|
|
527
|
-
expect { api_client.get_users(condition: 'condition') }
|
|
528
|
-
.to be_handled_as_an_error(MyApiClient::ClientError)
|
|
529
|
-
.when_receive(status_code: 200, body: { errors: { code: 10 } }.to_json)
|
|
530
|
-
end
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
また、以下のように正常なレスポンスが返ってきた時に誤ってエラーハンドリングされていないかをテストすることもできます。
|
|
534
|
-
|
|
535
|
-
```ruby
|
|
536
|
-
it do
|
|
537
|
-
expect { api_client.get_users(condition: 'condition') }
|
|
538
|
-
.not_to be_handled_as_an_error(MyApiClient::ClientError)
|
|
539
|
-
.when_receive(status_code: 200, body: { users: { id: 1 } }.to_json)
|
|
540
|
-
end
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
##### `retry_on` を定義している場合
|
|
544
|
-
|
|
545
|
-
以下のように `retry_on` を API Client に定義している場合:
|
|
546
|
-
|
|
547
|
-
```ruby
|
|
548
|
-
class ExampleApiClient < MyApiClient::Base
|
|
549
|
-
endpoint 'https://example.com'
|
|
550
|
-
|
|
551
|
-
error_handling json: { '$.errors.code': 20 }, raise: MyApiClient::ApiLimitError
|
|
552
|
-
retry_on MyApiClient::ApiLimitError, wait: 30.seconds, attempts: 3
|
|
553
|
-
end
|
|
554
|
-
```
|
|
555
|
-
|
|
556
|
-
`after_retry` と `times` という Custom Matcher を利用することが出来ます。
|
|
557
|
-
|
|
558
|
-
```ruby
|
|
559
|
-
it do
|
|
560
|
-
expect { api_client.get_users(condition: 'condition') }
|
|
561
|
-
.to be_handled_as_an_error(MyApiClient::ApiLimitError)
|
|
562
|
-
.after_retry(3).times
|
|
563
|
-
.when_receive(status_code: 200, body: { errors: { code: 20 } }.to_json)
|
|
564
|
-
end
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
### Stubbing
|
|
568
|
-
|
|
569
|
-
#### `response` option
|
|
570
|
-
|
|
571
|
-
以下のような `ApiClient` を定義しているとします。
|
|
572
|
-
|
|
573
|
-
```ruby
|
|
574
|
-
class ExampleApiClient < MyApiClient::Base
|
|
575
|
-
endpoint 'https://example.com'
|
|
576
|
-
|
|
577
|
-
def request(user_id:)
|
|
578
|
-
get "users/#{user_id}"
|
|
579
|
-
end
|
|
580
|
-
end
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
`stub_api_client_all` や `stub_api_client` を使うことで、 `ExampleApiClient#request` をスタブ化することができます。これで `#request` を実行してもリアルな HTTP リクエストが実行されなくなります。
|
|
584
|
-
|
|
585
|
-
```ruby
|
|
586
|
-
stub_api_client_all(
|
|
587
|
-
ExampleApiClient,
|
|
588
|
-
request: { response: { id: 12345 } }
|
|
589
|
-
)
|
|
590
|
-
|
|
591
|
-
response = ExampleApiClient.new.request(user_id: 1)
|
|
592
|
-
response.id # => 12345
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
`response` は省略することも可能です。
|
|
596
|
-
|
|
597
|
-
```ruby
|
|
598
|
-
stub_api_client_all(
|
|
599
|
-
ExampleApiClient,
|
|
600
|
-
request: { id: 12345 }
|
|
601
|
-
)
|
|
602
|
-
|
|
603
|
-
response = ExampleApiClient.new.request(user_id: 1)
|
|
604
|
-
response.id # => 12345
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
#### Proc
|
|
608
|
-
|
|
609
|
-
リクスエストパラメータを使ったレスポンスを返すようにスタブ化したい場合は、 `Proc` を利用することで実現できます。
|
|
610
|
-
|
|
611
|
-
```ruby
|
|
612
|
-
stub_api_client_all(
|
|
613
|
-
ExampleApiClient,
|
|
614
|
-
request: ->(params) { { id: params[:user_id] } }
|
|
615
|
-
)
|
|
616
|
-
|
|
617
|
-
response = ExampleApiClient.new.request(user_id: 1)
|
|
618
|
-
response.id # => 1
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
#### Return value of `#stub_api_client_all` and `#stub_api_client`
|
|
622
|
-
|
|
623
|
-
`#stub_api_client_all` や `#stub_api_client` の戻り値はスタブ化した API Client のスタブオブジェクトです。`receive` や `have_received` を使ったテストを書きたい場合は、これらの値を利用すると良いでしょう。
|
|
624
|
-
|
|
625
|
-
```ruby
|
|
626
|
-
def execute_api_request
|
|
627
|
-
ExampleApiClient.new.request(user_id: 1)
|
|
628
|
-
end
|
|
629
|
-
|
|
630
|
-
api_clinet = stub_api_client_all(ExampleApiClient, request: nil)
|
|
631
|
-
execute_api_request
|
|
632
|
-
expect(api_client).to have_received(:request).with(user_id: 1)
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
#### `raise` option
|
|
636
|
-
|
|
637
|
-
例外が発生する場合のテストを書きたい場合は、 `raise` オプションを利用することができます。
|
|
638
|
-
|
|
639
|
-
```ruby
|
|
640
|
-
def execute_api_request
|
|
641
|
-
ExampleApiClient.new.request(user_id: 1)
|
|
642
|
-
end
|
|
643
|
-
|
|
644
|
-
stub_api_client_all(ExampleApiClient, request: { raise: MyApiClient::Error })
|
|
645
|
-
expect { execute_api_request }.to raise_error(MyApiClient::Error)
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
なお、発生した例外に含まれるレスポンスパラメータやステータスコードもスタブ化したい場合は、 `response` オプションと同時に指定することが可能です。
|
|
649
|
-
|
|
650
|
-
```ruby
|
|
651
|
-
stub_api_client_all(
|
|
652
|
-
ExampleApiClient,
|
|
653
|
-
request: {
|
|
654
|
-
raise: MyApiClient::Error,
|
|
655
|
-
response: { message: 'error' },
|
|
656
|
-
status_code: 429
|
|
657
|
-
}
|
|
658
|
-
)
|
|
659
|
-
|
|
660
|
-
begin
|
|
661
|
-
ExampleApiClient.new.request(user_id: 1)
|
|
662
|
-
rescue MyApiClient::Error => e
|
|
663
|
-
response_body = e.params.response.data.to_h
|
|
664
|
-
expect(response_body).to eq(message: 'error')
|
|
665
|
-
status_code = e.params.response.status
|
|
666
|
-
expect(status_code).to eq(429)
|
|
667
|
-
end
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
#### `pageable` option
|
|
671
|
-
|
|
672
|
-
`#pageable_get` (`#pget`) を使った実装用に `pageable` というオプションが利用できます。
|
|
673
|
-
`pageable` に設定する値は `Enumerable` である必要があります。
|
|
674
|
-
|
|
675
|
-
```ruby
|
|
676
|
-
stub_api_client_all(
|
|
677
|
-
MyPaginationApiClient,
|
|
678
|
-
pagination: {
|
|
679
|
-
pageable: [
|
|
680
|
-
{ page: 1 },
|
|
681
|
-
{ page: 2 },
|
|
682
|
-
{ page: 3 },
|
|
683
|
-
],
|
|
684
|
-
}
|
|
685
|
-
)
|
|
686
|
-
|
|
687
|
-
MyPaginationApiClient.new.pagination.each do |response|
|
|
688
|
-
response.page #=> 1, 2, 3
|
|
689
|
-
end
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
なお、 `Enumerable` の各値にはここまで紹介した `response`, `raise`, `Proc` など全てのオプションが利用可能です。
|
|
693
|
-
|
|
694
|
-
```ruby
|
|
695
|
-
stub_api_client_all(
|
|
696
|
-
MyPaginationApiClient,
|
|
697
|
-
pagination: {
|
|
698
|
-
pageable: [
|
|
699
|
-
{ response: { page: 1 } },
|
|
700
|
-
{ page: 2 },
|
|
701
|
-
->(params) { { page: 3, user_id: params[:user_id] } },
|
|
702
|
-
{ raise: MyApiClient::ClientError::IamTeapot },
|
|
703
|
-
],
|
|
704
|
-
}
|
|
705
|
-
)
|
|
706
|
-
```
|
|
707
|
-
|
|
708
|
-
また、 `Enumerator` を使えば無限に続くページネーションを定義することもできます。
|
|
709
|
-
|
|
710
|
-
```ruby
|
|
711
|
-
stub_api_client_all(
|
|
712
|
-
MyPaginationApiClient,
|
|
713
|
-
pagination: {
|
|
714
|
-
pageable: Enumerator.new do |y|
|
|
715
|
-
loop.with_index(1) do |_, i|
|
|
716
|
-
y << { page: i }
|
|
717
|
-
end
|
|
718
|
-
end,
|
|
719
|
-
}
|
|
720
|
-
)
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
## Deployment
|
|
724
|
-
|
|
725
|
-
この gem のリリースには [gem_comet](https://github.com/ryz310/gem_comet) を利用しています。
|
|
726
|
-
`gem_comet` の README.md にも使い方が載っていますが、備忘録のため、こちらにもリリースフローを記載しておきます。
|
|
727
|
-
|
|
728
|
-
### Preparement
|
|
729
|
-
|
|
730
|
-
以下のコマンドで `.envrc` を作成し、 `GITHUB_ACCESS_TOKEN` を設定します。
|
|
731
|
-
|
|
732
|
-
```sh
|
|
733
|
-
$ cp .envrc.skeleton .envrc
|
|
734
|
-
```
|
|
735
|
-
|
|
736
|
-
以下のコマンドで `gem_comet` をインストールします。
|
|
737
|
-
|
|
738
|
-
```sh
|
|
739
|
-
$ gem install gem_comet
|
|
740
|
-
```
|
|
741
|
-
|
|
742
|
-
### USAGE
|
|
743
|
-
|
|
744
|
-
以下のコマンドで、最後のリリースから現在までに merge した PR の一覧を確認できます。
|
|
745
|
-
|
|
746
|
-
```sh
|
|
747
|
-
$ gem_comet changelog
|
|
748
|
-
```
|
|
749
|
-
|
|
750
|
-
以下のコマンドで gem のリリースを実行します。
|
|
751
|
-
`{VERSION}` には新しく付与するバージョン番号を指定します。
|
|
752
|
-
|
|
753
|
-
```sh
|
|
754
|
-
$ gem_comet release {VERSION}
|
|
755
|
-
```
|
|
756
|
-
|
|
757
|
-
実行すると、 https://github.com/ryz310/my_api_client/pulls に以下のような PR が作成されます。
|
|
758
|
-
|
|
759
|
-
- [Update v0\.16\.1](https://github.com/ryz310/my_api_client/pull/297)
|
|
760
|
-
- [Release v0\.16\.1](https://github.com/ryz310/my_api_client/pull/298)
|
|
761
|
-
|
|
762
|
-
まず、 `Update v{VERSION}` という PR から merge に取り掛かります。
|
|
763
|
-
|
|
764
|
-
PR のコメントにも TODO が記載されていますが、まず、バージョン番号が正しく採番されているかを確認します。
|
|
765
|
-
|
|
766
|
-
See: [314a4c0](https://github.com/ryz310/my_api_client/pull/297/commits/314a4c06f66324ce77b640b1ee8db5c84ee038a2)
|
|
767
|
-
|
|
768
|
-
次に `CHANGELOG.md` を編集して、 CHANGELOG を見やすく整理します。
|
|
769
|
-
|
|
770
|
-
See: [33a2d17](https://github.com/ryz310/my_api_client/pull/297/commits/33a2d1703c773813c837e74ee3181906b2f2e502)
|
|
771
|
-
|
|
772
|
-
これらが整ったら、 `Update v{VERSION}` を merge します。
|
|
773
|
-
|
|
774
|
-
これでリリース準備が整ったので、`Release v{VERSION}` の merge に取り掛かります。
|
|
775
|
-
|
|
776
|
-
この PR にこれからリリースする gem に対する変更が全て載っています。
|
|
777
|
-
変更内容の最終確認をして、 CI も通ったことを確認したら `Release v{VERSION}` を merge します。
|
|
778
|
-
|
|
779
|
-
あとは Circle CI 側で gem のリリースが自動実行されるので、暫く待ちましょう。
|
|
780
|
-
|
|
781
|
-
お疲れさまでした :tea:
|
|
782
|
-
|
|
783
|
-
## Contributing
|
|
784
|
-
|
|
785
|
-
不具合の報告や Pull Request を歓迎しています。OSS という事で自分はなるべく頑張って英語を使うようにしていますが、日本語での報告でも大丈夫です :+1:
|