ueki 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +51 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +162 -0
- data/Rakefile +12 -0
- data/lib/ueki/http_client/default_requester.rb +137 -0
- data/lib/ueki/http_client/exception_class_builder.rb +69 -0
- data/lib/ueki/http_client/json_response_body_parser.rb +21 -0
- data/lib/ueki/http_client/request_body_converter.rb +25 -0
- data/lib/ueki/http_client/requester_shorthand.rb +28 -0
- data/lib/ueki/http_client/unsuccessful_response_exception_class_picker.rb +31 -0
- data/lib/ueki/http_client.rb +37 -0
- data/lib/ueki/version.rb +5 -0
- data/lib/ueki.rb +7 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a26fe40e5550c573ba85ceaf58122fa63cf27c358d579b14b9c72fbe985eb637
|
4
|
+
data.tar.gz: e3391e1a4302b29ed51f0379f268bd4dfff89e05a1d406a04d4b87e8fcf8424d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: de1e9a7eb8b7a79609ff6e78f6b0a557e85a4b907f43348aba985a3dbee989061dbf0d10a6e35060e2ea02cd2b15a5161915ee70885c12e1928bee671622b882
|
7
|
+
data.tar.gz: 1c6f4e75b67b4b7b9e5f77763bab3fcdd7411884fb9b0629018861831a0573602e13b6c074aa301e0ce835335663b16865cdb0e7e287eccb735f14ec96667052
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-performance
|
3
|
+
- rubocop-rake
|
4
|
+
- rubocop-rspec
|
5
|
+
|
6
|
+
AllCops:
|
7
|
+
TargetRubyVersion: 3.1
|
8
|
+
NewCops: enable
|
9
|
+
|
10
|
+
Security/IoMethods:
|
11
|
+
Exclude:
|
12
|
+
- "spec/ueki/http_client/fake_requester.rb"
|
13
|
+
|
14
|
+
Style/Documentation:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Style/StringLiterals:
|
18
|
+
EnforcedStyle: double_quotes
|
19
|
+
|
20
|
+
Style/StringLiteralsInInterpolation:
|
21
|
+
EnforcedStyle: double_quotes
|
22
|
+
|
23
|
+
Layout/LineLength:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
Metrics/AbcSize:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
Metrics/CyclomaticComplexity:
|
30
|
+
Enabled: false
|
31
|
+
|
32
|
+
Metrics/MethodLength:
|
33
|
+
Enabled: false
|
34
|
+
|
35
|
+
Metrics/ParameterLists:
|
36
|
+
Enabled: false
|
37
|
+
|
38
|
+
Naming/MemoizedInstanceVariableName:
|
39
|
+
EnforcedStyleForLeadingUnderscores: required
|
40
|
+
|
41
|
+
RSpec/DescribeClass:
|
42
|
+
Enabled: false
|
43
|
+
|
44
|
+
RSpec/ExampleLength:
|
45
|
+
Enabled: false
|
46
|
+
|
47
|
+
RSpec/MultipleExpectations:
|
48
|
+
Enabled: false
|
49
|
+
|
50
|
+
RSpec/NestedGroups:
|
51
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Tomohiko Mimura
|
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.md
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
# Ueki
|
2
|
+
|
3
|
+
Ueki provides "definition of simple request methods such as `get`, `post`, etc." and "definition and handling of error exception classes for timeout error and each status code" required for HTTP Client Library.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Install the gem and add to the application's Gemfile by executing:
|
8
|
+
|
9
|
+
$ bundle add ueki
|
10
|
+
|
11
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
12
|
+
|
13
|
+
$ gem install ueki
|
14
|
+
|
15
|
+
> [!CAUTION]
|
16
|
+
> `Ueki` uses faraday by default.
|
17
|
+
> However, it is possible to choose not to use faraday, so there is no dependency.
|
18
|
+
> If you want to use faraday, please install [faraday](https://github.com/lostisland/faraday).
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
Simply include the Module created by Ueki in your own HTTP Client Library.
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
class BookStoreClient
|
26
|
+
include Ueki::HttpClient.new('http://example.com')
|
27
|
+
|
28
|
+
# Class Method
|
29
|
+
def self.delete_book(id)
|
30
|
+
delete("/books/#{id}")
|
31
|
+
end
|
32
|
+
|
33
|
+
# Instance Method
|
34
|
+
def books(limit:, offset: 0)
|
35
|
+
get('/books', params: { limit: limit, offset: offset })
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def default_headers
|
41
|
+
h = super
|
42
|
+
h['X-Request-Id'] = Current.request_id if Current.request_id.present?
|
43
|
+
h
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
BookStoreClient.new.post('/books', params: { title: 'Programming Ruby' })
|
48
|
+
#=> { id: 1, title: 'Programming Ruby' }
|
49
|
+
|
50
|
+
BookStoreClient.new.books(limit: 5)
|
51
|
+
#=> { books: [{ id: 1, title: 'Programming Ruby' }] }
|
52
|
+
|
53
|
+
BookStoreClient.delete_book(1)
|
54
|
+
#=> nil
|
55
|
+
|
56
|
+
BookStoreClient.get('/books/1')
|
57
|
+
#=> BookStoreClient::NotFoundError (status: 404, body: { message: 'Not Found' })
|
58
|
+
```
|
59
|
+
|
60
|
+
Exception classes are defined according to the following tree structure.
|
61
|
+
```
|
62
|
+
BookStoreClient
|
63
|
+
└── Error
|
64
|
+
├── RequestError
|
65
|
+
│ ├── TimeoutError
|
66
|
+
│ └── UnexpectedError
|
67
|
+
└── UnsuccessfulResponseError
|
68
|
+
├── BadRequestError
|
69
|
+
│ ├── UnauthorizedError
|
70
|
+
│ ├── ForbiddenError
|
71
|
+
│ ├── NotFoundError
|
72
|
+
│ ├── RequestTimeoutError
|
73
|
+
│ ├── ConflictError
|
74
|
+
│ ├── UnprocessableEntityError
|
75
|
+
│ └── TooManyRequestsError
|
76
|
+
└── ServerError
|
77
|
+
```
|
78
|
+
|
79
|
+
## Why not faraday?
|
80
|
+
|
81
|
+
faraday has enough features.
|
82
|
+
However, all request error exceptions are subclasses of `Faraday::Error`.
|
83
|
+
|
84
|
+
That behavior does not allow for proper exception handling, so we repeatedly implement a process that replaces the exception.
|
85
|
+
```ruby
|
86
|
+
class BookStoreClient
|
87
|
+
class Error < StandardError; end
|
88
|
+
class ClientError < Error; end
|
89
|
+
class ServerError < Error; end
|
90
|
+
|
91
|
+
def books(limit:, offset: 0)
|
92
|
+
connection = Faraday.new("https://example.com") do |builder|
|
93
|
+
builder.response :raise_error
|
94
|
+
end
|
95
|
+
|
96
|
+
connection.get("/books")
|
97
|
+
rescue Faraday::Error::ClientError
|
98
|
+
raise ClientError
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
Ueki frees us from this tedious and redundant implementation!
|
104
|
+
|
105
|
+
## How to customize
|
106
|
+
|
107
|
+
"Request Processing" is not important for Ueki.
|
108
|
+
Therefore, this part can be freely customized.
|
109
|
+
|
110
|
+
There are two ways to customize.
|
111
|
+
|
112
|
+
### Q. I enable keep alive and don't want to output logs?
|
113
|
+
|
114
|
+
A. You can use [net_http_persistent](https://github.com/lostisland/faraday-net_http_persistent) adapter by overriding `_initialize_faraday_connection`. Set up your favorite faraday middleware to match.
|
115
|
+
|
116
|
+
See [lib/ueki/http_client/default_requester.rb](https://github.com/tmimura39/ueki/blob/main/lib/ueki/http_client/default_requester.rb) for details.
|
117
|
+
```ruby
|
118
|
+
class BookStoreClient
|
119
|
+
include Ueki::HttpClient.new('http://example.com')
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def _initialize_faraday_connection(request_options)
|
124
|
+
Faraday.new(url: self.class::ENDPOINT, headers: _default_headers, request: request_options) do |builder|
|
125
|
+
builder.adapter :net_http_persistent, pool_size: 5 do |http|
|
126
|
+
http.idle_timeout = 100
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
### Q. How can I use faraday independent request processing across multiple Client Libraries?
|
134
|
+
|
135
|
+
A. It is recommended to create a Requester module.
|
136
|
+
|
137
|
+
A module like [lib/ueki/http_client/default_requester.rb](https://github.com/tmimura39/ueki/blob/main/lib/ueki/http_client/default_requester.rb) could be created and applied to Ueki.
|
138
|
+
In this case, you can use "automatic exception class definition" and "exception class acquisition processing according to status code ( `#pickup_unsuccessful_response_exception_class` )"
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
class BookStoreClient
|
142
|
+
include Ueki::HttpClient.new('http://example.com', requester: CustomRequester)
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
I am happy with my current DefaultRequester.
|
147
|
+
So this customization method is not sophisticated.
|
148
|
+
I will gladly accept any better suggestions.
|
149
|
+
|
150
|
+
## Development
|
151
|
+
|
152
|
+
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.
|
153
|
+
|
154
|
+
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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
155
|
+
|
156
|
+
## Contributing
|
157
|
+
|
158
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tmimura39/ueki.
|
159
|
+
|
160
|
+
## License
|
161
|
+
|
162
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "faraday"
|
4
|
+
|
5
|
+
module Ueki
|
6
|
+
class HttpClient
|
7
|
+
# HTTP Request processing is defined.
|
8
|
+
# You can also use your own Requester class with the same I/F.
|
9
|
+
module DefaultRequester
|
10
|
+
UNSPECIFIED = Object.new.freeze
|
11
|
+
private_constant :UNSPECIFIED
|
12
|
+
|
13
|
+
def get(path, params: nil, headers: {}, request_options: {}, response_body_parser: UNSPECIFIED)
|
14
|
+
response_body_parser = default_response_body_parser_if_unspecifiied(response_body_parser)
|
15
|
+
request(:get, path:, params:, headers:, request_options:, response_body_parser:)
|
16
|
+
end
|
17
|
+
|
18
|
+
def post(path, params: nil, headers: {}, request_options: {}, request_body_converter: UNSPECIFIED, response_body_parser: UNSPECIFIED)
|
19
|
+
response_body_parser = default_response_body_parser_if_unspecifiied(response_body_parser)
|
20
|
+
request_body_converter = default_request_body_converter_if_unspecified(request_body_converter)
|
21
|
+
|
22
|
+
headers[:"Content-Type"] ||= "application/json" unless params.nil?
|
23
|
+
params = request_body_converter.call(content_type: headers[:"Content-Type"], params:)
|
24
|
+
request(:post, path:, params:, headers:, request_options:, response_body_parser:)
|
25
|
+
end
|
26
|
+
|
27
|
+
def put(path, params: nil, headers: {}, request_options: {}, request_body_converter: UNSPECIFIED, response_body_parser: UNSPECIFIED)
|
28
|
+
response_body_parser = default_response_body_parser_if_unspecifiied(response_body_parser)
|
29
|
+
request_body_converter = default_request_body_converter_if_unspecified(request_body_converter)
|
30
|
+
|
31
|
+
headers[:"Content-Type"] ||= "application/json" unless params.nil?
|
32
|
+
params = request_body_converter.call(content_type: headers[:"Content-Type"], params:)
|
33
|
+
request(:put, path:, params:, headers:, request_options:, response_body_parser:)
|
34
|
+
end
|
35
|
+
|
36
|
+
def patch(path, params: nil, headers: {}, request_options: {}, request_body_converter: UNSPECIFIED, response_body_parser: UNSPECIFIED)
|
37
|
+
response_body_parser = default_response_body_parser_if_unspecifiied(response_body_parser)
|
38
|
+
request_body_converter = default_request_body_converter_if_unspecified(request_body_converter)
|
39
|
+
|
40
|
+
headers[:"Content-Type"] ||= "application/json" unless params.nil?
|
41
|
+
params = request_body_converter.call(content_type: headers[:"Content-Type"], params:)
|
42
|
+
request(:patch, path:, params:, headers:, request_options:, response_body_parser:)
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete(path, params: nil, headers: {}, request_options: {}, response_body_parser: UNSPECIFIED)
|
46
|
+
response_body_parser = default_response_body_parser_if_unspecifiied(response_body_parser)
|
47
|
+
request(:delete, path:, params:, headers:, request_options:, response_body_parser:)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def default_response_body_parser_if_unspecifiied(response_body_parser)
|
53
|
+
if response_body_parser == UNSPECIFIED
|
54
|
+
require_relative "json_response_body_parser"
|
55
|
+
JsonResponseBodyParser
|
56
|
+
else
|
57
|
+
response_body_parser
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def default_request_body_converter_if_unspecified(request_body_converter)
|
62
|
+
if request_body_converter == UNSPECIFIED
|
63
|
+
require_relative "request_body_converter"
|
64
|
+
RequestBodyConverter
|
65
|
+
else
|
66
|
+
request_body_converter
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def request(method, path:, params:, headers:, request_options:, response_body_parser:)
|
71
|
+
response = _with_request_error_handling do
|
72
|
+
_request(method, path:, params:, headers:, request_options:)
|
73
|
+
end
|
74
|
+
assert_successful_response(response:, response_body_parser:)
|
75
|
+
|
76
|
+
response_body_parser.nil? ? response.body : response_body_parser.call(response.body)
|
77
|
+
end
|
78
|
+
|
79
|
+
def assert_successful_response(response:, response_body_parser:)
|
80
|
+
status = _pickup_status(response)
|
81
|
+
exception_class = pickup_unsuccessful_response_exception_class(status)
|
82
|
+
return if exception_class.nil?
|
83
|
+
|
84
|
+
message, status, body, headers = _extract_from_raw_response(response:, response_body_parser:)
|
85
|
+
raise exception_class.new(message, status:, body:, headers:, response:)
|
86
|
+
end
|
87
|
+
|
88
|
+
# ===========================================================================================
|
89
|
+
# The methods defined below depend on faraday.
|
90
|
+
# If you want to customize faraday or use another HTTP library, override the methods.
|
91
|
+
# If no customization is required, use the default.
|
92
|
+
|
93
|
+
def _request(method, path:, params:, headers:, request_options:)
|
94
|
+
_faraday_connection(request_options).public_send(method, path, params, headers)
|
95
|
+
end
|
96
|
+
|
97
|
+
def _faraday_connection(request_options)
|
98
|
+
@_faraday_connection ||= {}
|
99
|
+
@_faraday_connection[request_options] ||= _initialize_faraday_connection(request_options)
|
100
|
+
end
|
101
|
+
|
102
|
+
def _initialize_faraday_connection(request_options)
|
103
|
+
Faraday.new(url: self.class::ENDPOINT, headers: _default_headers, request: request_options) do |builder|
|
104
|
+
builder.response :logger, (defined?(Rails) ? Rails.logger : nil) do |logger|
|
105
|
+
logger.filter(/Authorization:\ (.*)/, "Authorization: [token]")
|
106
|
+
end
|
107
|
+
|
108
|
+
builder.adapter :net_http
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def _default_headers
|
113
|
+
{ user_agent: self.class.name }
|
114
|
+
end
|
115
|
+
|
116
|
+
def _pickup_status(response)
|
117
|
+
response.status
|
118
|
+
end
|
119
|
+
|
120
|
+
def _extract_from_raw_response(response:, response_body_parser:)
|
121
|
+
message = response.to_hash.except(:request_headers, :response)
|
122
|
+
status = response.status
|
123
|
+
body = response_body_parser.call(response.body)
|
124
|
+
headers = response.headers
|
125
|
+
[message, status, body, headers]
|
126
|
+
end
|
127
|
+
|
128
|
+
def _with_request_error_handling
|
129
|
+
yield
|
130
|
+
rescue Faraday::TimeoutError => e
|
131
|
+
raise self.class::TimeoutError, e
|
132
|
+
rescue StandardError => e
|
133
|
+
raise self.class::UnexpectedError, e
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ueki
|
4
|
+
class HttpClient
|
5
|
+
class ExceptionClassBuilder
|
6
|
+
def initialize
|
7
|
+
@error = Class.new(StandardError)
|
8
|
+
@request_error = build_request_error(@error)
|
9
|
+
@timeout_error = Class.new(@request_error)
|
10
|
+
@unexpected_error = Class.new(@request_error)
|
11
|
+
@unsuccessful_response_error = build_unsuccessful_response_error(@error)
|
12
|
+
@bad_request_error = Class.new(@unsuccessful_response_error)
|
13
|
+
@unauthorized_error = Class.new(@bad_request_error)
|
14
|
+
@forbidden_error = Class.new(@bad_request_error)
|
15
|
+
@not_found_error = Class.new(@bad_request_error)
|
16
|
+
@request_timeout_error = Class.new(@bad_request_error)
|
17
|
+
@conflict_error = Class.new(@bad_request_error)
|
18
|
+
@unprocessable_entity_error = Class.new(@bad_request_error)
|
19
|
+
@too_many_requests_error = Class.new(@bad_request_error)
|
20
|
+
@server_error = Class.new(@unsuccessful_response_error)
|
21
|
+
end
|
22
|
+
|
23
|
+
def exception_classes
|
24
|
+
{
|
25
|
+
Error: @error,
|
26
|
+
RequestError: @request_error,
|
27
|
+
TimeoutError: @timeout_error,
|
28
|
+
UnexpectedError: @unexpected_error,
|
29
|
+
UnsuccessfulResponseError: @unsuccessful_response_error,
|
30
|
+
BadRequestError: @bad_request_error,
|
31
|
+
UnauthorizedError: @unauthorized_error,
|
32
|
+
ForbiddenError: @forbidden_error,
|
33
|
+
NotFoundError: @not_found_error,
|
34
|
+
RequestTimeoutError: @request_timeout_error,
|
35
|
+
ConflictError: @conflict_error,
|
36
|
+
UnprocessableEntityError: @unprocessable_entity_error,
|
37
|
+
TooManyRequestsError: @too_many_requests_error,
|
38
|
+
ServerError: @server_error
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def build_request_error(error)
|
45
|
+
Class.new(error) do
|
46
|
+
def initialize(exception)
|
47
|
+
super("#{exception.class.name}: #{exception.message}")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def build_unsuccessful_response_error(error)
|
53
|
+
Class.new(error) do
|
54
|
+
attr_reader :status, :body, :headers, :response
|
55
|
+
|
56
|
+
def initialize(message, status:, body:, headers:, response:)
|
57
|
+
@status = status
|
58
|
+
@body = body
|
59
|
+
@headers = headers
|
60
|
+
@response = response
|
61
|
+
|
62
|
+
super(message)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
private_constant :ExceptionClassBuilder
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Ueki
|
6
|
+
class HttpClient
|
7
|
+
# Simple JSON Parser (default)
|
8
|
+
# If it cannot be parsed as JSON, return the value before parsing
|
9
|
+
module JsonResponseBodyParser
|
10
|
+
module_function
|
11
|
+
|
12
|
+
def call(body)
|
13
|
+
return if body.nil? || (body.is_a?(String) && body.empty?)
|
14
|
+
|
15
|
+
JSON.parse(body, symbolize_names: true)
|
16
|
+
rescue JSON::ParserError
|
17
|
+
body
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "uri"
|
5
|
+
|
6
|
+
module Ueki
|
7
|
+
class HttpClient
|
8
|
+
module RequestBodyConverter
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def call(content_type:, params:)
|
12
|
+
return if params.nil?
|
13
|
+
|
14
|
+
case content_type
|
15
|
+
when "application/json"
|
16
|
+
params&.to_json
|
17
|
+
when "application/x-www-form-urlencoded"
|
18
|
+
URI.encode_www_form(params)
|
19
|
+
else
|
20
|
+
params
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ueki
|
4
|
+
class HttpClient
|
5
|
+
module RequesterShorthand
|
6
|
+
def get(path, **args)
|
7
|
+
new.get(path, **args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def post(path, **args)
|
11
|
+
new.post(path, **args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def put(path, **args)
|
15
|
+
new.put(path, **args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def patch(path, **args)
|
19
|
+
new.patch(path, **args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def delete(path, **args)
|
23
|
+
new.delete(path, **args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
private_constant :RequesterShorthand
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ueki
|
4
|
+
class HttpClient
|
5
|
+
module UnsuccessfulResponseExceptionClassPicker
|
6
|
+
def pickup_unsuccessful_response_exception_class(status)
|
7
|
+
case status
|
8
|
+
when 401
|
9
|
+
self.class::UnauthorizedError
|
10
|
+
when 403
|
11
|
+
self.class::ForbiddenError
|
12
|
+
when 404
|
13
|
+
self.class::NotFoundError
|
14
|
+
when 408
|
15
|
+
self.class::RequestTimeoutError
|
16
|
+
when 409
|
17
|
+
self.class::ConflictError
|
18
|
+
when 422
|
19
|
+
self.class::UnprocessableEntityError
|
20
|
+
when 429
|
21
|
+
self.class::TooManyRequestsError
|
22
|
+
when 400..499
|
23
|
+
self.class::BadRequestError
|
24
|
+
when 500..599
|
25
|
+
self.class::ServerError
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
private_constant :UnsuccessfulResponseExceptionClassPicker
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ueki
|
4
|
+
# Provides a module that automatically defines exception classes and methods required for the HTTP Client Library.
|
5
|
+
class HttpClient < Module
|
6
|
+
def initialize(endpoint, requester: nil)
|
7
|
+
super()
|
8
|
+
@endpoint = endpoint
|
9
|
+
|
10
|
+
@requester =
|
11
|
+
if requester.nil?
|
12
|
+
require_relative "http_client/default_requester"
|
13
|
+
DefaultRequester
|
14
|
+
else
|
15
|
+
requester
|
16
|
+
end
|
17
|
+
|
18
|
+
@exception_classes = ExceptionClassBuilder.new.exception_classes
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def included(descendant)
|
24
|
+
descendant.const_set(:ENDPOINT, @endpoint)
|
25
|
+
@exception_classes.each_pair do |name, object|
|
26
|
+
descendant.const_set(name, object)
|
27
|
+
end
|
28
|
+
descendant.include @requester
|
29
|
+
descendant.include UnsuccessfulResponseExceptionClassPicker
|
30
|
+
descendant.extend RequesterShorthand
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
require_relative "http_client/exception_class_builder"
|
36
|
+
require_relative "http_client/requester_shorthand"
|
37
|
+
require_relative "http_client/unsuccessful_response_exception_class_picker"
|
data/lib/ueki/version.rb
ADDED
data/lib/ueki.rb
ADDED
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ueki
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tomohiko Mimura
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-08-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Ueki provides "simple request method" and "error exception class definition
|
14
|
+
and handling"
|
15
|
+
email:
|
16
|
+
- mito.5525@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- ".rspec"
|
22
|
+
- ".rubocop.yml"
|
23
|
+
- CHANGELOG.md
|
24
|
+
- LICENSE.txt
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- lib/ueki.rb
|
28
|
+
- lib/ueki/http_client.rb
|
29
|
+
- lib/ueki/http_client/default_requester.rb
|
30
|
+
- lib/ueki/http_client/exception_class_builder.rb
|
31
|
+
- lib/ueki/http_client/json_response_body_parser.rb
|
32
|
+
- lib/ueki/http_client/request_body_converter.rb
|
33
|
+
- lib/ueki/http_client/requester_shorthand.rb
|
34
|
+
- lib/ueki/http_client/unsuccessful_response_exception_class_picker.rb
|
35
|
+
- lib/ueki/version.rb
|
36
|
+
homepage: https://github.com/tmimura39/ueki
|
37
|
+
licenses:
|
38
|
+
- MIT
|
39
|
+
metadata:
|
40
|
+
allowed_push_host: https://rubygems.org
|
41
|
+
homepage_uri: https://github.com/tmimura39/ueki
|
42
|
+
source_code_uri: https://github.com/tmimura39/ueki
|
43
|
+
changelog_uri: https://github.com/tmimura39/ueki/tree/main/CHANGELOG.md
|
44
|
+
rubygems_mfa_required: 'true'
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 3.1.0
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubygems_version: 3.5.17
|
61
|
+
signing_key:
|
62
|
+
specification_version: 4
|
63
|
+
summary: Module to assist in creating your Own HTTP client library
|
64
|
+
test_files: []
|