Module: GraphWeaver::ErrorFiltering

Includes:
Kernel
Included in:
QueryError, Response
Defined in:
lib/graph_weaver/errors.rb

Overview

Shared filtering over a collection of GraphQLErrors, for surfacing field-level failures programmatically. Host must define #errors.

Instance Method Summary collapse

Instance Method Details

#dataObject

Raises:

  • (NotImplementedError)


170
171
172
# File 'lib/graph_weaver/errors.rb', line 170

def data
  raise NotImplementedError, "#{self.class} must define #data"
end

#each_error(&block) ⇒ Object



200
201
202
# File 'lib/graph_weaver/errors.rb', line 200

def each_error(&block)
  errors_by_field.each(&block)
end

#entity_id(error) ⇒ Object

The id of the record an error points into, resolved by walking the error's path through the (partial) typed data: an error at ["people", 3, "email"] resolves to people.id. nil when the data is missing, the path doesn't walk, or the record has no id field.



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/graph_weaver/errors.rb', line 208

def entity_id(error)
  # untyped by nature: the walk traverses whatever structs this query
  # generated, reassigning across types at each step
  node = T.let(data, T.untyped)
  return unless node && error.path

  error.path[0..-2].each do |segment|
    node = if segment.is_a?(Integer) || segment.to_s.match?(/\A\d+\z/)
      node.is_a?(Array) ? node[segment.to_i] : nil
    else
      prop = GraphWeaver::Inflect.underscore(segment.to_s).to_sym
      node.respond_to?(prop) ? node.public_send(prop) : nil
    end
    return if node.nil?
  end

  node.respond_to?(:id) ? node.id : nil
end

#errorsObject

the host's interface, overridden by its attr_readers

Raises:

  • (NotImplementedError)


166
167
168
# File 'lib/graph_weaver/errors.rb', line 166

def errors
  raise NotImplementedError, "#{self.class} must define #errors"
end

#errors_at(path) ⇒ Object

Errors touching a field path — "user.email" or ["user", "email"]; prefix match, so deeper errors count too. List indices appear as path segments ("people.0.email").



177
178
179
180
# File 'lib/graph_weaver/errors.rb', line 177

def errors_at(path)
  want = (path.is_a?(String) ? path.split(".") : path).map(&:to_s)
  errors.select { |error| error.path && error.path.map(&:to_s).first(want.size) == want }
end

#errors_by_fieldObject

Errors grouped by the field they point at (index-stripped dotted path; nil key for global errors) — iterate with each_error:

 response.each_error do |field, errors|
   form.add_error(field, errors.map(&:message))
 end


196
197
198
# File 'lib/graph_weaver/errors.rb', line 196

def errors_by_field
  errors.group_by(&:field)
end

#reportObject

The user-facing rollup: errors keyed by field, with the ids of the actual records that failed inlined — "the 3 in people.3.email is useless to a user; people.email plus which people is the answer".

 { "people.email" => { "messages" => [...], "codes" => [...],
   "entity_ids" => ["7", "9"], "errors" => [full to_h...] } }


233
234
235
236
237
238
239
240
241
242
# File 'lib/graph_weaver/errors.rb', line 233

def report
  errors_by_field.to_h do |field, field_errors|
    [field, {
      "messages" => field_errors.map(&:message),
      "codes" => field_errors.filter_map(&:code).uniq,
      "entity_ids" => field_errors.filter_map { |e| entity_id(e) }.uniq,
      "errors" => field_errors.map(&:to_h),
    }]
  end
end

#schema_stale?Boolean

True when any error looks like the server rejected the query's shape — the schema has likely changed since the module was generated. Refresh the schema dump and regenerate (rake graph_weaver:schema:refresh && rake graph_weaver:generate).

Returns:

  • (Boolean)


186
187
188
# File 'lib/graph_weaver/errors.rb', line 186

def schema_stale?
  errors.any?(&:validation?)
end