Skip to content
6 changes: 3 additions & 3 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ Layout/ParameterAlignment:
Layout/SpaceInsideHashLiteralBraces:
EnforcedStyle: no_space

Lint/MissingSuper:
Enabled: false

Metrics/ParameterLists:
CountKeywordArgs: false

Expand All @@ -40,9 +43,6 @@ Minitest/MultipleAssertions:
Style/Alias:
EnforcedStyle: prefer_alias_method

Style/Documentation:
Enabled: false

Style/FrozenStringLiteralComment:
EnforcedStyle: never

Expand Down
2 changes: 1 addition & 1 deletion lib/x/bearer_token_authenticator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class BearerTokenAuthenticator < Authenticator
# @return [BearerTokenAuthenticator] a new instance
# @example Create a new bearer token authenticator
# authenticator = X::BearerTokenAuthenticator.new(bearer_token: "token")
def initialize(bearer_token:) # rubocop:disable Lint/MissingSuper
def initialize(bearer_token:)
@bearer_token = bearer_token
end

Expand Down
22 changes: 18 additions & 4 deletions lib/x/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require_relative "client_credentials"
require_relative "connection"
require_relative "oauth_authenticator"
require_relative "oauth2_authenticator"
require_relative "redirect_handler"
require_relative "request_builder"
require_relative "response_parser"
Expand All @@ -28,19 +29,28 @@ class Client
# @example Get or set the base URL
# client.base_url = "https://api.twitter.com/1.1/"
attr_accessor :base_url

# The default class for parsing JSON arrays
# @api public
# @return [Class] the default class for parsing JSON arrays
# @example Get or set the default array class
# client.default_array_class = Set
attr_accessor :default_array_class

# The default class for parsing JSON objects
# @api public
# @return [Class] the default class for parsing JSON objects
# @example Get or set the default object class
# client.default_object_class = OpenStruct
attr_accessor :default_object_class

# The authenticator for API requests
# @api public
# @return [Authenticator] the authenticator instance
# @example Check if the OAuth 2.0 token has expired
# client.authenticator.token_expired?
attr_reader :authenticator

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
Expand All @@ -54,6 +64,9 @@ class Client
# @param access_token [String, nil] the access token for OAuth authentication
# @param access_token_secret [String, nil] the access token secret for OAuth 1.0a authentication
# @param bearer_token [String, nil] the bearer token for authentication
# @param client_id [String, nil] the OAuth 2.0 client ID
# @param client_secret [String, nil] the OAuth 2.0 client secret
# @param refresh_token [String, nil] the OAuth 2.0 refresh token
# @param base_url [String] the base URL for API requests
# @param open_timeout [Integer] the timeout for opening connections in seconds
# @param read_timeout [Integer] the timeout for reading responses in seconds
Expand All @@ -69,7 +82,7 @@ class Client
# @example Create a client with OAuth 1.0a authentication
# client = X::Client.new(api_key: "key", api_key_secret: "secret", access_token: "token", access_token_secret: "token_secret")
def initialize(api_key: nil, api_key_secret: nil, access_token: nil, access_token_secret: nil,
bearer_token: nil,
bearer_token: nil, client_id: nil, client_secret: nil, refresh_token: nil,
base_url: DEFAULT_BASE_URL,
open_timeout: Connection::DEFAULT_OPEN_TIMEOUT,
read_timeout: Connection::DEFAULT_READ_TIMEOUT,
Expand All @@ -79,7 +92,8 @@ def initialize(api_key: nil, api_key_secret: nil, access_token: nil, access_toke
default_array_class: DEFAULT_ARRAY_CLASS,
default_object_class: DEFAULT_OBJECT_CLASS,
max_redirects: RedirectHandler::DEFAULT_MAX_REDIRECTS)
initialize_credentials(api_key:, api_key_secret:, access_token:, access_token_secret:, bearer_token:)
initialize_credentials(api_key:, api_key_secret:, access_token:, access_token_secret:, bearer_token:,
client_id:, client_secret:, refresh_token:)
initialize_authenticator
@base_url = base_url
@default_array_class = default_array_class
Expand Down Expand Up @@ -137,9 +151,9 @@ def delete(endpoint, headers: {}, array_class: default_array_class, object_class
# @return [Hash, Array, nil] the parsed response body
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:, uri:, body:, headers:, authenticator: @authenticator)
request = @request_builder.build(http_method:, uri:, body:, headers:, authenticator:)
response = @connection.perform(request:)
response = @redirect_handler.handle(response:, request:, base_url:, authenticator: @authenticator)
response = @redirect_handler.handle(response:, request:, base_url:, authenticator:)
@response_parser.parse(response:, array_class:, object_class:)
end
end
Expand Down
78 changes: 76 additions & 2 deletions lib/x/client_credentials.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,56 @@ module ClientCredentials
# @example Get the API key
# client.api_key
attr_reader :api_key

# The API key secret for OAuth 1.0a authentication
# @api public
# @return [String, nil] the API key secret for OAuth 1.0a authentication
# @example Get the API key secret
# client.api_key_secret
attr_reader :api_key_secret

# The access token for OAuth authentication
# @api public
# @return [String, nil] the access token for OAuth authentication
# @example Get the access token
# client.access_token
attr_reader :access_token

# The access token secret for OAuth 1.0a authentication
# @api public
# @return [String, nil] the access token secret for OAuth 1.0a authentication
# @example Get the access token secret
# client.access_token_secret
attr_reader :access_token_secret

# The bearer token for authentication
# @api public
# @return [String, nil] the bearer token for authentication
# @example Get the bearer token
# client.bearer_token
attr_reader :bearer_token

# The OAuth 2.0 client ID
# @api public
# @return [String, nil] the OAuth 2.0 client ID
# @example Get the client ID
# client.client_id
attr_reader :client_id

# The OAuth 2.0 client secret
# @api public
# @return [String, nil] the OAuth 2.0 client secret
# @example Get the client secret
# client.client_secret
attr_reader :client_secret

# The OAuth 2.0 refresh token
# @api public
# @return [String, nil] the OAuth 2.0 refresh token
# @example Get the refresh token
# client.refresh_token
attr_reader :refresh_token

# Set the API key for OAuth 1.0a authentication
#
# @api public
Expand Down Expand Up @@ -93,24 +118,64 @@ def bearer_token=(bearer_token)
initialize_authenticator
end

# Set the OAuth 2.0 client ID
#
# @api public
# @param client_id [String] the OAuth 2.0 client ID
# @return [void]
# @example Set the client ID
# client.client_id = "new_id"
def client_id=(client_id)
@client_id = client_id
initialize_authenticator
end

# Set the OAuth 2.0 client secret
#
# @api public
# @param client_secret [String] the OAuth 2.0 client secret
# @return [void]
# @example Set the client secret
# client.client_secret = "new_secret"
def client_secret=(client_secret)
@client_secret = client_secret
initialize_authenticator
end

# Set the OAuth 2.0 refresh token
#
# @api public
# @param refresh_token [String] the OAuth 2.0 refresh token
# @return [void]
# @example Set the refresh token
# client.refresh_token = "new_token"
def refresh_token=(refresh_token)
@refresh_token = refresh_token
initialize_authenticator
end

private

# Initialize credential instance variables
# @api private
# @return [void]
def initialize_credentials(api_key:, api_key_secret:, access_token:, access_token_secret:, bearer_token:)
def initialize_credentials(api_key:, api_key_secret:, access_token:, access_token_secret:, bearer_token:,
client_id:, client_secret:, refresh_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
@client_id = client_id
@client_secret = client_secret
@refresh_token = refresh_token
end

# Initialize the appropriate authenticator based on available credentials
# @api private
# @return [Authenticator] the initialized authenticator
def initialize_authenticator
@authenticator = oauth_authenticator || bearer_authenticator || @authenticator || Authenticator.new
@authenticator = oauth_authenticator || oauth2_authenticator || bearer_authenticator || @authenticator || Authenticator.new
end

# Build an OAuth 1.0a authenticator if credentials are available
Expand All @@ -122,6 +187,15 @@ def oauth_authenticator
OAuthAuthenticator.new(api_key:, api_key_secret:, access_token:, access_token_secret:)
end

# Build an OAuth 2.0 authenticator if credentials are available
# @api private
# @return [OAuth2Authenticator, nil] the OAuth 2.0 authenticator or nil
def oauth2_authenticator
return unless client_id && client_secret && access_token && refresh_token

OAuth2Authenticator.new(client_id:, client_secret:, access_token:, refresh_token:)
end

# Build a bearer token authenticator if credentials are available
# @api private
# @return [BearerTokenAuthenticator, nil] the bearer token authenticator or nil
Expand Down
Loading