x 0.1.0 → 0.2.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: 480eb23ebeb098bcc5c2d4924279d2d4608f8cf2a8e5eb7897edf45cc0311bc4
4
- data.tar.gz: c97f4694a91b3d3ad9f60d55001d48080329254d28db75aff74d646264fe38f2
3
+ metadata.gz: c40d2b65cf1479d65e6f434d6d9c98cb32ad7aa62b739fd1207b491b7e276e4d
4
+ data.tar.gz: 6dee4de59342deb6bbc8a5897f7a96e18ae6b7a856083ae84e1ee26580ce7482
5
5
  SHA512:
6
- metadata.gz: 86adf1889c2e820353c4455a08c443480682d4827addd074a7e1fe3e60937e70b638118d31802c8cc57d457865c917d2f1a83d739448659519f42c9cbede74ed
7
- data.tar.gz: 714dcea3deff757a2d15548ed1fdbb17749ca5b901e9463a5b3a223120b05974ab792d1c3230406dc07984358a6d9e7c4d781402aa025fa5e257d967ee5ecbcf
6
+ metadata.gz: d64f01c2f011e53621214597fd1a9ac80e777ce22db2eb5d48c6b2f760743efa2fa3aa6848e6549b3fc25290c80456873262ddf92dbffaff2d134040cb87555f
7
+ data.tar.gz: 8fc7d2aca33c67534ac775cc97f6afce95ef035716c5799e3571fb7b3a0218fd5b4646b41d8c66f61e3bba1d58f22b6c56c67ca273199b99dd7c55829827215a
data/.rubocop.yml CHANGED
@@ -15,8 +15,8 @@ Style/StringLiteralsInInterpolation:
15
15
  Enabled: true
16
16
  EnforcedStyle: double_quotes
17
17
 
18
- Layout/LineLength:
19
- Max: 120
20
-
21
18
  Style/FrozenStringLiteralComment:
22
19
  Enabled: false
20
+
21
+ Metrics/ParameterLists:
22
+ CountKeywordArgs: false
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2023-08-02
4
+
5
+ - Allow configuration of base URL (4bc0531)
6
+ - Improve error handling (14dc0cd)
7
+
3
8
  ## [0.1.0] - 2023-08-02
4
9
 
5
10
  - Initial release
data/Gemfile CHANGED
@@ -9,4 +9,5 @@ gem "rubocop", ">= 1.21"
9
9
  gem "rubocop-minitest", ">= 0.31"
10
10
  gem "rubocop-performance", ">= 1.18"
11
11
  gem "rubocop-rake", ">= 0.6"
12
+ gem "simplecov", ">= 0.22"
12
13
  gem "webmock", ">= 3.18.1"
data/README.md CHANGED
@@ -15,17 +15,20 @@ If bundler is not being used to manage dependencies, install the gem by executin
15
15
  ## Usage
16
16
 
17
17
  ```ruby
18
- api_key = "YOUR_X_API_KEY"
19
- api_key_secret = "YOUR_X_API_KEY_SECRET"
20
- access_token = "YOUR_X_ACCESS_TOKEN"
21
- access_token_secret = "YOUR_X_ACCESS_TOKEN_SECRET"
18
+ x_api_key = "YOUR_X_API_KEY"
19
+ x_api_key_secret = "YOUR_X_API_KEY_SECRET"
20
+ x_access_token = "YOUR_X_ACCESS_TOKEN"
21
+ x_access_token_secret = "YOUR_X_ACCESS_TOKEN_SECRET"
22
22
 
23
- x_client = X::Client.new(api_key: api_key, api_key_secret: api_key_secret, access_token: access_token, access_token_secret: access_token_secret)
23
+ x_client = X::Client.new(api_key: x_api_key,
24
+ api_key_secret: x_api_key_secret,
25
+ access_token: x_access_token,
26
+ access_token_secret: x_access_token_secret)
24
27
 
25
28
  begin
26
29
  response = x_client.get("users/me")
27
30
  puts JSON.pretty_generate(response)
28
- rescue StandardError => e
31
+ rescue X::Error => e
29
32
  puts "Error: #{e.message}"
30
33
  end
31
34
  ```
data/lib/x/client.rb CHANGED
@@ -1,94 +1,193 @@
1
- require "oauth"
2
1
  require "json"
3
2
  require "net/http"
3
+ require "oauth"
4
4
 
5
5
  module X
6
- # Main client that handles HTTP authentication and requests
6
+ # HTTP client that handles authentication and requests
7
7
  class Client
8
- BASE_URL = "https://api.twitter.com/2/".freeze
9
- HTTP_METHODS = {
10
- get: Net::HTTP::Get,
11
- post: Net::HTTP::Post,
12
- put: Net::HTTP::Put,
13
- delete: Net::HTTP::Delete
14
- }.freeze
15
-
16
- def initialize(bearer_token: nil, api_key: nil, api_key_secret: nil, access_token: nil, access_token_secret: nil)
17
- @use_bearer_token = !bearer_token.nil?
18
-
19
- if @use_bearer_token
20
- @bearer_token = bearer_token
21
- else
22
- unless api_key && api_key_secret && access_token && access_token_secret
23
- raise ArgumentError, "Missing OAuth credentials."
24
- end
25
-
26
- @consumer = OAuth::Consumer.new(api_key, api_key_secret, site: BASE_URL)
27
- @access_token = OAuth::Token.new(access_token, access_token_secret)
28
- end
8
+ DEFAULT_BASE_URL = "https://api.twitter.com/2/".freeze
9
+ DEFAULT_USER_AGENT = "X-Client/#{VERSION} Ruby/#{RUBY_VERSION}".freeze
10
+
11
+ def initialize(bearer_token: nil, api_key: nil, api_key_secret: nil, access_token: nil, access_token_secret: nil,
12
+ base_url: DEFAULT_BASE_URL, user_agent: DEFAULT_USER_AGENT)
13
+ @http_request = HttpRequest.new(bearer_token: bearer_token,
14
+ api_key: api_key,
15
+ api_key_secret: api_key_secret,
16
+ access_token: access_token,
17
+ access_token_secret: access_token_secret,
18
+ base_url: base_url,
19
+ user_agent: user_agent)
29
20
  end
30
21
 
31
22
  def get(endpoint)
32
- response = send_request(:get, endpoint)
33
- handle_response(response)
23
+ handle_response { @http_request.get(endpoint) }
34
24
  end
35
25
 
36
26
  def post(endpoint, body = nil)
37
- response = send_request(:post, endpoint, body)
38
- handle_response(response)
27
+ handle_response { @http_request.post(endpoint, body) }
39
28
  end
40
29
 
41
30
  def put(endpoint, body = nil)
42
- response = send_request(:put, endpoint, body)
43
- handle_response(response)
31
+ handle_response { @http_request.put(endpoint, body) }
44
32
  end
45
33
 
46
34
  def delete(endpoint)
47
- response = send_request(:delete, endpoint)
48
- handle_response(response)
35
+ handle_response { @http_request.delete(endpoint) }
49
36
  end
50
37
 
51
38
  private
52
39
 
53
- def send_request(http_method, endpoint, body = nil)
54
- url = URI.parse(BASE_URL + endpoint)
55
- http = Net::HTTP.new(url.host, url.port)
56
- http.use_ssl = true
40
+ def handle_response
41
+ response = yield
42
+ ErrorHandler.new(response).handle
43
+ end
57
44
 
58
- request = create_request(http_method, url, body)
59
- add_authorization(request)
45
+ # HTTP client requester
46
+ class HttpRequest
47
+ HTTP_METHODS = {
48
+ get: Net::HTTP::Get,
49
+ post: Net::HTTP::Post,
50
+ put: Net::HTTP::Put,
51
+ delete: Net::HTTP::Delete
52
+ }.freeze
53
+
54
+ def initialize(bearer_token: nil, api_key: nil, api_key_secret: nil, access_token: nil, access_token_secret: nil,
55
+ base_url: nil, user_agent: nil)
56
+ @base_url = base_url
57
+ @use_bearer_token = !bearer_token.nil?
58
+ @user_agent = user_agent || Client::DEFAULT_USER_AGENT
59
+
60
+ if @use_bearer_token
61
+ @bearer_token = bearer_token
62
+ else
63
+ initialize_oauth(api_key, api_key_secret, access_token, access_token_secret)
64
+ end
65
+ end
60
66
 
61
- http.request(request)
62
- end
67
+ def get(endpoint)
68
+ send_request(:get, endpoint)
69
+ end
63
70
 
64
- def create_request(http_method, url, body)
65
- http_method_class = HTTP_METHODS[http_method]
71
+ def post(endpoint, body = nil)
72
+ send_request(:post, endpoint, body)
73
+ end
66
74
 
67
- raise ArgumentError, "Unsupported HTTP method: #{http_method}" unless http_method_class
75
+ def put(endpoint, body = nil)
76
+ send_request(:put, endpoint, body)
77
+ end
68
78
 
69
- request = http_method_class.new(url)
70
- request.body = body if body && http_method != :get
71
- request
72
- end
79
+ def delete(endpoint)
80
+ send_request(:delete, endpoint)
81
+ end
82
+
83
+ private
84
+
85
+ def initialize_oauth(api_key, api_key_secret, access_token, access_token_secret)
86
+ unless api_key && api_key_secret && access_token && access_token_secret
87
+ raise ArgumentError, "Missing OAuth credentials."
88
+ end
89
+
90
+ @consumer = OAuth::Consumer.new(api_key, api_key_secret, site: @base_url)
91
+ @access_token = OAuth::Token.new(access_token, access_token_secret)
92
+ end
93
+
94
+ def send_request(http_method, endpoint, body = nil)
95
+ url = URI.parse(@base_url + endpoint)
96
+ http = Net::HTTP.new(url.host, url.port)
97
+ http.use_ssl = true
98
+
99
+ request = create_request(http_method, url, body)
100
+ add_authorization(request)
101
+ add_user_agent(request)
102
+
103
+ http.request(request)
104
+ end
73
105
 
74
- def add_authorization(request)
75
- if @use_bearer_token
76
- request["Authorization"] = "Bearer #{@bearer_token}"
77
- else
78
- @consumer.sign!(request, @access_token)
106
+ def create_request(http_method, url, body)
107
+ http_method_class = HTTP_METHODS[http_method]
108
+
109
+ raise ArgumentError, "Unsupported HTTP method: #{http_method}" unless http_method_class
110
+
111
+ request = http_method_class.new(url)
112
+ request.body = body if body && http_method != :get
113
+ request
114
+ end
115
+
116
+ def add_authorization(request)
117
+ if @use_bearer_token
118
+ request["Authorization"] = "Bearer #{@bearer_token}"
119
+ else
120
+ @consumer.sign!(request, @access_token)
121
+ end
122
+ end
123
+
124
+ def add_user_agent(request)
125
+ request["User-Agent"] = @user_agent if @user_agent
79
126
  end
80
127
  end
81
128
 
82
- def handle_response(response)
83
- case response
84
- when Net::HTTPSuccess
85
- JSON.parse(response.body)
86
- when Net::HTTPUnauthorized
129
+ # HTTP client error handler
130
+ class ErrorHandler
131
+ HTTP_STATUS_HANDLERS = {
132
+ Net::HTTPOK => :handle_success_response,
133
+ Net::HTTPBadRequest => :handle_bad_request_response,
134
+ Net::HTTPForbidden => :handle_forbidden_response,
135
+ Net::HTTPUnauthorized => :handle_unauthorized_response,
136
+ Net::HTTPNotFound => :handle_not_found_response,
137
+ Net::HTTPTooManyRequests => :handle_too_many_requests_response,
138
+ Net::HTTPInternalServerError => :handle_server_error_response,
139
+ Net::HTTPServiceUnavailable => :handle_service_unavailable_response
140
+ }.freeze
141
+
142
+ def initialize(response)
143
+ @response = response
144
+ end
145
+
146
+ def handle
147
+ handler_method = HTTP_STATUS_HANDLERS[@response.class]
148
+ if handler_method
149
+ send(handler_method)
150
+ else
151
+ handle_unexpected_response
152
+ end
153
+ end
154
+
155
+ private
156
+
157
+ def handle_success_response
158
+ JSON.parse(@response.body)
159
+ end
160
+
161
+ def handle_bad_request_response
162
+ raise X::BadRequestError, "Bad request: #{@response.code} #{@response.message}"
163
+ end
164
+
165
+ def handle_forbidden_response
166
+ raise X::ForbiddenError, "Forbidden: #{@response.code} #{@response.message}"
167
+ end
168
+
169
+ def handle_unauthorized_response
87
170
  raise X::AuthenticationError, "Authentication failed. Please check your credentials."
88
- when Net::HTTPServerError
89
- raise X::ServerError, "An internal server error occurred."
90
- else
91
- raise X::Error, "Unexpected response: #{response.code} #{response.message}"
171
+ end
172
+
173
+ def handle_not_found_response
174
+ raise X::NotFoundError, "Not found: #{@response.code} #{@response.message}"
175
+ end
176
+
177
+ def handle_too_many_requests_response
178
+ raise X::TooManyRequestsError, "Too many requests: #{@response.code} #{@response.message}"
179
+ end
180
+
181
+ def handle_server_error_response
182
+ raise X::ServerError, "Internal server error: #{@response.code} #{@response.message}"
183
+ end
184
+
185
+ def handle_service_unavailable_response
186
+ raise X::ServiceUnavailableError, "Service unavailable: #{@response.code} #{@response.message}"
187
+ end
188
+
189
+ def handle_unexpected_response
190
+ raise X::Error, "Unexpected response: #{@response.code}"
92
191
  end
93
192
  end
94
193
  end
data/lib/x/errors.rb ADDED
@@ -0,0 +1,11 @@
1
+ module X
2
+ class Error < ::StandardError; end
3
+ class ClientError < Error; end
4
+ class AuthenticationError < ClientError; end
5
+ class BadRequestError < ClientError; end
6
+ class ForbiddenError < ClientError; end
7
+ class NotFoundError < ClientError; end
8
+ class TooManyRequestsError < ClientError; end
9
+ class ServerError < Error; end
10
+ class ServiceUnavailableError < ServerError; end
11
+ end
data/lib/x/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module X
2
- VERSION = "0.1.0".freeze
2
+ VERSION = "0.2.0".freeze
3
3
  end
data/lib/x.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  require_relative "x/version"
2
+ require_relative "x/errors"
2
3
  require_relative "x/client"
3
- require_relative "x/error"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: x
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erik Berlin
@@ -39,7 +39,7 @@ files:
39
39
  - Rakefile
40
40
  - lib/x.rb
41
41
  - lib/x/client.rb
42
- - lib/x/error.rb
42
+ - lib/x/errors.rb
43
43
  - lib/x/version.rb
44
44
  - sig/x.rbs
45
45
  homepage: https://github.com/sferik/x-ruby
data/lib/x/error.rb DELETED
@@ -1,5 +0,0 @@
1
- module X
2
- class Error < StandardError; end
3
- class AuthenticationError < Error; end
4
- class ServerError < Error; end
5
- end