Skip to content

Commit

Permalink
Allow passing custom objects per-request
Browse files Browse the repository at this point in the history
  • Loading branch information
sferik committed Dec 3, 2023
1 parent 45e4b99 commit d7fc80f
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 114 deletions.
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@ AllCops:
NewCops: enable
TargetRubyVersion: 3.0

Style/MethodCallWithoutArgsParentheses:
AllowedMethods: [array_class, object_class]

Minitest/MultipleAssertions:
Max: 5
40 changes: 23 additions & 17 deletions lib/x/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ class Client

DEFAULT_BASE_URL = "https://api.twitter.com/2/".freeze

attr_accessor :base_url
attr_accessor :base_url, :array_class, :object_class
attr_reader :api_key, :api_key_secret, :access_token, :access_token_secret, :bearer_token

def_delegators :@connection, :open_timeout, :read_timeout, :write_timeout, :proxy_url, :debug_output
def_delegators :@connection, :open_timeout=, :read_timeout=, :write_timeout=, :proxy_url=, :debug_output=
def_delegators :@redirect_handler, :max_redirects
def_delegators :@redirect_handler, :max_redirects=
def_delegators :@response_parser, :array_class, :object_class
def_delegators :@response_parser, :array_class=, :object_class=

def initialize(api_key: nil, api_key_secret: nil, access_token: nil, access_token_secret: nil,
bearer_token: nil,
Expand All @@ -34,32 +32,34 @@ def initialize(api_key: nil, api_key_secret: nil, access_token: nil, access_toke
object_class: nil,
max_redirects: RedirectHandler::DEFAULT_MAX_REDIRECTS)

initialize_oauth(api_key, api_key_secret, access_token, access_token_secret)
@bearer_token = bearer_token
initialize_oauth(api_key, api_key_secret, access_token, access_token_secret, bearer_token)
initialize_authenticator
@base_url = base_url
initialize_response_objects(array_class, 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)
@request_builder = RequestBuilder.new
@redirect_handler = RedirectHandler.new(connection: @connection, request_builder: @request_builder,
max_redirects: max_redirects)
@response_parser = ResponseParser.new(array_class: array_class, object_class: object_class)
@response_parser = ResponseParser.new
end

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

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

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

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

def api_key=(api_key)
Expand Down Expand Up @@ -89,11 +89,17 @@ def bearer_token=(bearer_token)

private

def initialize_oauth(api_key, api_key_secret, access_token, access_token_secret)
def initialize_oauth(api_key, api_key_secret, access_token, access_token_secret, bearer_token)
@api_key = api_key
@api_key_secret = api_key_secret
@access_token = access_token
@access_token_secret = access_token_secret
@bearer_token = bearer_token
end

def initialize_response_objects(array_class, object_class)
@array_class = array_class
@object_class = object_class
end

def initialize_authenticator
Expand All @@ -109,14 +115,14 @@ def initialize_authenticator
end
end

def execute_request(http_method, endpoint, headers:, body: nil)
def execute_request(http_method, endpoint, body: nil, headers: {}, array_class: array_class(), object_class: 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)
@response_parser.parse(response: response, array_class: array_class, object_class: object_class)
end
end
end
9 changes: 1 addition & 8 deletions lib/x/response_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,7 @@ class ResponseParser
}.freeze
JSON_CONTENT_TYPE_REGEXP = %r{application/json}

attr_accessor :array_class, :object_class

def initialize(array_class: nil, object_class: nil)
@array_class = array_class
@object_class = object_class
end

def parse(response:)
def parse(response:, array_class: nil, object_class: nil)
raise error(response) unless response.is_a?(Net::HTTPSuccess)

return unless json?(response)
Expand Down
12 changes: 4 additions & 8 deletions sig/x.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -193,15 +193,10 @@ module X
end

class ResponseParser
DEFAULT_ARRAY_CLASS: Class
DEFAULT_OBJECT_CLASS: Class
ERROR_MAP: Hash[Integer, singleton(Unauthorized) | singleton(BadRequest) | singleton(Forbidden) | singleton(InternalServerError) | singleton(NotFound) | singleton(PayloadTooLarge) | singleton(ServiceUnavailable) | singleton(TooManyRequests)]
JSON_CONTENT_TYPE_REGEXP: Regexp

attr_accessor array_class: Class
attr_accessor object_class: Class
def initialize: (?array_class: Class, ?object_class: Class) -> void
def parse: (response: Net::HTTPResponse) -> untyped
def parse: (response: Net::HTTPResponse, ?array_class: Class?, ?object_class: Class?) -> untyped

private
def error: (Net::HTTPResponse response) -> (Unauthorized | BadRequest | Forbidden | InternalServerError | NotFound | PayloadTooLarge | ServiceUnavailable | TooManyRequests)
Expand Down Expand Up @@ -246,9 +241,10 @@ module X
def delete: (String endpoint, ?headers: Hash[String, String]) -> untyped

private
def initialize_oauth: (String? api_key, String? api_key_secret, String? access_token, String? access_token_secret) -> void
def initialize_oauth: (String? api_key, String? api_key_secret, String? access_token, String? access_token_secret, String? bearer_token) -> void
def initialize_response_objects: (Class? array_class, Class? object_class) -> void
def initialize_authenticator: -> Authenticator
def execute_request: (Symbol http_method, String endpoint, headers: Hash[String, String], ?body: String?) -> untyped
def execute_request: (Symbol http_method, String endpoint, ?body: String?, ?headers: Hash[String, String], ?array_class: Class?, ?object_class: Class?) -> untyped
end

module MediaUploader
Expand Down
85 changes: 37 additions & 48 deletions test/x/client_request_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,64 +8,53 @@ def setup
@client = Client.new
end

def test_get_request
stub_request(:get, "https://api.twitter.com/2/tweets")
@client.get("tweets")

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

def test_get_request_with_headers
headers = {"User-Agent" => "Custom User Agent"}
stub_request(:get, "https://api.twitter.com/2/tweets")
@client.get("tweets", headers: headers)

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

def test_post_request
stub_request(:post, "https://api.twitter.com/2/tweets")
@client.post("tweets")

assert_requested :post, "https://api.twitter.com/2/tweets"
end
X::RequestBuilder::HTTP_METHODS.each_key do |http_method|
define_method "test_#{http_method}_request" do
stub_request(http_method, "https://api.twitter.com/2/tweets")
@client.public_send(http_method, "tweets")

def test_post_request_with_headers
headers = {"User-Agent" => "Custom User Agent"}
stub_request(:post, "https://api.twitter.com/2/tweets")
@client.post("tweets", headers: headers)

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

def test_put_request
stub_request(:put, "https://api.twitter.com/2/tweets")
@client.put("tweets")
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)

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

def test_put_request_with_headers
headers = {"User-Agent" => "Custom User Agent"}
stub_request(:put, "https://api.twitter.com/2/tweets")
@client.put("tweets", headers: headers)
define_method "test_#{http_method}_request_with_custom_response_objects" do
stub_request(http_method, "https://api.twitter.com/2/tweets")
.to_return(body: '{"set": [1, 2, 2, 3]}', headers: {"Content-Type" => "application/json"})
ostruct = @client.public_send(http_method, "tweets", object_class: OpenStruct, array_class: Set)

assert_requested :put, "https://api.twitter.com/2/tweets", headers: headers
end
assert_kind_of OpenStruct, ostruct
assert_kind_of Set, ostruct.set
assert_equal Set.new([1, 2, 3]), ostruct.set
end

def test_delete_request
stub_request(:delete, "https://api.twitter.com/2/tweets")
@client.delete("tweets")
define_method "test_#{http_method}_request_with_custom_response_objects_client_configuration" do
stub_request(http_method, "https://api.twitter.com/2/tweets")
.to_return(body: '{"set": [1, 2, 2, 3]}', headers: {"Content-Type" => "application/json"})
client = Client.new(object_class: OpenStruct, array_class: Set)
ostruct = client.public_send(http_method, "tweets")

assert_requested :delete, "https://api.twitter.com/2/tweets"
assert_kind_of OpenStruct, ostruct
assert_kind_of Set, ostruct.set
assert_equal Set.new([1, 2, 3]), ostruct.set
end
end

def test_delete_request_with_headers
headers = {"User-Agent" => "Custom User Agent"}
stub_request(:delete, "https://api.twitter.com/2/tweets")
@client.delete("tweets", headers: headers)
def test_execute_request_with_custom_response_objects_client_configuration
stub_request(:get, "https://api.twitter.com/2/tweets")
.to_return(body: '{"set": [1, 2, 2, 3]}', headers: {"Content-Type" => "application/json"})
client = Client.new(object_class: OpenStruct, array_class: Set)
ostruct = client.send(:execute_request, :get, "tweets")

assert_requested :delete, "https://api.twitter.com/2/tweets", headers: headers
assert_kind_of OpenStruct, ostruct
assert_kind_of Set, ostruct.set
assert_equal Set.new([1, 2, 3]), ostruct.set
end

def test_redirect_handler_preserves_authentication
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 @@ -10,7 +10,7 @@ def setup

ResponseParser::ERROR_MAP.each do |status, error_class|
name = error_class.name.split("::").last
define_method("test_initialize_#{name.downcase}_error") do
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)

Expand All @@ -21,7 +21,7 @@ def setup
end

Connection::NETWORK_ERRORS.each do |error_class|
define_method("test_#{error_class.name.split("::").last.downcase}_raises_network_error") do
define_method "test_#{error_class.name.split("::").last.downcase}_raises_network_error" do
stub_request(:get, "https://api.twitter.com/2/tweets").to_raise(error_class)

assert_raises NetworkError do
Expand Down
Loading

0 comments on commit d7fc80f

Please sign in to comment.