Class: X::Problem

Inherits:
Object
  • Object
show all
Defined in:
x-core/lib/x/core/problem.rb

Overview

A problem the API described, in a response that failed or in one that otherwise succeeded

The API describes what went wrong the same way whether it refused the request, which raises an HTTPError whose HTTPError#problem is one of these, or answered it with the resources it could and named the rest as errors, which the object layer reads as the problems of a resource or a page. Code that acts on the reason rather than logging it reads the same object either way.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attrs) ⇒ Problem

Initialize a problem from the attributes the API reported

Examples:

Build a problem

X::Problem.new({"title" => "Not Found Error"})

Parameters:

  • attrs (Hash{String => Object}) —

    the attributes



59
60
61
62
# File 'x-core/lib/x/core/problem.rb', line 59

def initialize(attrs)
  @attrs = deep_freeze(attrs)
  freeze
end

Instance Attribute Details

#attrs ⇒ Hash{String => Object} (readonly) Also known as: to_h

The raw attributes of the problem

Examples:

Get the raw attributes

problem.attrs # => {"title" => "Not Found Error", "detail" => "Could not find tweet with pinned_tweet_id: [1].", ...}

Returns:

  • (Hash{String => Object}) —

    the attributes



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

def attrs
  @attrs
end

Class Method Details

.all_from(body) ⇒ Array<Problem>

The problems a response body reports

Examples:

Read the problems of a response

X::Problem.all_from(client.get("users/me"))

Parameters:

  • body (Hash, nil) —

    the parsed response body

Returns:

  • (Array<Problem>) —

    the problems, empty if there are none



47
48
49
50
# File 'x-core/lib/x/core/problem.rb', line 47

def self.all_from(body)
  entries = Array(body.to_h["errors"]) #: Array[untyped]
  entries.filter_map { |attrs| Hash.try_convert(attrs)&.then { |hash| new(hash) } }.freeze
end

Instance Method Details

#==(other) ⇒ Boolean Also known as: eql?

Check whether another problem is the same problem

Examples:

Check whether a response reported a problem before

seen.include?(problem)

Parameters:

  • other (Object) —

    the other problem

Returns:

  • (Boolean) —

    true if the other problem is a Problem of the same attributes



195
# File 'x-core/lib/x/core/problem.rb', line 195

def ==(other) = other.instance_of?(self.class) && attrs.eql?(other.attrs)

#about?(resource) ⇒ Boolean

Check whether the problem is about a resource

The resource_id of a problem is the String the API gave, where the resources of x-objects hold an Integer identifier, so the identifier of the resource, or the identifier given, is compared with it as a String. A username names the user a problem names by it. A problem that names no resource is about none.

Examples:

Check whether a problem is about a user

problem.about?(user) # => true

Check whether a problem is about a post by its identifier

error.problems.any? { |problem| problem.about?(1_234_567_890) }

Parameters:

  • resource (#id, Integer, String) —

    the resource, or its identifier

Returns:

  • (Boolean) —

    true if the resource_id of the problem names the resource



180
181
182
183
184
185
186
# File 'x-core/lib/x/core/problem.rb', line 180

def about?(resource)
  id = case resource
  when Integer, String then resource
  else resource.id
  end
  resource_id.eql?(id.to_s)
end

#as_json ⇒ Hash{String => Object}

The attributes, as a JSON encoder and ActiveSupport read them

ActiveSupport's Object#as_json would otherwise read the instance variables, which is the same Hash under another name.

Examples:

Serialize a problem

problem.as_json # => {"title" => "Not Found Error"}

Returns:

  • (Hash{String => Object}) —

    the attributes



215
# File 'x-core/lib/x/core/problem.rb', line 215

def as_json(*) = attrs

#detail ⇒ String?

The description of this occurrence of the problem

Examples:

Get the detail

problem.detail # => "Could not find tweet with pinned_tweet_id: [1]."

Returns:

  • (String, nil) —

    the detail



78
# File 'x-core/lib/x/core/problem.rb', line 78

def detail = attrs["detail"]

#disconnect? ⇒ Boolean

Check whether the problem is an operational-disconnect of a stream

X sends one before it closes a stream for its own reasons. A stream reconnects after a line that holds such problems alone, as it does after a connection that dropped.

Examples:

Tell a disconnect apart from another error of a stream

error.problems.all?(&:disconnect?)

Returns:

  • (Boolean) —

    true for an operational-disconnect problem



153
# File 'x-core/lib/x/core/problem.rb', line 153

def disconnect? = type.to_s.end_with?("/operational-disconnect")

#encode_with(coder) ⇒ void

This method returns an undefined value.

Write the state Marshal writes as YAML

YAML would write the instance variables of the problem, and read them back into one that is not frozen, so it says how it is written: each part of the state Marshal writes, under its name.

Examples:

Write a problem as YAML

YAML.dump(problem)

Parameters:

  • coder (Psych::Coder) —

    the coder YAML writes the problem with



275
# File 'x-core/lib/x/core/problem.rb', line 275

def encode_with(coder) = YAML_KEYS.zip(marshal_dump) { |key, value| coder[key] = value }

#hash ⇒ Integer

The hash of the problem, which equal problems share

Examples:

Count the distinct problems

problems.uniq.size

Returns:

  • (Integer) —

    the hash



204
# File 'x-core/lib/x/core/problem.rb', line 204

def hash = [self.class, attrs].hash

#init_with(coder) ⇒ void

This method returns an undefined value.

Restore a problem YAML read, frozen, as Marshal restores one

Examples:

Read a problem written as YAML

YAML.unsafe_load(YAML.dump(problem)).title

Parameters:

  • coder (Psych::Coder) —

    the coder YAML read the problem with

Raises:



285
# File 'x-core/lib/x/core/problem.rb', line 285

def init_with(coder) = marshal_load(coder.map.values_at(*YAML_KEYS))

#inspect ⇒ String

Summarize the problem for the console

A problem the API described in a response that succeeded carries a detail, and one it named among the errors of a request it refused carries a message in its place, so the summary reads whichever of the two it holds.

Examples:

Inspect a problem of a response that succeeded

problem.inspect # => #<X::Problem Not Found Error: Could not find tweet with pinned_tweet_id: [1].>

Inspect a problem of a request the API refused

problem.inspect # => #<X::Problem Could not authenticate you>

Returns:

  • (String) —

    the class name, title, and detail, or message



237
# File 'x-core/lib/x/core/problem.rb', line 237

def inspect = "#<#{self.class} #{[title, detail || message].compact.join(": ")}>"

#marshal_dump ⇒ Array(Integer, Hash{String => Object})

The state Marshal writes

What is written is plain data, led by the number of its format, so that a problem written by one release of 1.x is read by a later one: its attributes, as the API described it.

Examples:

Cache the problems of a response

Rails.cache.write("problems", X::Problem.all_from(body))

Returns:

  • (Array(Integer, Hash{String => Object})) —

    the number of the format, then the attributes



248
# File 'x-core/lib/x/core/problem.rb', line 248

def marshal_dump = [MARSHAL_FORMAT, attrs]

#marshal_load(state) ⇒ void

This method returns an undefined value.

Restore a problem Marshal read, frozen as the problem that was written was

Examples:

Read cached problems

Marshal.load(Marshal.dump(problem)).title

Parameters:

  • state (Array) —

    the state Marshal wrote

Raises:



258
259
260
261
262
263
# File 'x-core/lib/x/core/problem.rb', line 258

def marshal_load(state)
  format, attrs = state
  raise UnsupportedMarshalFormat, "#{self.class} reads format #{MARSHAL_FORMAT} of Marshal, not #{format.inspect}" unless MARSHAL_FORMAT.eql?(format)

  initialize(attrs)
end

#message ⇒ String?

The message the API gave for a request it refused

The errors of a request the API refused carry a message where the problems of a response that succeeded carry a detail, so a problem read from a failed request reads as one of either.

Examples:

Get the message

problem.message # => "Could not authenticate you"

Returns:

  • (String, nil) —

    the message



135
# File 'x-core/lib/x/core/problem.rb', line 135

def message = attrs["message"]

#not_found? ⇒ Boolean

Check whether the problem is a resource that was not found

Examples:

Skip the posts that no longer exist

problems.reject(&:not_found?)

Returns:

  • (Boolean) —

    true for a resource-not-found problem



143
# File 'x-core/lib/x/core/problem.rb', line 143

def not_found? = type.to_s.end_with?("/resource-not-found")

#parameter ⇒ String?

The request parameter the problem concerns

Examples:

Get the parameter

problem.parameter # => "pinned_tweet_id"

Returns:

  • (String, nil) —

    the parameter, such as ids or pinned_tweet_id



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

def parameter = attrs["parameter"]

#resource_id ⇒ String?

The identifier of the resource the problem concerns

It is the String the API gave, whatever the kind of resource, since the API names a user by a username as often as by an identifier, and a space, a place, or media by an identifier that is not a number; #about? compares it with a resource, or the identifier of one, as a String.

Examples:

Get the resource identifier

problem.resource_id # => "1"

Returns:

  • (String, nil) —

    the resource identifier



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

def resource_id = attrs["resource_id"]

#resource_type ⇒ String?

The kind of resource the problem concerns

Examples:

Get the resource type

problem.resource_type # => "tweet"

Returns:

  • (String, nil) —

    the resource type, such as tweet or user



94
# File 'x-core/lib/x/core/problem.rb', line 94

def resource_type = attrs["resource_type"]

#title ⇒ String?

The short, general description of the problem

Examples:

Get the title

problem.title # => "Not Found Error"

Returns:

  • (String, nil) —

    the title



70
# File 'x-core/lib/x/core/problem.rb', line 70

def title = attrs["title"]

#to_json(state = nil) ⇒ String

The attributes as JSON

Examples:

Serialize a problem

problem.to_json # => "{\"title\":\"Not Found Error\"}"

Parameters:

  • state (JSON::State, nil) (defaults to: nil) —

    the state a JSON encoder passes, which the attributes are given

Returns:

  • (String) —

    the attributes as a JSON object



224
# File 'x-core/lib/x/core/problem.rb', line 224

def to_json(state = nil) = as_json.to_json(state)

#type ⇒ String?

The URI that identifies the kind of problem

Examples:

Get the type

problem.type # => "https://api.x.com/2/problems/resource-not-found"

Returns:

  • (String, nil) —

    the type



86
# File 'x-core/lib/x/core/problem.rb', line 86

def type = attrs["type"]

#usage_capped? ⇒ Boolean

Check whether the problem is the usage cap of the project, reached for the month

X refuses every request of a project that has used the posts its plan allows for the month with a 429 of this problem, until the month ends, so a client neither waits for it nor sends the request again, and a stream does not reconnect after it, however its rate limits are set.

Examples:

Tell the usage cap apart from a rate limit

wait_for_the_next_month if error.problem&.usage_capped?

Returns:

  • (Boolean) —

    true for a usage-capped problem



165
# File 'x-core/lib/x/core/problem.rb', line 165

def usage_capped? = type.to_s.end_with?("/usage-capped")

#value ⇒ Object?

The value of the parameter the problem concerns

It is the value the API gave, as the request sent it, so an identifier is a String, as resource_id is.

Examples:

Get the value

problem.value # => "1"

Returns:

  • (Object, nil) —

    the value



124
# File 'x-core/lib/x/core/problem.rb', line 124

def value = attrs["value"]