my_api_client 0.1.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 +7 -0
- data/.circleci/config.yml +172 -0
- data/.envrc.skeleton +1 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +46 -0
- data/.rubocop_todo.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +123 -0
- data/LICENSE.txt +21 -0
- data/README.jp.md +232 -0
- data/README.md +45 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/release +34 -0
- data/bin/setup +8 -0
- data/lib/generators/rails/USAGE +11 -0
- data/lib/generators/rails/api_client_generator.rb +32 -0
- data/lib/generators/rails/templates/api_client.rb.erb +38 -0
- data/lib/generators/rails/templates/application_api_client.rb.erb +34 -0
- data/lib/generators/rspec/USAGE +8 -0
- data/lib/generators/rspec/api_client_generator.rb +30 -0
- data/lib/generators/rspec/templates/api_client_spec.rb.erb +13 -0
- data/lib/my_api_client.rb +34 -0
- data/lib/my_api_client/base.rb +36 -0
- data/lib/my_api_client/config.rb +20 -0
- data/lib/my_api_client/error_handling.rb +86 -0
- data/lib/my_api_client/errors.rb +66 -0
- data/lib/my_api_client/exceptions.rb +57 -0
- data/lib/my_api_client/logger.rb +36 -0
- data/lib/my_api_client/params/params.rb +26 -0
- data/lib/my_api_client/params/request.rb +29 -0
- data/lib/my_api_client/request.rb +85 -0
- data/lib/my_api_client/version.rb +5 -0
- data/my_api_client.gemspec +42 -0
- metadata +288 -0
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
|
+
[](https://circleci.com/gh/ryz310/my_api_client) [](https://codeclimate.com/github/ryz310/my_api_client/maintainability) [](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
|
+
[](https://circleci.com/gh/ryz310/my_api_client) [](https://codeclimate.com/github/ryz310/my_api_client/maintainability) [](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
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,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
|