x 0.4.0 → 0.5.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 +5 -0
- data/README.md +1 -1
- data/lib/x/client.rb +16 -48
- data/lib/x/client_defaults.rb +2 -1
- data/lib/x/connection.rb +27 -8
- data/lib/x/errors/error.rb +6 -2
- data/lib/x/errors/errors.rb +1 -0
- data/lib/x/errors/too_many_requests_error.rb +4 -2
- data/lib/x/request_builder.rb +32 -3
- data/lib/x/response_handler.rb +17 -11
- data/lib/x/version.rb +3 -37
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ff49ecaad847aef0d9da4bdfc4f89cc3bc54da5edbadab40be265e9ec2ec1cf6
|
4
|
+
data.tar.gz: 1ddef9c1479654f53ed7a889cb33b3d1e4d972c005c4f631726bb4270721c2b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 406e9aaa6f6166f9d267718ac22e94324b243c85475b30cb7569db957683014644542a3b44a6a3d337f0658a1204e8656b1b0c4ed2355b12ffc195c34b0734e4
|
7
|
+
data.tar.gz: b0754554e6272ef9b9ede3f650c3c407b71ae6ed8ede70d6da314ca408874898e83c2d6c14b31335817b0fb036422adde8ccefe3c13c274794c2e08229196812
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.5.0] - 2023-08-10
|
4
|
+
|
5
|
+
- Add configurable write timeout (2a31f84)
|
6
|
+
- Use built-in Gem::Version class (066e0b6)
|
7
|
+
|
3
8
|
## [0.4.0] - 2023-08-06
|
4
9
|
|
5
10
|
- Refactor Client into Authenticator, RequestBuilder, Connection, ResponseHandler (6bee1e9)
|
data/README.md
CHANGED
@@ -52,7 +52,7 @@ ads_client.get("accounts")
|
|
52
52
|
|
53
53
|
## History and Philosophy
|
54
54
|
|
55
|
-
This library is a rewrite of the [Twitter Ruby library](https://github.com/sferik/twitter). Over 16 years, that library ballooned to over 3,000 lines of code (plus 7,500 lines of tests). At the time of writing, this library is about
|
55
|
+
This library is a rewrite of the [Twitter Ruby library](https://github.com/sferik/twitter). Over 16 years, that library ballooned to over 3,000 lines of code (plus 7,500 lines of tests). At the time of writing, this library is about 300 lines of code (plus 200 test lines) and I’d like to keep it that way. That doesn’t mean new features won’t be added over time, but the benefits of potential new features must be weighed against the benefits of simplicity:
|
56
56
|
|
57
57
|
* Less code is easier to maintain.
|
58
58
|
* Less code means fewer bugs.
|
data/lib/x/client.rb
CHANGED
@@ -11,26 +11,25 @@ module X
|
|
11
11
|
extend Forwardable
|
12
12
|
include ClientDefaults
|
13
13
|
|
14
|
-
attr_reader :base_url
|
15
|
-
attr_accessor :content_type, :open_timeout, :read_timeout, :user_agent, :array_class, :object_class
|
16
|
-
|
17
14
|
def_delegators :@authenticator, :bearer_token, :api_key, :api_key_secret, :access_token, :access_token_secret
|
18
15
|
def_delegators :@authenticator, :bearer_token=, :api_key=, :api_key_secret=, :access_token=, :access_token_secret=
|
16
|
+
def_delegators :@connection, :base_url, :open_timeout, :read_timeout, :write_timeout
|
17
|
+
def_delegators :@connection, :base_url=, :open_timeout=, :read_timeout=, :write_timeout=
|
18
|
+
def_delegators :@request_builder, :content_type, :user_agent
|
19
|
+
def_delegators :@request_builder, :content_type=, :user_agent=
|
20
|
+
def_delegators :@response_handler, :array_class, :object_class
|
21
|
+
def_delegators :@response_handler, :array_class=, :object_class=
|
19
22
|
|
20
23
|
def initialize(bearer_token: nil, api_key: nil, api_key_secret: nil, access_token: nil, access_token_secret: nil,
|
21
|
-
base_url: DEFAULT_BASE_URL, content_type: DEFAULT_CONTENT_TYPE,
|
22
|
-
open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT,
|
24
|
+
base_url: DEFAULT_BASE_URL, content_type: DEFAULT_CONTENT_TYPE, user_agent: DEFAULT_USER_AGENT,
|
25
|
+
open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT, write_timeout: DEFAULT_WRITE_TIMEOUT,
|
23
26
|
array_class: DEFAULT_ARRAY_CLASS, object_class: DEFAULT_OBJECT_CLASS)
|
24
|
-
|
25
27
|
@authenticator = Authenticator.new(bearer_token: bearer_token, api_key: api_key, api_key_secret: api_key_secret,
|
26
28
|
access_token: access_token, access_token_secret: access_token_secret)
|
27
|
-
|
28
|
-
|
29
|
-
@
|
30
|
-
@
|
31
|
-
@user_agent = user_agent
|
32
|
-
@array_class = array_class
|
33
|
-
@object_class = object_class
|
29
|
+
@connection = Connection.new(base_url: base_url, open_timeout: open_timeout, read_timeout: read_timeout,
|
30
|
+
write_timeout: write_timeout)
|
31
|
+
@request_builder = RequestBuilder.new(content_type: content_type, user_agent: user_agent)
|
32
|
+
@response_handler = ResponseHandler.new(array_class: array_class, object_class: object_class)
|
34
33
|
end
|
35
34
|
|
36
35
|
def get(endpoint)
|
@@ -49,44 +48,13 @@ module X
|
|
49
48
|
send_request(:delete, endpoint)
|
50
49
|
end
|
51
50
|
|
52
|
-
def base_url=(new_base_url)
|
53
|
-
uri = URI(new_base_url)
|
54
|
-
raise ArgumentError, "Invalid base URL" unless uri.is_a?(URI::HTTPS) || uri.is_a?(URI::HTTP)
|
55
|
-
|
56
|
-
@base_url = uri
|
57
|
-
end
|
58
|
-
|
59
51
|
private
|
60
52
|
|
61
53
|
def send_request(http_method, endpoint, body = nil)
|
62
|
-
request =
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
ResponseHandler.new(response, @array_class, @object_class).handle
|
68
|
-
end
|
69
|
-
|
70
|
-
def add_headers(request)
|
71
|
-
add_authorization(request)
|
72
|
-
add_content_type(request)
|
73
|
-
add_user_agent(request)
|
74
|
-
end
|
75
|
-
|
76
|
-
def add_authorization(request)
|
77
|
-
if @authenticator.bearer_token
|
78
|
-
request["Authorization"] = "Bearer #{@bearer_token}"
|
79
|
-
else
|
80
|
-
@authenticator.sign!(request)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def add_content_type(request)
|
85
|
-
request["Content-Type"] = @content_type if @content_type
|
86
|
-
end
|
87
|
-
|
88
|
-
def add_user_agent(request)
|
89
|
-
request["User-Agent"] = @user_agent if @user_agent
|
54
|
+
request = @request_builder.build(authenticator: @authenticator, http_method: http_method, base_url: base_url,
|
55
|
+
endpoint: endpoint, body: body)
|
56
|
+
response = @connection.send_request(request: request)
|
57
|
+
@response_handler.handle(response: response)
|
90
58
|
end
|
91
59
|
end
|
92
60
|
end
|
data/lib/x/client_defaults.rb
CHANGED
@@ -8,6 +8,7 @@ module X
|
|
8
8
|
DEFAULT_OBJECT_CLASS = Hash
|
9
9
|
DEFAULT_OPEN_TIMEOUT = 60 # seconds
|
10
10
|
DEFAULT_READ_TIMEOUT = 60 # seconds
|
11
|
-
|
11
|
+
DEFAULT_WRITE_TIMEOUT = 60 # seconds
|
12
|
+
DEFAULT_USER_AGENT = "X-Client/#{VERSION} Ruby/#{RUBY_VERSION}".freeze
|
12
13
|
end
|
13
14
|
end
|
data/lib/x/connection.rb
CHANGED
@@ -1,21 +1,40 @@
|
|
1
|
+
require "forwardable"
|
1
2
|
require "net/http"
|
2
|
-
|
3
|
+
require "uri"
|
3
4
|
require_relative "errors/errors"
|
5
|
+
require_relative "errors/network_error"
|
4
6
|
|
5
7
|
module X
|
6
8
|
# Sends HTTP requests
|
7
9
|
class Connection
|
10
|
+
extend Forwardable
|
8
11
|
include Errors
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
attr_reader :base_url
|
14
|
+
|
15
|
+
def_delegators :@http_client, :open_timeout, :read_timeout, :write_timeout
|
16
|
+
def_delegators :@http_client, :open_timeout=, :read_timeout=, :write_timeout=
|
17
|
+
|
18
|
+
def initialize(base_url:, open_timeout:, read_timeout:, write_timeout:)
|
19
|
+
self.base_url = URI(base_url)
|
20
|
+
@http_client = Net::HTTP.new(@base_url.host, @base_url.port)
|
21
|
+
@http_client.use_ssl = @base_url.scheme == "https"
|
22
|
+
@http_client.open_timeout = open_timeout
|
23
|
+
@http_client.read_timeout = read_timeout
|
24
|
+
@http_client.write_timeout = write_timeout
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_request(request:)
|
28
|
+
@http_client.request(request)
|
17
29
|
rescue *NETWORK_ERRORS => e
|
18
30
|
raise NetworkError, "Network error: #{e.message}"
|
19
31
|
end
|
32
|
+
|
33
|
+
def base_url=(new_base_url)
|
34
|
+
uri = URI(new_base_url)
|
35
|
+
raise ArgumentError, "Invalid base URL" unless uri.is_a?(URI::HTTPS) || uri.is_a?(URI::HTTP)
|
36
|
+
|
37
|
+
@base_url = uri
|
38
|
+
end
|
20
39
|
end
|
21
40
|
end
|
data/lib/x/errors/error.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require "json"
|
2
|
+
require "net/http"
|
3
|
+
require_relative "../client_defaults"
|
2
4
|
|
3
5
|
module X
|
4
6
|
# Base error class
|
@@ -6,8 +8,10 @@ module X
|
|
6
8
|
include ClientDefaults
|
7
9
|
attr_reader :object
|
8
10
|
|
9
|
-
def initialize(msg = nil, response
|
10
|
-
|
11
|
+
def initialize(msg = nil, response: nil, array_class: DEFAULT_ARRAY_CLASS, object_class: DEFAULT_OBJECT_CLASS)
|
12
|
+
if json_response?(response)
|
13
|
+
@object = JSON.parse(response.body, array_class: array_class, object_class: object_class)
|
14
|
+
end
|
11
15
|
super(msg)
|
12
16
|
end
|
13
17
|
|
data/lib/x/errors/errors.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
-
require "net/http"
|
2
1
|
require_relative "client_error"
|
2
|
+
require_relative "../client_defaults"
|
3
3
|
|
4
4
|
module X
|
5
5
|
# Rate limit error
|
6
6
|
class TooManyRequestsError < ClientError
|
7
|
-
|
7
|
+
include ClientDefaults
|
8
|
+
|
9
|
+
def initialize(msg = nil, response: nil, array_class: DEFAULT_ARRAY_CLASS, object_class: DEFAULT_OBJECT_CLASS)
|
8
10
|
@response = response
|
9
11
|
super
|
10
12
|
end
|
data/lib/x/request_builder.rb
CHANGED
@@ -11,12 +11,25 @@ module X
|
|
11
11
|
delete: Net::HTTP::Delete
|
12
12
|
}.freeze
|
13
13
|
|
14
|
-
|
14
|
+
attr_accessor :content_type, :user_agent
|
15
|
+
|
16
|
+
def initialize(content_type:, user_agent:)
|
17
|
+
@content_type = content_type
|
18
|
+
@user_agent = user_agent
|
19
|
+
end
|
20
|
+
|
21
|
+
def build(authenticator:, http_method:, base_url:, endpoint:, body: nil)
|
15
22
|
url = URI.join(base_url, endpoint)
|
16
|
-
create_request(http_method, url, body)
|
23
|
+
request = create_request(http_method, url, body)
|
24
|
+
add_authorization(authenticator, request)
|
25
|
+
add_content_type(request)
|
26
|
+
add_user_agent(request)
|
27
|
+
request
|
17
28
|
end
|
18
29
|
|
19
|
-
|
30
|
+
private
|
31
|
+
|
32
|
+
def create_request(http_method, url, body)
|
20
33
|
http_method_class = HTTP_METHODS[http_method]
|
21
34
|
|
22
35
|
raise ArgumentError, "Unsupported HTTP method: #{http_method}" unless http_method_class
|
@@ -25,5 +38,21 @@ module X
|
|
25
38
|
request.body = body if body && http_method != :get
|
26
39
|
request
|
27
40
|
end
|
41
|
+
|
42
|
+
def add_authorization(authenticator, request)
|
43
|
+
if authenticator.bearer_token
|
44
|
+
request["Authorization"] = "Bearer #{@bearer_token}"
|
45
|
+
else
|
46
|
+
authenticator.sign!(request)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_content_type(request)
|
51
|
+
request["Content-Type"] = content_type if content_type
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_user_agent(request)
|
55
|
+
request["User-Agent"] = user_agent if user_agent
|
56
|
+
end
|
28
57
|
end
|
29
58
|
end
|
data/lib/x/response_handler.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "json"
|
2
|
+
require "net/http"
|
2
3
|
require_relative "errors/errors"
|
3
4
|
|
4
5
|
module X
|
@@ -7,28 +8,33 @@ module X
|
|
7
8
|
include ClientDefaults
|
8
9
|
include Errors
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
attr_accessor :array_class, :object_class
|
12
|
+
|
13
|
+
def initialize(array_class:, object_class:)
|
12
14
|
@array_class = array_class
|
13
15
|
@object_class = object_class
|
14
16
|
end
|
15
17
|
|
16
|
-
def handle
|
17
|
-
if successful_json_response?
|
18
|
-
return JSON.parse(
|
18
|
+
def handle(response:)
|
19
|
+
if successful_json_response?(response)
|
20
|
+
return JSON.parse(response.body, array_class: array_class, object_class: object_class)
|
19
21
|
end
|
20
22
|
|
21
|
-
error_class = ERROR_CLASSES[
|
22
|
-
error_message = "#{
|
23
|
-
raise error_class, error_message if
|
23
|
+
error_class = ERROR_CLASSES[response.code.to_i] || Error
|
24
|
+
error_message = "#{response.code} #{response.message}"
|
25
|
+
raise error_class, error_message if empty_response_body?(response)
|
24
26
|
|
25
|
-
raise error_class.new(error_message,
|
27
|
+
raise error_class.new(error_message, response: response, array_class: array_class, object_class: object_class)
|
26
28
|
end
|
27
29
|
|
28
30
|
private
|
29
31
|
|
30
|
-
def successful_json_response?
|
31
|
-
|
32
|
+
def successful_json_response?(response)
|
33
|
+
response.is_a?(Net::HTTPSuccess) && response.body && response["content-type"] == DEFAULT_CONTENT_TYPE
|
34
|
+
end
|
35
|
+
|
36
|
+
def empty_response_body?(response)
|
37
|
+
response.body.nil? || response.body.empty?
|
32
38
|
end
|
33
39
|
end
|
34
40
|
end
|
data/lib/x/version.rb
CHANGED
@@ -1,39 +1,5 @@
|
|
1
|
-
|
2
|
-
# The version of this library
|
3
|
-
module Version
|
4
|
-
module_function
|
5
|
-
|
6
|
-
def major
|
7
|
-
0
|
8
|
-
end
|
9
|
-
|
10
|
-
def minor
|
11
|
-
4
|
12
|
-
end
|
13
|
-
|
14
|
-
def patch
|
15
|
-
0
|
16
|
-
end
|
1
|
+
require "rubygems/version"
|
17
2
|
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
def to_h
|
23
|
-
{
|
24
|
-
major: major,
|
25
|
-
minor: minor,
|
26
|
-
patch: patch,
|
27
|
-
pre: pre
|
28
|
-
}
|
29
|
-
end
|
30
|
-
|
31
|
-
def to_a
|
32
|
-
[major, minor, patch, pre].compact
|
33
|
-
end
|
34
|
-
|
35
|
-
def to_s
|
36
|
-
to_a.join(".")
|
37
|
-
end
|
38
|
-
end
|
3
|
+
module X
|
4
|
+
VERSION = Gem::Version.create("0.5.0")
|
39
5
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: x
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Erik Berlin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-08-
|
11
|
+
date: 2023-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oauth
|