x 0.9.1 → 0.11.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 +4 -4
 - data/CHANGELOG.md +43 -27
 - data/README.md +6 -2
 - data/lib/x/authenticator.rb +8 -0
 - data/lib/x/bearer_token_authenticator.rb +5 -3
 - data/lib/x/cgi.rb +15 -0
 - data/lib/x/client.rb +40 -37
 - data/lib/x/connection.rb +42 -65
 - data/lib/x/errors/bad_gateway.rb +5 -0
 - data/lib/x/errors/{not_found_error.rb → bad_request.rb} +1 -1
 - data/lib/x/errors/connection_exception.rb +5 -0
 - data/lib/x/errors/error.rb +1 -9
 - data/lib/x/errors/{forbidden_error.rb → forbidden.rb} +1 -1
 - data/lib/x/errors/gateway_timeout.rb +5 -0
 - data/lib/x/errors/{bad_request_error.rb → gone.rb} +1 -1
 - data/lib/x/errors/internal_server_error.rb +5 -0
 - data/lib/x/errors/network_error.rb +1 -1
 - data/lib/x/errors/not_acceptable.rb +5 -0
 - data/lib/x/errors/not_found.rb +5 -0
 - data/lib/x/errors/payload_too_large.rb +5 -0
 - data/lib/x/errors/service_unavailable.rb +5 -0
 - data/lib/x/errors/too_many_redirects.rb +5 -0
 - data/lib/x/errors/too_many_requests.rb +29 -0
 - data/lib/x/errors/unauthorized.rb +5 -0
 - data/lib/x/errors/unprocessable_entity.rb +5 -0
 - data/lib/x/media_uploader.rb +122 -0
 - data/lib/x/oauth_authenticator.rb +10 -15
 - data/lib/x/redirect_handler.rb +26 -24
 - data/lib/x/request_builder.rb +22 -28
 - data/lib/x/response_parser.rb +92 -0
 - data/lib/x/version.rb +1 -1
 - data/sig/x.rbs +160 -71
 - metadata +22 -11
 - data/lib/x/errors/authentication_error.rb +0 -5
 - data/lib/x/errors/service_unavailable_error.rb +0 -5
 - data/lib/x/errors/too_many_redirects_error.rb +0 -5
 - data/lib/x/errors/too_many_requests_error.rb +0 -29
 - data/lib/x/response_handler.rb +0 -54
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: e5af820e6e3cd8b04becf201ad7af96c8d95d9206112673888f91c5ecf923187
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 6f0f561be192153eca2cc7356c823c87bd98f0987256875892c0a2e9ea12b82d
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 8b208d5ba10604a34a57b0dd2ee5dc0992eb801cfbcc287a7f1b0d54444f214050a2c25b2ec6291868a8c35fff09260ad91a82641be830b87d3e6abf0207fc3e
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 24c6a1083358b60065698320372d53404bab9c319aaad13b097508a7041653ac9d45aabd8e6f7a99204accddfc1beeeb2bce18bd67fa9ba816eb6282cbab4227
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -1,66 +1,82 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            ## [ 
     | 
| 
      
 1 
     | 
    
         
            +
            ## [0.11.0] - 2023-10-24
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            * Add base Authenticator class (8c66ce2)
         
     | 
| 
      
 4 
     | 
    
         
            +
            * Consistently use keyword arguments (3beb271)
         
     | 
| 
      
 5 
     | 
    
         
            +
            * Use patern matching to build request (4d001c7)
         
     | 
| 
      
 6 
     | 
    
         
            +
            * Rename ResponseHandler to ResponseParser (498e890)
         
     | 
| 
      
 7 
     | 
    
         
            +
            * Rename methods to be more consistent (5b8c655)
         
     | 
| 
      
 8 
     | 
    
         
            +
            * Rename MediaUpload to MediaUploader (84f0c15)
         
     | 
| 
      
 9 
     | 
    
         
            +
            * Add mutant and kill mutants (b124968)
         
     | 
| 
      
 10 
     | 
    
         
            +
            * Fix authentication bug with request URLs that contain spaces (8de3174)
         
     | 
| 
      
 11 
     | 
    
         
            +
            * Refactor errors (853d39c)
         
     | 
| 
      
 12 
     | 
    
         
            +
            * Make Connection class threadsafe (d95d285)
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            ## [0.10.0] - 2023-10-08
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            * Add media upload helper methods (6c6a267)
         
     | 
| 
      
 17 
     | 
    
         
            +
            * Add PayloadTooLargeError class (cd61850)
         
     | 
| 
       2 
18 
     | 
    
         | 
| 
       3 
19 
     | 
    
         
             
            ## [0.9.1] - 2023-10-06
         
     | 
| 
       4 
20 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
      
 21 
     | 
    
         
            +
            * Allow successful empty responses (06bf7db)
         
     | 
| 
      
 22 
     | 
    
         
            +
            * Update default User-Agent string (296b36a)
         
     | 
| 
      
 23 
     | 
    
         
            +
            * Move query parameter escaping into RequestBuilder (56d6bd2)
         
     | 
| 
       8 
24 
     | 
    
         | 
| 
       9 
25 
     | 
    
         
             
            ## [0.9.0] - 2023-09-26
         
     | 
| 
       10 
26 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
      
 27 
     | 
    
         
            +
            * Add support for HTTP proxies (3740f4f)
         
     | 
| 
       12 
28 
     | 
    
         | 
| 
       13 
29 
     | 
    
         
             
            ## [0.8.1] - 2023-09-20
         
     | 
| 
       14 
30 
     | 
    
         | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
      
 31 
     | 
    
         
            +
            * Fix bug where setting Connection#base_uri= doesn't update the HTTP client (d5a89db)
         
     | 
| 
       16 
32 
     | 
    
         | 
| 
       17 
33 
     | 
    
         
             
            ## [0.8.0] - 2023-09-14
         
     | 
| 
       18 
34 
     | 
    
         | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
      
 35 
     | 
    
         
            +
            * Add (back) bearer token authentication (62e141d)
         
     | 
| 
      
 36 
     | 
    
         
            +
            * Follow redirects (90a8c55)
         
     | 
| 
      
 37 
     | 
    
         
            +
            * Parse error responses with Content-Type: application/problem+json (0b697d9)
         
     | 
| 
       22 
38 
     | 
    
         | 
| 
       23 
39 
     | 
    
         
             
            ## [0.7.1] - 2023-09-02
         
     | 
| 
       24 
40 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
      
 41 
     | 
    
         
            +
            * Fix bug in X::Authenticator#split_uri (ebc9d5f)
         
     | 
| 
       26 
42 
     | 
    
         | 
| 
       27 
43 
     | 
    
         
             
            ## [0.7.0] - 2023-09-02
         
     | 
| 
       28 
44 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
      
 45 
     | 
    
         
            +
            * Remove OAuth gem (7c29bb1)
         
     | 
| 
       30 
46 
     | 
    
         | 
| 
       31 
47 
     | 
    
         
             
            ## [0.6.0] - 2023-08-30
         
     | 
| 
       32 
48 
     | 
    
         | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
      
 49 
     | 
    
         
            +
            * Add configurable debug output stream for logging (fd2d4b0)
         
     | 
| 
      
 50 
     | 
    
         
            +
            * Remove bearer token authentication (efff940)
         
     | 
| 
      
 51 
     | 
    
         
            +
            * Define RBS type signatures (d7f63ba)
         
     | 
| 
       36 
52 
     | 
    
         | 
| 
       37 
53 
     | 
    
         
             
            ## [0.5.1] - 2023-08-16
         
     | 
| 
       38 
54 
     | 
    
         | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
      
 55 
     | 
    
         
            +
            * Fix bearer token authentication (1a1ca93)
         
     | 
| 
       40 
56 
     | 
    
         | 
| 
       41 
57 
     | 
    
         
             
            ## [0.5.0] - 2023-08-10
         
     | 
| 
       42 
58 
     | 
    
         | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
      
 59 
     | 
    
         
            +
            * Add configurable write timeout (2a31f84)
         
     | 
| 
      
 60 
     | 
    
         
            +
            * Use built-in Gem::Version class (066e0b6)
         
     | 
| 
       45 
61 
     | 
    
         | 
| 
       46 
62 
     | 
    
         
             
            ## [0.4.0] - 2023-08-06
         
     | 
| 
       47 
63 
     | 
    
         | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
      
 64 
     | 
    
         
            +
            * Refactor Client into Authenticator, RequestBuilder, Connection, ResponseHandler (6bee1e9)
         
     | 
| 
      
 65 
     | 
    
         
            +
            * Add configurable open timeout (1000f9d)
         
     | 
| 
      
 66 
     | 
    
         
            +
            * Allow configuration of content type (f33a732)
         
     | 
| 
       51 
67 
     | 
    
         | 
| 
       52 
68 
     | 
    
         
             
            ## [0.3.0] - 2023-08-04
         
     | 
| 
       53 
69 
     | 
    
         | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
      
 70 
     | 
    
         
            +
            * Add accessors to X::Client (e61fa73)
         
     | 
| 
      
 71 
     | 
    
         
            +
            * Add configurable read timeout (41502b9)
         
     | 
| 
      
 72 
     | 
    
         
            +
            * Handle network-related errors (9ed1fb4)
         
     | 
| 
      
 73 
     | 
    
         
            +
            * Include response body in errors (a203e6a)
         
     | 
| 
       58 
74 
     | 
    
         | 
| 
       59 
75 
     | 
    
         
             
            ## [0.2.0] - 2023-08-02
         
     | 
| 
       60 
76 
     | 
    
         | 
| 
       61 
     | 
    
         
            -
             
     | 
| 
       62 
     | 
    
         
            -
             
     | 
| 
      
 77 
     | 
    
         
            +
            * Allow configuration of base URL (4bc0531)
         
     | 
| 
      
 78 
     | 
    
         
            +
            * Improve error handling (14dc0cd)
         
     | 
| 
       63 
79 
     | 
    
         | 
| 
       64 
80 
     | 
    
         
             
            ## [0.1.0] - 2023-08-02
         
     | 
| 
       65 
81 
     | 
    
         | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
      
 82 
     | 
    
         
            +
            * Initial release
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -130,11 +130,15 @@ Pull requests will only be accepted if they meet all the following criteria: 
     | 
|
| 
       130 
130 
     | 
    
         | 
| 
       131 
131 
     | 
    
         
             
                   bundle exec rake standard
         
     | 
| 
       132 
132 
     | 
    
         | 
| 
       133 
     | 
    
         
            -
            2.  
     | 
| 
      
 133 
     | 
    
         
            +
            2. 100% C0 code coverage. This can be verified with:
         
     | 
| 
       134 
134 
     | 
    
         | 
| 
       135 
135 
     | 
    
         
             
                   bundle exec rake test
         
     | 
| 
       136 
136 
     | 
    
         | 
| 
       137 
     | 
    
         
            -
            3.  
     | 
| 
      
 137 
     | 
    
         
            +
            3. 100% mutation coverage. This can be verified with:
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                   bundle exec rake mutant
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
            4. RBS type signatures (in `sig/x.rbs`). This can be verified with:
         
     | 
| 
       138 
142 
     | 
    
         | 
| 
       139 
143 
     | 
    
         
             
                   bundle exec rake steep
         
     | 
| 
       140 
144 
     | 
    
         | 
| 
         @@ -1,14 +1,16 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative "authenticator"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            module X
         
     | 
| 
       2 
4 
     | 
    
         
             
              # Handles bearer token authentication
         
     | 
| 
       3 
     | 
    
         
            -
              class BearerTokenAuthenticator
         
     | 
| 
      
 5 
     | 
    
         
            +
              class BearerTokenAuthenticator < Authenticator
         
     | 
| 
       4 
6 
     | 
    
         
             
                attr_accessor :bearer_token
         
     | 
| 
       5 
7 
     | 
    
         | 
| 
       6 
     | 
    
         
            -
                def initialize(bearer_token)
         
     | 
| 
      
 8 
     | 
    
         
            +
                def initialize(bearer_token:) # rubocop:disable Lint/MissingSuper
         
     | 
| 
       7 
9 
     | 
    
         
             
                  @bearer_token = bearer_token
         
     | 
| 
       8 
10 
     | 
    
         
             
                end
         
     | 
| 
       9 
11 
     | 
    
         | 
| 
       10 
12 
     | 
    
         
             
                def header(_request)
         
     | 
| 
       11 
     | 
    
         
            -
                  "Bearer #{bearer_token}"
         
     | 
| 
      
 13 
     | 
    
         
            +
                  {"Authorization" => "Bearer #{bearer_token}"}
         
     | 
| 
       12 
14 
     | 
    
         
             
                end
         
     | 
| 
       13 
15 
     | 
    
         
             
              end
         
     | 
| 
       14 
16 
     | 
    
         
             
            end
         
     | 
    
        data/lib/x/cgi.rb
    ADDED
    
    | 
         @@ -0,0 +1,15 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "cgi"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module X
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Namespaced CGI class
         
     | 
| 
      
 5 
     | 
    
         
            +
              class CGI
         
     | 
| 
      
 6 
     | 
    
         
            +
                # TODO: Replace CGI.escape with CGI.escapeURIComponent when support for Ruby 3.1 is dropped
         
     | 
| 
      
 7 
     | 
    
         
            +
                def self.escape(value)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  ::CGI.escape(value).gsub("+", "%20")
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def self.escape_params(params)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  params.map { |k, v| "#{k}=#{escape(v)}" }.join("&")
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/x/client.rb
    CHANGED
    
    | 
         @@ -1,84 +1,87 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require "forwardable"
         
     | 
| 
       2 
2 
     | 
    
         
             
            require_relative "bearer_token_authenticator"
         
     | 
| 
       3 
     | 
    
         
            -
            require_relative "oauth_authenticator"
         
     | 
| 
       4 
3 
     | 
    
         
             
            require_relative "connection"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative "oauth_authenticator"
         
     | 
| 
       5 
5 
     | 
    
         
             
            require_relative "redirect_handler"
         
     | 
| 
       6 
6 
     | 
    
         
             
            require_relative "request_builder"
         
     | 
| 
       7 
     | 
    
         
            -
            require_relative " 
     | 
| 
      
 7 
     | 
    
         
            +
            require_relative "response_parser"
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
9 
     | 
    
         
             
            module X
         
     | 
| 
       10 
10 
     | 
    
         
             
              # Main public interface
         
     | 
| 
       11 
11 
     | 
    
         
             
              class Client
         
     | 
| 
       12 
12 
     | 
    
         
             
                extend Forwardable
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
      
 14 
     | 
    
         
            +
                DEFAULT_BASE_URL = "https://api.twitter.com/2/".freeze
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                attr_accessor :base_url
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
       14 
18 
     | 
    
         
             
                def_delegators :@authenticator, :bearer_token, :api_key, :api_key_secret, :access_token, :access_token_secret
         
     | 
| 
       15 
19 
     | 
    
         
             
                def_delegators :@authenticator, :bearer_token=, :api_key=, :api_key_secret=, :access_token=, :access_token_secret=
         
     | 
| 
       16 
     | 
    
         
            -
                def_delegators :@connection, : 
     | 
| 
       17 
     | 
    
         
            -
                def_delegators :@connection, : 
     | 
| 
       18 
     | 
    
         
            -
                def_delegators :@ 
     | 
| 
       19 
     | 
    
         
            -
                def_delegators :@ 
     | 
| 
       20 
     | 
    
         
            -
                def_delegators :@ 
     | 
| 
       21 
     | 
    
         
            -
                def_delegators :@ 
     | 
| 
      
 20 
     | 
    
         
            +
                def_delegators :@connection, :open_timeout, :read_timeout, :write_timeout, :proxy_url, :debug_output
         
     | 
| 
      
 21 
     | 
    
         
            +
                def_delegators :@connection, :open_timeout=, :read_timeout=, :write_timeout=, :proxy_url=, :debug_output=
         
     | 
| 
      
 22 
     | 
    
         
            +
                def_delegators :@redirect_handler, :max_redirects
         
     | 
| 
      
 23 
     | 
    
         
            +
                def_delegators :@redirect_handler, :max_redirects=
         
     | 
| 
      
 24 
     | 
    
         
            +
                def_delegators :@response_parser, :array_class, :object_class
         
     | 
| 
      
 25 
     | 
    
         
            +
                def_delegators :@response_parser, :array_class=, :object_class=
         
     | 
| 
       22 
26 
     | 
    
         | 
| 
       23 
27 
     | 
    
         
             
                def initialize(bearer_token: nil,
         
     | 
| 
       24 
28 
     | 
    
         
             
                  api_key: nil, api_key_secret: nil, access_token: nil, access_token_secret: nil,
         
     | 
| 
       25 
     | 
    
         
            -
                  base_url:  
     | 
| 
      
 29 
     | 
    
         
            +
                  base_url: DEFAULT_BASE_URL,
         
     | 
| 
       26 
30 
     | 
    
         
             
                  open_timeout: Connection::DEFAULT_OPEN_TIMEOUT,
         
     | 
| 
       27 
31 
     | 
    
         
             
                  read_timeout: Connection::DEFAULT_READ_TIMEOUT,
         
     | 
| 
       28 
32 
     | 
    
         
             
                  write_timeout: Connection::DEFAULT_WRITE_TIMEOUT,
         
     | 
| 
       29 
33 
     | 
    
         
             
                  proxy_url: nil,
         
     | 
| 
       30 
     | 
    
         
            -
                  content_type: RequestBuilder::DEFAULT_CONTENT_TYPE,
         
     | 
| 
       31 
     | 
    
         
            -
                  user_agent: RequestBuilder::DEFAULT_USER_AGENT,
         
     | 
| 
       32 
34 
     | 
    
         
             
                  debug_output: nil,
         
     | 
| 
       33 
     | 
    
         
            -
                  array_class:  
     | 
| 
       34 
     | 
    
         
            -
                  object_class:  
     | 
| 
      
 35 
     | 
    
         
            +
                  array_class: nil,
         
     | 
| 
      
 36 
     | 
    
         
            +
                  object_class: nil,
         
     | 
| 
       35 
37 
     | 
    
         
             
                  max_redirects: RedirectHandler::DEFAULT_MAX_REDIRECTS)
         
     | 
| 
       36 
38 
     | 
    
         | 
| 
      
 39 
     | 
    
         
            +
                  @base_url = base_url
         
     | 
| 
       37 
40 
     | 
    
         
             
                  initialize_authenticator(bearer_token, api_key, api_key_secret, access_token, access_token_secret)
         
     | 
| 
       38 
     | 
    
         
            -
                  @connection = Connection.new( 
     | 
| 
      
 41 
     | 
    
         
            +
                  @connection = Connection.new(open_timeout: open_timeout, read_timeout: read_timeout,
         
     | 
| 
       39 
42 
     | 
    
         
             
                    write_timeout: write_timeout, debug_output: debug_output, proxy_url: proxy_url)
         
     | 
| 
       40 
     | 
    
         
            -
                  @request_builder = RequestBuilder.new 
     | 
| 
       41 
     | 
    
         
            -
                  @redirect_handler = RedirectHandler.new(@authenticator,  
     | 
| 
       42 
     | 
    
         
            -
                    max_redirects: max_redirects)
         
     | 
| 
       43 
     | 
    
         
            -
                  @ 
     | 
| 
      
 43 
     | 
    
         
            +
                  @request_builder = RequestBuilder.new
         
     | 
| 
      
 44 
     | 
    
         
            +
                  @redirect_handler = RedirectHandler.new(authenticator: @authenticator, connection: @connection,
         
     | 
| 
      
 45 
     | 
    
         
            +
                    request_builder: @request_builder, max_redirects: max_redirects)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  @response_parser = ResponseParser.new(array_class: array_class, object_class: object_class)
         
     | 
| 
       44 
47 
     | 
    
         
             
                end
         
     | 
| 
       45 
48 
     | 
    
         | 
| 
       46 
     | 
    
         
            -
                def get(endpoint)
         
     | 
| 
       47 
     | 
    
         
            -
                   
     | 
| 
      
 49 
     | 
    
         
            +
                def get(endpoint, headers: {})
         
     | 
| 
      
 50 
     | 
    
         
            +
                  execute_request(:get, endpoint, headers: headers)
         
     | 
| 
       48 
51 
     | 
    
         
             
                end
         
     | 
| 
       49 
52 
     | 
    
         | 
| 
       50 
     | 
    
         
            -
                def post(endpoint, body = nil)
         
     | 
| 
       51 
     | 
    
         
            -
                   
     | 
| 
      
 53 
     | 
    
         
            +
                def post(endpoint, body = nil, headers: {})
         
     | 
| 
      
 54 
     | 
    
         
            +
                  execute_request(:post, endpoint, body: body, headers: headers)
         
     | 
| 
       52 
55 
     | 
    
         
             
                end
         
     | 
| 
       53 
56 
     | 
    
         | 
| 
       54 
     | 
    
         
            -
                def put(endpoint, body = nil)
         
     | 
| 
       55 
     | 
    
         
            -
                   
     | 
| 
      
 57 
     | 
    
         
            +
                def put(endpoint, body = nil, headers: {})
         
     | 
| 
      
 58 
     | 
    
         
            +
                  execute_request(:put, endpoint, body: body, headers: headers)
         
     | 
| 
       56 
59 
     | 
    
         
             
                end
         
     | 
| 
       57 
60 
     | 
    
         | 
| 
       58 
     | 
    
         
            -
                def delete(endpoint)
         
     | 
| 
       59 
     | 
    
         
            -
                   
     | 
| 
      
 61 
     | 
    
         
            +
                def delete(endpoint, headers: {})
         
     | 
| 
      
 62 
     | 
    
         
            +
                  execute_request(:delete, endpoint, headers: headers)
         
     | 
| 
       60 
63 
     | 
    
         
             
                end
         
     | 
| 
       61 
64 
     | 
    
         | 
| 
       62 
65 
     | 
    
         
             
                private
         
     | 
| 
       63 
66 
     | 
    
         | 
| 
       64 
67 
     | 
    
         
             
                def initialize_authenticator(bearer_token, api_key, api_key_secret, access_token, access_token_secret)
         
     | 
| 
       65 
68 
     | 
    
         
             
                  @authenticator = if bearer_token
         
     | 
| 
       66 
     | 
    
         
            -
                    BearerTokenAuthenticator.new(bearer_token)
         
     | 
| 
      
 69 
     | 
    
         
            +
                    BearerTokenAuthenticator.new(bearer_token: bearer_token)
         
     | 
| 
       67 
70 
     | 
    
         
             
                  elsif api_key && api_key_secret && access_token && access_token_secret
         
     | 
| 
       68 
     | 
    
         
            -
                     
     | 
| 
      
 71 
     | 
    
         
            +
                    OAuthAuthenticator.new(api_key: api_key, api_key_secret: api_key_secret, access_token: access_token,
         
     | 
| 
      
 72 
     | 
    
         
            +
                      access_token_secret: access_token_secret)
         
     | 
| 
       69 
73 
     | 
    
         
             
                  else
         
     | 
| 
       70 
     | 
    
         
            -
                     
     | 
| 
       71 
     | 
    
         
            -
                      "Client must be initialized with either a bearer_token or " \
         
     | 
| 
       72 
     | 
    
         
            -
                      "an api_key, api_key_secret, access_token, and access_token_secret"
         
     | 
| 
      
 74 
     | 
    
         
            +
                    Authenticator.new
         
     | 
| 
       73 
75 
     | 
    
         
             
                  end
         
     | 
| 
       74 
76 
     | 
    
         
             
                end
         
     | 
| 
       75 
77 
     | 
    
         | 
| 
       76 
     | 
    
         
            -
                def  
     | 
| 
       77 
     | 
    
         
            -
                  uri = URI.join( 
     | 
| 
       78 
     | 
    
         
            -
                  request = @request_builder.build(@authenticator, http_method, uri, body: body 
     | 
| 
       79 
     | 
    
         
            -
             
     | 
| 
       80 
     | 
    
         
            -
                   
     | 
| 
       81 
     | 
    
         
            -
                  @ 
     | 
| 
      
 78 
     | 
    
         
            +
                def execute_request(http_method, endpoint, headers:, body: nil)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  uri = URI.join(base_url, endpoint)
         
     | 
| 
      
 80 
     | 
    
         
            +
                  request = @request_builder.build(authenticator: @authenticator, http_method: http_method, uri: uri, body: body,
         
     | 
| 
      
 81 
     | 
    
         
            +
                    headers: headers)
         
     | 
| 
      
 82 
     | 
    
         
            +
                  response = @connection.perform(request: request)
         
     | 
| 
      
 83 
     | 
    
         
            +
                  response = @redirect_handler.handle(response: response, request: request, base_url: base_url)
         
     | 
| 
      
 84 
     | 
    
         
            +
                  @response_parser.parse(response: response)
         
     | 
| 
       82 
85 
     | 
    
         
             
                end
         
     | 
| 
       83 
86 
     | 
    
         
             
              end
         
     | 
| 
       84 
87 
     | 
    
         
             
            end
         
     | 
    
        data/lib/x/connection.rb
    CHANGED
    
    | 
         @@ -1,5 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require "forwardable"
         
     | 
| 
       2 
2 
     | 
    
         
             
            require "net/http"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "openssl"
         
     | 
| 
       3 
4 
     | 
    
         
             
            require "uri"
         
     | 
| 
       4 
5 
     | 
    
         
             
            require_relative "errors/network_error"
         
     | 
| 
       5 
6 
     | 
    
         | 
| 
         @@ -8,96 +9,72 @@ module X 
     | 
|
| 
       8 
9 
     | 
    
         
             
              class Connection
         
     | 
| 
       9 
10 
     | 
    
         
             
                extend Forwardable
         
     | 
| 
       10 
11 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                 
     | 
| 
       12 
     | 
    
         
            -
                DEFAULT_HOST = "https://api.twitter.com".freeze
         
     | 
| 
      
 12 
     | 
    
         
            +
                DEFAULT_HOST = "api.twitter.com".freeze
         
     | 
| 
       13 
13 
     | 
    
         
             
                DEFAULT_PORT = 443
         
     | 
| 
       14 
14 
     | 
    
         
             
                DEFAULT_OPEN_TIMEOUT = 60 # seconds
         
     | 
| 
       15 
15 
     | 
    
         
             
                DEFAULT_READ_TIMEOUT = 60 # seconds
         
     | 
| 
       16 
16 
     | 
    
         
             
                DEFAULT_WRITE_TIMEOUT = 60 # seconds
         
     | 
| 
      
 17 
     | 
    
         
            +
                DEFAULT_DEBUG_OUTPUT = File.open(File::NULL, "w")
         
     | 
| 
       17 
18 
     | 
    
         
             
                NETWORK_ERRORS = [
         
     | 
| 
       18 
19 
     | 
    
         
             
                  Errno::ECONNREFUSED,
         
     | 
| 
      
 20 
     | 
    
         
            +
                  Errno::ECONNRESET,
         
     | 
| 
       19 
21 
     | 
    
         
             
                  Net::OpenTimeout,
         
     | 
| 
       20 
     | 
    
         
            -
                  Net::ReadTimeout
         
     | 
| 
      
 22 
     | 
    
         
            +
                  Net::ReadTimeout,
         
     | 
| 
      
 23 
     | 
    
         
            +
                  OpenSSL::SSL::SSLError
         
     | 
| 
       21 
24 
     | 
    
         
             
                ].freeze
         
     | 
| 
       22 
25 
     | 
    
         | 
| 
       23 
     | 
    
         
            -
                 
     | 
| 
      
 26 
     | 
    
         
            +
                attr_accessor :open_timeout, :read_timeout, :write_timeout, :debug_output
         
     | 
| 
      
 27 
     | 
    
         
            +
                attr_reader :proxy_url, :proxy_uri
         
     | 
| 
       24 
28 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
                 
     | 
| 
       26 
     | 
    
         
            -
                 
     | 
| 
       27 
     | 
    
         
            -
                def_delegator  
     | 
| 
      
 29 
     | 
    
         
            +
                def_delegator :proxy_uri, :host, :proxy_host
         
     | 
| 
      
 30 
     | 
    
         
            +
                def_delegator :proxy_uri, :port, :proxy_port
         
     | 
| 
      
 31 
     | 
    
         
            +
                def_delegator :proxy_uri, :user, :proxy_user
         
     | 
| 
      
 32 
     | 
    
         
            +
                def_delegator :proxy_uri, :password, :proxy_pass
         
     | 
| 
       28 
33 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
                def initialize( 
     | 
| 
       30 
     | 
    
         
            -
                   
     | 
| 
       31 
     | 
    
         
            -
                  @ 
     | 
| 
       32 
     | 
    
         
            -
                   
     | 
| 
       33 
     | 
    
         
            -
                   
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
                    write_timeout: write_timeout,
         
     | 
| 
       37 
     | 
    
         
            -
                    debug_output: debug_output
         
     | 
| 
       38 
     | 
    
         
            -
                  )
         
     | 
| 
      
 34 
     | 
    
         
            +
                def initialize(open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT,
         
     | 
| 
      
 35 
     | 
    
         
            +
                  write_timeout: DEFAULT_WRITE_TIMEOUT, debug_output: DEFAULT_DEBUG_OUTPUT, proxy_url: nil)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  @open_timeout = open_timeout
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @read_timeout = read_timeout
         
     | 
| 
      
 38 
     | 
    
         
            +
                  @write_timeout = write_timeout
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @debug_output = debug_output
         
     | 
| 
      
 40 
     | 
    
         
            +
                  self.proxy_url = proxy_url unless proxy_url.nil?
         
     | 
| 
       39 
41 
     | 
    
         
             
                end
         
     | 
| 
       40 
42 
     | 
    
         | 
| 
       41 
     | 
    
         
            -
                def  
     | 
| 
       42 
     | 
    
         
            -
                   
     | 
| 
      
 43 
     | 
    
         
            +
                def perform(request:)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  host = request.uri.host || DEFAULT_HOST
         
     | 
| 
      
 45 
     | 
    
         
            +
                  port = request.uri.port || DEFAULT_PORT
         
     | 
| 
      
 46 
     | 
    
         
            +
                  http_client = build_http_client(host, port)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  http_client.use_ssl = request.uri.scheme.eql?("https")
         
     | 
| 
      
 48 
     | 
    
         
            +
                  http_client.request(request)
         
     | 
| 
       43 
49 
     | 
    
         
             
                rescue *NETWORK_ERRORS => e
         
     | 
| 
       44 
     | 
    
         
            -
                  raise NetworkError, "Network error: #{e 
     | 
| 
      
 50 
     | 
    
         
            +
                  raise NetworkError, "Network error: #{e}"
         
     | 
| 
       45 
51 
     | 
    
         
             
                end
         
     | 
| 
       46 
52 
     | 
    
         | 
| 
       47 
     | 
    
         
            -
                def  
     | 
| 
       48 
     | 
    
         
            -
                   
     | 
| 
       49 
     | 
    
         
            -
                   
     | 
| 
      
 53 
     | 
    
         
            +
                def proxy_url=(proxy_url)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  @proxy_url = proxy_url
         
     | 
| 
      
 55 
     | 
    
         
            +
                  proxy_uri = URI(proxy_url)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  raise ArgumentError, "Invalid proxy URL: #{proxy_uri}" unless proxy_uri.is_a?(URI::HTTP)
         
     | 
| 
       50 
57 
     | 
    
         | 
| 
       51 
     | 
    
         
            -
                  @ 
     | 
| 
       52 
     | 
    
         
            -
                  update_http_client_settings
         
     | 
| 
       53 
     | 
    
         
            -
                end
         
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
                def debug_output
         
     | 
| 
       56 
     | 
    
         
            -
                  @http_client.instance_variable_get(:@debug_output)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  @proxy_uri = proxy_uri
         
     | 
| 
       57 
59 
     | 
    
         
             
                end
         
     | 
| 
       58 
60 
     | 
    
         | 
| 
       59 
61 
     | 
    
         
             
                private
         
     | 
| 
       60 
62 
     | 
    
         | 
| 
       61 
     | 
    
         
            -
                def  
     | 
| 
       62 
     | 
    
         
            -
                   
     | 
| 
       63 
     | 
    
         
            -
             
     | 
| 
       64 
     | 
    
         
            -
                  @http_client.write_timeout = write_timeout
         
     | 
| 
       65 
     | 
    
         
            -
                  @http_client.set_debug_output(debug_output) if debug_output
         
     | 
| 
       66 
     | 
    
         
            -
                end
         
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
       68 
     | 
    
         
            -
                def current_http_client_settings
         
     | 
| 
       69 
     | 
    
         
            -
                  {
         
     | 
| 
       70 
     | 
    
         
            -
                    open_timeout: @http_client.open_timeout,
         
     | 
| 
       71 
     | 
    
         
            -
                    read_timeout: @http_client.read_timeout,
         
     | 
| 
       72 
     | 
    
         
            -
                    write_timeout: @http_client.write_timeout,
         
     | 
| 
       73 
     | 
    
         
            -
                    debug_output: debug_output
         
     | 
| 
       74 
     | 
    
         
            -
                  }
         
     | 
| 
       75 
     | 
    
         
            -
                end
         
     | 
| 
       76 
     | 
    
         
            -
             
     | 
| 
       77 
     | 
    
         
            -
                def update_http_client_settings
         
     | 
| 
       78 
     | 
    
         
            -
                  conditionally_apply_http_client_settings do
         
     | 
| 
       79 
     | 
    
         
            -
                    host = @base_uri.host || DEFAULT_HOST
         
     | 
| 
       80 
     | 
    
         
            -
                    port = @base_uri.port || DEFAULT_PORT
         
     | 
| 
       81 
     | 
    
         
            -
                    @http_client = build_http_client(host: host, port: port)
         
     | 
| 
       82 
     | 
    
         
            -
                    @http_client.use_ssl = @base_uri.scheme == "https"
         
     | 
| 
       83 
     | 
    
         
            -
                  end
         
     | 
| 
       84 
     | 
    
         
            -
                end
         
     | 
| 
       85 
     | 
    
         
            -
             
     | 
| 
       86 
     | 
    
         
            -
                def build_http_client(host:, port:)
         
     | 
| 
       87 
     | 
    
         
            -
                  if @proxy_uri.nil?
         
     | 
| 
       88 
     | 
    
         
            -
                    Net::HTTP.new(host, port)
         
     | 
| 
      
 63 
     | 
    
         
            +
                def build_http_client(host = DEFAULT_HOST, port = DEFAULT_PORT)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  http_client = if proxy_uri
         
     | 
| 
      
 65 
     | 
    
         
            +
                    Net::HTTP.new(host, port, proxy_host, proxy_port, proxy_user, proxy_pass)
         
     | 
| 
       89 
66 
     | 
    
         
             
                  else
         
     | 
| 
       90 
     | 
    
         
            -
                    Net::HTTP.new(host, port 
     | 
| 
      
 67 
     | 
    
         
            +
                    Net::HTTP.new(host, port)
         
     | 
| 
       91 
68 
     | 
    
         
             
                  end
         
     | 
| 
      
 69 
     | 
    
         
            +
                  configure_http_client(http_client)
         
     | 
| 
       92 
70 
     | 
    
         
             
                end
         
     | 
| 
       93 
71 
     | 
    
         | 
| 
       94 
     | 
    
         
            -
                def  
     | 
| 
       95 
     | 
    
         
            -
                   
     | 
| 
       96 
     | 
    
         
            -
                     
     | 
| 
       97 
     | 
    
         
            -
                     
     | 
| 
       98 
     | 
    
         
            -
                     
     | 
| 
       99 
     | 
    
         
            -
             
     | 
| 
       100 
     | 
    
         
            -
                    yield
         
     | 
| 
      
 72 
     | 
    
         
            +
                def configure_http_client(http_client)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  http_client.tap do |c|
         
     | 
| 
      
 74 
     | 
    
         
            +
                    c.open_timeout = open_timeout
         
     | 
| 
      
 75 
     | 
    
         
            +
                    c.read_timeout = read_timeout
         
     | 
| 
      
 76 
     | 
    
         
            +
                    c.write_timeout = write_timeout
         
     | 
| 
      
 77 
     | 
    
         
            +
                    c.set_debug_output(debug_output)
         
     | 
| 
       101 
78 
     | 
    
         
             
                  end
         
     | 
| 
       102 
79 
     | 
    
         
             
                end
         
     | 
| 
       103 
80 
     | 
    
         
             
              end
         
     | 
    
        data/lib/x/errors/error.rb
    CHANGED
    
    | 
         @@ -1,15 +1,7 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            require "json"
         
     | 
| 
       2 
     | 
    
         
            -
            require "net/http"
         
     | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
1 
     | 
    
         
             
            module X
         
     | 
| 
       5 
2 
     | 
    
         
             
              # Base error class
         
     | 
| 
       6 
3 
     | 
    
         
             
              class Error < ::StandardError
         
     | 
| 
       7 
     | 
    
         
            -
                 
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
                attr_reader :object
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
                def initialize(msg, response:)
         
     | 
| 
       12 
     | 
    
         
            -
                  @object = JSON.parse(response.body || "{}") if JSON_CONTENT_TYPE_REGEXP.match?(response["content-type"])
         
     | 
| 
      
 4 
     | 
    
         
            +
                def initialize(msg, _response = nil)
         
     | 
| 
       13 
5 
     | 
    
         
             
                  super(msg)
         
     | 
| 
       14 
6 
     | 
    
         
             
                end
         
     | 
| 
       15 
7 
     | 
    
         
             
              end
         
     | 
| 
         @@ -0,0 +1,29 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative "client_error"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module X
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Rate limit error
         
     | 
| 
      
 5 
     | 
    
         
            +
              class TooManyRequests < ClientError
         
     | 
| 
      
 6 
     | 
    
         
            +
                def initialize(msg, response)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @response = response
         
     | 
| 
      
 8 
     | 
    
         
            +
                  super(msg)
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def limit
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @response["x-rate-limit-limit"].to_i
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def remaining
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @response["x-rate-limit-remaining"].to_i
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def reset_at
         
     | 
| 
      
 20 
     | 
    
         
            +
                  Time.at(@response["x-rate-limit-reset"].to_i)
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def reset_in
         
     | 
| 
      
 24 
     | 
    
         
            +
                  [(reset_at - Time.now).ceil, 0].max
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                alias_method :retry_after, :reset_in
         
     | 
| 
      
 28 
     | 
    
         
            +
              end
         
     | 
| 
      
 29 
     | 
    
         
            +
            end
         
     |