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.md
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
[](https://github.com/ryz310/my_api_client/actions/workflows/ci.yml)
|
|
2
2
|
[](https://badge.fury.io/rb/my_api_client)
|
|
3
|
-
[](https://qlty.sh/gh/ryz310/projects/my_api_client)
|
|
4
|
+
[](https://qlty.sh/gh/ryz310/projects/my_api_client)
|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
日本語ドキュメントは [こちら](README.jp.md)
|
|
8
|
-
|
|
9
7
|
# MyApiClient
|
|
10
8
|
|
|
11
|
-
This gem is an API client builder
|
|
9
|
+
This gem is an API client builder that provides generic functionality for defining API request classes. Its architecture is based on [Sawyer](https://github.com/lostisland/sawyer) and [Faraday](https://github.com/lostisland/faraday), with enhanced error-handling features.
|
|
10
|
+
|
|
11
|
+
Sawyer can be difficult for generating dummy data and may conflict with other gems in some cases, so this project may reduce direct Sawyer dependency in the future.
|
|
12
12
|
|
|
13
|
-
It is
|
|
13
|
+
It is primarily designed for Ruby on Rails, but it also works in other environments. If you find any issues, please report them on the Issues page.
|
|
14
14
|
|
|
15
15
|
[toc]
|
|
16
16
|
|
|
17
17
|
## Supported Versions
|
|
18
18
|
|
|
19
|
-
- Ruby 3.
|
|
20
|
-
- Rails
|
|
19
|
+
- Ruby 3.2, 3.3, 3.4, 4.0
|
|
20
|
+
- Rails 7.2, 8.0, 8.1
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
23
23
|
|
|
@@ -27,7 +27,7 @@ Add this line to your application's Gemfile:
|
|
|
27
27
|
gem 'my_api_client'
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
If you are using Ruby on Rails, you can use the
|
|
30
|
+
If you are using Ruby on Rails, you can use the generator.
|
|
31
31
|
|
|
32
32
|
```sh
|
|
33
33
|
$ rails g api_client path/to/resource get:path/to/resource --endpoint https://example.com
|
|
@@ -63,7 +63,7 @@ class ExampleApiClient < MyApiClient::Base
|
|
|
63
63
|
|
|
64
64
|
# POST https://example.com/v1/users
|
|
65
65
|
#
|
|
66
|
-
# @param name [String] Username
|
|
66
|
+
# @param name [String] Username to create
|
|
67
67
|
# @return [Sawyer::Resource] HTTP resource parameter
|
|
68
68
|
def post_user(name:)
|
|
69
69
|
post 'users', headers: headers, body: { name: name }
|
|
@@ -79,21 +79,21 @@ class ExampleApiClient < MyApiClient::Base
|
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
api_client = ExampleApiClient.new(access_token: 'access_token')
|
|
83
|
+
api_client.get_users #=> #<Sawyer::Resource>
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
`endpoint` defines the base URL for requests. Each method then adds a relative path. In the example above, `get 'users'` sends `GET https://example.com/v1/users`.
|
|
87
87
|
|
|
88
|
-
Next, define `#initialize
|
|
88
|
+
Next, define `#initialize` to set values such as an access token or API key. You can omit it if you do not need any instance state.
|
|
89
89
|
|
|
90
|
-
Then define `#get_users` and `#post_user`.
|
|
90
|
+
Then define methods such as `#get_users` and `#post_user`. Inside those methods, call HTTP helpers like `#get` and `#post`. You can also use `#patch`, `#put`, and `#delete`.
|
|
91
91
|
|
|
92
92
|
### Pagination
|
|
93
93
|
|
|
94
|
-
Some APIs include a URL
|
|
94
|
+
Some APIs include a URL for the next page in the response.
|
|
95
95
|
|
|
96
|
-
MyApiClient provides
|
|
96
|
+
MyApiClient provides `#pageable_get` to treat such APIs as an enumerable. An example is shown below:
|
|
97
97
|
|
|
98
98
|
```ruby
|
|
99
99
|
class MyPaginationApiClient < ApplicationApiClient
|
|
@@ -112,9 +112,9 @@ class MyPaginationApiClient < ApplicationApiClient
|
|
|
112
112
|
end
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
In the above
|
|
115
|
+
In the example above, the first request is `GET https://example.com/v1/pagination?page=1`. It then continues requesting the URL in `$.links.next` from each response.
|
|
116
116
|
|
|
117
|
-
For example, in the
|
|
117
|
+
For example, in the response below, `$.links.next` points to `"https://example.com/pagination?page=3"`:
|
|
118
118
|
|
|
119
119
|
```json
|
|
120
120
|
{
|
|
@@ -126,15 +126,15 @@ For example, in the following response, `$.link.next` indicates `"https://exampl
|
|
|
126
126
|
}
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
-
`#pageable_get` returns [Enumerator::Lazy](https://docs.ruby-lang.org/ja/latest/class/Enumerator=3a=3aLazy.html), so you can
|
|
129
|
+
`#pageable_get` returns [Enumerator::Lazy](https://docs.ruby-lang.org/ja/latest/class/Enumerator=3a=3aLazy.html), so you can iterate using `#each` or `#next`:
|
|
130
130
|
|
|
131
131
|
```ruby
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
api_client = MyPaginationApiClient.new
|
|
133
|
+
api_client.pagination.each do |response|
|
|
134
134
|
# Do something.
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
-
result =
|
|
137
|
+
result = api_client.pagination
|
|
138
138
|
result.next # => 1st page result
|
|
139
139
|
result.next # => 2nd page result
|
|
140
140
|
result.next # => 3rd page result
|
|
@@ -153,7 +153,7 @@ end
|
|
|
153
153
|
|
|
154
154
|
### Error handling
|
|
155
155
|
|
|
156
|
-
MyApiClient
|
|
156
|
+
MyApiClient lets you define error handling rules that raise exceptions based on response content. For example:
|
|
157
157
|
|
|
158
158
|
```ruby
|
|
159
159
|
class ExampleApiClient < MyApiClient::Base
|
|
@@ -174,7 +174,7 @@ class ExampleApiClient < MyApiClient::Base
|
|
|
174
174
|
|
|
175
175
|
private
|
|
176
176
|
|
|
177
|
-
# @param params [MyApiClient::Params::Params] HTTP
|
|
177
|
+
# @param params [MyApiClient::Params::Params] HTTP request and response params
|
|
178
178
|
# @param logger [MyApiClient::Request::Logger] Logger for a request processing
|
|
179
179
|
def my_error_handling(params, logger)
|
|
180
180
|
logger.warn "Response Body: #{params.response.body.inspect}"
|
|
@@ -182,19 +182,19 @@ class ExampleApiClient < MyApiClient::Base
|
|
|
182
182
|
end
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
-
|
|
185
|
+
Let's go through each option. First, this rule checks `status_code`:
|
|
186
186
|
|
|
187
187
|
```ruby
|
|
188
188
|
error_handling status_code: 400..499, raise: MyApiClient::ClientError
|
|
189
189
|
```
|
|
190
190
|
|
|
191
|
-
This
|
|
191
|
+
This raises `MyApiClient::ClientError` when the response status code is in `400..499` for requests from `ExampleApiClient`. Error handling rules are also inherited by child classes.
|
|
192
192
|
|
|
193
|
-
|
|
193
|
+
You can specify `Integer`, `Range`, or `Regexp` for `status_code`.
|
|
194
194
|
|
|
195
|
-
A class
|
|
195
|
+
A class inheriting from `MyApiClient::Error` can be specified for `raise`. See [here](https://github.com/ryz310/my_api_client/blob/master/lib/my_api_client/errors) for built-in error classes. If `raise` is omitted, `MyApiClient::Error` is raised.
|
|
196
196
|
|
|
197
|
-
Next,
|
|
197
|
+
Next, here is an example using a block:
|
|
198
198
|
|
|
199
199
|
```ruby
|
|
200
200
|
error_handling status_code: 500..599, raise: MyApiClient::ServerError do |_params, logger|
|
|
@@ -202,38 +202,38 @@ error_handling status_code: 500..599, raise: MyApiClient::ServerError do |_param
|
|
|
202
202
|
end
|
|
203
203
|
```
|
|
204
204
|
|
|
205
|
-
In
|
|
205
|
+
In this example, when the status code is `500..599`, the block runs before `MyApiClient::ServerError` is raised. The `params` argument includes both request and response information.
|
|
206
206
|
|
|
207
|
-
`logger` is
|
|
207
|
+
`logger` is a request-scoped logger. If you log with this instance, request information is automatically included, which is useful for debugging:
|
|
208
208
|
|
|
209
209
|
```text
|
|
210
|
-
API request `GET https://example.com/path/to/
|
|
210
|
+
API request `GET https://example.com/path/to/resource`: "Server error occurred."
|
|
211
211
|
```
|
|
212
212
|
|
|
213
213
|
```ruby
|
|
214
214
|
error_handling json: { '$.errors.code': 10..19 }, with: :my_error_handling
|
|
215
215
|
```
|
|
216
216
|
|
|
217
|
-
For `json`,
|
|
217
|
+
For `json`, use [JSONPath](https://goessner.net/articles/JsonPath/) as the hash key, extract values from response JSON, and match them against expected values. You can specify `String`, `Integer`, `Range`, or `Regexp` as matcher values.
|
|
218
218
|
|
|
219
|
-
In
|
|
219
|
+
In this case, it matches JSON such as:
|
|
220
220
|
|
|
221
221
|
```json
|
|
222
222
|
{
|
|
223
|
-
"
|
|
223
|
+
"errors": {
|
|
224
224
|
"code": 10,
|
|
225
225
|
"message": "Some error has occurred."
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
```
|
|
229
229
|
|
|
230
|
-
For `headers`, specify response
|
|
230
|
+
For `headers`, specify a response-header key and match its value. You can specify `String` or `Regexp` as matcher values.
|
|
231
231
|
|
|
232
232
|
```ruby
|
|
233
233
|
error_handling headers: { 'www-authenticate': /invalid token/ }, with: :my_error_handling
|
|
234
234
|
```
|
|
235
235
|
|
|
236
|
-
In
|
|
236
|
+
In this case, it matches response headers such as:
|
|
237
237
|
|
|
238
238
|
```text
|
|
239
239
|
cache-control: no-cache, no-store, max-age=0, must-revalidate
|
|
@@ -242,7 +242,7 @@ www-authenticate: Bearer error="invalid_token", error_description="invalid token
|
|
|
242
242
|
content-length: 104
|
|
243
243
|
```
|
|
244
244
|
|
|
245
|
-
By specifying
|
|
245
|
+
By specifying an instance method name in `with`, you can run arbitrary logic before raising an exception. The method receives `params` and `logger`, just like a block. Note that `block` and `with` cannot be used together.
|
|
246
246
|
|
|
247
247
|
```ruby
|
|
248
248
|
# @param params [MyApiClient::Params::Params] HTTP req and res params
|
|
@@ -254,7 +254,7 @@ end
|
|
|
254
254
|
|
|
255
255
|
#### Default error handling
|
|
256
256
|
|
|
257
|
-
|
|
257
|
+
By default, MyApiClient treats 4xx and 5xx responses as exceptions. In the 4xx range, it raises an exception class inheriting from `MyApiClient::ClientError`; in the 5xx range, it raises one inheriting from `MyApiClient::ServerError`.
|
|
258
258
|
|
|
259
259
|
Also, `retry_on` is defined by default for `MyApiClient::NetworkError`.
|
|
260
260
|
|
|
@@ -268,7 +268,7 @@ They are defined [here](https://github.com/ryz310/my_api_client/blob/master/lib/
|
|
|
268
268
|
error_handling json: { '$.errors.code': :negative? }
|
|
269
269
|
```
|
|
270
270
|
|
|
271
|
-
|
|
271
|
+
This is an experimental feature. By specifying a `Symbol` as the value for `status` or `json`, MyApiClient calls that method on the extracted value and uses the result for matching. In the example above, it matches the following JSON. If `#negative?` does not exist on the target object, the method is not called.
|
|
272
272
|
|
|
273
273
|
#### forbid_nil
|
|
274
274
|
|
|
@@ -276,15 +276,37 @@ Although it is an experimental function, by specifying `Symbol` for value of `st
|
|
|
276
276
|
error_handling status_code: 200, json: :forbid_nil
|
|
277
277
|
```
|
|
278
278
|
|
|
279
|
-
|
|
279
|
+
Some services expect a non-empty response body but occasionally receive an empty one. This experimental option, `json: :forbid_nil`, helps detect that case. Normally, an empty response body is not treated as an error, but with this option it is. Be careful of false positives, because some APIs intentionally return empty responses.
|
|
280
280
|
|
|
281
281
|
#### MyApiClient::Params::Params
|
|
282
282
|
|
|
283
|
-
|
|
283
|
+
`MyApiClient::Params::Params` is a value object that combines request and response details.
|
|
284
|
+
An instance of this class is passed to error handlers (`block`/`with`) and is also available from `MyApiClient::Error#params`.
|
|
285
|
+
|
|
286
|
+
- `#request`: `MyApiClient::Params::Request` (method, URL, headers, and body)
|
|
287
|
+
- `#response`: `Sawyer::Response` (or `nil` for network errors)
|
|
288
|
+
|
|
289
|
+
It also provides `#metadata` (`#to_bugsnag` alias), which merges request/response data into a single hash for logging and error reporting.
|
|
290
|
+
|
|
291
|
+
```ruby
|
|
292
|
+
begin
|
|
293
|
+
api_client.request
|
|
294
|
+
rescue MyApiClient::Error => e
|
|
295
|
+
e.params.metadata
|
|
296
|
+
# => {
|
|
297
|
+
# request_line: "GET https://example.com/v1/users?search=foo",
|
|
298
|
+
# request_headers: { "Authorization" => "Bearer token" },
|
|
299
|
+
# response_status: 429,
|
|
300
|
+
# response_headers: { "content-type" => "application/json" },
|
|
301
|
+
# response_body: { errors: [{ code: 20 }] },
|
|
302
|
+
# duration: 0.123
|
|
303
|
+
# }
|
|
304
|
+
end
|
|
305
|
+
```
|
|
284
306
|
|
|
285
307
|
#### MyApiClient::Error
|
|
286
308
|
|
|
287
|
-
If
|
|
309
|
+
If an API response matches a rule defined in `error_handling`, the exception class specified in `raise` is triggered. This exception class must inherit from `MyApiClient::Error`.
|
|
288
310
|
|
|
289
311
|
This exception class has a method called `#params`, which allows you to refer to request and response parameters.
|
|
290
312
|
|
|
@@ -302,11 +324,11 @@ end
|
|
|
302
324
|
|
|
303
325
|
#### Bugsnag breadcrumbs
|
|
304
326
|
|
|
305
|
-
If you are using [Bugsnag-Ruby v6.
|
|
327
|
+
If you are using [Bugsnag-Ruby v6.19.0](https://github.com/bugsnag/bugsnag-ruby/releases/tag/v6.19.0) or later, the [breadcrumbs feature](https://docs.bugsnag.com/platforms/ruby/other/#logging-breadcrumbs) is supported automatically. When `MyApiClient::Error` occurs, `Bugsnag.leave_breadcrumb` is called internally, so you can inspect request and response details in the Bugsnag console.
|
|
306
328
|
|
|
307
329
|
### Retry
|
|
308
330
|
|
|
309
|
-
Next,
|
|
331
|
+
Next, let's look at retry support in MyApiClient.
|
|
310
332
|
|
|
311
333
|
```ruby
|
|
312
334
|
class ExampleApiClient < MyApiClient::Base
|
|
@@ -319,13 +341,13 @@ class ExampleApiClient < MyApiClient::Base
|
|
|
319
341
|
end
|
|
320
342
|
```
|
|
321
343
|
|
|
322
|
-
|
|
344
|
+
When an API request is executed many times, network errors can occur. Sometimes the network is unavailable for a long time, but often the error is temporary. In MyApiClient, network-related exceptions are wrapped as `MyApiClient::NetworkError`. Using `retry_on`, you can handle such exceptions and retry requests with configurable wait time and attempt count, similar to `ActiveJob`.
|
|
323
345
|
|
|
324
|
-
|
|
346
|
+
`retry_on MyApiClient::NetworkError` is enabled by default, so you do not need to define it unless you want custom `wait` or `attempts` values.
|
|
325
347
|
|
|
326
|
-
|
|
348
|
+
Unlike `ActiveJob`, retries are performed synchronously. In practice, this is most useful for short-lived network interruptions. You can also retry for API rate limits as in the example above, but handling that with `ActiveJob` may be a better fit depending on your workload.
|
|
327
349
|
|
|
328
|
-
|
|
350
|
+
`discard_on` is also implemented, but details are omitted here because a strong use case has not been identified yet.
|
|
329
351
|
|
|
330
352
|
#### Convenient usage
|
|
331
353
|
|
|
@@ -344,7 +366,7 @@ error_handling json: { '$.errors.code': 20 },
|
|
|
344
366
|
retry: { wait: 30.seconds, attempts: 3 }
|
|
345
367
|
```
|
|
346
368
|
|
|
347
|
-
If you do not need to specify `wait` or`
|
|
369
|
+
If you do not need to specify `wait` or `attempts` in `retry_on`, you can use `retry: true`:
|
|
348
370
|
|
|
349
371
|
```ruby
|
|
350
372
|
error_handling json: { '$.errors.code': 20 },
|
|
@@ -359,7 +381,7 @@ Keep the following in mind when using the `retry` option:
|
|
|
359
381
|
|
|
360
382
|
#### MyApiClient::NetworkError
|
|
361
383
|
|
|
362
|
-
As mentioned above,
|
|
384
|
+
As mentioned above, MyApiClient wraps network exceptions as `MyApiClient::NetworkError`. Like other client errors, its parent class is `MyApiClient::Error`. The list of wrapped exception classes is available in `MyApiClient::NETWORK_ERRORS`. You can inspect the original exception via `#original_error`:
|
|
363
385
|
|
|
364
386
|
```ruby
|
|
365
387
|
begin
|
|
@@ -370,15 +392,99 @@ rescue MyApiClient::NetworkError => e
|
|
|
370
392
|
end
|
|
371
393
|
```
|
|
372
394
|
|
|
373
|
-
|
|
395
|
+
Unlike normal API errors that are raised after receiving a response, this exception is raised during request execution. Therefore, the exception instance does not include response parameters.
|
|
374
396
|
|
|
375
397
|
### Timeout
|
|
376
398
|
|
|
377
|
-
|
|
399
|
+
You can configure HTTP timeout values per API client class:
|
|
400
|
+
|
|
401
|
+
```ruby
|
|
402
|
+
class ApplicationApiClient < MyApiClient::Base
|
|
403
|
+
http_open_timeout 2.seconds
|
|
404
|
+
http_read_timeout 3.seconds
|
|
405
|
+
end
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
- `http_open_timeout`: maximum wait time to open a connection
|
|
409
|
+
- `http_read_timeout`: maximum wait time for each HTTP read
|
|
410
|
+
|
|
411
|
+
Internally, these are passed to Faraday request options as `open_timeout` and `timeout`.
|
|
412
|
+
If a timeout occurs, it is wrapped and raised as `MyApiClient::NetworkError`.
|
|
378
413
|
|
|
379
414
|
### Logger
|
|
380
415
|
|
|
381
|
-
|
|
416
|
+
Each API client class has a configurable logger (`self.logger`).
|
|
417
|
+
By default, MyApiClient uses `Logger.new($stdout)`, and in Rails apps you typically set:
|
|
418
|
+
|
|
419
|
+
```ruby
|
|
420
|
+
class ApplicationApiClient < MyApiClient::Base
|
|
421
|
+
self.logger = Rails.logger
|
|
422
|
+
end
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
MyApiClient wraps this logger with `MyApiClient::Request::Logger` and prefixes messages with request information:
|
|
426
|
+
|
|
427
|
+
```text
|
|
428
|
+
API request `GET https://example.com/v1/users`: "Start"
|
|
429
|
+
API request `GET https://example.com/v1/users`: "Duration 100.0 msec"
|
|
430
|
+
API request `GET https://example.com/v1/users`: "Success (200)"
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
On failure, it logs:
|
|
434
|
+
|
|
435
|
+
```text
|
|
436
|
+
API request `GET https://example.com/v1/users`: "Failure (Net::OpenTimeout)"
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## One request for one class
|
|
440
|
+
|
|
441
|
+
In many cases, APIs on the same host share request headers and error structures, so defining multiple endpoints in one class is practical. If you prefer API-level separation, you can also use a "one class per API" design:
|
|
442
|
+
|
|
443
|
+
```ruby
|
|
444
|
+
class ExampleApiClient < MyApiClient::Base
|
|
445
|
+
endpoint 'https://example.com'
|
|
446
|
+
|
|
447
|
+
error_handling status_code: 400..599
|
|
448
|
+
|
|
449
|
+
attr_reader :access_token
|
|
450
|
+
|
|
451
|
+
def initialize(access_token:)
|
|
452
|
+
@access_token = access_token
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
private
|
|
456
|
+
|
|
457
|
+
def headers
|
|
458
|
+
{
|
|
459
|
+
'Content-Type': 'application/json;charset=UTF-8',
|
|
460
|
+
'Authorization': "Bearer #{access_token}",
|
|
461
|
+
}
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
class GetUsersApiClient < ExampleApiClient
|
|
466
|
+
error_handling json: { '$.errors.code': 10 }, raise: MyApiClient::ClientError
|
|
467
|
+
|
|
468
|
+
# GET https://example.com/users
|
|
469
|
+
#
|
|
470
|
+
# @return [Sawyer::Resource] HTTP resource parameter
|
|
471
|
+
def request
|
|
472
|
+
get 'users', query: { key: 'value' }, headers: headers
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
class PostUserApiClient < ExampleApiClient
|
|
477
|
+
error_handling json: { '$.errors.code': 10 }, raise: MyApiClient::ApiLimitError
|
|
478
|
+
|
|
479
|
+
# POST https://example.com/users
|
|
480
|
+
#
|
|
481
|
+
# @param name [String] Username to create
|
|
482
|
+
# @return [Sawyer::Resource] HTTP resource parameter
|
|
483
|
+
def request(name:)
|
|
484
|
+
post 'users', headers: headers, body: { name: name }
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
```
|
|
382
488
|
|
|
383
489
|
## RSpec
|
|
384
490
|
|
|
@@ -393,7 +499,7 @@ require 'my_api_client/rspec'
|
|
|
393
499
|
|
|
394
500
|
### Testing
|
|
395
501
|
|
|
396
|
-
Suppose you have defined
|
|
502
|
+
Suppose you have defined an `ApiClient` like this:
|
|
397
503
|
|
|
398
504
|
```ruby
|
|
399
505
|
class ExampleApiClient < MyApiClient::Base
|
|
@@ -424,21 +530,251 @@ class ExampleApiClient < MyApiClient::Base
|
|
|
424
530
|
end
|
|
425
531
|
```
|
|
426
532
|
|
|
427
|
-
|
|
533
|
+
When you define a new API client, these are the two main test targets:
|
|
534
|
+
|
|
535
|
+
1. It sends the expected HTTP request (method, URL, headers/query/body)
|
|
536
|
+
2. It handles error responses as expected (`error_handling`)
|
|
537
|
+
|
|
538
|
+
MyApiClient provides custom matchers for both.
|
|
539
|
+
|
|
540
|
+
#### 1. Request assertion (`request_to` + `with`)
|
|
541
|
+
|
|
542
|
+
Use `request_to` to assert method/URL and `with` to assert `headers`, `query`, or `body`.
|
|
543
|
+
`expect` must receive a block.
|
|
544
|
+
|
|
545
|
+
```ruby
|
|
546
|
+
RSpec.describe ExampleApiClient, type: :api_client do
|
|
547
|
+
let(:api_client) { described_class.new(access_token: 'access token') }
|
|
548
|
+
let(:headers) do
|
|
549
|
+
{
|
|
550
|
+
'Content-Type': 'application/json;charset=UTF-8',
|
|
551
|
+
'Authorization': 'Bearer access token',
|
|
552
|
+
}
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
describe '#get_users' do
|
|
556
|
+
it do
|
|
557
|
+
expect { api_client.get_users(condition: 'condition') }
|
|
558
|
+
.to request_to(:get, 'https://example.com/v1/users')
|
|
559
|
+
.with(headers: headers, query: { search: 'condition' })
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
#### 2. Error handling assertion (`be_handled_as_an_error` + `when_receive`)
|
|
566
|
+
|
|
567
|
+
Use `be_handled_as_an_error` to assert the raised error class, and `when_receive` to provide mock response input (`status_code`, `headers`, `body`).
|
|
568
|
+
|
|
569
|
+
```ruby
|
|
570
|
+
it do
|
|
571
|
+
expect { api_client.get_users(condition: 'condition') }
|
|
572
|
+
.to be_handled_as_an_error(MyApiClient::ClientError)
|
|
573
|
+
.when_receive(status_code: 200, body: { errors: { code: 10 } }.to_json)
|
|
574
|
+
end
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
You can also assert that a response is *not* handled as an error:
|
|
578
|
+
|
|
579
|
+
```ruby
|
|
580
|
+
it do
|
|
581
|
+
expect { api_client.get_users(condition: 'condition') }
|
|
582
|
+
.not_to be_handled_as_an_error(MyApiClient::ClientError)
|
|
583
|
+
.when_receive(status_code: 200, body: { users: [{ id: 1 }] }.to_json)
|
|
584
|
+
end
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
If the client has `retry_on`, you can assert retry count with `after_retry(...).times`:
|
|
588
|
+
|
|
589
|
+
```ruby
|
|
590
|
+
it do
|
|
591
|
+
expect { api_client.get_users(condition: 'condition') }
|
|
592
|
+
.to be_handled_as_an_error(MyApiClient::ApiLimitError)
|
|
593
|
+
.after_retry(3).times
|
|
594
|
+
.when_receive(status_code: 200, body: { errors: { code: 20 } }.to_json)
|
|
595
|
+
end
|
|
596
|
+
```
|
|
428
597
|
|
|
429
598
|
### Stubbing
|
|
430
599
|
|
|
431
|
-
|
|
600
|
+
Use `stub_api_client_all` or `stub_api_client` to stub API client methods without real HTTP.
|
|
601
|
+
|
|
602
|
+
#### `response` option
|
|
603
|
+
|
|
604
|
+
```ruby
|
|
605
|
+
class ExampleApiClient < MyApiClient::Base
|
|
606
|
+
endpoint 'https://example.com'
|
|
607
|
+
|
|
608
|
+
def request(user_id:)
|
|
609
|
+
get "users/#{user_id}"
|
|
610
|
+
end
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
stub_api_client_all(
|
|
614
|
+
ExampleApiClient,
|
|
615
|
+
request: { response: { id: 12_345 } }
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
response = ExampleApiClient.new.request(user_id: 1)
|
|
619
|
+
response.id # => 12345
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
`response` can be omitted as shorthand:
|
|
623
|
+
|
|
624
|
+
```ruby
|
|
625
|
+
stub_api_client_all(
|
|
626
|
+
ExampleApiClient,
|
|
627
|
+
request: { id: 12_345 }
|
|
628
|
+
)
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
#### Proc response
|
|
632
|
+
|
|
633
|
+
You can generate response data from request arguments:
|
|
634
|
+
|
|
635
|
+
```ruby
|
|
636
|
+
stub_api_client_all(
|
|
637
|
+
ExampleApiClient,
|
|
638
|
+
request: ->(params) { { id: params[:user_id] } }
|
|
639
|
+
)
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
#### Return value of `stub_api_client_all` / `stub_api_client`
|
|
643
|
+
|
|
644
|
+
Both methods return a spy object, so you can assert received calls:
|
|
645
|
+
|
|
646
|
+
```ruby
|
|
647
|
+
def execute_api_request
|
|
648
|
+
ExampleApiClient.new.request(user_id: 1)
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
api_client = stub_api_client_all(ExampleApiClient, request: nil)
|
|
652
|
+
execute_api_request
|
|
653
|
+
expect(api_client).to have_received(:request).with(user_id: 1)
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
#### `raise` option
|
|
657
|
+
|
|
658
|
+
To test error paths, use the `raise` option:
|
|
659
|
+
|
|
660
|
+
```ruby
|
|
661
|
+
stub_api_client_all(ExampleApiClient, request: { raise: MyApiClient::Error })
|
|
662
|
+
expect { ExampleApiClient.new.request(user_id: 1) }.to raise_error(MyApiClient::Error)
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
You can combine `raise`, `response`, and `status_code`:
|
|
666
|
+
|
|
667
|
+
```ruby
|
|
668
|
+
stub_api_client_all(
|
|
669
|
+
ExampleApiClient,
|
|
670
|
+
request: {
|
|
671
|
+
raise: MyApiClient::Error,
|
|
672
|
+
response: { message: 'error' },
|
|
673
|
+
status_code: 429,
|
|
674
|
+
}
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
begin
|
|
678
|
+
ExampleApiClient.new.request(user_id: 1)
|
|
679
|
+
rescue MyApiClient::Error => e
|
|
680
|
+
e.params.response.data.to_h # => { message: "error" }
|
|
681
|
+
e.params.response.status # => 429
|
|
682
|
+
end
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
#### `pageable` option
|
|
686
|
+
|
|
687
|
+
For `#pageable_get` (`#pget`), you can stub page-by-page responses:
|
|
688
|
+
|
|
689
|
+
```ruby
|
|
690
|
+
stub_api_client_all(
|
|
691
|
+
MyPaginationApiClient,
|
|
692
|
+
pagination: {
|
|
693
|
+
pageable: [
|
|
694
|
+
{ page: 1 },
|
|
695
|
+
{ page: 2 },
|
|
696
|
+
{ page: 3 },
|
|
697
|
+
],
|
|
698
|
+
}
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
MyPaginationApiClient.new.pagination.each do |response|
|
|
702
|
+
response.page #=> 1, 2, 3
|
|
703
|
+
end
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
Each page entry supports the same options (`response`, `raise`, `Proc`, etc.).
|
|
707
|
+
You can also pass an `Enumerator` for endless pagination stubs.
|
|
432
708
|
|
|
433
709
|
## Development
|
|
434
710
|
|
|
435
711
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
436
712
|
|
|
437
|
-
|
|
713
|
+
### Integration Specs With Real HTTP
|
|
714
|
+
|
|
715
|
+
The integration specs under `spec/integrations/api_clients/` call the local `my_api` Rails server via HTTP.
|
|
716
|
+
|
|
717
|
+
Run with Docker Compose:
|
|
718
|
+
|
|
719
|
+
```sh
|
|
720
|
+
docker compose up -d --build my_api
|
|
721
|
+
docker compose run --rm test bundle exec rspec
|
|
722
|
+
docker compose down --volumes --remove-orphans
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
Run only integration specs:
|
|
726
|
+
|
|
727
|
+
```sh
|
|
728
|
+
docker compose up -d --build my_api
|
|
729
|
+
docker compose run --rm test bundle exec rspec spec/integrations/api_clients
|
|
730
|
+
docker compose down --volumes --remove-orphans
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
734
|
+
|
|
735
|
+
## Deployment
|
|
736
|
+
|
|
737
|
+
This project uses [gem_comet](https://github.com/ryz310/gem_comet) for release automation.
|
|
738
|
+
|
|
739
|
+
### Preparation
|
|
740
|
+
|
|
741
|
+
Create `.envrc` and set `GITHUB_ACCESS_TOKEN`:
|
|
742
|
+
|
|
743
|
+
```sh
|
|
744
|
+
cp .envrc.skeleton .envrc
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
Install `gem_comet`:
|
|
748
|
+
|
|
749
|
+
```sh
|
|
750
|
+
gem install gem_comet
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### Usage
|
|
754
|
+
|
|
755
|
+
Check PRs merged since the previous release:
|
|
756
|
+
|
|
757
|
+
```sh
|
|
758
|
+
gem_comet changelog
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
Start a release with a new version:
|
|
762
|
+
|
|
763
|
+
```sh
|
|
764
|
+
gem_comet release {VERSION}
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
This creates two PRs:
|
|
768
|
+
|
|
769
|
+
- `Update v{VERSION}`
|
|
770
|
+
- `Release v{VERSION}`
|
|
771
|
+
|
|
772
|
+
Merge `Update v{VERSION}` first after checking version bump and polishing `CHANGELOG.md`.
|
|
773
|
+
Then verify `Release v{VERSION}` (including CI) and merge it to publish the gem.
|
|
438
774
|
|
|
439
775
|
## Contributing
|
|
440
776
|
|
|
441
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
777
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ryz310/my_api_client. Reports in Japanese are also welcome. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
442
778
|
|
|
443
779
|
## License
|
|
444
780
|
|
|
@@ -446,4 +782,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
446
782
|
|
|
447
783
|
## Code of Conduct
|
|
448
784
|
|
|
449
|
-
Everyone interacting in the MyApiClient project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
|
785
|
+
Everyone interacting in the MyApiClient project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ryz310/my_api_client/blob/master/CODE_OF_CONDUCT.md).
|