x 0.5.1 → 0.6.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: 27d4866e1c2d6c395cb876a5d7b63f91d62e7c16a6f03cf7a13b5d7d6f0a6169
4
- data.tar.gz: 651cae67b8b3a7a277169918ab387678937dee586665f8676dbf2674ec13479a
3
+ metadata.gz: 7ab2b0ef63fad89db285efc1527b16a3b199691a5cb51013cc55f32fd1fcde56
4
+ data.tar.gz: e035f65fea8a8409a69a58f08ade709c2affe7c0afc12f62e6928d095ac2aa7f
5
5
  SHA512:
6
- metadata.gz: 2663a18ed461013e25b943ace001c57f0309ff860836edb3ef6e5888100921ee4e262d6851387f0a7ea999c9f7dbdaa7147820a243fa7796de03b29b24e472f7
7
- data.tar.gz: e421119a4505742e55132a1782c9c0ebebc36dc801a5c263f540b39eec24bd022b0acf4d843d9af8ecf893d06a45e52c0f1ec68c684ee21db079df0826efa850
6
+ metadata.gz: 41f24b7dc58d4d886da758a0d2b1430c68617c2594975c1fd17c9a04c7cde8a826e6672dd03c4be3ae3ba0d0022cfe8d0f6c6b5a97e2df64511a92e56e2c6361
7
+ data.tar.gz: ffc98321391ec7b71bed31dc82832086c39a79a76b18d0f0ae284a062d329f49fb96cd66421c8bae3b204a48978cc97a079be54b05883f6e462c2be2827f3a94
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.0] - 2023-08-30
4
+
5
+ - Add configurable debug output stream for logging (fd2d4b0)
6
+ - Remove bearer token authentication (efff940)
7
+ - Define RBS type signatures (d7f63ba)
8
+
3
9
  ## [0.5.1] - 2023-08-16
4
10
 
5
11
  - Fix bearer token authentication (1a1ca93)
data/Gemfile CHANGED
@@ -6,11 +6,13 @@ gemspec
6
6
  gem "hashie", ">= 5"
7
7
  gem "minitest", ">= 5.19"
8
8
  gem "rake", ">= 13.0.6"
9
+ gem "rbs", ">= 3.2.1"
9
10
  gem "rubocop", ">= 1.21"
10
11
  gem "rubocop-minitest", ">= 0.31"
11
12
  gem "rubocop-performance", ">= 1.18"
12
13
  gem "rubocop-rake", ">= 0.6"
13
14
  gem "simplecov", ">= 0.22"
14
15
  gem "standard", ">= 1.30.1"
16
+ gem "steep", ">= 1.5.3"
15
17
  gem "timecop", ">= 0.9.6"
16
18
  gem "webmock", ">= 3.18.1"
data/README.md CHANGED
@@ -18,8 +18,8 @@ If bundler is not being used to manage dependencies, install the gem by executin
18
18
  x_oauth_credentials = {
19
19
  api_key: "INSERT YOUR X API KEY HERE",
20
20
  api_key_secret: "INSERT YOUR X API KEY SECRET HERE",
21
- access_token: "INSERT YOUR X API ACCESS TOKEN HERE",
22
- access_token_secret: "INSERT YOUR X API ACCESS TOKEN SECRET HERE",
21
+ access_token: "INSERT YOUR X ACCESS TOKEN HERE",
22
+ access_token_secret: "INSERT YOUR X ACCESS TOKEN SECRET HERE",
23
23
  }
24
24
 
25
25
  # Initialize X API client with OAuth credentials
data/Rakefile CHANGED
@@ -12,4 +12,12 @@ require "rubocop/rake_task"
12
12
 
13
13
  RuboCop::RakeTask.new
14
14
 
15
- task default: %i[test rubocop standard]
15
+ require "steep"
16
+ require "steep/cli"
17
+
18
+ desc "Type check with Steep"
19
+ task :steep do
20
+ Steep::CLI.new(argv: ["check"], stdout: $stdout, stderr: $stderr, stdin: $stdin).run
21
+ end
22
+
23
+ task default: %i[test rubocop standard steep]
data/Steepfile ADDED
@@ -0,0 +1,9 @@
1
+ target :lib do
2
+ signature "sig"
3
+ check "lib"
4
+ library "forwardable"
5
+ library "json"
6
+ library "net-http"
7
+ library "uri"
8
+ configure_code_diagnostics(Steep::Diagnostic::Ruby.strict)
9
+ end
@@ -2,12 +2,10 @@ require "oauth"
2
2
  require "forwardable"
3
3
 
4
4
  module X
5
- # Handles OAuth and bearer token authentication
5
+ # Handles OAuth authentication
6
6
  class Authenticator
7
7
  extend Forwardable
8
8
 
9
- attr_accessor :bearer_token
10
-
11
9
  def_delegator :@access_token, :secret, :access_token_secret
12
10
  def_delegator :@access_token, :secret=, :access_token_secret=
13
11
  def_delegator :@access_token, :token, :access_token
@@ -17,27 +15,13 @@ module X
17
15
  def_delegator :@consumer, :secret, :api_key_secret
18
16
  def_delegator :@consumer, :secret=, :api_key_secret=
19
17
 
20
- def initialize(bearer_token:, api_key:, api_key_secret:, access_token:, access_token_secret:)
21
- if bearer_token
22
- @bearer_token = bearer_token
23
- else
24
- initialize_oauth(api_key, api_key_secret, access_token, access_token_secret)
25
- end
18
+ def initialize(api_key, api_key_secret, access_token, access_token_secret)
19
+ @consumer = OAuth::Consumer.new(api_key, api_key_secret, site: ClientDefaults::DEFAULT_BASE_URL)
20
+ @access_token = OAuth::Token.new(access_token, access_token_secret)
26
21
  end
27
22
 
28
23
  def sign!(request)
29
24
  @consumer.sign!(request, @access_token)
30
25
  end
31
-
32
- private
33
-
34
- def initialize_oauth(api_key, api_key_secret, access_token, access_token_secret)
35
- unless api_key && api_key_secret && access_token && access_token_secret
36
- raise ArgumentError, "Missing OAuth credentials"
37
- end
38
-
39
- @consumer = OAuth::Consumer.new(api_key, api_key_secret, site: ClientDefaults::DEFAULT_BASE_URL)
40
- @access_token = OAuth::Token.new(access_token, access_token_secret)
41
- end
42
26
  end
43
27
  end
data/lib/x/client.rb CHANGED
@@ -11,25 +11,23 @@ module X
11
11
  extend Forwardable
12
12
  include ClientDefaults
13
13
 
14
- def_delegators :@authenticator, :bearer_token, :api_key, :api_key_secret, :access_token, :access_token_secret
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=
14
+ def_delegators :@authenticator, :api_key, :api_key_secret, :access_token, :access_token_secret
15
+ def_delegators :@authenticator, :api_key=, :api_key_secret=, :access_token=, :access_token_secret=
16
+ def_delegators :@connection, :base_url, :open_timeout, :read_timeout, :write_timeout, :debug_output
17
+ def_delegators :@connection, :base_url=, :open_timeout=, :read_timeout=, :write_timeout=, :debug_output=
18
18
  def_delegators :@request_builder, :content_type, :user_agent
19
19
  def_delegators :@request_builder, :content_type=, :user_agent=
20
20
  def_delegators :@response_handler, :array_class, :object_class
21
21
  def_delegators :@response_handler, :array_class=, :object_class=
22
22
 
23
- def initialize(bearer_token: nil, api_key: nil, api_key_secret: nil, access_token: nil, access_token_secret: nil,
23
+ def initialize(api_key:, api_key_secret:, access_token:, access_token_secret:,
24
24
  base_url: DEFAULT_BASE_URL, content_type: DEFAULT_CONTENT_TYPE, user_agent: DEFAULT_USER_AGENT,
25
25
  open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT, write_timeout: DEFAULT_WRITE_TIMEOUT,
26
- array_class: DEFAULT_ARRAY_CLASS, object_class: DEFAULT_OBJECT_CLASS)
27
- @authenticator = Authenticator.new(bearer_token: bearer_token, api_key: api_key, api_key_secret: api_key_secret,
28
- access_token: access_token, access_token_secret: access_token_secret)
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)
26
+ debug_output: nil, array_class: DEFAULT_ARRAY_CLASS, object_class: DEFAULT_OBJECT_CLASS)
27
+ @authenticator = Authenticator.new(api_key, api_key_secret, access_token, access_token_secret)
28
+ @connection = Connection.new(base_url, open_timeout, read_timeout, write_timeout, debug_output: debug_output)
29
+ @request_builder = RequestBuilder.new(content_type, user_agent)
30
+ @response_handler = ResponseHandler.new(array_class, object_class)
33
31
  end
34
32
 
35
33
  def get(endpoint)
@@ -51,10 +49,9 @@ module X
51
49
  private
52
50
 
53
51
  def send_request(http_method, endpoint, body = nil)
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)
52
+ request = @request_builder.build(@authenticator, http_method, base_url, endpoint, body: body)
53
+ response = @connection.send_request(request)
54
+ @response_handler.handle(response)
58
55
  end
59
56
  end
60
57
  end
data/lib/x/connection.rb CHANGED
@@ -14,17 +14,19 @@ module X
14
14
 
15
15
  def_delegators :@http_client, :open_timeout, :read_timeout, :write_timeout
16
16
  def_delegators :@http_client, :open_timeout=, :read_timeout=, :write_timeout=
17
+ def_delegator :@http_client, :set_debug_output, :debug_output=
17
18
 
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"
19
+ def initialize(url, open_timeout, read_timeout, write_timeout, debug_output: nil)
20
+ self.base_url = url
21
+ @http_client = Net::HTTP.new(base_url.host, base_url.port) if base_url.host
22
+ @http_client.use_ssl = base_url.scheme == "https"
22
23
  @http_client.open_timeout = open_timeout
23
24
  @http_client.read_timeout = read_timeout
24
25
  @http_client.write_timeout = write_timeout
26
+ @http_client.set_debug_output(debug_output) if debug_output
25
27
  end
26
28
 
27
- def send_request(request:)
29
+ def send_request(request)
28
30
  @http_client.request(request)
29
31
  rescue *NETWORK_ERRORS => e
30
32
  raise NetworkError, "Network error: #{e.message}"
@@ -36,5 +38,9 @@ module X
36
38
 
37
39
  @base_url = uri
38
40
  end
41
+
42
+ def debug_output
43
+ @http_client.instance_variable_get(:@debug_output)
44
+ end
39
45
  end
40
46
  end
@@ -8,7 +8,7 @@ module X
8
8
  include ClientDefaults
9
9
  attr_reader :object
10
10
 
11
- def initialize(msg = nil, response: nil, array_class: DEFAULT_ARRAY_CLASS, object_class: DEFAULT_OBJECT_CLASS)
11
+ def initialize(msg, response:, array_class: DEFAULT_ARRAY_CLASS, object_class: DEFAULT_OBJECT_CLASS)
12
12
  if json_response?(response)
13
13
  @object = JSON.parse(response.body, array_class: array_class, object_class: object_class)
14
14
  end
@@ -1,5 +1,5 @@
1
1
  require_relative "error"
2
2
 
3
3
  module X
4
- class NetworkError < Error; end
4
+ class NetworkError < StandardError; end
5
5
  end
@@ -6,21 +6,21 @@ module X
6
6
  class TooManyRequestsError < ClientError
7
7
  include ClientDefaults
8
8
 
9
- def initialize(msg = nil, response: nil, array_class: DEFAULT_ARRAY_CLASS, object_class: DEFAULT_OBJECT_CLASS)
9
+ def initialize(msg, response:, array_class: DEFAULT_ARRAY_CLASS, object_class: DEFAULT_OBJECT_CLASS)
10
10
  @response = response
11
11
  super
12
12
  end
13
13
 
14
14
  def limit
15
- @response&.fetch("x-rate-limit-limit", 0).to_i
15
+ @response.fetch("x-rate-limit-limit", 0).to_i
16
16
  end
17
17
 
18
18
  def remaining
19
- @response&.fetch("x-rate-limit-remaining", 0).to_i
19
+ @response.fetch("x-rate-limit-remaining", 0).to_i
20
20
  end
21
21
 
22
22
  def reset_at
23
- Time.at(@response&.fetch("x-rate-limit-reset", 0).to_i).utc if @response
23
+ Time.at(@response.fetch("x-rate-limit-reset", 0).to_i).utc
24
24
  end
25
25
 
26
26
  def reset_in
@@ -13,13 +13,13 @@ module X
13
13
 
14
14
  attr_accessor :content_type, :user_agent
15
15
 
16
- def initialize(content_type:, user_agent:)
16
+ def initialize(content_type, user_agent)
17
17
  @content_type = content_type
18
18
  @user_agent = user_agent
19
19
  end
20
20
 
21
- def build(authenticator:, http_method:, base_url:, endpoint:, body: nil)
22
- url = URI.join(base_url, endpoint)
21
+ def build(authenticator, http_method, base_url, endpoint, body: nil)
22
+ url = URI.join(base_url.to_s, endpoint)
23
23
  request = create_request(http_method, url, body)
24
24
  add_authorization(request, authenticator)
25
25
  add_content_type(request)
@@ -40,11 +40,7 @@ module X
40
40
  end
41
41
 
42
42
  def add_authorization(request, authenticator)
43
- if authenticator.bearer_token
44
- request.add_field("Authorization", "Bearer #{authenticator.bearer_token}")
45
- else
46
- authenticator.sign!(request)
47
- end
43
+ authenticator.sign!(request)
48
44
  end
49
45
 
50
46
  def add_content_type(request)
@@ -10,20 +10,18 @@ module X
10
10
 
11
11
  attr_accessor :array_class, :object_class
12
12
 
13
- def initialize(array_class:, object_class:)
13
+ def initialize(array_class, object_class)
14
14
  @array_class = array_class
15
15
  @object_class = object_class
16
16
  end
17
17
 
18
- def handle(response:)
18
+ def handle(response)
19
19
  if successful_json_response?(response)
20
20
  return JSON.parse(response.body, array_class: array_class, object_class: object_class)
21
21
  end
22
22
 
23
23
  error_class = ERROR_CLASSES[response.code.to_i] || Error
24
24
  error_message = "#{response.code} #{response.message}"
25
- raise error_class, error_message if empty_response_body?(response)
26
-
27
25
  raise error_class.new(error_message, response: response, array_class: array_class, object_class: object_class)
28
26
  end
29
27
 
@@ -32,9 +30,5 @@ module X
32
30
  def successful_json_response?(response)
33
31
  response.is_a?(Net::HTTPSuccess) && response.body && response["content-type"] == DEFAULT_CONTENT_TYPE
34
32
  end
35
-
36
- def empty_response_body?(response)
37
- response.body.nil? || response.body.empty?
38
- end
39
33
  end
40
34
  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.5.1")
4
+ VERSION = Gem::Version.create("0.6.0")
5
5
  end
data/sig/x.rbs CHANGED
@@ -1,4 +1,142 @@
1
+ module OAuth
2
+ class Consumer
3
+ def initialize: (String api_key, String api_key_secret, site: String) -> void
4
+ def sign!: (Net::HTTPRequest request, OAuth::Token token) -> void
5
+ end
6
+ class Token
7
+ def initialize: (String access_token, String access_token_secret) -> void
8
+ end
9
+ end
10
+
1
11
  module X
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
12
+ VERSION: Gem::Version
13
+
14
+ class Authenticator
15
+ extend Forwardable
16
+ @consumer: OAuth::Consumer
17
+ @access_token: OAuth::Token
18
+
19
+ def initialize: (String api_key, String api_key_secret, String access_token, String access_token_secret) -> void
20
+ def sign!: (Net::HTTPRequest request) -> void
21
+ end
22
+
23
+ module ClientDefaults
24
+ DEFAULT_BASE_URL: String
25
+ DEFAULT_CONTENT_TYPE: String
26
+ DEFAULT_OPEN_TIMEOUT: Integer
27
+ DEFAULT_READ_TIMEOUT: Integer
28
+ DEFAULT_WRITE_TIMEOUT: Integer
29
+ DEFAULT_USER_AGENT: String
30
+ DEFAULT_ARRAY_CLASS: Class
31
+ DEFAULT_OBJECT_CLASS: Class
32
+ end
33
+
34
+ class Error < StandardError
35
+ include ClientDefaults
36
+
37
+ attr_reader object: untyped
38
+ def initialize: (String msg, response: Net::HTTPResponse, ?array_class: Class, ?object_class: Class) -> void
39
+
40
+ private
41
+ def json_response?: (Net::HTTPResponse response) -> bool
42
+ end
43
+
44
+ class ClientError < Error
45
+ end
46
+
47
+ class BadRequestError < ClientError
48
+ end
49
+
50
+ class AuthenticationError < ClientError
51
+ end
52
+
53
+ class ForbiddenError < ClientError
54
+ end
55
+
56
+ class NotFoundError < ClientError
57
+ end
58
+
59
+ class TooManyRequestsError < ClientError
60
+ include ClientDefaults
61
+ @response: Net::HTTPResponse
62
+
63
+ def initialize: (String msg, response: Net::HTTPResponse, ?array_class: Class, ?object_class: Class) -> void
64
+ def limit: -> Integer
65
+ def remaining: -> Integer
66
+ def reset_at: -> Time
67
+ def reset_in: -> Integer?
68
+ end
69
+
70
+ class ServerError < Error
71
+ end
72
+
73
+ class ServiceUnavailableError < ServerError
74
+ end
75
+
76
+ module Errors
77
+ ERROR_CLASSES: Hash[Integer, singleton(AuthenticationError) | singleton(BadRequestError) | singleton(ForbiddenError) | singleton(NotFoundError) | singleton(ServerError) | singleton(ServiceUnavailableError) | singleton(TooManyRequestsError)]
78
+ NETWORK_ERRORS: Array[(singleton(::Errno::ECONNREFUSED) | singleton(::Net::OpenTimeout) | singleton(::Net::ReadTimeout))]
79
+ end
80
+
81
+ class NetworkError < Error
82
+ end
83
+
84
+ class Connection
85
+ extend Forwardable
86
+ include Errors
87
+ @http_client: Net::HTTP
88
+
89
+ attr_reader base_url: URI::Generic
90
+ def initialize: (URI::Generic | String url, Float | Integer open_timeout, Float | Integer read_timeout, Float | Integer write_timeout, ?debug_output: IO?) -> void
91
+ def send_request: (Net::HTTPRequest request) -> Net::HTTPResponse
92
+ def base_url=: (URI::Generic | String new_base_url) -> URI::Generic
93
+ def debug_output: -> IO?
94
+ end
95
+
96
+ class RequestBuilder
97
+ HTTP_METHODS: Hash[::Symbol, (singleton(::Net::HTTP::Get) | singleton(::Net::HTTP::Post) | singleton(::Net::HTTP::Put) | singleton(::Net::HTTP::Delete))]
98
+
99
+ attr_accessor content_type: String
100
+ attr_accessor user_agent: String
101
+ def initialize: (String content_type, String user_agent) -> void
102
+ def build: (Authenticator authenticator, :delete | :get | :post | :put http_method, URI::Generic base_url, String endpoint, ?body: nil) -> (Net::HTTPRequest)
103
+
104
+ private
105
+ def create_request: (:delete | :get | :post | :put http_method, URI::Generic url, nil body) -> (Net::HTTPRequest)
106
+ def add_authorization: (Net::HTTPRequest request, Authenticator authenticator) -> void
107
+ def add_content_type: (Net::HTTPRequest request) -> void
108
+ def add_user_agent: (Net::HTTPRequest request) -> void
109
+ end
110
+
111
+ class ResponseHandler
112
+ include Errors
113
+ include ClientDefaults
114
+
115
+ attr_accessor array_class: Class
116
+ attr_accessor object_class: Class
117
+ def initialize: (Class array_class, Class object_class) -> void
118
+ def handle: (Net::HTTPResponse response) -> untyped
119
+
120
+ private
121
+ def successful_json_response?: (Net::HTTPResponse response) -> bool
122
+ end
123
+
124
+ class Client
125
+ extend Forwardable
126
+ include ClientDefaults
127
+ @authenticator: Authenticator
128
+ @connection: Connection
129
+ @request_builder: RequestBuilder
130
+ @response_handler: ResponseHandler
131
+
132
+ attr_reader base_url: URI::Generic
133
+ def initialize: (api_key: String, api_key_secret: String, access_token: String, access_token_secret: String, ?base_url: URI::Generic | String, ?content_type: String, ?user_agent: String, ?open_timeout: Float | Integer, ?read_timeout: Float | Integer, ?write_timeout: Float | Integer, ?debug_output: IO?, ?array_class: Class, ?object_class: Class) -> void
134
+ def get: (String endpoint) -> untyped
135
+ def post: (String endpoint, ?nil body) -> untyped
136
+ def put: (String endpoint, ?nil body) -> untyped
137
+ def delete: (String endpoint) -> untyped
138
+
139
+ private
140
+ def send_request: (:delete | :get | :post | :put http_method, String endpoint, ?nil body) -> untyped
141
+ end
4
142
  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.5.1
4
+ version: 0.6.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-16 00:00:00.000000000 Z
11
+ date: 2023-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oauth
@@ -37,6 +37,7 @@ files:
37
37
  - LICENSE.txt
38
38
  - README.md
39
39
  - Rakefile
40
+ - Steepfile
40
41
  - lib/x.rb
41
42
  - lib/x/authenticator.rb
42
43
  - lib/x/client.rb