x 0.11.0 → 0.12.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5af820e6e3cd8b04becf201ad7af96c8d95d9206112673888f91c5ecf923187
4
- data.tar.gz: 6f0f561be192153eca2cc7356c823c87bd98f0987256875892c0a2e9ea12b82d
3
+ metadata.gz: 5c6252a6765246fe6ca0820e5ce96f9e66d9611e91b2d59753730fad6b87f67e
4
+ data.tar.gz: 7a7dc55ece60d848cde08c151e887a1cc86808408a864c05934c4be906b2021a
5
5
  SHA512:
6
- metadata.gz: 8b208d5ba10604a34a57b0dd2ee5dc0992eb801cfbcc287a7f1b0d54444f214050a2c25b2ec6291868a8c35fff09260ad91a82641be830b87d3e6abf0207fc3e
7
- data.tar.gz: 24c6a1083358b60065698320372d53404bab9c319aaad13b097508a7041653ac9d45aabd8e6f7a99204accddfc1beeeb2bce18bd67fa9ba816eb6282cbab4227
6
+ metadata.gz: 4216ad5a466dd1a714a638a45d9c0996f40c16247f7fc6eb0ff756d301c3cd63aac5ca95fd421940634e3661db5cb41e19300b73f8ef01056c14e21ad89edea1
7
+ data.tar.gz: a27036e7dd594f9b13b5cd66267c57b63679352ea3eeeaada13adbba838c6cdad5d8f841fe35b808950fc610808c219679e72334091b3d8de1dca5899ba386b5
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
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
+
1
7
  ## [0.11.0] - 2023-10-24
2
8
 
3
9
  * Add base Authenticator class (8c66ce2)
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
@@ -1,8 +1,10 @@
1
1
  module X
2
2
  # Base Authenticator class
3
3
  class Authenticator
4
+ AUTHENTICATION_HEADER = "Authorization".freeze
5
+
4
6
  def header(_request)
5
- {"Authorization" => ""}
7
+ {AUTHENTICATION_HEADER => ""}
6
8
  end
7
9
  end
8
10
  end
@@ -10,7 +10,7 @@ module X
10
10
  end
11
11
 
12
12
  def header(_request)
13
- {"Authorization" => "Bearer #{bearer_token}"}
13
+ {AUTHENTICATION_HEADER => "Bearer #{bearer_token}"}
14
14
  end
15
15
  end
16
16
  end
data/lib/x/client.rb CHANGED
@@ -14,9 +14,8 @@ module X
14
14
  DEFAULT_BASE_URL = "https://api.twitter.com/2/".freeze
15
15
 
16
16
  attr_accessor :base_url
17
+ attr_reader :api_key, :api_key_secret, :access_token, :access_token_secret, :bearer_token
17
18
 
18
- def_delegators :@authenticator, :bearer_token, :api_key, :api_key_secret, :access_token, :access_token_secret
19
- def_delegators :@authenticator, :bearer_token=, :api_key=, :api_key_secret=, :access_token=, :access_token_secret=
20
19
  def_delegators :@connection, :open_timeout, :read_timeout, :write_timeout, :proxy_url, :debug_output
21
20
  def_delegators :@connection, :open_timeout=, :read_timeout=, :write_timeout=, :proxy_url=, :debug_output=
22
21
  def_delegators :@redirect_handler, :max_redirects
@@ -24,25 +23,27 @@ module X
24
23
  def_delegators :@response_parser, :array_class, :object_class
25
24
  def_delegators :@response_parser, :array_class=, :object_class=
26
25
 
27
- def initialize(bearer_token: nil,
28
- api_key: nil, api_key_secret: nil, access_token: nil, access_token_secret: nil,
26
+ def initialize(api_key: nil, api_key_secret: nil, access_token: nil, access_token_secret: nil,
27
+ bearer_token: nil,
29
28
  base_url: DEFAULT_BASE_URL,
30
29
  open_timeout: Connection::DEFAULT_OPEN_TIMEOUT,
31
30
  read_timeout: Connection::DEFAULT_READ_TIMEOUT,
32
31
  write_timeout: Connection::DEFAULT_WRITE_TIMEOUT,
32
+ debug_output: Connection::DEFAULT_DEBUG_OUTPUT,
33
33
  proxy_url: nil,
34
- debug_output: nil,
35
34
  array_class: nil,
36
35
  object_class: nil,
37
36
  max_redirects: RedirectHandler::DEFAULT_MAX_REDIRECTS)
38
37
 
38
+ initialize_oauth(api_key, api_key_secret, access_token, access_token_secret)
39
+ @bearer_token = bearer_token
40
+ initialize_authenticator
39
41
  @base_url = base_url
40
- initialize_authenticator(bearer_token, api_key, api_key_secret, access_token, access_token_secret)
41
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
44
  @request_builder = RequestBuilder.new
44
- @redirect_handler = RedirectHandler.new(authenticator: @authenticator, connection: @connection,
45
- request_builder: @request_builder, max_redirects: max_redirects)
45
+ @redirect_handler = RedirectHandler.new(connection: @connection, request_builder: @request_builder,
46
+ max_redirects: max_redirects)
46
47
  @response_parser = ResponseParser.new(array_class: array_class, object_class: object_class)
47
48
  end
48
49
 
@@ -62,25 +63,60 @@ module X
62
63
  execute_request(:delete, endpoint, headers: headers)
63
64
  end
64
65
 
66
+ def api_key=(api_key)
67
+ @api_key = api_key
68
+ initialize_authenticator
69
+ end
70
+
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
89
+ end
90
+
65
91
  private
66
92
 
67
- def initialize_authenticator(bearer_token, api_key, api_key_secret, access_token, access_token_secret)
68
- @authenticator = if bearer_token
69
- BearerTokenAuthenticator.new(bearer_token: bearer_token)
70
- elsif api_key && api_key_secret && access_token && access_token_secret
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
71
102
  OAuthAuthenticator.new(api_key: api_key, api_key_secret: api_key_secret, access_token: access_token,
72
103
  access_token_secret: access_token_secret)
73
- else
104
+ elsif bearer_token
105
+ BearerTokenAuthenticator.new(bearer_token: bearer_token)
106
+ elsif @authenticator.nil?
74
107
  Authenticator.new
108
+ else
109
+ @authenticator
75
110
  end
76
111
  end
77
112
 
78
113
  def execute_request(http_method, endpoint, headers:, body: nil)
79
114
  uri = URI.join(base_url, endpoint)
80
- request = @request_builder.build(authenticator: @authenticator, http_method: http_method, uri: uri, body: body,
81
- headers: headers)
115
+ request = @request_builder.build(http_method: http_method, uri: uri, body: body, headers: headers,
116
+ authenticator: @authenticator)
82
117
  response = @connection.perform(request: request)
83
- response = @redirect_handler.handle(response: response, request: request, base_url: base_url)
118
+ response = @redirect_handler.handle(response: response, request: request, base_url: base_url,
119
+ authenticator: @authenticator)
84
120
  @response_parser.parse(response: response)
85
121
  end
86
122
  end
@@ -1,5 +1,5 @@
1
- require_relative "error"
1
+ require_relative "http_error"
2
2
 
3
3
  module X
4
- class ClientError < Error; end
4
+ class ClientError < HTTPError; end
5
5
  end
@@ -1,8 +1,3 @@
1
1
  module X
2
- # Base error class
3
- class Error < ::StandardError
4
- def initialize(msg, _response = nil)
5
- super(msg)
6
- end
7
- end
2
+ class Error < StandardError; end
8
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
@@ -1,5 +1,5 @@
1
- require_relative "error"
1
+ require_relative "http_error"
2
2
 
3
3
  module X
4
- class ServerError < Error; end
4
+ class ServerError < HTTPError; end
5
5
  end
@@ -1,5 +1,5 @@
1
- require_relative "client_error"
1
+ require_relative "error"
2
2
 
3
3
  module X
4
- class TooManyRedirects < ClientError; end
4
+ class TooManyRedirects < Error; end
5
5
  end
@@ -1,23 +1,18 @@
1
1
  require_relative "client_error"
2
2
 
3
3
  module X
4
- # Rate limit error
4
+ # Rate limit error class
5
5
  class TooManyRequests < ClientError
6
- def initialize(msg, response)
7
- @response = response
8
- super(msg)
9
- end
10
-
11
6
  def limit
12
- @response["x-rate-limit-limit"].to_i
7
+ response["x-rate-limit-limit"].to_i
13
8
  end
14
9
 
15
10
  def remaining
16
- @response["x-rate-limit-remaining"].to_i
11
+ response["x-rate-limit-remaining"].to_i
17
12
  end
18
13
 
19
14
  def reset_at
20
- Time.at(@response["x-rate-limit-reset"].to_i)
15
+ Time.at(response["x-rate-limit-reset"].to_i)
21
16
  end
22
17
 
23
18
  def reset_in
@@ -19,7 +19,7 @@ module X
19
19
  boundary: SecureRandom.hex)
20
20
  validate!(file_path: file_path, media_category: media_category)
21
21
  upload_client = client.dup.tap { |c| c.base_url = "https://upload.twitter.com/1.1/" }
22
- upload_body = construct_upload_body(file_path, media_type, boundary)
22
+ upload_body = construct_upload_body(file_path: file_path, media_type: media_type, boundary: boundary)
23
23
  headers = {"Content-Type" => "multipart/form-data, boundary=#{boundary}"}
24
24
  upload_client.post("media/upload.json?media_category=#{media_category}", upload_body, headers: headers)
25
25
  end
@@ -28,10 +28,11 @@ module X
28
28
  media_category), boundary: SecureRandom.hex, chunk_size_mb: 8)
29
29
  validate!(file_path: file_path, media_category: media_category)
30
30
  upload_client = client.dup.tap { |c| c.base_url = "https://upload.twitter.com/1.1/" }
31
- media = init(upload_client, file_path, media_type, media_category)
31
+ media = init(upload_client: upload_client, file_path: file_path, media_type: media_type,
32
+ media_category: media_category)
32
33
  chunk_size = chunk_size_mb * BYTES_PER_MB
33
- chunk_paths = split(file_path, chunk_size)
34
- append(upload_client, chunk_paths, media, media_type, boundary)
34
+ append(upload_client: upload_client, file_paths: split(file_path, chunk_size), media: media,
35
+ media_type: media_type, boundary: boundary)
35
36
  upload_client.post("media/upload.json?command=FINALIZE&media_id=#{media["media_id"]}")
36
37
  end
37
38
 
@@ -64,19 +65,13 @@ module X
64
65
  end
65
66
  end
66
67
 
67
- def init(upload_client, file_path, media_type, media_category)
68
- total_bytes = File.size(file_path)
69
- query = "command=INIT&media_type=#{media_type}&media_category=#{media_category}&total_bytes=#{total_bytes}"
70
- upload_client.post("media/upload.json?#{query}")
71
- end
72
-
73
68
  def split(file_path, chunk_size)
74
69
  file_number = -1
75
70
 
76
- [].tap do |chunk_paths|
71
+ [].tap do |file_paths|
77
72
  File.open(file_path, "rb") do |f|
78
73
  while (chunk = f.read(chunk_size))
79
- chunk_paths << "#{Dir.mktmpdir}/x#{format("%03d", file_number += 1)}".tap do |path|
74
+ file_paths << "#{Dir.mktmpdir}/x#{format("%03d", file_number += 1)}".tap do |path|
80
75
  File.write(path, chunk)
81
76
  end
82
77
  end
@@ -84,34 +79,41 @@ module X
84
79
  end
85
80
  end
86
81
 
87
- def append(upload_client, chunk_paths, media, media_type, boundary = SecureRandom.hex)
88
- threads = chunk_paths.map.with_index do |chunk_path, index|
82
+ def init(upload_client:, file_path:, media_type:, media_category:)
83
+ total_bytes = File.size(file_path)
84
+ query = "command=INIT&media_type=#{media_type}&media_category=#{media_category}&total_bytes=#{total_bytes}"
85
+ upload_client.post("media/upload.json?#{query}")
86
+ end
87
+
88
+ def append(upload_client:, file_paths:, media:, media_type:, boundary: SecureRandom.hex)
89
+ threads = file_paths.map.with_index do |file_path, index|
89
90
  Thread.new do
90
- upload_body = construct_upload_body(chunk_path, media_type, boundary)
91
+ upload_body = construct_upload_body(file_path: file_path, media_type: media_type, boundary: boundary)
91
92
  query = "command=APPEND&media_id=#{media["media_id"]}&segment_index=#{index}"
92
93
  headers = {"Content-Type" => "multipart/form-data, boundary=#{boundary}"}
93
- upload_chunk(upload_client, query, upload_body, chunk_path, headers)
94
+ upload_chunk(upload_client: upload_client, query: query, upload_body: upload_body, file_path: file_path,
95
+ headers: headers)
94
96
  end
95
97
  end
96
98
  threads.each(&:join)
97
99
  end
98
100
 
99
- def upload_chunk(upload_client, query, upload_body, chunk_path, headers = {})
101
+ def upload_chunk(upload_client:, query:, upload_body:, file_path:, headers: {})
100
102
  upload_client.post("media/upload.json?#{query}", upload_body, headers: headers)
101
103
  rescue NetworkError, ServerError
102
104
  retries ||= 0
103
105
  ((retries += 1) < MAX_RETRIES) ? retry : raise
104
106
  ensure
105
- cleanup_chunk(chunk_path)
107
+ cleanup_file(file_path)
106
108
  end
107
109
 
108
- def cleanup_chunk(chunk_path)
109
- dirname = File.dirname(chunk_path)
110
- File.delete(chunk_path)
110
+ def cleanup_file(file_path)
111
+ dirname = File.dirname(file_path)
112
+ File.delete(file_path)
111
113
  Dir.delete(dirname) if Dir.empty?(dirname)
112
114
  end
113
115
 
114
- def construct_upload_body(file_path, media_type, boundary = SecureRandom.hex)
116
+ def construct_upload_body(file_path:, media_type:, boundary: SecureRandom.hex)
115
117
  "--#{boundary}\r\n" \
116
118
  "Content-Disposition: form-data; name=\"media\"; filename=\"#{File.basename(file_path)}\"\r\n" \
117
119
  "Content-Type: #{media_type}\r\n\r\n" \
@@ -24,7 +24,7 @@ module X
24
24
 
25
25
  def header(request)
26
26
  method, url, query_params = parse_request(request)
27
- {"Authorization" => build_oauth_header(method, url, query_params)}
27
+ {AUTHENTICATION_HEADER => build_oauth_header(method, url, query_params)}
28
28
  end
29
29
 
30
30
  private
@@ -11,23 +11,22 @@ module X
11
11
  DEFAULT_MAX_REDIRECTS = 10
12
12
 
13
13
  attr_accessor :max_redirects
14
- attr_reader :authenticator, :connection, :request_builder
14
+ attr_reader :connection, :request_builder
15
15
 
16
- def initialize(authenticator: Authenticator.new, connection: Connection.new, request_builder: RequestBuilder.new,
16
+ def initialize(connection: Connection.new, request_builder: RequestBuilder.new,
17
17
  max_redirects: DEFAULT_MAX_REDIRECTS)
18
- @authenticator = authenticator
19
18
  @connection = connection
20
19
  @request_builder = request_builder
21
20
  @max_redirects = max_redirects
22
21
  end
23
22
 
24
- def handle(response:, request:, base_url:, redirect_count: 0)
23
+ def handle(response:, request:, base_url:, authenticator: Authenticator.new, redirect_count: 0)
25
24
  if response.is_a?(Net::HTTPRedirection)
26
25
  raise TooManyRedirects, "Too many redirects" if redirect_count > max_redirects
27
26
 
28
27
  new_uri = build_new_uri(response, base_url)
29
28
 
30
- new_request = build_request(request, new_uri, Integer(response.code))
29
+ new_request = build_request(request, new_uri, Integer(response.code), authenticator)
31
30
  new_response = connection.perform(request: new_request)
32
31
 
33
32
  handle(response: new_response, request: new_request, base_url: base_url, redirect_count: redirect_count + 1)
@@ -44,7 +43,7 @@ module X
44
43
  URI.join(base_url, location)
45
44
  end
46
45
 
47
- def build_request(request, new_uri, response_code)
46
+ def build_request(request, new_uri, response_code, authenticator)
48
47
  http_method, body = case response_code
49
48
  in 307 | 308
50
49
  [request.method.downcase.to_sym, request.body]
@@ -3,7 +3,7 @@ require "net/http"
3
3
  require_relative "errors/bad_gateway"
4
4
  require_relative "errors/bad_request"
5
5
  require_relative "errors/connection_exception"
6
- require_relative "errors/error"
6
+ require_relative "errors/http_error"
7
7
  require_relative "errors/forbidden"
8
8
  require_relative "errors/gateway_timeout"
9
9
  require_relative "errors/gone"
@@ -19,7 +19,7 @@ require_relative "errors/unprocessable_entity"
19
19
  module X
20
20
  # Process HTTP responses
21
21
  class ResponseParser
22
- ERROR_CLASSES = {
22
+ ERROR_MAP = {
23
23
  400 => BadRequest,
24
24
  401 => Unauthorized,
25
25
  403 => Forbidden,
@@ -35,7 +35,7 @@ module X
35
35
  503 => ServiceUnavailable,
36
36
  504 => GatewayTimeout
37
37
  }.freeze
38
- JSON_CONTENT_TYPE_REGEXP = %r{application/(problem\+|)json}
38
+ JSON_CONTENT_TYPE_REGEXP = %r{application/json}
39
39
 
40
40
  attr_accessor :array_class, :object_class
41
41
 
@@ -45,48 +45,25 @@ module X
45
45
  end
46
46
 
47
47
  def parse(response:)
48
- raise error(response) unless success?(response)
48
+ raise error(response) unless response.is_a?(Net::HTTPSuccess)
49
49
 
50
- JSON.parse(response.body, array_class: array_class, object_class: object_class) if json?(response)
50
+ return unless json?(response)
51
+
52
+ JSON.parse(response.body, array_class: array_class, object_class: object_class)
51
53
  end
52
54
 
53
55
  private
54
56
 
55
- def success?(response)
56
- response.is_a?(Net::HTTPSuccess)
57
- end
58
-
59
57
  def error(response)
60
- error_class(response).new(error_message(response), response)
58
+ error_class(response).new(response: response)
61
59
  end
62
60
 
63
61
  def error_class(response)
64
- ERROR_CLASSES[Integer(response.code)] || Error
65
- end
66
-
67
- def error_message(response)
68
- if json?(response)
69
- message_from_json_response(response)
70
- else
71
- response.message
72
- end
73
- end
74
-
75
- def message_from_json_response(response)
76
- response_object = JSON.parse(response.body)
77
- if response_object.key?("title") && response_object.key?("detail")
78
- "#{response_object.fetch("title")}: #{response_object.fetch("detail")}"
79
- elsif response_object.key?("error")
80
- response_object.fetch("error")
81
- elsif response_object["errors"].instance_of?(Array)
82
- response_object.fetch("errors").map { |error| error.fetch("message") }.join(", ")
83
- else
84
- response.message
85
- end
62
+ ERROR_MAP[Integer(response.code)] || HTTPError
86
63
  end
87
64
 
88
65
  def json?(response)
89
- response.body && JSON_CONTENT_TYPE_REGEXP.match?(response["content-type"])
66
+ JSON_CONTENT_TYPE_REGEXP.match?(response["content-type"])
90
67
  end
91
68
  end
92
69
  end
data/lib/x/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require "rubygems/version"
2
2
 
3
3
  module X
4
- VERSION = Gem::Version.create("0.11.0")
4
+ VERSION = Gem::Version.create("0.12.0")
5
5
  end
data/sig/x.rbs CHANGED
@@ -2,16 +2,18 @@ module X
2
2
  VERSION: Gem::Version
3
3
 
4
4
  class Authenticator
5
+ AUTHENTICATION_HEADER: String
6
+
5
7
  def header: (Net::HTTPRequest? request) -> Hash[String, String]
6
8
  end
7
9
 
8
- class BearerTokenAuthenticator
10
+ class BearerTokenAuthenticator < Authenticator
9
11
  attr_accessor bearer_token: String
10
12
  def initialize: (bearer_token: String) -> void
11
13
  def header: (Net::HTTPRequest? request) -> Hash[String, String]
12
14
  end
13
15
 
14
- class OAuthAuthenticator
16
+ class OAuthAuthenticator < Authenticator
15
17
  OAUTH_VERSION: String
16
18
  OAUTH_SIGNATURE_METHOD: String
17
19
  OAUTH_SIGNATURE_ALGORITHM: String
@@ -37,7 +39,10 @@ module X
37
39
  def escape: (String value) -> String
38
40
  end
39
41
 
40
- class ClientError < Error
42
+ class Error < StandardError
43
+ end
44
+
45
+ class ClientError < HTTPError
41
46
  end
42
47
 
43
48
  class BadGateway < ClientError
@@ -49,8 +54,18 @@ module X
49
54
  class ConnectionException < ClientError
50
55
  end
51
56
 
52
- class Error < StandardError
53
- def initialize: (String msg, ?Net::HTTPResponse? response) -> void
57
+ class HTTPError < Error
58
+ JSON_CONTENT_TYPE_REGEXP: Regexp
59
+
60
+ attr_reader response : Net::HTTPResponse
61
+ attr_reader code : String
62
+
63
+ def initialize: (response: Net::HTTPResponse) -> void
64
+
65
+ private
66
+ def error_message: (Net::HTTPResponse response) -> String
67
+ def message_from_json_response: (Net::HTTPResponse response) -> String
68
+ def json?: (Net::HTTPResponse response) -> bool
54
69
  end
55
70
 
56
71
  class Forbidden < ClientError
@@ -77,19 +92,18 @@ module X
77
92
  class PayloadTooLarge < ClientError
78
93
  end
79
94
 
80
- class ServerError < Error
95
+ class ServerError < HTTPError
81
96
  end
82
97
 
83
98
  class ServiceUnavailable < ServerError
84
99
  end
85
100
 
86
- class TooManyRedirects < ClientError
101
+ class TooManyRedirects < Error
87
102
  end
88
103
 
89
104
  class TooManyRequests < ClientError
90
105
  @response: Net::HTTPResponse
91
106
 
92
- def initialize: (String msg, Net::HTTPResponse response) -> void
93
107
  def limit: -> Integer
94
108
  def remaining: -> Integer
95
109
  def reset_at: -> Time
@@ -154,19 +168,19 @@ module X
154
168
  attr_reader connection: Connection
155
169
  attr_reader request_builder: RequestBuilder
156
170
  attr_reader max_redirects: Integer
157
- def initialize: (authenticator: Authenticator, connection: Connection, request_builder: RequestBuilder, ?max_redirects: Integer) -> void
158
- def handle: (response: Net::HTTPResponse, request: Net::HTTPRequest, base_url: String, ?redirect_count: Integer) -> Net::HTTPResponse
171
+ def initialize: (connection: Connection, request_builder: RequestBuilder, ?max_redirects: Integer) -> void
172
+ def handle: (response: Net::HTTPResponse, request: Net::HTTPRequest, base_url: String, ?authenticator: Authenticator, ?redirect_count: Integer) -> Net::HTTPResponse
159
173
 
160
174
  private
161
175
  def build_new_uri: (Net::HTTPResponse response, String base_url) -> URI::Generic
162
- def build_request: (Net::HTTPRequest request, URI::Generic new_uri, Integer response_code) -> Net::HTTPRequest
176
+ def build_request: (Net::HTTPRequest request, URI::Generic new_uri, Integer response_code, Authenticator authenticator) -> Net::HTTPRequest
163
177
  def send_new_request: (URI::Generic new_uri, Net::HTTPRequest new_request) -> Net::HTTPResponse
164
178
  end
165
179
 
166
180
  class ResponseParser
167
181
  DEFAULT_ARRAY_CLASS: Class
168
182
  DEFAULT_OBJECT_CLASS: Class
169
- ERROR_CLASSES: Hash[Integer, singleton(Unauthorized) | singleton(BadRequest) | singleton(Forbidden) | singleton(InternalServerError) | singleton(NotFound) | singleton(PayloadTooLarge) | singleton(ServiceUnavailable) | singleton(TooManyRequests)]
183
+ ERROR_MAP: Hash[Integer, singleton(Unauthorized) | singleton(BadRequest) | singleton(Forbidden) | singleton(InternalServerError) | singleton(NotFound) | singleton(PayloadTooLarge) | singleton(ServiceUnavailable) | singleton(TooManyRequests)]
170
184
  JSON_CONTENT_TYPE_REGEXP: Regexp
171
185
 
172
186
  attr_accessor array_class: Class
@@ -175,11 +189,8 @@ module X
175
189
  def parse: (response: Net::HTTPResponse) -> untyped
176
190
 
177
191
  private
178
- def success?: (Net::HTTPResponse response) -> bool
179
192
  def error: (Net::HTTPResponse response) -> (Unauthorized | BadRequest | Forbidden | InternalServerError | NotFound | PayloadTooLarge | ServiceUnavailable | TooManyRequests)
180
193
  def error_class: (Net::HTTPResponse response) -> (singleton(Unauthorized) | singleton(BadRequest) | singleton(Forbidden) | singleton(InternalServerError) | singleton(NotFound) | singleton(PayloadTooLarge) | singleton(ServiceUnavailable) | singleton(TooManyRequests))
181
- def error_message: (Net::HTTPResponse response) -> String
182
- def message_from_json_response: (Net::HTTPResponse response) -> String
183
194
  def json?: (Net::HTTPResponse response) -> bool
184
195
  end
185
196
 
@@ -213,14 +224,15 @@ module X
213
224
  attr_accessor redirect_handler: RedirectHandler
214
225
  attr_accessor response_parser: ResponseParser
215
226
 
216
- def initialize: (?bearer_token: String?, ?api_key: String?, ?api_key_secret: String?, ?access_token: String?, ?access_token_secret: String?, ?base_url: String, ?open_timeout: Float | Integer, ?read_timeout: Float | Integer, ?write_timeout: Float | Integer, ?proxy_url: URI::Generic? | String?, ?debug_output: IO, ?array_class: Class, ?object_class: Class, ?max_redirects: Integer) -> void
227
+ def initialize: (?api_key: String?, ?api_key_secret: String?, ?access_token: String?, ?access_token_secret: String?, ?bearer_token: String?, ?base_url: String, ?open_timeout: Float | Integer, ?read_timeout: Float | Integer, ?write_timeout: Float | Integer, ?proxy_url: URI::Generic? | String?, ?debug_output: IO, ?array_class: Class, ?object_class: Class, ?max_redirects: Integer) -> void
217
228
  def get: (String endpoint, ?headers: Hash[String, String]) -> untyped
218
229
  def post: (String endpoint, ?String? body, ?headers: Hash[String, String]) -> untyped
219
230
  def put: (String endpoint, ?String? body, ?headers: Hash[String, String]) -> untyped
220
231
  def delete: (String endpoint, ?headers: Hash[String, String]) -> untyped
221
232
 
222
233
  private
223
- def initialize_authenticator: (String? bearer_token, String? api_key, String? api_key_secret, String? access_token, String? access_token_secret) -> (BearerTokenAuthenticator | OAuthAuthenticator | Authenticator)
234
+ def initialize_oauth: (String? api_key, String? api_key_secret, String? access_token, String? access_token_secret) -> void
235
+ def initialize_authenticator: -> Authenticator
224
236
  def execute_request: (Symbol http_method, String endpoint, headers: Hash[String, String], ?body: String?) -> untyped
225
237
  end
226
238
 
@@ -253,13 +265,13 @@ module X
253
265
  private
254
266
  def validate!: (file_path: String, media_category: String) -> nil
255
267
  def infer_media_type: (String file_path, String media_category) -> String
256
- def init: (Client upload_client, String file_path, String media_type, String media_category) -> untyped
257
268
  def split: (String file_path, Integer chunk_size) -> Array[String]
258
- def append: (Client upload_client, Array[String] chunk_paths, untyped media, String media_type, ?String boundary) -> Array[String]
259
- def upload_chunk: (Client upload_client, String query, String chunk_path, String media_type, ?Hash[String, String] headers) -> Integer?
260
- def cleanup_chunk: (String chunk_path) -> Integer?
261
- def finalize: (Client upload_client, untyped media) -> untyped
262
- def construct_upload_body: (String file_path, String media_type, ?String boundary) -> String
269
+ def init: (upload_client: Client, file_path: String, media_type: String, media_category: String) -> untyped
270
+ def append: (upload_client: Client, file_paths: Array[String], media: untyped, media_type: String, ?boundary: String) -> Array[String]
271
+ def upload_chunk: (upload_client: Client, query: String, upload_body: String, file_path: String, ?headers: Hash[String, String]) -> Integer?
272
+ def cleanup_file: (String file_path) -> Integer?
273
+ def finalize: (upload_client: Client, media: untyped) -> untyped
274
+ def construct_upload_body: (file_path: String, media_type: String, ?boundary: String) -> String
263
275
  end
264
276
 
265
277
  class CGI
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.11.0
4
+ version: 0.12.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-10-24 00:00:00.000000000 Z
11
+ date: 2023-11-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -36,6 +36,7 @@ files:
36
36
  - lib/x/errors/forbidden.rb
37
37
  - lib/x/errors/gateway_timeout.rb
38
38
  - lib/x/errors/gone.rb
39
+ - lib/x/errors/http_error.rb
39
40
  - lib/x/errors/internal_server_error.rb
40
41
  - lib/x/errors/network_error.rb
41
42
  - lib/x/errors/not_acceptable.rb