x 0.5.1 → 0.6.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: 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