Skip to content

Commit

Permalink
Drop support for Ruby < 3.1.3
Browse files Browse the repository at this point in the history
  • Loading branch information
sferik committed Mar 29, 2024
1 parent ce537d7 commit be62c0d
Show file tree
Hide file tree
Showing 27 changed files with 92 additions and 127 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ jobs:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.0"
ruby-version: "3.1"
bundler-cache: true
- run: bundle exec rake lint
2 changes: 1 addition & 1 deletion .github/workflows/mutant.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ jobs:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.0"
ruby-version: "3.1"
bundler-cache: true
- run: bundle exec rake mutant
2 changes: 1 addition & 1 deletion .github/workflows/steep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ jobs:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.0"
ruby-version: "3.1"
bundler-cache: true
- run: bundle exec rake steep
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ jobs:
build:
strategy:
matrix:
ruby: ["3.0", "3.1", "3.2", "3.3"]
ruby: ["3.1", "3.2", "3.3"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require:

AllCops:
NewCops: enable
TargetRubyVersion: 3.0
TargetRubyVersion: 3.1

Layout/ArgumentAlignment:
EnforcedStyle: with_fixed_indentation
Expand Down
4 changes: 2 additions & 2 deletions examples/chunked_media_upload.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
file_path = "path/to/your/media.mp4"
media_category = "tweet_video" # other options include: tweet_image, tweet_gif, dm_image, dm_video, dm_gif, subtitles

media = X::MediaUploader.chunked_upload(client: client, file_path: file_path, media_category: media_category)
media = X::MediaUploader.chunked_upload(client:, file_path:, media_category:)

X::MediaUploader.await_processing(client: client, media: media)
X::MediaUploader.await_processing(client:, media:)

tweet_body = {text: "Posting media from @gem!", media: {media_ids: [media["media_id_string"]]}}

Expand Down
2 changes: 1 addition & 1 deletion examples/post_media_upload.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
file_path = "path/to/your/media.jpg"
media_category = "tweet_image" # other options are: dm_image or subtitles; for videos or GIFs use chunked_upload

media = X::MediaUploader.upload(client: client, file_path: file_path, media_category: media_category)
media = X::MediaUploader.upload(client:, file_path:, media_category:)

tweet_body = {text: "Posting media from @gem!", media: {media_ids: [media["media_id_string"]]}}

Expand Down
10 changes: 0 additions & 10 deletions lib/x/cgi.rb

This file was deleted.

26 changes: 12 additions & 14 deletions lib/x/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,26 @@ def initialize(api_key: nil, api_key_secret: nil, access_token: nil, access_toke
initialize_authenticator
@base_url = base_url
initialize_default_classes(default_array_class, default_object_class)
@connection = Connection.new(open_timeout: open_timeout, read_timeout: read_timeout, write_timeout: write_timeout,
debug_output: debug_output, proxy_url: proxy_url)
@connection = Connection.new(open_timeout:, read_timeout:, write_timeout:, debug_output:, proxy_url:)
@request_builder = RequestBuilder.new
@redirect_handler = RedirectHandler.new(connection: @connection, request_builder: @request_builder, max_redirects: max_redirects)
@redirect_handler = RedirectHandler.new(connection: @connection, request_builder: @request_builder, max_redirects:)
@response_parser = ResponseParser.new
end

def get(endpoint, headers: {}, array_class: default_array_class, object_class: default_object_class)
execute_request(:get, endpoint, headers: headers, array_class: array_class, object_class: object_class)
execute_request(:get, endpoint, headers:, array_class:, object_class:)
end

def post(endpoint, body = nil, headers: {}, array_class: default_array_class, object_class: default_object_class)
execute_request(:post, endpoint, body: body, headers: headers, array_class: array_class, object_class: object_class)
execute_request(:post, endpoint, body:, headers:, array_class:, object_class:)
end

def put(endpoint, body = nil, headers: {}, array_class: default_array_class, object_class: default_object_class)
execute_request(:put, endpoint, body: body, headers: headers, array_class: array_class, object_class: object_class)
execute_request(:put, endpoint, body:, headers:, array_class:, object_class:)
end

def delete(endpoint, headers: {}, array_class: default_array_class, object_class: default_object_class)
execute_request(:delete, endpoint, headers: headers, array_class: array_class, object_class: object_class)
execute_request(:delete, endpoint, headers:, array_class:, object_class:)
end

def api_key=(api_key)
Expand Down Expand Up @@ -103,10 +102,9 @@ def initialize_default_classes(default_array_class, default_object_class)

def initialize_authenticator
@authenticator = if api_key && api_key_secret && access_token && access_token_secret
OAuthAuthenticator.new(api_key: api_key, api_key_secret: api_key_secret, access_token: access_token,
access_token_secret: access_token_secret)
OAuthAuthenticator.new(api_key:, api_key_secret:, access_token:, access_token_secret:)
elsif bearer_token
BearerTokenAuthenticator.new(bearer_token: bearer_token)
BearerTokenAuthenticator.new(bearer_token:)
elsif @authenticator.nil?
Authenticator.new
else
Expand All @@ -116,10 +114,10 @@ def initialize_authenticator

def execute_request(http_method, endpoint, body: nil, headers: {}, array_class: default_array_class, object_class: default_object_class)
uri = URI.join(base_url, endpoint)
request = @request_builder.build(http_method: http_method, uri: uri, body: body, headers: headers, authenticator: @authenticator)
response = @connection.perform(request: request)
response = @redirect_handler.handle(response: response, request: request, base_url: base_url, authenticator: @authenticator)
@response_parser.parse(response: response, array_class: array_class, object_class: object_class)
request = @request_builder.build(http_method:, uri:, body:, headers:, authenticator: @authenticator)
response = @connection.perform(request:)
response = @redirect_handler.handle(response:, request:, base_url:, authenticator: @authenticator)
@response_parser.parse(response:, array_class:, object_class:)
end
end
end
2 changes: 1 addition & 1 deletion lib/x/errors/too_many_requests.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def rate_limit

def rate_limits
@rate_limits ||= RateLimit::TYPES.filter_map do |type|
RateLimit.new(type: type, response: response) if response["x-#{type}-remaining"].eql?("0")
RateLimit.new(type:, response:) if response["x-#{type}-remaining"].eql?("0")
end
end

Expand Down
19 changes: 9 additions & 10 deletions lib/x/media_uploader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,20 @@ module MediaUploader

def upload(client:, file_path:, media_category:, media_type: infer_media_type(file_path, media_category),
boundary: SecureRandom.hex)
validate!(file_path: file_path, media_category: media_category)
validate!(file_path:, media_category:)
upload_client = client.dup.tap { |c| c.base_url = "https://upload.twitter.com/1.1/" }
upload_body = construct_upload_body(file_path: file_path, media_type: media_type, boundary: boundary)
upload_body = construct_upload_body(file_path:, media_type:, boundary:)
headers = {"Content-Type" => "multipart/form-data, boundary=#{boundary}"}
upload_client.post("media/upload.json?media_category=#{media_category}", upload_body, headers: headers)
upload_client.post("media/upload.json?media_category=#{media_category}", upload_body, headers:)
end

def chunked_upload(client:, file_path:, media_category:, media_type: infer_media_type(file_path,
media_category), boundary: SecureRandom.hex, chunk_size_mb: 8)
validate!(file_path: file_path, media_category: media_category)
validate!(file_path:, media_category:)
upload_client = client.dup.tap { |c| c.base_url = "https://upload.twitter.com/1.1/" }
media = init(upload_client: upload_client, file_path: file_path, media_type: media_type, media_category: media_category)
media = init(upload_client:, file_path:, media_type:, media_category:)
chunk_size = chunk_size_mb * BYTES_PER_MB
append(upload_client: upload_client, file_paths: split(file_path, chunk_size), media: media, media_type: media_type,
boundary: boundary)
append(upload_client:, file_paths: split(file_path, chunk_size), media:, media_type:, boundary:)
upload_client.post("media/upload.json?command=FINALIZE&media_id=#{media["media_id"]}")
end

Expand Down Expand Up @@ -88,17 +87,17 @@ def init(upload_client:, file_path:, media_type:, media_category:)
def append(upload_client:, file_paths:, media:, media_type:, boundary: SecureRandom.hex)
threads = file_paths.map.with_index do |file_path, index|
Thread.new do
upload_body = construct_upload_body(file_path: file_path, media_type: media_type, boundary: boundary)
upload_body = construct_upload_body(file_path:, media_type:, boundary:)
query = "command=APPEND&media_id=#{media["media_id"]}&segment_index=#{index}"
headers = {"Content-Type" => "multipart/form-data, boundary=#{boundary}"}
upload_chunk(upload_client: upload_client, query: query, upload_body: upload_body, file_path: file_path, headers: headers)
upload_chunk(upload_client:, query:, upload_body:, file_path:, headers:)
end
end
threads.each(&:join)
end

def upload_chunk(upload_client:, query:, upload_body:, file_path:, headers: {})
upload_client.post("media/upload.json?#{query}", upload_body, headers: headers)
upload_client.post("media/upload.json?#{query}", upload_body, headers:)
rescue NetworkError, ServerError
retries ||= 0
((retries += 1) < MAX_RETRIES) ? retry : raise
Expand Down
6 changes: 3 additions & 3 deletions lib/x/oauth_authenticator.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
require "base64"
require "cgi"
require "json"
require "openssl"
require "securerandom"
require "uri"
require_relative "authenticator"
require_relative "cgi"

module X
class OAuthAuthenticator < Authenticator
Expand Down Expand Up @@ -71,15 +71,15 @@ def hmac_signature(base_string)
end

def signature_base_string(method, url, params)
"#{method}&#{CGI.escape(url)}&#{CGI.escape(URI.encode_www_form(params.sort))}"
"#{method}&#{CGI.escapeURIComponent(url)}&#{CGI.escapeURIComponent(URI.encode_www_form(params.sort))}"
end

def signing_key
"#{api_key_secret}&#{access_token_secret}"
end

def format_oauth_header(params)
"OAuth #{params.sort.map { |k, v| "#{k}=\"#{CGI.escape(v)}\"" }.join(", ")}"
"OAuth #{params.sort.map { |k, v| "#{k}=\"#{CGI.escapeURIComponent(v)}\"" }.join(", ")}"
end
end
end
4 changes: 2 additions & 2 deletions lib/x/redirect_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def handle(response:, request:, base_url:, authenticator: Authenticator.new, red
new_request = build_request(request, new_uri, Integer(response.code), authenticator)
new_response = connection.perform(request: new_request)

handle(response: new_response, request: new_request, base_url: base_url, redirect_count: redirect_count + 1)
handle(response: new_response, request: new_request, base_url:, redirect_count: redirect_count + 1)
else
response
end
Expand All @@ -50,7 +50,7 @@ def build_request(request, uri, response_code, authenticator)
[:get, nil]
end

request_builder.build(http_method: http_method, uri: uri, body: body, authenticator: authenticator)
request_builder.build(http_method:, uri:, body:, authenticator:)
end
end
end
6 changes: 3 additions & 3 deletions lib/x/request_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ class RequestBuilder
}.freeze

def build(http_method:, uri:, body: nil, headers: {}, authenticator: Authenticator.new)
request = create_request(http_method: http_method, uri: uri, body: body)
add_headers(request: request, headers: headers)
add_authentication(request: request, authenticator: authenticator)
request = create_request(http_method:, uri:, body:)
add_headers(request:, headers:)
add_authentication(request:, authenticator:)
request
end

Expand Down
4 changes: 2 additions & 2 deletions lib/x/response_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ def parse(response:, array_class: nil, object_class: nil)

return unless json?(response)

JSON.parse(response.body, array_class: array_class, object_class: object_class)
JSON.parse(response.body, array_class:, object_class:)
end

private

def error(response)
error_class(response).new(response: response)
error_class(response).new(response:)
end

def error_class(response)
Expand Down
4 changes: 0 additions & 4 deletions sig/x.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,4 @@ module X
def finalize: (upload_client: Client, media: untyped) -> untyped
def construct_upload_body: (file_path: String, media_type: String, ?boundary: String) -> String
end

class CGI
def self.escape: (String value) -> String
end
end
14 changes: 0 additions & 14 deletions test/x/cgi_test.rb

This file was deleted.

12 changes: 6 additions & 6 deletions test/x/client_request_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ def setup
define_method :"test_#{http_method}_request_with_headers" do
headers = {"User-Agent" => "Custom User Agent"}
stub_request(http_method, "https://api.twitter.com/2/tweets")
@client.public_send(http_method, "tweets", headers: headers)
@client.public_send(http_method, "tweets", headers:)

assert_requested http_method, "https://api.twitter.com/2/tweets", headers: headers
assert_requested http_method, "https://api.twitter.com/2/tweets", headers:
end

define_method :"test_#{http_method}_request_with_custom_response_objects" do
Expand Down Expand Up @@ -89,21 +89,21 @@ def test_follows_307_redirect
.to_return(status: 307, headers: {"Location" => "https://api.twitter.com/new_endpoint"})
body = {key: "value"}.to_json
stub_request(:post, "https://api.twitter.com/new_endpoint")
.with(body: body)
.with(body:)
@client.post("/temporary_redirect", body)

assert_requested :post, "https://api.twitter.com/new_endpoint", body: body
assert_requested :post, "https://api.twitter.com/new_endpoint", body:
end

def test_follows_308_redirect
stub_request(:put, "https://api.twitter.com/temporary_redirect")
.to_return(status: 308, headers: {"Location" => "https://api.twitter.com/new_endpoint"})
body = {key: "value"}.to_json
stub_request(:put, "https://api.twitter.com/new_endpoint")
.with(body: body)
.with(body:)
@client.put("/temporary_redirect", body)

assert_requested :put, "https://api.twitter.com/new_endpoint", body: body
assert_requested :put, "https://api.twitter.com/new_endpoint", body:
end

def test_avoids_infinite_redirect_loop
Expand Down
6 changes: 3 additions & 3 deletions test/x/connection_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,23 +109,23 @@ def test_set_env_proxy
def test_perform
stub_request(:get, "http://example.com:80")
request = Net::HTTP::Get.new(URI("http://example.com:80"))
@connection.perform(request: request)
@connection.perform(request:)

assert_requested :get, "http://example.com:80"
end

def test_network_error
stub_request(:get, "https://example.com").to_raise(Errno::ECONNREFUSED)
request = Net::HTTP::Get.new(URI("https://example.com"))
error = assert_raises(NetworkError) { @connection.perform(request: request) }
error = assert_raises(NetworkError) { @connection.perform(request:) }

assert_equal "Network error: Connection refused - Exception from WebMock", error.message
end

def test_no_host_or_port
stub_request(:get, "http://api.twitter.com:443/2/tweets")
request = Net::HTTP::Get.new(URI("http://api.twitter.com:443/2/tweets"))
request.stub(:uri, URI("/2/tweets")) { @connection.perform(request: request) }
request.stub(:uri, URI("/2/tweets")) { @connection.perform(request:) }

assert_requested :get, "http://api.twitter.com:443/2/tweets"
end
Expand Down
4 changes: 2 additions & 2 deletions test/x/error_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def setup
name = error_class.name.split("::").last
define_method :"test_initialize_#{name.downcase}_error" do
response = Net::HTTPResponse::CODE_TO_OBJ[status.to_s].new("1.1", status, error_class.name)
exception = error_class.new(response: response)
exception = error_class.new(response:)

assert_equal error_class.name, exception.message
assert_equal response, exception.response
Expand Down Expand Up @@ -41,7 +41,7 @@ def test_unexpected_response
def test_problem_json
body = {error: "problem"}.to_json
stub_request(:get, "https://api.twitter.com/2/tweets")
.to_return(status: 400, headers: {"content-type" => "application/problem+json"}, body: body)
.to_return(status: 400, headers: {"content-type" => "application/problem+json"}, body:)

begin
@client.get("tweets")
Expand Down
Loading

0 comments on commit be62c0d

Please sign in to comment.