x 0.10.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -28
- data/README.md +12 -2
- data/lib/x/authenticator.rb +10 -0
- data/lib/x/bearer_token_authenticator.rb +5 -3
- data/lib/x/cgi.rb +15 -0
- data/lib/x/client.rb +85 -49
- data/lib/x/connection.rb +39 -76
- data/lib/x/errors/bad_gateway.rb +5 -0
- data/lib/x/errors/{not_found_error.rb → bad_request.rb} +1 -1
- data/lib/x/errors/client_error.rb +2 -2
- data/lib/x/errors/connection_exception.rb +5 -0
- data/lib/x/errors/error.rb +1 -11
- data/lib/x/errors/{forbidden_error.rb → forbidden.rb} +1 -1
- data/lib/x/errors/gateway_timeout.rb +5 -0
- data/lib/x/errors/{bad_request_error.rb → gone.rb} +1 -1
- data/lib/x/errors/http_error.rb +42 -0
- data/lib/x/errors/network_error.rb +1 -1
- data/lib/x/errors/not_acceptable.rb +5 -0
- data/lib/x/errors/not_found.rb +5 -0
- data/lib/x/errors/payload_too_large.rb +5 -0
- data/lib/x/errors/server_error.rb +2 -2
- data/lib/x/errors/service_unavailable.rb +5 -0
- data/lib/x/errors/too_many_redirects.rb +5 -0
- data/lib/x/errors/too_many_requests.rb +24 -0
- data/lib/x/errors/unauthorized.rb +5 -0
- data/lib/x/errors/unprocessable_entity.rb +5 -0
- data/lib/x/{media_upload.rb → media_uploader.rb} +34 -35
- data/lib/x/oauth_authenticator.rb +10 -15
- data/lib/x/redirect_handler.rb +26 -23
- data/lib/x/request_builder.rb +22 -35
- data/lib/x/response_parser.rb +69 -0
- data/lib/x/version.rb +1 -1
- data/sig/x.rbs +118 -92
- metadata +22 -13
- data/lib/x/errors/authentication_error.rb +0 -5
- data/lib/x/errors/payload_too_large_error.rb +0 -5
- data/lib/x/errors/service_unavailable_error.rb +0 -5
- data/lib/x/errors/too_many_redirects_error.rb +0 -5
- data/lib/x/errors/too_many_requests_error.rb +0 -29
- data/lib/x/response_handler.rb +0 -63
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c6252a6765246fe6ca0820e5ce96f9e66d9611e91b2d59753730fad6b87f67e
|
4
|
+
data.tar.gz: 7a7dc55ece60d848cde08c151e887a1cc86808408a864c05934c4be906b2021a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4216ad5a466dd1a714a638a45d9c0996f40c16247f7fc6eb0ff756d301c3cd63aac5ca95fd421940634e3661db5cb41e19300b73f8ef01056c14e21ad89edea1
|
7
|
+
data.tar.gz: a27036e7dd594f9b13b5cd66267c57b63679352ea3eeeaada13adbba838c6cdad5d8f841fe35b808950fc610808c219679e72334091b3d8de1dca5899ba386b5
|
data/CHANGELOG.md
CHANGED
@@ -1,69 +1,88 @@
|
|
1
|
+
## [0.12.0] - 2023-11-02
|
2
|
+
* Ensure Authenticator is passed to RedirectHandler (fc8557b)
|
3
|
+
* Add AUTHENTICATION_HEADER to X::Authenticator base class (85a2818)
|
4
|
+
* Introduce X::HTTPError (90ae132)
|
5
|
+
* Add `code` attribute to error classes (b003639)
|
6
|
+
|
7
|
+
## [0.11.0] - 2023-10-24
|
8
|
+
|
9
|
+
* Add base Authenticator class (8c66ce2)
|
10
|
+
* Consistently use keyword arguments (3beb271)
|
11
|
+
* Use patern matching to build request (4d001c7)
|
12
|
+
* Rename ResponseHandler to ResponseParser (498e890)
|
13
|
+
* Rename methods to be more consistent (5b8c655)
|
14
|
+
* Rename MediaUpload to MediaUploader (84f0c15)
|
15
|
+
* Add mutant and kill mutants (b124968)
|
16
|
+
* Fix authentication bug with request URLs that contain spaces (8de3174)
|
17
|
+
* Refactor errors (853d39c)
|
18
|
+
* Make Connection class threadsafe (d95d285)
|
19
|
+
|
1
20
|
## [0.10.0] - 2023-10-08
|
2
21
|
|
3
|
-
|
4
|
-
|
22
|
+
* Add media upload helper methods (6c6a267)
|
23
|
+
* Add PayloadTooLargeError class (cd61850)
|
5
24
|
|
6
25
|
## [0.9.1] - 2023-10-06
|
7
26
|
|
8
|
-
|
9
|
-
|
10
|
-
|
27
|
+
* Allow successful empty responses (06bf7db)
|
28
|
+
* Update default User-Agent string (296b36a)
|
29
|
+
* Move query parameter escaping into RequestBuilder (56d6bd2)
|
11
30
|
|
12
31
|
## [0.9.0] - 2023-09-26
|
13
32
|
|
14
|
-
|
33
|
+
* Add support for HTTP proxies (3740f4f)
|
15
34
|
|
16
35
|
## [0.8.1] - 2023-09-20
|
17
36
|
|
18
|
-
|
37
|
+
* Fix bug where setting Connection#base_uri= doesn't update the HTTP client (d5a89db)
|
19
38
|
|
20
39
|
## [0.8.0] - 2023-09-14
|
21
40
|
|
22
|
-
|
23
|
-
|
24
|
-
|
41
|
+
* Add (back) bearer token authentication (62e141d)
|
42
|
+
* Follow redirects (90a8c55)
|
43
|
+
* Parse error responses with Content-Type: application/problem+json (0b697d9)
|
25
44
|
|
26
45
|
## [0.7.1] - 2023-09-02
|
27
46
|
|
28
|
-
|
47
|
+
* Fix bug in X::Authenticator#split_uri (ebc9d5f)
|
29
48
|
|
30
49
|
## [0.7.0] - 2023-09-02
|
31
50
|
|
32
|
-
|
51
|
+
* Remove OAuth gem (7c29bb1)
|
33
52
|
|
34
53
|
## [0.6.0] - 2023-08-30
|
35
54
|
|
36
|
-
|
37
|
-
|
38
|
-
|
55
|
+
* Add configurable debug output stream for logging (fd2d4b0)
|
56
|
+
* Remove bearer token authentication (efff940)
|
57
|
+
* Define RBS type signatures (d7f63ba)
|
39
58
|
|
40
59
|
## [0.5.1] - 2023-08-16
|
41
60
|
|
42
|
-
|
61
|
+
* Fix bearer token authentication (1a1ca93)
|
43
62
|
|
44
63
|
## [0.5.0] - 2023-08-10
|
45
64
|
|
46
|
-
|
47
|
-
|
65
|
+
* Add configurable write timeout (2a31f84)
|
66
|
+
* Use built-in Gem::Version class (066e0b6)
|
48
67
|
|
49
68
|
## [0.4.0] - 2023-08-06
|
50
69
|
|
51
|
-
|
52
|
-
|
53
|
-
|
70
|
+
* Refactor Client into Authenticator, RequestBuilder, Connection, ResponseHandler (6bee1e9)
|
71
|
+
* Add configurable open timeout (1000f9d)
|
72
|
+
* Allow configuration of content type (f33a732)
|
54
73
|
|
55
74
|
## [0.3.0] - 2023-08-04
|
56
75
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
76
|
+
* Add accessors to X::Client (e61fa73)
|
77
|
+
* Add configurable read timeout (41502b9)
|
78
|
+
* Handle network-related errors (9ed1fb4)
|
79
|
+
* Include response body in errors (a203e6a)
|
61
80
|
|
62
81
|
## [0.2.0] - 2023-08-02
|
63
82
|
|
64
|
-
|
65
|
-
|
83
|
+
* Allow configuration of base URL (4bc0531)
|
84
|
+
* Improve error handling (14dc0cd)
|
66
85
|
|
67
86
|
## [0.1.0] - 2023-08-02
|
68
87
|
|
69
|
-
|
88
|
+
* Initial release
|
data/README.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
![Tests](https://github.com/sferik/x-ruby/actions/workflows/test.yml/badge.svg)
|
2
|
+
![Linter](https://github.com/sferik/x-ruby/actions/workflows/lint.yml/badge.svg)
|
3
|
+
![Mutant](https://github.com/sferik/x-ruby/actions/workflows/mutant.yml/badge.svg)
|
4
|
+
![Typer Checker](https://github.com/sferik/x-ruby/actions/workflows/type_check.yml/badge.svg)
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/x.svg)](https://rubygems.org/gems/x)
|
6
|
+
|
1
7
|
# A [Ruby](https://www.ruby-lang.org) interface to the [X API](https://developer.x.com)
|
2
8
|
|
3
9
|
## Follow
|
@@ -130,11 +136,15 @@ Pull requests will only be accepted if they meet all the following criteria:
|
|
130
136
|
|
131
137
|
bundle exec rake standard
|
132
138
|
|
133
|
-
2.
|
139
|
+
2. 100% C0 code coverage. This can be verified with:
|
134
140
|
|
135
141
|
bundle exec rake test
|
136
142
|
|
137
|
-
3.
|
143
|
+
3. 100% mutation coverage. This can be verified with:
|
144
|
+
|
145
|
+
bundle exec rake mutant
|
146
|
+
|
147
|
+
4. RBS type signatures (in `sig/x.rbs`). This can be verified with:
|
138
148
|
|
139
149
|
bundle exec rake steep
|
140
150
|
|
@@ -1,14 +1,16 @@
|
|
1
|
+
require_relative "authenticator"
|
2
|
+
|
1
3
|
module X
|
2
4
|
# Handles bearer token authentication
|
3
|
-
class BearerTokenAuthenticator
|
5
|
+
class BearerTokenAuthenticator < Authenticator
|
4
6
|
attr_accessor :bearer_token
|
5
7
|
|
6
|
-
def initialize(bearer_token)
|
8
|
+
def initialize(bearer_token:) # rubocop:disable Lint/MissingSuper
|
7
9
|
@bearer_token = bearer_token
|
8
10
|
end
|
9
11
|
|
10
12
|
def header(_request)
|
11
|
-
"Bearer #{bearer_token}"
|
13
|
+
{AUTHENTICATION_HEADER => "Bearer #{bearer_token}"}
|
12
14
|
end
|
13
15
|
end
|
14
16
|
end
|
data/lib/x/cgi.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "cgi"
|
2
|
+
|
3
|
+
module X
|
4
|
+
# Namespaced CGI class
|
5
|
+
class CGI
|
6
|
+
# TODO: Replace CGI.escape with CGI.escapeURIComponent when support for Ruby 3.1 is dropped
|
7
|
+
def self.escape(value)
|
8
|
+
::CGI.escape(value).gsub("+", "%20")
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.escape_params(params)
|
12
|
+
params.map { |k, v| "#{k}=#{escape(v)}" }.join("&")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/x/client.rb
CHANGED
@@ -1,87 +1,123 @@
|
|
1
1
|
require "forwardable"
|
2
2
|
require_relative "bearer_token_authenticator"
|
3
|
-
require_relative "oauth_authenticator"
|
4
3
|
require_relative "connection"
|
4
|
+
require_relative "oauth_authenticator"
|
5
5
|
require_relative "redirect_handler"
|
6
6
|
require_relative "request_builder"
|
7
|
-
require_relative "
|
7
|
+
require_relative "response_parser"
|
8
8
|
|
9
9
|
module X
|
10
10
|
# Main public interface
|
11
11
|
class Client
|
12
12
|
extend Forwardable
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
def_delegators :@
|
20
|
-
def_delegators :@
|
21
|
-
def_delegators :@
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
def initialize(
|
27
|
-
|
28
|
-
base_url:
|
14
|
+
DEFAULT_BASE_URL = "https://api.twitter.com/2/".freeze
|
15
|
+
|
16
|
+
attr_accessor :base_url
|
17
|
+
attr_reader :api_key, :api_key_secret, :access_token, :access_token_secret, :bearer_token
|
18
|
+
|
19
|
+
def_delegators :@connection, :open_timeout, :read_timeout, :write_timeout, :proxy_url, :debug_output
|
20
|
+
def_delegators :@connection, :open_timeout=, :read_timeout=, :write_timeout=, :proxy_url=, :debug_output=
|
21
|
+
def_delegators :@redirect_handler, :max_redirects
|
22
|
+
def_delegators :@redirect_handler, :max_redirects=
|
23
|
+
def_delegators :@response_parser, :array_class, :object_class
|
24
|
+
def_delegators :@response_parser, :array_class=, :object_class=
|
25
|
+
|
26
|
+
def initialize(api_key: nil, api_key_secret: nil, access_token: nil, access_token_secret: nil,
|
27
|
+
bearer_token: nil,
|
28
|
+
base_url: DEFAULT_BASE_URL,
|
29
29
|
open_timeout: Connection::DEFAULT_OPEN_TIMEOUT,
|
30
30
|
read_timeout: Connection::DEFAULT_READ_TIMEOUT,
|
31
31
|
write_timeout: Connection::DEFAULT_WRITE_TIMEOUT,
|
32
|
+
debug_output: Connection::DEFAULT_DEBUG_OUTPUT,
|
32
33
|
proxy_url: nil,
|
33
|
-
|
34
|
-
|
35
|
-
debug_output: nil,
|
36
|
-
array_class: ResponseHandler::DEFAULT_ARRAY_CLASS,
|
37
|
-
object_class: ResponseHandler::DEFAULT_OBJECT_CLASS,
|
34
|
+
array_class: nil,
|
35
|
+
object_class: nil,
|
38
36
|
max_redirects: RedirectHandler::DEFAULT_MAX_REDIRECTS)
|
39
37
|
|
40
|
-
|
41
|
-
@
|
38
|
+
initialize_oauth(api_key, api_key_secret, access_token, access_token_secret)
|
39
|
+
@bearer_token = bearer_token
|
40
|
+
initialize_authenticator
|
41
|
+
@base_url = base_url
|
42
|
+
@connection = Connection.new(open_timeout: open_timeout, read_timeout: read_timeout,
|
42
43
|
write_timeout: write_timeout, debug_output: debug_output, proxy_url: proxy_url)
|
43
|
-
@request_builder = RequestBuilder.new
|
44
|
-
@redirect_handler = RedirectHandler.new(
|
44
|
+
@request_builder = RequestBuilder.new
|
45
|
+
@redirect_handler = RedirectHandler.new(connection: @connection, request_builder: @request_builder,
|
45
46
|
max_redirects: max_redirects)
|
46
|
-
@
|
47
|
+
@response_parser = ResponseParser.new(array_class: array_class, object_class: object_class)
|
48
|
+
end
|
49
|
+
|
50
|
+
def get(endpoint, headers: {})
|
51
|
+
execute_request(:get, endpoint, headers: headers)
|
52
|
+
end
|
53
|
+
|
54
|
+
def post(endpoint, body = nil, headers: {})
|
55
|
+
execute_request(:post, endpoint, body: body, headers: headers)
|
47
56
|
end
|
48
57
|
|
49
|
-
def
|
50
|
-
|
58
|
+
def put(endpoint, body = nil, headers: {})
|
59
|
+
execute_request(:put, endpoint, body: body, headers: headers)
|
51
60
|
end
|
52
61
|
|
53
|
-
def
|
54
|
-
|
62
|
+
def delete(endpoint, headers: {})
|
63
|
+
execute_request(:delete, endpoint, headers: headers)
|
55
64
|
end
|
56
65
|
|
57
|
-
def
|
58
|
-
|
66
|
+
def api_key=(api_key)
|
67
|
+
@api_key = api_key
|
68
|
+
initialize_authenticator
|
59
69
|
end
|
60
70
|
|
61
|
-
def
|
62
|
-
|
71
|
+
def api_key_secret=(api_key_secret)
|
72
|
+
@api_key_secret = api_key_secret
|
73
|
+
initialize_authenticator
|
74
|
+
end
|
75
|
+
|
76
|
+
def access_token=(access_token)
|
77
|
+
@access_token = access_token
|
78
|
+
initialize_authenticator
|
79
|
+
end
|
80
|
+
|
81
|
+
def access_token_secret=(access_token_secret)
|
82
|
+
@access_token_secret = access_token_secret
|
83
|
+
initialize_authenticator
|
84
|
+
end
|
85
|
+
|
86
|
+
def bearer_token=(bearer_token)
|
87
|
+
@bearer_token = bearer_token
|
88
|
+
initialize_authenticator
|
63
89
|
end
|
64
90
|
|
65
91
|
private
|
66
92
|
|
67
|
-
def
|
68
|
-
@
|
69
|
-
|
70
|
-
|
71
|
-
|
93
|
+
def initialize_oauth(api_key, api_key_secret, access_token, access_token_secret)
|
94
|
+
@api_key = api_key
|
95
|
+
@api_key_secret = api_key_secret
|
96
|
+
@access_token = access_token
|
97
|
+
@access_token_secret = access_token_secret
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize_authenticator
|
101
|
+
@authenticator = if api_key && api_key_secret && access_token && access_token_secret
|
102
|
+
OAuthAuthenticator.new(api_key: api_key, api_key_secret: api_key_secret, access_token: access_token,
|
103
|
+
access_token_secret: access_token_secret)
|
104
|
+
elsif bearer_token
|
105
|
+
BearerTokenAuthenticator.new(bearer_token: bearer_token)
|
106
|
+
elsif @authenticator.nil?
|
107
|
+
Authenticator.new
|
72
108
|
else
|
73
|
-
|
74
|
-
"Client must be initialized with either a bearer_token or " \
|
75
|
-
"an api_key, api_key_secret, access_token, and access_token_secret"
|
109
|
+
@authenticator
|
76
110
|
end
|
77
111
|
end
|
78
112
|
|
79
|
-
def
|
80
|
-
uri = URI.join(
|
81
|
-
request = @request_builder.build(
|
82
|
-
|
83
|
-
|
84
|
-
@
|
113
|
+
def execute_request(http_method, endpoint, headers:, body: nil)
|
114
|
+
uri = URI.join(base_url, endpoint)
|
115
|
+
request = @request_builder.build(http_method: http_method, uri: uri, body: body, headers: headers,
|
116
|
+
authenticator: @authenticator)
|
117
|
+
response = @connection.perform(request: request)
|
118
|
+
response = @redirect_handler.handle(response: response, request: request, base_url: base_url,
|
119
|
+
authenticator: @authenticator)
|
120
|
+
@response_parser.parse(response: response)
|
85
121
|
end
|
86
122
|
end
|
87
123
|
end
|
data/lib/x/connection.rb
CHANGED
@@ -9,12 +9,12 @@ module X
|
|
9
9
|
class Connection
|
10
10
|
extend Forwardable
|
11
11
|
|
12
|
-
|
13
|
-
DEFAULT_HOST = "https://api.twitter.com".freeze
|
12
|
+
DEFAULT_HOST = "api.twitter.com".freeze
|
14
13
|
DEFAULT_PORT = 443
|
15
14
|
DEFAULT_OPEN_TIMEOUT = 60 # seconds
|
16
15
|
DEFAULT_READ_TIMEOUT = 60 # seconds
|
17
16
|
DEFAULT_WRITE_TIMEOUT = 60 # seconds
|
17
|
+
DEFAULT_DEBUG_OUTPUT = File.open(File::NULL, "w")
|
18
18
|
NETWORK_ERRORS = [
|
19
19
|
Errno::ECONNREFUSED,
|
20
20
|
Errno::ECONNRESET,
|
@@ -23,95 +23,58 @@ module X
|
|
23
23
|
OpenSSL::SSL::SSLError
|
24
24
|
].freeze
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
def_delegator
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
)
|
26
|
+
attr_accessor :open_timeout, :read_timeout, :write_timeout, :debug_output
|
27
|
+
attr_reader :proxy_url, :proxy_uri
|
28
|
+
|
29
|
+
def_delegator :proxy_uri, :host, :proxy_host
|
30
|
+
def_delegator :proxy_uri, :port, :proxy_port
|
31
|
+
def_delegator :proxy_uri, :user, :proxy_user
|
32
|
+
def_delegator :proxy_uri, :password, :proxy_pass
|
33
|
+
|
34
|
+
def initialize(open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT,
|
35
|
+
write_timeout: DEFAULT_WRITE_TIMEOUT, debug_output: DEFAULT_DEBUG_OUTPUT, proxy_url: nil)
|
36
|
+
@open_timeout = open_timeout
|
37
|
+
@read_timeout = read_timeout
|
38
|
+
@write_timeout = write_timeout
|
39
|
+
@debug_output = debug_output
|
40
|
+
self.proxy_url = proxy_url unless proxy_url.nil?
|
42
41
|
end
|
43
42
|
|
44
|
-
def
|
45
|
-
|
43
|
+
def perform(request:)
|
44
|
+
host = request.uri.host || DEFAULT_HOST
|
45
|
+
port = request.uri.port || DEFAULT_PORT
|
46
|
+
http_client = build_http_client(host, port)
|
47
|
+
http_client.use_ssl = request.uri.scheme.eql?("https")
|
48
|
+
http_client.request(request)
|
46
49
|
rescue *NETWORK_ERRORS => e
|
47
|
-
raise NetworkError
|
50
|
+
raise NetworkError, "Network error: #{e}"
|
48
51
|
end
|
49
52
|
|
50
|
-
def
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
@base_uri = base_uri
|
55
|
-
update_http_client_settings
|
56
|
-
end
|
53
|
+
def proxy_url=(proxy_url)
|
54
|
+
@proxy_url = proxy_url
|
55
|
+
proxy_uri = URI(proxy_url)
|
56
|
+
raise ArgumentError, "Invalid proxy URL: #{proxy_uri}" unless proxy_uri.is_a?(URI::HTTP)
|
57
57
|
|
58
|
-
|
59
|
-
@http_client.instance_variable_get(:@debug_output)
|
60
|
-
end
|
61
|
-
|
62
|
-
def configuration
|
63
|
-
{
|
64
|
-
base_url: base_uri.to_s,
|
65
|
-
open_timeout: open_timeout,
|
66
|
-
read_timeout: read_timeout,
|
67
|
-
write_timeout: write_timeout,
|
68
|
-
proxy_url: proxy_uri.to_s,
|
69
|
-
debug_output: debug_output
|
70
|
-
}
|
58
|
+
@proxy_uri = proxy_uri
|
71
59
|
end
|
72
60
|
|
73
61
|
private
|
74
62
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
@http_client.write_timeout = write_timeout
|
79
|
-
@http_client.set_debug_output(debug_output) if debug_output
|
80
|
-
end
|
81
|
-
|
82
|
-
def current_http_client_settings
|
83
|
-
{
|
84
|
-
open_timeout: @http_client.open_timeout,
|
85
|
-
read_timeout: @http_client.read_timeout,
|
86
|
-
write_timeout: @http_client.write_timeout,
|
87
|
-
debug_output: debug_output
|
88
|
-
}
|
89
|
-
end
|
90
|
-
|
91
|
-
def update_http_client_settings
|
92
|
-
conditionally_apply_http_client_settings do
|
93
|
-
host = @base_uri.host || DEFAULT_HOST
|
94
|
-
port = @base_uri.port || DEFAULT_PORT
|
95
|
-
@http_client = build_http_client(host: host, port: port)
|
96
|
-
@http_client.use_ssl = @base_uri.scheme == "https"
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def build_http_client(host:, port:)
|
101
|
-
if defined?(@proxy_uri)
|
102
|
-
Net::HTTP.new(host, port, @proxy_uri&.host, @proxy_uri&.port, @proxy_uri&.user, @proxy_uri&.password)
|
63
|
+
def build_http_client(host = DEFAULT_HOST, port = DEFAULT_PORT)
|
64
|
+
http_client = if proxy_uri
|
65
|
+
Net::HTTP.new(host, port, proxy_host, proxy_port, proxy_user, proxy_pass)
|
103
66
|
else
|
104
67
|
Net::HTTP.new(host, port)
|
105
68
|
end
|
69
|
+
configure_http_client(http_client)
|
106
70
|
end
|
107
71
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
yield
|
72
|
+
def configure_http_client(http_client)
|
73
|
+
http_client.tap do |c|
|
74
|
+
c.open_timeout = open_timeout
|
75
|
+
c.read_timeout = read_timeout
|
76
|
+
c.write_timeout = write_timeout
|
77
|
+
c.set_debug_output(debug_output)
|
115
78
|
end
|
116
79
|
end
|
117
80
|
end
|
data/lib/x/errors/error.rb
CHANGED
@@ -1,13 +1,3 @@
|
|
1
|
-
require "json"
|
2
|
-
|
3
1
|
module X
|
4
|
-
|
5
|
-
class Error < ::StandardError
|
6
|
-
attr_reader :object
|
7
|
-
|
8
|
-
def initialize(msg, response:)
|
9
|
-
@object = JSON.parse(response.body) if response&.body && !response.body.empty?
|
10
|
-
super(msg)
|
11
|
-
end
|
12
|
-
end
|
2
|
+
class Error < StandardError; end
|
13
3
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "json"
|
2
|
+
require_relative "error"
|
3
|
+
|
4
|
+
module X
|
5
|
+
# Base HTTP error class
|
6
|
+
class HTTPError < Error
|
7
|
+
JSON_CONTENT_TYPE_REGEXP = %r{application/(problem\+|)json}
|
8
|
+
|
9
|
+
attr_reader :response, :code
|
10
|
+
|
11
|
+
def initialize(response:)
|
12
|
+
super(error_message(response))
|
13
|
+
@response = response
|
14
|
+
@code = response.code
|
15
|
+
end
|
16
|
+
|
17
|
+
def error_message(response)
|
18
|
+
if json?(response)
|
19
|
+
message_from_json_response(response)
|
20
|
+
else
|
21
|
+
response.message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def message_from_json_response(response)
|
26
|
+
response_object = JSON.parse(response.body)
|
27
|
+
if response_object.key?("title") && response_object.key?("detail")
|
28
|
+
"#{response_object.fetch("title")}: #{response_object.fetch("detail")}"
|
29
|
+
elsif response_object.key?("error")
|
30
|
+
response_object.fetch("error")
|
31
|
+
elsif response_object["errors"].instance_of?(Array)
|
32
|
+
response_object.fetch("errors").map { |error| error.fetch("message") }.join(", ")
|
33
|
+
else
|
34
|
+
response.message
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def json?(response)
|
39
|
+
JSON_CONTENT_TYPE_REGEXP.match?(response["content-type"])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|