Class: X::Response

Inherits:
Object
  • Object
show all
Includes:
ResponseHeaders
Defined in:
x-core/lib/x/core/response.rb

Overview

A summary of one API response, or one object of a stream, which a client passes to its on_response hook

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(http_method:, uri:, http_response: nil, status: nil, headers: nil, body: nil) ⇒ Response

Summarize a response

Public, so that an on_response hook can be tested with a summary built from the status, headers, and body of a response, or from a Net::HTTP response, as the client builds one for each response it reads.

Examples:

Summarize a response

X::Response.new(http_method: :get, uri: URI("https://api.x.com/2/users/me"), status: 200,
  headers: {"x-rate-limit-remaining" => "74"}, body: %({"data":{"id":"1"}}))

Summarize a Net::HTTP response

X::Response.new(http_response:, http_method: :get, uri: URI("https://api.x.com/2/users/me"))

Parameters:

  • http_method (Symbol, String) —

    the HTTP method of the request, in any case, which is read as a lowercase Symbol, as an error reads it

  • uri (URI::Generic) —

    the URI of the request

  • http_response (Net::HTTPResponse, nil) (defaults to: nil) —

    the HTTP response, or nil for one built of the status, headers, and body

  • status (Integer, nil) (defaults to: nil) —

    the status of the response, from 100 to 599, when it is not given

  • headers (Hash{String => String}, nil) (defaults to: nil) —

    the headers of the response, when it is not given

  • body (String, nil) (defaults to: nil) —

    the part of the body summarized, such as one object of a stream, or nil for all of it, which is the body of a response built of the status

Raises:

  • (ArgumentError) —

    if the HTTP response is given beside a status or headers, or neither it nor a status is given, or the status is not from 100 to 599, or the headers are not a Hash of names to values



74
75
76
77
78
79
# File 'x-core/lib/x/core/response.rb', line 74

def initialize(http_method:, uri:, http_response: nil, status: nil, headers: nil, body: nil)
  @http_method = http_method.downcase.to_sym
  @uri = uri
  @http_response = BuiltResponse.of(http_response, status:, headers:, body: (body if http_response.nil?))
  @body = body
end

Instance Attribute Details

#http_method ⇒ Symbol (readonly)

The HTTP method of the request

Examples:

Get the HTTP method

response.http_method # => :get

Returns:

  • (Symbol) —

    the HTTP method



30
31
32
# File 'x-core/lib/x/core/response.rb', line 30

def http_method
  @http_method
end

#http_response ⇒ Net::HTTPResponse (readonly)

The response itself, as the client received it

It is an escape hatch, for what a summary does not read: the status is #status, the headers are #headers, and the body is #body. It is the Net::HTTP response the client sent the request with, or the one built of the status, headers, and body the summary was given.

Examples:

Read the reason phrase of the status line

response.http_response.message # => "OK"

Returns:

  • (Net::HTTPResponse) —

    the HTTP response



49
50
51
# File 'x-core/lib/x/core/response.rb', line 49

def http_response
  @http_response
end

#uri ⇒ URI::Generic (readonly)

The URI of the request

Examples:

Get the path of the request

response.uri.path # => "/2/users/me"

Returns:

  • (URI::Generic) —

    the request URI



37
38
39
# File 'x-core/lib/x/core/response.rb', line 37

def uri
  @uri
end

Instance Method Details

#body ⇒ String?

The body summarized: one streamed object, or else the whole body

It is tagged UTF-8, the encoding of the JSON the API sends. A body that is not valid UTF-8 keeps its bytes, so valid_encoding? tells it apart, and scrub replaces what is not UTF-8.

Examples:

Log the body

logger.debug(response.body)

Returns:

  • (String, nil) —

    the body, tagged UTF-8



90
# File 'x-core/lib/x/core/response.rb', line 90

def body = @body || http_response.body

#headers ⇒ Hash{String => String}

The headers of the response

The names are lowercase, and a field the API sent more than once is joined with a comma.

Examples:

Read how long the API took to answer

response.headers["x-response-time"]

Returns:

  • (Hash{String => String}) —

    the headers, frozen



# File 'x-core/lib/x/core/response.rb', line 16

#rate_limit ⇒ RateLimit?

The 15-minute rate limit of the endpoint, which nearly every response reports

Examples:

Slow down near the limit

sleep response.rate_limit.reset_in if response.rate_limit&.remaining&.zero?

Returns:

  • (RateLimit, nil) —

    the rate limit, or nil if the response reports none



122
# File 'x-core/lib/x/core/response.rb', line 122

def rate_limit = rate_limits.find { |limit| limit.type.eql?(RateLimit::RATE_LIMIT_TYPE) }

#rate_limits ⇒ Array<RateLimit>

The rate limits the response reports in its headers

Examples:

Print how many requests remain in each window

response.rate_limits.each { |limit| puts "#{limit.type}: #{limit.remaining}" }

Returns:

  • (Array<RateLimit>) —

    the 15-minute limit, and the 24-hour app and user limits when reported



114
# File 'x-core/lib/x/core/response.rb', line 114

def rate_limits = RateLimit.__send__(:all_from, http_response)

#resource_count ⇒ Integer

The number of resources the body holds, in data and includes together

Examples:

Total the resources a client has read

total += response.resource_count

Returns:

  • (Integer) —

    the resource count



148
# File 'x-core/lib/x/core/response.rb', line 148

def resource_count = resource_counts.values.sum

#resource_counts ⇒ Hash{String => Integer}

The number of resources the body holds, as data and as each kind of include

The API bills reads by the resource, so these counts are the units a request consumed. The body is parsed once, however many times a summary is asked what it holds. A body whose includes is not an object holds no includes to count.

Examples:

Count the users a lookup returned

response.resource_counts # => {"data" => 1, "posts" => 1}

Returns:

  • (Hash{String => Integer}) —

    the count of data and of each include, such as users and posts



134
135
136
137
138
139
140
# File 'x-core/lib/x/core/response.rb', line 134

def resource_counts
  body = parsed_body
  data = body["data"]
  counts = {"data" => Array.try_convert(data)&.size || [data].compact.size}
  Hash.try_convert(body["includes"])&.each { |key, resources| counts[key] = Array(resources).size }
  counts
end

#status ⇒ Integer

The HTTP status code

Examples:

Get the status code

response.status # => 200

Returns:

  • (Integer) —

    the status code



98
# File 'x-core/lib/x/core/response.rb', line 98

def status = Integer(http_response.code)

#success? ⇒ Boolean

Check whether the request succeeded

Examples:

Count the failed requests

failures += 1 unless response.success?

Returns:

  • (Boolean) —

    true for a 2xx status



106
# File 'x-core/lib/x/core/response.rb', line 106

def success? = http_response.is_a?(Net::HTTPSuccess)