x 0.19.0 → 1.0.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +9 -0
  3. data/CHANGELOG.md +648 -15
  4. data/README.md +506 -73
  5. data/SECURITY.md +66 -0
  6. data/UPGRADING.md +670 -0
  7. data/lib/x/version.rb +16 -1
  8. data/lib/x.rb +13 -1
  9. data/sig/manifest.yaml +5 -0
  10. data/sig/x.rbs +5 -361
  11. metadata +57 -49
  12. data/bin/console +0 -10
  13. data/bin/setup +0 -6
  14. data/lib/x/account_uploader.rb +0 -168
  15. data/lib/x/authenticator.rb +0 -21
  16. data/lib/x/bearer_token_authenticator.rb +0 -36
  17. data/lib/x/client.rb +0 -191
  18. data/lib/x/client_credentials.rb +0 -208
  19. data/lib/x/connection.rb +0 -183
  20. data/lib/x/errors/bad_gateway.rb +0 -6
  21. data/lib/x/errors/bad_request.rb +0 -6
  22. data/lib/x/errors/client_error.rb +0 -6
  23. data/lib/x/errors/connection_exception.rb +0 -6
  24. data/lib/x/errors/error.rb +0 -4
  25. data/lib/x/errors/forbidden.rb +0 -6
  26. data/lib/x/errors/gateway_timeout.rb +0 -6
  27. data/lib/x/errors/gone.rb +0 -6
  28. data/lib/x/errors/http_error.rb +0 -84
  29. data/lib/x/errors/internal_server_error.rb +0 -6
  30. data/lib/x/errors/invalid_media_type.rb +0 -6
  31. data/lib/x/errors/network_error.rb +0 -6
  32. data/lib/x/errors/not_acceptable.rb +0 -6
  33. data/lib/x/errors/not_found.rb +0 -6
  34. data/lib/x/errors/payload_too_large.rb +0 -6
  35. data/lib/x/errors/server_error.rb +0 -6
  36. data/lib/x/errors/service_unavailable.rb +0 -6
  37. data/lib/x/errors/too_many_redirects.rb +0 -6
  38. data/lib/x/errors/too_many_requests.rb +0 -58
  39. data/lib/x/errors/unauthorized.rb +0 -6
  40. data/lib/x/errors/unprocessable_entity.rb +0 -6
  41. data/lib/x/media_upload_validator.rb +0 -36
  42. data/lib/x/media_uploader.rb +0 -234
  43. data/lib/x/oauth2_authenticator.rb +0 -169
  44. data/lib/x/oauth_authenticator.rb +0 -182
  45. data/lib/x/rate_limit.rb +0 -89
  46. data/lib/x/redirect_handler.rb +0 -109
  47. data/lib/x/request_builder.rb +0 -93
  48. data/lib/x/response_parser.rb +0 -81
  49. data/lib/x/stream_parser.rb +0 -75
@@ -1,109 +0,0 @@
1
- require "net/http"
2
- require "uri"
3
- require_relative "authenticator"
4
- require_relative "connection"
5
- require_relative "errors/too_many_redirects"
6
- require_relative "request_builder"
7
-
8
- module X
9
- # Handles HTTP redirects for API requests
10
- # @api public
11
- class RedirectHandler
12
- # Default maximum number of redirects to follow
13
- DEFAULT_MAX_REDIRECTS = 10
14
-
15
- # The maximum number of redirects to follow
16
- # @api public
17
- # @return [Integer] the maximum number of redirects to follow
18
- # @example Get or set the maximum redirects
19
- # handler.max_redirects = 5
20
- attr_accessor :max_redirects
21
-
22
- # The connection for making requests
23
- # @api public
24
- # @return [Connection] the connection for making requests
25
- # @example Get the connection
26
- # handler.connection
27
- attr_reader :connection
28
-
29
- # The request builder for creating requests
30
- # @api public
31
- # @return [RequestBuilder] the request builder for creating requests
32
- # @example Get the request builder
33
- # handler.request_builder
34
- attr_reader :request_builder
35
-
36
- # Initialize a new RedirectHandler
37
- #
38
- # @api public
39
- # @param connection [Connection] the connection for making requests
40
- # @param request_builder [RequestBuilder] the request builder for creating requests
41
- # @param max_redirects [Integer] the maximum number of redirects to follow
42
- # @return [RedirectHandler] a new instance
43
- # @example Create a redirect handler
44
- # handler = X::RedirectHandler.new(connection: conn, request_builder: builder)
45
- def initialize(connection: Connection.new, request_builder: RequestBuilder.new,
46
- max_redirects: DEFAULT_MAX_REDIRECTS)
47
- @connection = connection
48
- @request_builder = request_builder
49
- @max_redirects = max_redirects
50
- end
51
-
52
- # Handle redirects for an HTTP response
53
- #
54
- # @api public
55
- # @param response [Net::HTTPResponse] the HTTP response to handle
56
- # @param request [Net::HTTPRequest] the original HTTP request
57
- # @param base_url [String] the base URL for the request
58
- # @param authenticator [Authenticator] the authenticator for requests
59
- # @param redirect_count [Integer] the current redirect count
60
- # @return [Net::HTTPResponse] the final HTTP response after following redirects
61
- # @raise [TooManyRedirects] if the maximum number of redirects is exceeded
62
- # @example Handle a response
63
- # response = handler.handle(response: resp, request: req, base_url: url)
64
- def handle(response:, request:, base_url:, authenticator: Authenticator.new, redirect_count: 0)
65
- if response.is_a?(Net::HTTPRedirection)
66
- raise TooManyRedirects, "Too many redirects" if redirect_count > max_redirects
67
-
68
- new_uri = build_new_uri(response, base_url)
69
-
70
- new_request = build_request(request, new_uri, Integer(response.code), authenticator)
71
- new_response = connection.perform(request: new_request)
72
-
73
- handle(response: new_response, request: new_request, base_url:, redirect_count: redirect_count + 1)
74
- else
75
- response
76
- end
77
- end
78
-
79
- private
80
-
81
- # Build a new URI from the redirect response
82
- # @api private
83
- # @param response [Net::HTTPResponse] the redirect response
84
- # @param base_url [String] the base URL
85
- # @return [URI] the new URI
86
- def build_new_uri(response, base_url)
87
- location = response.fetch("location")
88
- # If location is relative, it will join with the original base URL, otherwise it will overwrite it
89
- URI.join(base_url, location)
90
- end
91
-
92
- # Build a new request for the redirect
93
- # @api private
94
- # @param request [Net::HTTPRequest] the original request
95
- # @param uri [URI] the new URI
96
- # @param response_code [Integer] the HTTP response code
97
- # @param authenticator [Authenticator] the authenticator
98
- # @return [Net::HTTPRequest] the new request
99
- def build_request(request, uri, response_code, authenticator)
100
- http_method = :get
101
- if [307, 308].include?(response_code)
102
- http_method = request.method.downcase.to_sym
103
- body = request.body
104
- end
105
-
106
- request_builder.build(http_method:, uri:, body:, authenticator:)
107
- end
108
- end
109
- end
@@ -1,93 +0,0 @@
1
- require "net/http"
2
- require "uri"
3
- require_relative "authenticator"
4
- require_relative "version"
5
-
6
- module X
7
- # Builds HTTP requests for the X API
8
- # @api public
9
- class RequestBuilder
10
- # Default headers for API requests
11
- DEFAULT_HEADERS = {
12
- "Content-Type" => "application/json; charset=utf-8",
13
- "User-Agent" => "X-Client/#{VERSION} #{RUBY_ENGINE}/#{RUBY_VERSION} (#{RUBY_PLATFORM})"
14
- }.freeze
15
- # Mapping of HTTP method symbols to Net::HTTP classes
16
- HTTP_METHODS = {
17
- get: Net::HTTP::Get,
18
- post: Net::HTTP::Post,
19
- put: Net::HTTP::Put,
20
- delete: Net::HTTP::Delete
21
- }.freeze
22
-
23
- # Build an HTTP request
24
- #
25
- # @api public
26
- # @param http_method [Symbol] the HTTP method (:get, :post, :put, :delete)
27
- # @param uri [URI] the request URI
28
- # @param body [String, nil] the request body
29
- # @param headers [Hash] additional headers for the request
30
- # @param authenticator [Authenticator] the authenticator for the request
31
- # @return [Net::HTTPRequest] the built HTTP request
32
- # @raise [ArgumentError] if the HTTP method is not supported
33
- # @example Build a GET request
34
- # builder.build(http_method: :get, uri: URI("https://api.x.com/2/users/me"))
35
- def build(http_method:, uri:, body: nil, headers: {}, authenticator: Authenticator.new)
36
- request = create_request(http_method:, uri:, body:)
37
- add_headers(request:, headers:)
38
- add_authentication(request:, authenticator:)
39
- request
40
- end
41
-
42
- private
43
-
44
- # Create an HTTP request
45
- # @api private
46
- # @param http_method [Symbol] the HTTP method
47
- # @param uri [URI] the request URI
48
- # @param body [String, nil] the request body
49
- # @return [Net::HTTPRequest] the created request
50
- def create_request(http_method:, uri:, body:)
51
- http_method_class = HTTP_METHODS[http_method]
52
-
53
- raise ArgumentError, "Unsupported HTTP method: #{http_method}" unless http_method_class
54
-
55
- escaped_uri = escape_query_params(uri)
56
- request = http_method_class.new(escaped_uri)
57
- request.body = body
58
- request
59
- end
60
-
61
- # Add authentication to a request
62
- # @api private
63
- # @param request [Net::HTTPRequest] the request
64
- # @param authenticator [Authenticator] the authenticator
65
- # @return [void]
66
- def add_authentication(request:, authenticator:)
67
- authenticator.header(request).each do |key, value|
68
- request[key] = value
69
- end
70
- end
71
-
72
- # Add headers to a request
73
- # @api private
74
- # @param request [Net::HTTPRequest] the request
75
- # @param headers [Hash] additional headers
76
- # @return [void]
77
- def add_headers(request:, headers:)
78
- DEFAULT_HEADERS.merge(headers).each do |key, value|
79
- request[key] = value
80
- end
81
- end
82
-
83
- # Escape query parameters in a URI
84
- # @api private
85
- # @param uri [URI] the URI
86
- # @return [URI] the URI with escaped query parameters
87
- def escape_query_params(uri)
88
- URI(uri).tap do |u|
89
- u.query = URI.encode_www_form(URI.decode_www_form(u.query)).gsub("%2C", ",") if u.query
90
- end
91
- end
92
- end
93
- end
@@ -1,81 +0,0 @@
1
- require "json"
2
- require "net/http"
3
- require_relative "errors/bad_gateway"
4
- require_relative "errors/bad_request"
5
- require_relative "errors/connection_exception"
6
- require_relative "errors/http_error"
7
- require_relative "errors/forbidden"
8
- require_relative "errors/gateway_timeout"
9
- require_relative "errors/gone"
10
- require_relative "errors/internal_server_error"
11
- require_relative "errors/not_acceptable"
12
- require_relative "errors/not_found"
13
- require_relative "errors/payload_too_large"
14
- require_relative "errors/service_unavailable"
15
- require_relative "errors/too_many_requests"
16
- require_relative "errors/unauthorized"
17
- require_relative "errors/unprocessable_entity"
18
-
19
- module X
20
- # Parses HTTP responses from the X API
21
- # @api public
22
- class ResponseParser
23
- # Mapping of HTTP status codes to error classes
24
- ERROR_MAP = {
25
- 400 => BadRequest,
26
- 401 => Unauthorized,
27
- 403 => Forbidden,
28
- 404 => NotFound,
29
- 406 => NotAcceptable,
30
- 409 => ConnectionException,
31
- 410 => Gone,
32
- 413 => PayloadTooLarge,
33
- 422 => UnprocessableEntity,
34
- 429 => TooManyRequests,
35
- 500 => InternalServerError,
36
- 502 => BadGateway,
37
- 503 => ServiceUnavailable,
38
- 504 => GatewayTimeout
39
- }.freeze
40
-
41
- # Parse an HTTP response
42
- #
43
- # @api public
44
- # @param response [Net::HTTPResponse] the HTTP response to parse
45
- # @param array_class [Class, nil] the class for parsing JSON arrays
46
- # @param object_class [Class, nil] the class for parsing JSON objects
47
- # @return [Hash, Array, nil] the parsed response body
48
- # @raise [HTTPError] if the response is not successful
49
- # @example Parse a response
50
- # parser.parse(response: response)
51
- def parse(response:, array_class: nil, object_class: nil)
52
- raise error(response) unless response.is_a?(Net::HTTPSuccess)
53
-
54
- return if response.instance_of?(Net::HTTPNoContent)
55
-
56
- begin
57
- JSON.parse(response.body, array_class:, object_class:)
58
- rescue JSON::ParserError
59
- nil
60
- end
61
- end
62
-
63
- private
64
-
65
- # Create an error from a response
66
- # @api private
67
- # @param response [Net::HTTPResponse] the HTTP response
68
- # @return [HTTPError] the error
69
- def error(response)
70
- error_class(response).new(response:)
71
- end
72
-
73
- # Get the error class for a response
74
- # @api private
75
- # @param response [Net::HTTPResponse] the HTTP response
76
- # @return [Class] the error class
77
- def error_class(response)
78
- ERROR_MAP[Integer(response.code)] || HTTPError
79
- end
80
- end
81
- end
@@ -1,75 +0,0 @@
1
- require "json"
2
- require "net/http"
3
- require_relative "response_parser"
4
-
5
- module X
6
- # Handles streaming responses from the X API
7
- # @api public
8
- class StreamParser
9
- # Line delimiter for streaming responses
10
- LINE_DELIMITER = "\r\n".freeze
11
-
12
- # Process a streaming response and yield parsed JSON objects
13
- #
14
- # @api public
15
- # @param response [Net::HTTPResponse] the HTTP response to stream
16
- # @param response_parser [ResponseParser] the response parser for error handling
17
- # @param array_class [Class, nil] the class for parsing JSON arrays
18
- # @param object_class [Class, nil] the class for parsing JSON objects
19
- # @yield [Hash, Array] each parsed JSON object from the stream
20
- # @return [void]
21
- # @raise [HTTPError] if the response is not successful
22
- # @example Process a streaming response
23
- # handler.process(response: response, response_parser: parser) { |json| puts json }
24
- def process(response:, response_parser:, array_class: nil, object_class: nil, &block)
25
- response_parser.parse(response:) unless response.is_a?(Net::HTTPSuccess)
26
-
27
- buffer = +""
28
- response.read_body do |chunk|
29
- buffer << chunk
30
- process_buffer(buffer:, array_class:, object_class:, &block)
31
- end
32
- process_remaining(buffer:, array_class:, object_class:, &block)
33
- end
34
-
35
- private
36
-
37
- # Process complete lines from the buffer
38
- # @api private
39
- # @param buffer [String] the accumulated data buffer
40
- # @param array_class [Class, nil] the class for parsing JSON arrays
41
- # @param object_class [Class, nil] the class for parsing JSON objects
42
- # @yield [Hash, Array] each parsed JSON object
43
- # @return [void]
44
- def process_buffer(buffer:, array_class:, object_class:, &)
45
- while (line_end = buffer.index(LINE_DELIMITER))
46
- line = buffer.slice!(0, line_end) # : String
47
- buffer.delete_prefix!(LINE_DELIMITER)
48
- yield_json(line:, array_class:, object_class:, &) unless line.empty?
49
- end
50
- end
51
-
52
- # Process any remaining data after the stream ends
53
- # @api private
54
- # @param buffer [String] the remaining data buffer
55
- # @param array_class [Class, nil] the class for parsing JSON arrays
56
- # @param object_class [Class, nil] the class for parsing JSON objects
57
- # @yield [Hash, Array] the parsed JSON object
58
- # @return [void]
59
- def process_remaining(buffer:, array_class:, object_class:, &)
60
- buffer.strip!
61
- yield_json(line: buffer, array_class:, object_class:, &) unless buffer.empty?
62
- end
63
-
64
- # Parse a line as JSON and yield the result
65
- # @api private
66
- # @param line [String] the JSON line to parse
67
- # @param array_class [Class, nil] the class for parsing JSON arrays
68
- # @param object_class [Class, nil] the class for parsing JSON objects
69
- # @yield [Hash, Array] the parsed JSON object
70
- # @return [void]
71
- def yield_json(line:, array_class:, object_class:)
72
- yield JSON.parse(line, array_class:, object_class:)
73
- end
74
- end
75
- end