my_api_client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 ryz310
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.jp.md ADDED
@@ -0,0 +1,232 @@
1
+ [![CircleCI](https://circleci.com/gh/ryz310/my_api_client.svg?style=svg)](https://circleci.com/gh/ryz310/my_api_client) [![Maintainability](https://api.codeclimate.com/v1/badges/861a2c8f168bbe995107/maintainability)](https://codeclimate.com/github/ryz310/my_api_client/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/861a2c8f168bbe995107/test_coverage)](https://codeclimate.com/github/ryz310/my_api_client/test_coverage)
2
+
3
+ # MyApiClient
4
+
5
+ MyApiClient は API リクエストクラスを作成するための汎用的な機能を提供します。Sawyer や Faraday をベースにエラーハンドリングの機能を強化した構造になっています。ただし、 Sawyer はダミーデータの作成が難しかったり、他の gem で競合することがよくあるので、将来的には依存しないように変更していくかもしれません。
6
+
7
+ また、 Ruby on Rails で利用することを想定してますが、それ以外の環境でも動作するように作っているつもりです。不具合などあれば Issue ページからご報告下さい。
8
+
9
+ ## Installation
10
+
11
+ この gem は macOS と Linux で作動します。まずは、my_api_client を Gemfile に追加します:
12
+
13
+ ```ruby
14
+ gem 'my_api_client'
15
+ ```
16
+
17
+ Ruby on Rails を利用している場合は `generator` 機能を利用できます。
18
+
19
+ ```sh
20
+ $ rails g api_client path/to/resource https://example.com get_user:get:path/to/resource
21
+
22
+ create app/api_clients/application_api_client.rb
23
+ create app/api_clients/path/to/resource_api_client.rb
24
+ invoke rspec
25
+ create spec/api_clients/path/to/resource_api_client_spec.rb
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ### Basic
31
+
32
+ 最もシンプルな利用例を以下に示します。
33
+
34
+ ```rb
35
+ class ExampleApiClient < MyApiClient::Base
36
+ endpoint 'https://example.com'
37
+
38
+ attr_reader :access_token
39
+
40
+ def initialize(access_token:)
41
+ @access_token = access_token
42
+ end
43
+
44
+ # GET https://example.com/users
45
+ #
46
+ # @return [Sawyer::Response] HTTP response parameter
47
+ def get_users
48
+ get 'users', headers: headers, query: { key: 'value' }
49
+ end
50
+
51
+ # POST https://example.com/users
52
+ #
53
+ # @param name [String] Username which want to create
54
+ # @return [Sawyer::Response] HTTP response parameter
55
+ def post_user(name:)
56
+ post 'users', headers: headers, body: { name: name }
57
+ end
58
+
59
+ private
60
+
61
+ def headers
62
+ {
63
+ 'Content-Type': 'application/json;charset=UTF-8',
64
+ 'Authorization': "Bearer #{access_token}",
65
+ }
66
+ end
67
+ end
68
+
69
+ api_clinet = ExampleApiClient.new(access_token: 'access_token')
70
+ api_clinet.get_users #=> #<Sawyer::Response>
71
+ ```
72
+
73
+ クラス定義の最初に記述される `endpoint` にはリクエスト対象のスキーマとホストを定義します。ここにパス名を含めても反映されませんのでご注意ください。
74
+ 次に、 `#initialize` を定義します。上記の例のように Access Token や API Key などを設定することを想定します。必要なければ定義の省略も可能です。
75
+ 続いて、 `#get_users` や `#post_user` を定義します。メソッド名には API のタイトルを付けると良いと思います。メソッド内部で `#get` や `#post` を呼び出していますが、これがリクエスト時の HTTP Method になります。他にも `#patch` `#put` `#delete` が利用可能です。
76
+
77
+ ### Error handling
78
+
79
+ 上記のコードにエラーハンドリングを追加してみます。
80
+
81
+ ```rb
82
+ class ExampleApiClient < MyApiClient::Base
83
+ endpoint 'https://example.com'
84
+
85
+ error_handling status_code: 400..499, raise: MyApiClient::ClientError
86
+
87
+ error_handling status_code: 500..599 do |params, logger|
88
+ logger.warn 'Server error occurred.'
89
+ raise MyApiClient::ServerError, params
90
+ end
91
+
92
+ error_handling json: { '$.errors.code': 10..19 }, with: :my_error_handling
93
+
94
+ # Omission...
95
+
96
+ private
97
+
98
+ # @param params [MyApiClient::Params::Params] HTTP req and res params
99
+ # @param logger [MyApiClient::Logger] Logger for a request processing
100
+ def my_error_handling(params, logger)
101
+ logger.warn "Response Body: #{params.response.body.inspect}"
102
+ raise MyApiClient::ClientError, params
103
+ end
104
+ end
105
+ ```
106
+
107
+ 一つずつ解説していきます。まず、以下のように `status_code` を指定するものについて。
108
+
109
+ ```rb
110
+ error_handling status_code: 400..499, raise: MyApiClient::ClientError
111
+ ```
112
+
113
+ これは `ExampleApiClient` からのリクエスト全てにおいて、レスポンスのステータスコードが `400..499` であった場合に `MyApiClient::ClientError` が例外として発生するようになります。 `ExampleApiClient` を継承したクラスにもエラーハンドリングは適用されます。ステータスコードのエラーハンドリングは親クラスで定義すると良いと思います。
114
+
115
+ なお、 `status_code` には `Integer` `Range` `Regexp` が指定可能です。`raise` には `MyApiClient::Error` を継承したクラスが指定可能です。`my_api_client` で標準で定義しているエラークラスについては以下のソースコードをご確認下さい。
116
+
117
+ https://github.com/ryz310/my_api_client/blob/master/lib/my_api_client/errors.rb
118
+
119
+ 次に、 `raise` の代わりに Block を指定する場合について。
120
+
121
+ ```rb
122
+ error_handling status_code: 500..599 do |params, logger|
123
+ logger.warn 'Server error occurred.'
124
+ raise MyApiClient::ServerError, params
125
+ end
126
+ ```
127
+
128
+ 上記の例であれば、ステータスコードが `500..599` の場合に Block の内容が実行れます。引数の `params` にはリクエスト情報とレスポンス情報が含まれています。`logger` はログ出力用インスタンスですが、このインスタンスを使ってログ出力すると、以下のようにリクエスト情報がログ出力に含まれるようになり、デバッグの際に便利です。
129
+
130
+ ```text
131
+ API request `GET https://example.com/path/to/resouce`: "Server error occurred."
132
+ ```
133
+
134
+ リクエストに失敗した場合は例外処理を実行する、という設計が一般的だと思われるので、基本的にブロックの最後に `raise` を実行する事になると思います。
135
+
136
+ 最後に `json` と `with` を利用する場合について。
137
+
138
+ ```rb
139
+ error_handling json: { '$.errors.code': 10..19 }, with: :my_error_handling
140
+ ```
141
+
142
+ `json` には `Hash` の Key に [JSONPath](https://goessner.net/articles/JsonPath/) を指定して、レスポンス JSON から任意の値を取得し、 Value とマッチするかどうかでエラーハンドリングできます。Value には `String` `Integer` `Range` `Regexp` が指定可能です。上記の場合であれば、以下のような JSON にマッチします。
143
+
144
+ ```json
145
+ {
146
+ "erros": {
147
+ "code": 10,
148
+ "message": "Some error has occurred."
149
+ }
150
+ }
151
+ ```
152
+
153
+ `with` にはインスタンスメソッド名を指定することで、エラーを検出した際に任意のメソッドを実行させることができます。メソッドに渡される引数は Block 定義の場合と同じく `params` と `logger` です。
154
+
155
+ ```rb
156
+ # @param params [MyApiClient::Params::Params] HTTP req and res params
157
+ # @param logger [MyApiClient::Logger] Logger for a request processing
158
+ def my_error_handling(params, logger)
159
+ logger.warn "Response Body: #{params.response.body.inspect}"
160
+ raise MyApiClient::ClientError, params
161
+ end
162
+ ```
163
+
164
+ ### Retry
165
+
166
+ WIP
167
+
168
+ #### MyApiClient::NetworkError
169
+
170
+ WIP
171
+
172
+ ### One request for one class
173
+
174
+ 多くの場合、同一ホストの API は リクエストヘッダーやエラー情報が同じ構造になっているため、上記のように一つのクラス内に複数の API を定義する設計が理にかなっていますが、 API 毎に個別に定義したい場合は、以下のように 1 つのクラスに 1 の API という構造で設計することも可能です。
175
+
176
+ ```rb
177
+ class ExampleApiClient < MyApiClient::Base
178
+ endpoint 'https://example.com'
179
+
180
+ error_handling status_code: 400..599
181
+
182
+ attr_reader :access_token
183
+
184
+ def initialize(access_token:)
185
+ @access_token = access_token
186
+ end
187
+
188
+ private
189
+
190
+ def headers
191
+ {
192
+ 'Content-Type': 'application/json;charset=UTF-8',
193
+ 'Authorization': "Bearer #{access_token}",
194
+ }
195
+ end
196
+ end
197
+
198
+ class GetUsersApiClient < ExampleApiClient
199
+ error_handling json: { '$.errors.code': 10 }, raise: MyApiClient::ClientError
200
+
201
+ # GET https://example.com/users
202
+ #
203
+ # @return [Sawyer::Response] HTTP response parameter
204
+ def request
205
+ get 'users', query: { key: 'value' }, headers: headers
206
+ end
207
+ end
208
+
209
+ class PostUserApiClient < ExampleApiClient
210
+ error_handling json: { '$.errors.code': 10 }, raise: MyApiClient::ApiLimitError
211
+
212
+ # POST https://example.com/users
213
+ #
214
+ # @param name [String] Username which want to create
215
+ # @return [Sawyer::Response] HTTP response parameter
216
+ def request(name:)
217
+ post 'users', headers: headers, body: { name: name }
218
+ end
219
+ end
220
+ ```
221
+
222
+ ### Timeout
223
+
224
+ WIP
225
+
226
+ ### Logger
227
+
228
+ WIP
229
+
230
+ ## Contributing
231
+
232
+ 不具合の報告や Pull Request を歓迎しています。OSS という事で自分はなるべく頑張って英語を使うようにしていますが、日本語での報告でも大丈夫です :+1:
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ [![CircleCI](https://circleci.com/gh/ryz310/my_api_client.svg?style=svg)](https://circleci.com/gh/ryz310/my_api_client) [![Maintainability](https://api.codeclimate.com/v1/badges/861a2c8f168bbe995107/maintainability)](https://codeclimate.com/github/ryz310/my_api_client/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/861a2c8f168bbe995107/test_coverage)](https://codeclimate.com/github/ryz310/my_api_client/test_coverage)
2
+
3
+ # MyApiClient
4
+
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/my_api_client`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+
7
+ TODO: Delete this and the text above, and describe your gem
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'my_api_client'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install my_api_client
24
+
25
+ ## Usage
26
+
27
+ TODO: Write usage instructions here
28
+
29
+ ## Development
30
+
31
+ 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.
32
+
33
+ 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).
34
+
35
+ ## Contributing
36
+
37
+ 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.
38
+
39
+ ## License
40
+
41
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
42
+
43
+ ## Code of Conduct
44
+
45
+ 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).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'my_api_client'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/release ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # usage: bin/release VERSION
5
+
6
+ require 'bundler/setup'
7
+ require 'pr_comet'
8
+
9
+ VERSION_FORMAT = /\A\d+\.\d+\.\d+(\.(pre|beta|rc)\d?)?\z/.freeze
10
+ version = ARGV[0]
11
+ pr_comet = PrComet.new(base: 'master', branch: "update/v#{version}")
12
+
13
+ # Verifying
14
+ abort 'usage: bin/release VERSION' if version.nil?
15
+ abort 'A version must be like a `1.2.3`' unless version =~ VERSION_FORMAT
16
+
17
+ # Modify a version file
18
+ pr_comet.commit ':comet: Update version' do
19
+ File.write('lib/my_api_client/version.rb', <<~VERSION)
20
+ # frozen_string_literal: true
21
+
22
+ module MyApiClient
23
+ VERSION = '#{version}'
24
+ end
25
+ VERSION
26
+ end
27
+
28
+ # Bundle Update
29
+ pr_comet.commit ':comet: Run $ bundle update' do
30
+ `bundle update`
31
+ end
32
+
33
+ # Create a pull request
34
+ pr_comet.create!(title: "Update v#{version}", body: '')
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,11 @@
1
+ Description:
2
+ Generate a new api client class files.
3
+
4
+ Example:
5
+ `rails g api_client path/to/resource https://example.com get_user:get:path/to/resource`
6
+
7
+ This will create:
8
+ create app/api_clients/application_api_client.rb
9
+ create app/api_clients/path/to/resource_api_client.rb
10
+ invoke rspec
11
+ create spec/api_clients/path/to/resource_api_client_spec.rb
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ # rails g api_client
5
+ class ApiClientGenerator < Rails::Generators::NamedBase
6
+ source_root File.expand_path('templates', __dir__)
7
+ check_class_collision suffix: 'ApiClient'
8
+
9
+ argument :endpoint,
10
+ type: :string,
11
+ default: 'https://example.com',
12
+ banner: '{schema and hostname}'
13
+ argument :requests,
14
+ type: :array,
15
+ default: %w[get_resource:get:path/to/resource post_resource:post:path/to/resource],
16
+ banner: '{action}:{method}:{path} {action}:{method}:{path}'
17
+
18
+ def generate_root_class
19
+ file_path = File.join('app/api_clients', 'application_api_client.rb')
20
+ return if File.exist?(file_path)
21
+
22
+ template 'application_api_client.rb.erb', file_path
23
+ end
24
+
25
+ def generate_api_client
26
+ file_path = File.join('app/api_clients', "#{route_url.singularize}_api_client.rb")
27
+ template 'api_client.rb.erb', file_path
28
+ end
29
+
30
+ hook_for :test_framework
31
+ end
32
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= "#{class_name}ApiClient" %> < ::ApplicationApiClient
4
+ endpoint '<%= endpoint %>'
5
+
6
+ # error_handling json: { '$.errors.code': 10 } do |params, logger|
7
+ # # Behavior when detected an error.
8
+ # end
9
+
10
+ def initialize
11
+ end
12
+
13
+ <% requests.each do |request| -%>
14
+ <% action, http_method, pathname = request.sepalate(':') -%>
15
+ # <%= "#{http_method.upcase} #{endpoint}/#{pathname}" %>
16
+ #
17
+ # @return [Sawyer::Resource] Description of the API response
18
+ # @raise [MyApiClient::Error] Description of the error
19
+ # @see Reference of the API
20
+ def <%= action %>
21
+ <% if http_method == 'get' -%>
22
+ query = {}
23
+ <%= http_method %> '<%= path %>', query: query, headers: headers
24
+ <% else -%>
25
+ body = {}
26
+ <%= http_method %> '<%= path %>', body: body, headers: headers
27
+ <% end -%>
28
+ end
29
+ <% end -%>
30
+
31
+ private
32
+
33
+ def headers
34
+ {
35
+ 'Content-Type': 'application/json',
36
+ }
37
+ end
38
+ end