1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
|
# frozen_string_literal: true
require "graphql/client/hash_with_indifferent_access"
module GraphQL
class Client
# Public: Collection of errors associated with GraphQL object type.
#
# Inspired by ActiveModel::Errors.
class Errors
include Enumerable
# Internal: Normalize GraphQL Error "path" ensuring the path exists.
#
# Records "normalizedPath" value to error object.
#
# data - Hash of response data
# errors - Array of error Hashes
#
# Returns nothing.
def self.normalize_error_paths(data = nil, errors = [])
errors.each do |error|
path = ["data"]
current = data
error.fetch("path", []).each do |key|
break unless current
path << key
current = current[key]
end
error["normalizedPath"] = path
end
errors
end
# Internal: Initialize from collection of errors.
#
# errors - Array of GraphQL Hash error objects
# path - Array of String|Integer fields to data
# all - Boolean flag if all nested errors should be available
def initialize(errors = [], path = [], all = false)
@ast_path = path
@all = all
@raw_errors = errors
end
# Public: Return collection of all nested errors.
#
# data.errors[:node]
# data.errors.all[:node]
#
# Returns Errors collection.
def all
if @all
self
else
self.class.new(@raw_errors, @ast_path, true)
end
end
# Internal: Return collection of errors for a given subfield.
#
# data.errors.filter_by_path("node")
#
# Returns Errors collection.
def filter_by_path(field)
self.class.new(@raw_errors, @ast_path + [field], @all)
end
# Public: Access Hash of error messages.
#
# data.errors.messages["node"]
# data.errors.messages[:node]
#
# Returns HashWithIndifferentAccess.
def messages
return @messages if defined? @messages
messages = {}
details.each do |field, errors|
messages[field] ||= []
errors.each do |error|
messages[field] << error.fetch("message")
end
end
@messages = HashWithIndifferentAccess.new(messages)
end
# Public: Access Hash of error objects.
#
# data.errors.details["node"]
# data.errors.details[:node]
#
# Returns HashWithIndifferentAccess.
def details
return @details if defined? @details
details = {}
@raw_errors.each do |error|
path = error.fetch("normalizedPath", [])
matched_path = @all ? path[0, @ast_path.length] : path[0...-1]
next unless @ast_path == matched_path
field = path[@ast_path.length]
next unless field
details[field] ||= []
details[field] << error
end
@details = HashWithIndifferentAccess.new(details)
end
# Public: When passed a symbol or a name of a field, returns an array of
# errors for the method.
#
# data.errors[:node] # => ["couldn't find node by id"]
# data.errors['node'] # => ["couldn't find node by id"]
#
# Returns Array of errors.
def [](key)
messages.fetch(key, [])
end
# Public: Iterates through each error key, value pair in the error
# messages hash. Yields the field and the error for that attribute. If the
# field has more than one error message, yields once for each error
# message.
def each
return enum_for(:each) unless block_given?
messages.each_key do |field|
messages[field].each { |error| yield field, error }
end
end
# Public: Check if there are any errors on a given field.
#
# data.errors.messages # => {"node"=>["couldn't find node by id", "unauthorized"]}
# data.errors.include?("node") # => true
# data.errors.include?("version") # => false
#
# Returns true if the error messages include an error for the given field,
# otherwise false.
def include?(field)
self[field].any?
end
alias has_key? include?
alias key? include?
# Public: Count the number of errors on object.
#
# data.errors.messages # => {"node"=>["couldn't find node by id", "unauthorized"]}
# data.errors.size # => 2
#
# Returns the number of error messages.
def size
values.flatten.size
end
alias count size
# Public: Check if there are no errors on object.
#
# data.errors.messages # => {"node"=>["couldn't find node by id"]}
# data.errors.empty? # => false
#
# Returns true if no errors are found, otherwise false.
def empty?
size.zero?
end
alias blank? empty?
# Public: Returns all message keys.
#
# data.errors.messages # => {"node"=>["couldn't find node by id"]}
# data.errors.values # => ["node"]
#
# Returns Array of String field names.
def keys
messages.keys
end
# Public: Returns all message values.
#
# data.errors.messages # => {"node"=>["couldn't find node by id"]}
# data.errors.values # => [["couldn't find node by id"]]
#
# Returns Array of Array String messages.
def values
messages.values
end
# Public: Display console friendly representation of errors collection.
#
# Returns String.
def inspect
"#<#{self.class} @messages=#{messages.inspect} @details=#{details.inspect}>"
end
end
end
end
|