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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e5f85bad7fca01ea015e9a7acb13c6652ad6d06f651acacba1609f5299045ab
4
- data.tar.gz: e10427f17c76a569c5bea2c6f50cc71d812ea9987db1c288db2e319d976760c8
3
+ metadata.gz: ff49ecaad847aef0d9da4bdfc4f89cc3bc54da5edbadab40be265e9ec2ec1cf6
4
+ data.tar.gz: 1ddef9c1479654f53ed7a889cb33b3d1e4d972c005c4f631726bb4270721c2b0
5
5
  SHA512:
6
- metadata.gz: f5ab83ea7ee748d3e17c43bc5f7ce55a59685e7d4e10039c5e7cc28f0bca7b31c689e868f763c5f84b7ffcc9ea92f850b578cc14fede2c29cd24e73cad4c4548
7
- data.tar.gz: 01cf440dbad6c968d4cf8b83b4ac496d03309904c9d2c2b1aa01a0bd8d2894a5bfec1990ec7a01de488098b69917e82216cd1d53828c3d594ab49a7a6436e6b2
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 200 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:
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, user_agent: DEFAULT_USER_AGENT,
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
- self.base_url = base_url
28
- @content_type = content_type
29
- @open_timeout = open_timeout
30
- @read_timeout = read_timeout
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 = RequestBuilder.build(http_method, @base_url, endpoint, body)
63
- add_headers(request)
64
-
65
- response = Connection.send_request(@base_url, @open_timeout, @read_timeout, request)
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
@@ -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
- DEFAULT_USER_AGENT = "X-Client/#{Version} Ruby/#{RUBY_VERSION}".freeze
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
- require_relative "errors/network_error"
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
- def self.send_request(base_url, open_timeout, read_timeout, request)
11
- url = URI(base_url)
12
- http = Net::HTTP.new(url.host, url.port)
13
- http.use_ssl = url.scheme == "https"
14
- http.open_timeout = open_timeout
15
- http.read_timeout = read_timeout
16
- http.request(request)
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
@@ -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 = nil, object_class = DEFAULT_OBJECT_CLASS)
10
- @object = JSON.parse(response.body, object_class: object_class) if json_response?(response)
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
 
@@ -1,3 +1,4 @@
1
+ require "net/http"
1
2
  require_relative "bad_request_error"
2
3
  require_relative "authentication_error"
3
4
  require_relative "forbidden_error"
@@ -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
- def initialize(msg, response = nil, object_class = ClientDefaults::DEFAULT_OBJECT_CLASS)
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
@@ -11,12 +11,25 @@ module X
11
11
  delete: Net::HTTP::Delete
12
12
  }.freeze
13
13
 
14
- def self.build(http_method, base_url, endpoint, body = nil)
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
- def self.create_request(http_method, url, body)
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
@@ -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
- def initialize(response, array_class, object_class)
11
- @response = response
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(@response.body, array_class: @array_class, object_class: @object_class)
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[@response.code.to_i] || Error
22
- error_message = "#{@response.code} #{@response.message}"
23
- raise error_class, error_message if @response.body.nil? || @response.body.empty?
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, @response)
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
- @response.is_a?(Net::HTTPSuccess) && @response.body && @response["content-type"] == DEFAULT_CONTENT_TYPE
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
- module X
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
- def pre
19
- nil
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.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-06 00:00:00.000000000 Z
11
+ date: 2023-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oauth