my_api_client 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f79acd9dda7576ad4ef44c65c31d5c6fd4c3ebe64dc6ce8106bd81097931b84
4
- data.tar.gz: ff17d603eabd348ff39e7c209e3fa601bcbaeee1a97beeda73e46b9c6d16eda4
3
+ metadata.gz: d3ada9d2c8dbfadb9462b2cff381e9ccaf822a543f63672f6e5fdcd99f9ce4ed
4
+ data.tar.gz: b00cc646de8c05bc5378645940732e7dec5fc80d0dd4380e816f0a228e8b6f00
5
5
  SHA512:
6
- metadata.gz: e3258a69b3e289f8277d307759aeca39ea14dd8f0573ef686cb5fb24625b7afe30d9be4e65fe896c7ca9ebdd25612c2fe07b980e72bcb8dfe63598c0297fe46d
7
- data.tar.gz: 6cf8c1ee680e128f4ecfda474a528dc61323dba9ca592b7f044e5a9024a27eb6e8926cb4423909c7299198abca88d48aa127cb11d0b0f72d74e2e95c7310550d
6
+ metadata.gz: 977d55b39c0a35d56d4ff2ddc142765a7807f2b027471186d947cda8ea4f606d81fbfc842524318c9630f48a88aeb5adea5cc7e948233323f3072200e624d034
7
+ data.tar.gz: e8d4f395990d049afddc3e380044e5f65ad57d0130da1b08114d4bf28965a0d53fe756d147ebf17c7ffac3b236fc8c3f9e7fc7c74edfb90e9f57b0ed94b10930
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2019-05-27 23:30:42 +0000 using RuboCop version 0.70.0.
3
+ # on 2019-05-30 23:31:07 +0000 using RuboCop version 0.71.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- my_api_client (0.3.0)
4
+ my_api_client (0.4.0)
5
5
  activesupport (>= 4.2.0)
6
6
  jsonpath
7
7
  sawyer
@@ -69,7 +69,7 @@ GEM
69
69
  rspec-support (3.8.0)
70
70
  rspec_junit_formatter (0.4.1)
71
71
  rspec-core (>= 2, < 4, != 2.12.0)
72
- rubocop (0.70.0)
72
+ rubocop (0.71.0)
73
73
  jaro_winkler (~> 1.5.1)
74
74
  parallel (~> 1.10)
75
75
  parser (>= 2.6)
data/README.jp.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  # MyApiClient
4
4
 
5
- MyApiClient は API リクエストクラスを作成するための汎用的な機能を提供します。Sawyer や Faraday をベースにエラーハンドリングの機能を強化した構造になっています。ただし、 Sawyer はダミーデータの作成が難しかったり、他の gem で競合することがよくあるので、将来的には依存しないように変更していくかもしれません。
5
+ MyApiClient は API リクエストクラスを作成するための汎用的な機能を提供します。Sawyer や Faraday をベースにエラーハンドリングの機能を強化した構造になっています。
6
+
7
+ ただし、 Sawyer はダミーデータの作成が難しかったり、他の gem で競合することがよくあるので、将来的には依存しないように変更していくかもしれません。
6
8
 
7
9
  また、 Ruby on Rails で利用することを想定してますが、それ以外の環境でも動作するように作っているつもりです。不具合などあれば Issue ページからご報告下さい。
8
10
 
@@ -13,7 +15,7 @@ MyApiClient は API リクエストクラスを作成するための汎用的な
13
15
 
14
16
  ## Installation
15
17
 
16
- この gem は macOS と Linux で作動します。まずは、my_api_client を Gemfile に追加します:
18
+ この gem は macOS と Linux で作動します。まずは `my_api_client` を Gemfile に追加します:
17
19
 
18
20
  ```ruby
19
21
  gem 'my_api_client'
@@ -38,7 +40,7 @@ create spec/api_clients/path/to/resource_api_client_spec.rb
38
40
 
39
41
  ```ruby
40
42
  class ExampleApiClient < MyApiClient::Base
41
- endpoint 'https://example.com'
43
+ endpoint 'https://example.com/v1'
42
44
 
43
45
  attr_reader :access_token
44
46
 
@@ -46,14 +48,14 @@ class ExampleApiClient < MyApiClient::Base
46
48
  @access_token = access_token
47
49
  end
48
50
 
49
- # GET https://example.com/users
51
+ # GET https://example.com/v1/users
50
52
  #
51
53
  # @return [Sawyer::Response] HTTP response parameter
52
54
  def get_users
53
55
  get 'users', headers: headers, query: { key: 'value' }
54
56
  end
55
57
 
56
- # POST https://example.com/users
58
+ # POST https://example.com/v1/users
57
59
  #
58
60
  # @param name [String] Username which want to create
59
61
  # @return [Sawyer::Response] HTTP response parameter
@@ -75,8 +77,10 @@ api_clinet = ExampleApiClient.new(access_token: 'access_token')
75
77
  api_clinet.get_users #=> #<Sawyer::Response>
76
78
  ```
77
79
 
78
- クラス定義の最初に記述される `endpoint` にはリクエスト対象のスキーマとホストを定義します。ここにパス名を含めても反映されませんのでご注意ください。
80
+ クラス定義の最初に記述される `endpoint` にはリクエスト URL の共通部分を定義します。後述の各メソッドで後続の path を定義しますが、上記の例だと `get 'users'` と定義すると、 `GET https://example.com/v1/users` というリクエストが実行されます。
81
+
79
82
  次に、 `#initialize` を定義します。上記の例のように Access Token や API Key などを設定することを想定します。必要なければ定義の省略も可能です。
83
+
80
84
  続いて、 `#get_users` や `#post_user` を定義します。メソッド名には API のタイトルを付けると良いと思います。メソッド内部で `#get` や `#post` を呼び出していますが、これがリクエスト時の HTTP Method になります。他にも `#patch` `#put` `#delete` が利用可能です。
81
85
 
82
86
  ### Error handling
@@ -121,7 +125,7 @@ error_handling status_code: 400..499, raise: MyApiClient::ClientError
121
125
 
122
126
  https://github.com/ryz310/my_api_client/blob/master/lib/my_api_client/errors.rb
123
127
 
124
- 次に、 `raise` の代わりに Block を指定する場合について。
128
+ 次に、 `raise` の代わりに `block` を指定する場合について。
125
129
 
126
130
  ```ruby
127
131
  error_handling status_code: 500..599 do |params, logger|
@@ -130,7 +134,7 @@ error_handling status_code: 500..599 do |params, logger|
130
134
  end
131
135
  ```
132
136
 
133
- 上記の例であれば、ステータスコードが `500..599` の場合に Block の内容が実行れます。引数の `params` にはリクエスト情報とレスポンス情報が含まれています。`logger` はログ出力用インスタンスですが、このインスタンスを使ってログ出力すると、以下のようにリクエスト情報がログ出力に含まれるようになり、デバッグの際に便利です。
137
+ 上記の例であれば、ステータスコードが `500..599` の場合に `block` の内容が実行れます。引数の `params` にはリクエスト情報とレスポンス情報が含まれています。`logger` はログ出力用インスタンスですが、このインスタンスを使ってログ出力すると、以下のようにリクエスト情報がログ出力に含まれるようになり、デバッグの際に便利です。
134
138
 
135
139
  ```text
136
140
  API request `GET https://example.com/path/to/resouce`: "Server error occurred."
@@ -155,7 +159,7 @@ error_handling json: { '$.errors.code': 10..19 }, with: :my_error_handling
155
159
  }
156
160
  ```
157
161
 
158
- `with` にはインスタンスメソッド名を指定することで、エラーを検出した際に任意のメソッドを実行させることができます。メソッドに渡される引数は Block 定義の場合と同じく `params` と `logger` です。
162
+ `with` にはインスタンスメソッド名を指定することで、エラーを検出した際に任意のメソッドを実行させることができます。メソッドに渡される引数は `block` 定義の場合と同じく `params` と `logger` です。
159
163
 
160
164
  ```ruby
161
165
  # @param params [MyApiClient::Params::Params] HTTP req and res params
@@ -166,15 +170,59 @@ def my_error_handling(params, logger)
166
170
  end
167
171
  ```
168
172
 
169
- ### Retry
173
+ #### MyApiClient::Params::Params
170
174
 
171
175
  WIP
172
176
 
177
+ #### MyApiClient::Error
178
+
179
+ WIP
180
+
181
+ ### Retry
182
+
183
+ 次に `MyApiClient` が提供するリトライ機能についてご紹介致します。
184
+
185
+ ```ruby
186
+ class ExampleApiClient < MyApiClient::Base
187
+ endpoint 'https://example.com'
188
+
189
+ retry_on MyApiClient::NetworkError, wait: 0.1.seconds, attempts: 3
190
+ retry_on MyApiClient::ApiLimitError, wait: 30.seconds, attempts: 3
191
+
192
+ error_handling json: { '$.errors.code': 20 }, raise: MyApiClient::ApiLimitError
193
+ end
194
+ ```
195
+
196
+ API リクエストを何度も実行していると回線の不調などによりネットワークエラーが発生する事があります。長時間ネットワークが使えなくなるケースもありますが、瞬間的なエラーであるケースも多々あります。 `MyApiClient` ではネットワーク系の例外はまとめて `MyApiClient::NetworkError` として `raise` されます。この例外の詳細は後述しますが、 `retry_on` を利用する事で、 `ActiveJob` のように任意の例外処理を補足して、一定回数、一定の期間を空けて API リクエストをリトライさせる事ができます。
197
+
198
+ ただし、 `ActiveJob` とは異なり同期処理でリトライするため、ネットワークの瞬断に備えたリトライ以外ではあまり使う機会はないのではないかと思います。上記の例のように API Limit に備えてリトライするケースもあるかと思いますが、こちらは `ActiveJob` で対応した方が良いと思います。
199
+
200
+ ちなみに一応 `discard_on` も実装していますが、作者自身が有効な用途を見出せていないので、詳細は割愛します。良い利用方法があれば教えてください。
201
+
173
202
  #### MyApiClient::NetworkError
174
203
 
204
+ 前述の通りですが、 `MyApiClient` ではネットワーク系の例外はまとめて `MyApiClient::NetworkError` として `raise` されます。他の例外と同じく `MyApiClient::Error` を親クラスとしています。 `MyApiClient::NetworkError` として扱われる例外クラスの一覧は `MyApiClient::NETWORK_ERRORS` で参照できます。また、元となった例外は `#original_error` で参照できます。
205
+
206
+ ```ruby
207
+ begin
208
+ api_client.request
209
+ rescue MyApiClient::NetworkError => e
210
+ e.original_error # => #<Net::OpenTimeout>
211
+ e.params.response # => nil
212
+ end
213
+ ```
214
+
215
+ なお、通常の例外はリクエストの結果によって発生しますが、この例外はリクエスト中に発生するため、例外インスタンスにレスポンスパラメータは含まれません。
216
+
217
+ ### Timeout
218
+
175
219
  WIP
176
220
 
177
- ### One request for one class
221
+ ### Logger
222
+
223
+ WIP
224
+
225
+ ## One request for one class
178
226
 
179
227
  多くの場合、同一ホストの API は リクエストヘッダーやエラー情報が同じ構造になっているため、上記のように一つのクラス内に複数の API を定義する設計が理にかなっていますが、 API 毎に個別に定義したい場合は、以下のように 1 つのクラスに 1 の API という構造で設計することも可能です。
180
228
 
@@ -224,13 +272,7 @@ class PostUserApiClient < ExampleApiClient
224
272
  end
225
273
  ```
226
274
 
227
- ### Timeout
228
-
229
- WIP
230
-
231
- ### Logger
232
-
233
- WIP
275
+ ## Testing
234
276
 
235
277
  ### RSpec
236
278
 
@@ -262,7 +304,7 @@ response = ExampleApiClient.new.request(user_id: 1)
262
304
  response.id # => 12345
263
305
  ```
264
306
 
265
- リクスエストパラメータを使ったレスポンスを返すようにスタブ化したい場合は、 block を利用することで実現できます。
307
+ リクスエストパラメータを使ったレスポンスを返すようにスタブ化したい場合は、 `block` を利用することで実現できます。
266
308
 
267
309
  ```ruby
268
310
  my_api_client_stub(ExampleApiClient, :request) do |params|
data/lib/my_api_client.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'openssl'
4
+ require 'net/http'
3
5
  require 'logger'
4
6
  require 'jsonpath'
5
7
  require 'active_support'
@@ -16,5 +16,24 @@ module MyApiClient
16
16
  METHOD
17
17
  end
18
18
  end
19
+
20
+ # Extracts schema and hostname from endpoint
21
+ #
22
+ # @example Extracts schema and hostname from 'https://example.com/path/to/api'
23
+ # schema_and_hostname # => 'https://example.com'
24
+ # @return [String] description_of_returned_object
25
+ def schema_and_hostname
26
+ uri = URI.parse(endpoint)
27
+ "#{uri.scheme}://#{uri.host}"
28
+ end
29
+
30
+ # Extracts pathname from endpoint
31
+ #
32
+ # @example Extracts pathname from 'https://example.com/path/to/api'
33
+ # common_path # => 'path/to/api'
34
+ # @return [String] The pathanem
35
+ def common_path
36
+ URI.parse(endpoint).path
37
+ end
19
38
  end
20
39
  end
@@ -14,8 +14,9 @@ module MyApiClient
14
14
  # @return [Sawyer::Resource] description_of_returned_object
15
15
  # rubocop:disable Metrics/ParameterLists
16
16
  def _request(http_method, pathname, headers, query, body, logger)
17
- request_params = Params::Request.new(http_method, pathname, headers, query, body)
18
- request_logger = Logger.new(logger, faraday, http_method, pathname)
17
+ processed_path = [common_path, pathname].join('/').gsub('//', '/')
18
+ request_params = Params::Request.new(http_method, processed_path, headers, query, body)
19
+ request_logger = Logger.new(logger, faraday, http_method, processed_path)
19
20
  call(:_execute, request_params, request_logger)
20
21
  end
21
22
  # rubocop:enable Metrics/ParameterLists
@@ -26,7 +27,7 @@ module MyApiClient
26
27
  #
27
28
  # @return [Sawyer::Agent] description_of_returned_object
28
29
  def agent
29
- @agent ||= Sawyer::Agent.new(endpoint, faraday: faraday)
30
+ @agent ||= Sawyer::Agent.new(schema_and_hostname, faraday: faraday)
30
31
  end
31
32
 
32
33
  # Description of #faraday
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MyApiClient
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: my_api_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ryz310
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-05-29 00:00:00.000000000 Z
11
+ date: 2019-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport