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.
Files changed (248) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +8 -0
  3. data/.envrc.skeleton +0 -1
  4. data/.github/dependabot.yml +82 -7
  5. data/.github/workflows/ci.yml +334 -0
  6. data/.github/workflows/dependabot-auto-merge.yml +96 -0
  7. data/.github/workflows/release.yml +33 -0
  8. data/.github/workflows/rubocop_challenge.yml +33 -0
  9. data/.gitignore +1 -0
  10. data/.rubocop.yml +1 -1
  11. data/.ruby-version +1 -1
  12. data/AGENTS.md +128 -0
  13. data/CHANGELOG.md +54 -2
  14. data/CLAUDE.md +6 -0
  15. data/Dockerfile +22 -0
  16. data/Gemfile +1 -1
  17. data/Gemfile.lock +67 -59
  18. data/README.md +398 -62
  19. data/docker-compose.yml +29 -0
  20. data/docs/runbooks/dependabot_pr_auto_merge.md +113 -0
  21. data/gemfiles/rails_7.2.gemfile +1 -1
  22. data/gemfiles/{rails_7.1.gemfile → rails_8.0.gemfile} +2 -2
  23. data/gemfiles/{rails_6.1.gemfile → rails_8.1.gemfile} +2 -2
  24. data/lib/my_api_client/default_error_handlers.rb +1 -1
  25. data/lib/my_api_client/errors/client_error.rb +5 -1
  26. data/lib/my_api_client/integrations/bugsnag.rb +1 -1
  27. data/lib/my_api_client/version.rb +1 -1
  28. data/lib/my_api_client.rb +1 -1
  29. data/my_api/.ruby-version +1 -1
  30. data/my_api/AGENTS.md +35 -0
  31. data/my_api/Dockerfile +25 -0
  32. data/my_api/Gemfile +5 -16
  33. data/my_api/Gemfile.lock +263 -228
  34. data/my_api/README.md +36 -25
  35. data/my_api/Rakefile +3 -2
  36. data/my_api/app/controllers/application_controller.rb +1 -2
  37. data/my_api/app/controllers/error_controller.rb +2 -2
  38. data/my_api/app/controllers/header_controller.rb +6 -3
  39. data/my_api/app/controllers/pagination_controller.rb +5 -15
  40. data/my_api/app/controllers/rest_controller.rb +12 -16
  41. data/my_api/app/controllers/status_controller.rb +3 -4
  42. data/my_api/config/application.rb +12 -67
  43. data/{rails_app/rails_6.1 → my_api}/config/boot.rb +1 -1
  44. data/{rails_app/rails_7.1 → my_api}/config/environment.rb +0 -2
  45. data/my_api/config/environments/development.rb +6 -6
  46. data/my_api/config/environments/production.rb +5 -8
  47. data/my_api/config/environments/test.rb +5 -6
  48. data/my_api/config/routes.rb +2 -10
  49. data/my_api/config.ru +2 -4
  50. data/my_api/public/index.html +10 -90
  51. data/{rails_app/rails_7.0 → my_api}/spec/rails_helper.rb +3 -4
  52. data/my_api/spec/requests/error_controller_spec.rb +18 -0
  53. data/my_api/spec/requests/pagination_controller_spec.rb +22 -0
  54. data/my_api/spec/requests/rest_controller_spec.rb +22 -0
  55. data/my_api/spec/requests/status_controller_spec.rb +20 -0
  56. data/my_api/spec/spec_helper.rb +15 -23
  57. data/my_api_client.gemspec +2 -2
  58. data/rails_app/AGENTS.md +13 -0
  59. data/rails_app/rails_7.2/Dockerfile +1 -1
  60. data/rails_app/rails_7.2/Gemfile +2 -0
  61. data/rails_app/rails_7.2/Gemfile.lock +143 -122
  62. data/rails_app/{rails_7.1 → rails_8.0}/.dockerignore +16 -2
  63. data/rails_app/{rails_6.1 → rails_8.0}/.gitattributes +2 -1
  64. data/rails_app/rails_8.0/.github/workflows/ci.yml +39 -0
  65. data/rails_app/{rails_6.1 → rails_8.0}/.gitignore +11 -7
  66. data/rails_app/rails_8.0/.rubocop.yml +8 -0
  67. data/rails_app/rails_8.0/.ruby-version +1 -0
  68. data/rails_app/{rails_7.1 → rails_8.0}/Dockerfile +28 -18
  69. data/rails_app/rails_8.0/Gemfile +15 -0
  70. data/rails_app/rails_8.0/Gemfile.lock +286 -0
  71. data/rails_app/{rails_6.1 → rails_8.0}/Rakefile +1 -3
  72. data/rails_app/{rails_7.1 → rails_8.0}/app/controllers/application_controller.rb +0 -2
  73. data/rails_app/rails_8.0/app/jobs/application_job.rb +7 -0
  74. data/rails_app/rails_8.0/app/mailers/application_mailer.rb +4 -0
  75. data/rails_app/{rails_7.0 → rails_8.0}/app/models/application_record.rb +0 -2
  76. data/rails_app/rails_8.0/app/views/layouts/mailer.html.erb +13 -0
  77. data/rails_app/rails_8.0/app/views/layouts/mailer.text.erb +1 -0
  78. data/rails_app/rails_8.0/bin/brakeman +7 -0
  79. data/rails_app/rails_8.0/bin/dev +2 -0
  80. data/rails_app/rails_8.0/bin/docker-entrypoint +14 -0
  81. data/rails_app/rails_8.0/bin/rails +4 -0
  82. data/rails_app/rails_8.0/bin/rake +4 -0
  83. data/rails_app/rails_8.0/bin/rubocop +8 -0
  84. data/rails_app/{rails_7.1 → rails_8.0}/bin/setup +11 -12
  85. data/rails_app/rails_8.0/bin/thrust +5 -0
  86. data/rails_app/{rails_7.1 → rails_8.0}/config/application.rb +14 -16
  87. data/rails_app/rails_8.0/config/boot.rb +3 -0
  88. data/rails_app/rails_8.0/config/cable.yml +10 -0
  89. data/rails_app/rails_8.0/config/credentials.yml.enc +1 -0
  90. data/rails_app/{rails_7.1 → rails_8.0}/config/database.yml +18 -2
  91. data/rails_app/{rails_6.1 → rails_8.0}/config/environment.rb +1 -3
  92. data/rails_app/rails_8.0/config/environments/development.rb +70 -0
  93. data/rails_app/rails_8.0/config/environments/production.rb +86 -0
  94. data/rails_app/{rails_7.1 → rails_8.0}/config/environments/test.rb +16 -19
  95. data/rails_app/{rails_7.1 → rails_8.0}/config/initializers/cors.rb +0 -1
  96. data/rails_app/{rails_7.1 → rails_8.0}/config/initializers/filter_parameter_logging.rb +2 -4
  97. data/rails_app/{rails_7.0 → rails_8.0}/config/initializers/inflections.rb +0 -1
  98. data/rails_app/rails_8.0/config/puma.rb +41 -0
  99. data/rails_app/{rails_7.1 → rails_8.0}/config/routes.rb +1 -3
  100. data/rails_app/rails_8.0/config/storage.yml +34 -0
  101. data/rails_app/{rails_7.1 → rails_8.0}/config.ru +1 -3
  102. data/rails_app/{rails_7.1 → rails_8.0}/db/seeds.rb +0 -1
  103. data/rails_app/{rails_7.1 → rails_8.0}/spec/rails_helper.rb +1 -1
  104. data/rails_app/rails_8.1/.dockerignore +45 -0
  105. data/rails_app/rails_8.1/.gitattributes +9 -0
  106. data/rails_app/rails_8.1/.github/workflows/ci.yml +52 -0
  107. data/rails_app/rails_8.1/.gitignore +33 -0
  108. data/rails_app/rails_8.1/.rspec +1 -0
  109. data/rails_app/rails_8.1/.rubocop.yml +8 -0
  110. data/rails_app/rails_8.1/.ruby-version +1 -0
  111. data/rails_app/rails_8.1/Dockerfile +68 -0
  112. data/rails_app/rails_8.1/Gemfile +15 -0
  113. data/rails_app/rails_8.1/Gemfile.lock +289 -0
  114. data/rails_app/{rails_7.0 → rails_8.1}/Rakefile +1 -3
  115. data/rails_app/{rails_6.1 → rails_8.1}/app/controllers/application_controller.rb +0 -2
  116. data/rails_app/rails_8.1/app/jobs/application_job.rb +7 -0
  117. data/rails_app/rails_8.1/app/mailers/application_mailer.rb +4 -0
  118. data/rails_app/{rails_7.1 → rails_8.1}/app/models/application_record.rb +0 -2
  119. data/rails_app/rails_8.1/app/views/layouts/mailer.html.erb +13 -0
  120. data/rails_app/rails_8.1/app/views/layouts/mailer.text.erb +1 -0
  121. data/rails_app/rails_8.1/bin/brakeman +7 -0
  122. data/rails_app/rails_8.1/bin/bundler-audit +6 -0
  123. data/rails_app/rails_8.1/bin/ci +6 -0
  124. data/rails_app/rails_8.1/bin/dev +2 -0
  125. data/rails_app/{rails_7.1 → rails_8.1}/bin/docker-entrypoint +1 -1
  126. data/rails_app/rails_8.1/bin/rails +4 -0
  127. data/rails_app/rails_8.1/bin/rake +4 -0
  128. data/rails_app/rails_8.1/bin/rubocop +8 -0
  129. data/rails_app/{rails_7.0 → rails_8.1}/bin/setup +13 -13
  130. data/rails_app/rails_8.1/bin/thrust +5 -0
  131. data/rails_app/{rails_6.1 → rails_8.1}/config/application.rb +19 -17
  132. data/rails_app/rails_8.1/config/boot.rb +3 -0
  133. data/rails_app/rails_8.1/config/bundler-audit.yml +5 -0
  134. data/rails_app/rails_8.1/config/cable.yml +10 -0
  135. data/rails_app/rails_8.1/config/ci.rb +19 -0
  136. data/rails_app/rails_8.1/config/credentials.yml.enc +1 -0
  137. data/rails_app/rails_8.1/config/database.yml +40 -0
  138. data/rails_app/{rails_7.0 → rails_8.1}/config/environment.rb +1 -3
  139. data/rails_app/rails_8.1/config/environments/development.rb +73 -0
  140. data/rails_app/rails_8.1/config/environments/production.rb +86 -0
  141. data/rails_app/rails_8.1/config/environments/test.rb +53 -0
  142. data/rails_app/{rails_7.0 → rails_8.1}/config/initializers/cors.rb +1 -2
  143. data/rails_app/rails_8.1/config/initializers/filter_parameter_logging.rb +8 -0
  144. data/rails_app/{rails_7.1 → rails_8.1}/config/initializers/inflections.rb +0 -1
  145. data/rails_app/rails_8.1/config/locales/en.yml +31 -0
  146. data/rails_app/rails_8.1/config/puma.rb +42 -0
  147. data/rails_app/rails_8.1/config/routes.rb +10 -0
  148. data/rails_app/rails_8.1/config/storage.yml +27 -0
  149. data/rails_app/{rails_7.0 → rails_8.1}/config.ru +1 -3
  150. data/rails_app/rails_8.1/db/seeds.rb +9 -0
  151. data/rails_app/{rails_6.1 → rails_8.1}/spec/rails_helper.rb +1 -2
  152. metadata +139 -136
  153. data/.circleci/config.yml +0 -344
  154. data/README.jp.md +0 -785
  155. data/gemfiles/rails_7.0.gemfile +0 -15
  156. data/my_api/.envrc.skeleton +0 -3
  157. data/my_api/.jetskeep +0 -1
  158. data/my_api/Procfile +0 -7
  159. data/my_api/app/helpers/application_helper.rb +0 -5
  160. data/my_api/app/jobs/application_job.rb +0 -7
  161. data/my_api/app/models/application_item.rb +0 -5
  162. data/my_api/config/dynamodb.yml +0 -22
  163. data/my_api/spec/controllers/error_controller_spec.rb +0 -43
  164. data/my_api/spec/controllers/header_controller_spec.rb +0 -33
  165. data/my_api/spec/controllers/pagination_controller_spec.rb +0 -73
  166. data/my_api/spec/controllers/rest_controller_spec.rb +0 -99
  167. data/my_api/spec/controllers/status_controller_spec.rb +0 -47
  168. data/my_api/spec/fixtures/payloads/posts-index.json +0 -51
  169. data/my_api/spec/fixtures/payloads/posts-show.json +0 -53
  170. data/rails_app/rails_6.1/.rspec +0 -3
  171. data/rails_app/rails_6.1/Gemfile +0 -17
  172. data/rails_app/rails_6.1/Gemfile.lock +0 -195
  173. data/rails_app/rails_6.1/app/models/application_record.rb +0 -5
  174. data/rails_app/rails_6.1/bin/bundle +0 -122
  175. data/rails_app/rails_6.1/bin/rails +0 -6
  176. data/rails_app/rails_6.1/bin/rake +0 -6
  177. data/rails_app/rails_6.1/bin/setup +0 -35
  178. data/rails_app/rails_6.1/config/credentials.yml.enc +0 -1
  179. data/rails_app/rails_6.1/config/database.yml +0 -25
  180. data/rails_app/rails_6.1/config/environments/development.rb +0 -59
  181. data/rails_app/rails_6.1/config/environments/production.rb +0 -97
  182. data/rails_app/rails_6.1/config/environments/test.rb +0 -51
  183. data/rails_app/rails_6.1/config/initializers/application_controller_renderer.rb +0 -9
  184. data/rails_app/rails_6.1/config/initializers/backtrace_silencers.rb +0 -10
  185. data/rails_app/rails_6.1/config/initializers/cors.rb +0 -17
  186. data/rails_app/rails_6.1/config/initializers/filter_parameter_logging.rb +0 -8
  187. data/rails_app/rails_6.1/config/initializers/inflections.rb +0 -17
  188. data/rails_app/rails_6.1/config/initializers/mime_types.rb +0 -5
  189. data/rails_app/rails_6.1/config/initializers/wrap_parameters.rb +0 -16
  190. data/rails_app/rails_6.1/config/locales/en.yml +0 -33
  191. data/rails_app/rails_6.1/config/routes.rb +0 -5
  192. data/rails_app/rails_6.1/config.ru +0 -8
  193. data/rails_app/rails_6.1/db/seeds.rb +0 -8
  194. data/rails_app/rails_7.0/Gemfile +0 -13
  195. data/rails_app/rails_7.0/Gemfile.lock +0 -212
  196. data/rails_app/rails_7.0/app/controllers/application_controller.rb +0 -4
  197. data/rails_app/rails_7.0/bin/bundle +0 -122
  198. data/rails_app/rails_7.0/bin/rails +0 -6
  199. data/rails_app/rails_7.0/bin/rake +0 -6
  200. data/rails_app/rails_7.0/config/application.rb +0 -41
  201. data/rails_app/rails_7.0/config/boot.rb +0 -5
  202. data/rails_app/rails_7.0/config/credentials.yml.enc +0 -1
  203. data/rails_app/rails_7.0/config/database.yml +0 -25
  204. data/rails_app/rails_7.0/config/environments/development.rb +0 -58
  205. data/rails_app/rails_7.0/config/environments/production.rb +0 -70
  206. data/rails_app/rails_7.0/config/environments/test.rb +0 -52
  207. data/rails_app/rails_7.0/config/initializers/filter_parameter_logging.rb +0 -8
  208. data/rails_app/rails_7.0/config/locales/en.yml +0 -33
  209. data/rails_app/rails_7.0/config/routes.rb +0 -8
  210. data/rails_app/rails_7.0/db/seeds.rb +0 -8
  211. data/rails_app/rails_7.1/Gemfile +0 -16
  212. data/rails_app/rails_7.1/Gemfile.lock +0 -241
  213. data/rails_app/rails_7.1/README.md +0 -24
  214. data/rails_app/rails_7.1/Rakefile +0 -8
  215. data/rails_app/rails_7.1/bin/bundle +0 -122
  216. data/rails_app/rails_7.1/bin/rails +0 -6
  217. data/rails_app/rails_7.1/bin/rake +0 -6
  218. data/rails_app/rails_7.1/config/boot.rb +0 -5
  219. data/rails_app/rails_7.1/config/credentials.yml.enc +0 -1
  220. data/rails_app/rails_7.1/config/environments/development.rb +0 -58
  221. data/rails_app/rails_7.1/config/environments/production.rb +0 -74
  222. data/rails_app/rails_7.1/config/puma.rb +0 -46
  223. data/rails_app/rails_7.1/public/robots.txt +0 -2
  224. data/rails_app/rails_7.1/spec/spec_helper.rb +0 -13
  225. data/rails_app/rails_7.1/vendor/.keep +0 -0
  226. data/rails_app/rails_7.2/.github/dependabot.yml +0 -12
  227. /data/rails_app/{rails_7.1 → rails_8.0}/.rspec +0 -0
  228. /data/rails_app/{rails_6.1 → rails_8.0}/README.md +0 -0
  229. /data/rails_app/{rails_6.1 → rails_8.0}/app/controllers/concerns/.keep +0 -0
  230. /data/rails_app/{rails_6.1/app/javascript → rails_8.0/app/models/concerns}/.keep +0 -0
  231. /data/rails_app/{rails_7.1 → rails_8.0}/config/locales/en.yml +0 -0
  232. /data/rails_app/{rails_6.1/app/models/concerns → rails_8.0/lib/tasks}/.keep +0 -0
  233. /data/rails_app/{rails_6.1/lib/tasks → rails_8.0/log}/.keep +0 -0
  234. /data/rails_app/{rails_6.1 → rails_8.0}/public/robots.txt +0 -0
  235. /data/rails_app/{rails_6.1/tmp → rails_8.0/script}/.keep +0 -0
  236. /data/rails_app/{rails_6.1 → rails_8.0}/spec/spec_helper.rb +0 -0
  237. /data/rails_app/{rails_6.1/tmp/pids → rails_8.0/storage}/.keep +0 -0
  238. /data/rails_app/{rails_6.1 → rails_8.0}/vendor/.keep +0 -0
  239. /data/rails_app/{rails_7.0 → rails_8.1}/README.md +0 -0
  240. /data/rails_app/{rails_7.1 → rails_8.1}/app/controllers/concerns/.keep +0 -0
  241. /data/rails_app/{rails_7.1 → rails_8.1}/app/models/concerns/.keep +0 -0
  242. /data/rails_app/{rails_7.1 → rails_8.1}/lib/tasks/.keep +0 -0
  243. /data/rails_app/{rails_7.1/storage → rails_8.1/log}/.keep +0 -0
  244. /data/rails_app/{rails_7.0 → rails_8.1}/public/robots.txt +0 -0
  245. /data/rails_app/{rails_7.1/tmp → rails_8.1/script}/.keep +0 -0
  246. /data/rails_app/{rails_7.0 → rails_8.1}/spec/spec_helper.rb +0 -0
  247. /data/rails_app/{rails_7.1/tmp/pids → rails_8.1/storage}/.keep +0 -0
  248. /data/rails_app/{rails_7.1/tmp/storage → rails_8.1/vendor}/.keep +0 -0
data/README.md CHANGED
@@ -1,23 +1,23 @@
1
- [![CircleCI](https://circleci.com/gh/ryz310/my_api_client.svg?style=svg)](https://circleci.com/gh/ryz310/my_api_client)
1
+ [![CI](https://github.com/ryz310/my_api_client/actions/workflows/ci.yml/badge.svg)](https://github.com/ryz310/my_api_client/actions/workflows/ci.yml)
2
2
  [![Gem Version](https://badge.fury.io/rb/my_api_client.svg)](https://badge.fury.io/rb/my_api_client)
3
- [![Maintainability](https://api.codeclimate.com/v1/badges/861a2c8f168bbe995107/maintainability)](https://codeclimate.com/github/ryz310/my_api_client/maintainability)
4
- [![Test Coverage](https://api.codeclimate.com/v1/badges/861a2c8f168bbe995107/test_coverage)](https://codeclimate.com/github/ryz310/my_api_client/test_coverage)
3
+ [![Maintainability](https://qlty.sh/gh/ryz310/projects/my_api_client/maintainability.svg)](https://qlty.sh/gh/ryz310/projects/my_api_client)
4
+ [![Code Coverage](https://qlty.sh/gh/ryz310/projects/my_api_client/coverage.svg)](https://qlty.sh/gh/ryz310/projects/my_api_client)
5
5
  ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/ryz310/my_api_client)
6
6
 
7
- 日本語ドキュメントは [こちら](README.jp.md)
8
-
9
7
  # MyApiClient
10
8
 
11
- This gem is an API client builder. It provides generic functionality for creating API request classes. It has a structure based on [Sawyer](https://github.com/lostisland/sawyer) and [Faraday](https://github.com/lostisland/faraday) with enhanced error handling functions.
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 supposed to be used in Ruby on Rails, but it is made to work in other environments. If you have any problems, please report them from the Issue page.
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.1, 3.2, 3.3
20
- - Rails 6.1, 7.0, 7.1, 7.2
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 `generator` function.
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 which want to create
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
- api_clinet = ExampleApiClient.new(access_token: 'access_token')
83
- api_clinet.get_users #=> #<Sawyer::Resource>
82
+ api_client = ExampleApiClient.new(access_token: 'access_token')
83
+ api_client.get_users #=> #<Sawyer::Resource>
84
84
  ```
85
85
 
86
- The `endpoint` defines the intersection of the request URL. Each method described below defines a subsequent path. In the above example, `get 'users'` will request to `GET https://example.com/v1/users`.
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`. Suppose you want to set an Access Token, API Key, etc. as in the example above. You can omit the definition if you don't need it.
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`. It's a good idea to give the method name the title of the API. I'm calling `#get` and `#post` inside the method, which is the HTTP Method at the time of the request. You can also use `#patch` `#put` `#delete`.
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 in the response to get the continuation of the result.
94
+ Some APIs include a URL for the next page in the response.
95
95
 
96
- MyApiClient provides a method called `#pageable_get` to handle such APIs as enumerable. An example is shown below:
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 example, the request is first made for `GET https://example.com/v1/pagination?page=1`, followed by the URL contained in the response JSON `$.link.next`. Make a request to enumerable.
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 following response, `$.link.next` indicates `"https://example.com/pagination?page=3"`:
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 get the following result by `#each` or `#next`:
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
- api_clinet = MyPaginationApiClient.new
133
- api_clinet.pagination.each do |response|
132
+ api_client = MyPaginationApiClient.new
133
+ api_client.pagination.each do |response|
134
134
  # Do something.
135
135
  end
136
136
 
137
- result = api_clinet.pagination
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 allows you to define error handling that raises an exception depending on the content of the response. Here, as an example, error handling is defined in the above code:
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 reqest and response params
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
- I will explain one by one. First, about the one that specifies `status_code` as follows:
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 will cause `MyApiClient::ClientError` to occur as an exception if the status code of the response is `400..499` for all requests from `ExampleApiClient`. Error handling also applies to classes that inherit from `ExampleApiClient`.
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
- Note that `Integer` `Range`, and `Regexp` can be specified for `status_code`.
193
+ You can specify `Integer`, `Range`, or `Regexp` for `status_code`.
194
194
 
195
- A class that inherits `MyApiClient::Error` can be specified for `raise`. Please check [here](https://github.com/ryz310/my_api_client/blob/master/lib/my_api_client/errors) for the error class defined as standard in `my_api_client`. If `raise` is omitted, `MyApiClient::Error` will be raised.
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, about the case of specifying `block`:
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 the above example, if the status code is `500..599`, the contents of `block` will be executed before raising `MyApiClient::ServerError`. The argument `params` contains request and response information.
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 an instance for log output. If you log output using this instance, the request information will be included in the log output as shown below, which is convenient for debugging:
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/resouce`: "Server error occurred."
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`, specify [JSONPath](https://goessner.net/articles/JsonPath/) for the Key of `Hash`, get an arbitrary value from the response JSON, and check whether it matches value. You can handle errors. You can specify `String` `Integer` `Range` and `Regexp` for value.
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 the above case, it matches JSON as below:
219
+ In this case, it matches JSON such as:
220
220
 
221
221
  ```json
222
222
  {
223
- "erros": {
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 header for the Key of `Hash`, get an arbitrary value from the response header, and check whether it matches value. You can handle errors. You can specify `String` and `Regexp` for value.
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 the above case, it matches response header as below:
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 the instance method name in `with`, when an error is detected, any method can be executed before raising an exception. The arguments passed to the method are `params` and `logger` as in the `block` definition. Note that `block` and` with` cannot be used at the same time.
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
- In MyApiClient, the response of status code 400 ~ 500 series is handled as an exception by default. If the status code is in the 400s, an exception class that inherits `MyApiClient::ClientError` is raised, and in the 500s, an exception class that inherits `MyApiClient::ServerError` is raised.
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
- Although it is an experimental function, by specifying `Symbol` for value of `status` or `json`, you can call a method for the result value and judge the result. In the above case, it matches the following JSON. If `#negative?` does not exist in the target object, the method will not be called.
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
- It seems that some services expect an empty Response Body to be returned from the server, but an empty result is returned. This is also an experimental feature, but we have provided the `json: :forbid_nil` option to detect such cases. Normally, if the response body is empty, no error judgment is made, but if this option is specified, it will be detected as an error. Please be careful about false positives because some APIs have an empty normal response.
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
- WIP
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 the response of the API request matches the matcher defined in `error_handling`, the exception handling specified in `raise` will occur. This exception class must inherit `MyApiClient::Error`.
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.11.0](https://github.com/bugsnag/bugsnag-ruby/releases/tag/v6.11.0) or later, [breadcrumbs function](https://docs. bugsnag.com/platforms/ruby/other/#logging-breadcrumbs) is automatically supported. With this function, `Bugsnag.leave_breadcrumb` is called internally when `MyApiClient::Error` occurs, and you can check the request information, response information, etc. when an error occurs from the Bugsnag console.
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, I would like to introduce the retry function provided by MyApiClient.
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
- If the API request is executed many times, a network error may occur due to a line malfunction. In some cases, the network will be unavailable for a long time, but in many cases it will be a momentary error. In MyApiClient, network exceptions are collectively raised as `MyApiClient::NetworkError`. The details of this exception will be described later, but by using `retry_on`, it is possible to supplement arbitrary exception handling like `ActiveJob` and retry the API request a certain number of times and after a certain period of time.
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
- Note that `retry_on MyApiClient::NetworkError` is implemented as standard, so it will be applied automatically without any special definition. Please define and use it only when you want to set an arbitrary value for `wait` or `attempts`.
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
- However, unlike `ActiveJob`, it retries in synchronous processing, so I think that there is not much opportunity to use it other than retrying in case of a momentary network interruption. As in the above example, there may be cases where you retry in preparation for API Rate Limit, but it may be better to handle this with `ActiveJob`.
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
- By the way, `discard_on` is also implemented, but since the author himself has not found an effective use, I will omit the details. Please let me know if there is a good way to use it.
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` attempts` in `retry_on`, it works with `retry: true`:
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, in MyApiClient, network exceptions are collectively `raised` as `MyApiClient::NetworkError`. Like the other exceptions, it has `MyApiClient::Error` as its parent class. A list of exception classes treated as `MyApiClient::NetworkError` can be found in `MyApiClient::NETWORK_ERRORS`. You can also refer to the original exception with `#original_error`:
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
- Note that a normal exception is raised depending on the result of the request, but since this exception is raised during the request, the exception instance does not include the response parameter.
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
- WIP
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
- WIP
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 a `ApiClient` like this:
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
- WIP
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
- WIP
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
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
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/[USERNAME]/my_api_client. 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.
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/[USERNAME]/my_api_client/blob/master/CODE_OF_CONDUCT.md).
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).