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
|
# frozen_string_literal: true
module GraphQL
class Query
# Read-only access to query variables, applying default values if needed.
class Variables
extend Forwardable
# @return [Array<GraphQL::Query::VariableValidationError>] Any errors encountered when parsing the provided variables and literal values
attr_reader :errors
attr_reader :context
def initialize(ctx, ast_variables, provided_variables)
schema = ctx.schema
@context = ctx
@provided_variables = deep_stringify(provided_variables)
@errors = []
@storage = ast_variables.each_with_object({}) do |ast_variable, memo|
if schema.validate_max_errors && schema.validate_max_errors <= @errors.count
add_max_errors_reached_message
break
end
# Find the right value for this variable:
# - First, use the value provided at runtime
# - Then, fall back to the default value from the query string
# If it's still nil, raise an error if it's required.
variable_type = schema.type_from_ast(ast_variable.type, context: ctx)
if variable_type.nil? || !variable_type.unwrap.kind.input?
# Pass -- it will get handled by a validator
else
variable_name = ast_variable.name
default_value = ast_variable.default_value
provided_value = @provided_variables[variable_name]
value_was_provided = @provided_variables.key?(variable_name)
max_errors = schema.validate_max_errors - @errors.count if schema.validate_max_errors
begin
validation_result = variable_type.validate_input(provided_value, ctx, max_errors: max_errors)
if validation_result.valid?
if value_was_provided
# Add the variable if a value was provided
memo[variable_name] = provided_value
elsif default_value != nil
memo[variable_name] = if default_value.is_a?(Language::Nodes::NullValue)
nil
else
default_value
end
end
end
rescue GraphQL::ExecutionError => ex
# TODO: This should really include the path to the problematic node in the variable value
# like InputValidationResults generated by validate_non_null_input but unfortunately we don't
# have this information available in the coerce_input call chain. Note this path is the path
# that appears under errors.extensions.problems.path and NOT the result path under errors.path.
validation_result = GraphQL::Query::InputValidationResult.from_problem(ex.message)
end
if !validation_result.valid?
@errors << GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result)
end
end
end
end
def_delegators :@storage, :length, :key?, :[], :fetch, :to_h
private
def deep_stringify(val)
case val
when Array
val.map { |v| deep_stringify(v) }
when Hash
new_val = {}
val.each do |k, v|
new_val[k.to_s] = deep_stringify(v)
end
new_val
else
val
end
end
def add_max_errors_reached_message
message = "Too many errors processing variables, max validation error limit reached. Execution aborted"
validation_result = GraphQL::Query::InputValidationResult.from_problem(message)
errors << GraphQL::Query::VariableValidationError.new(nil, nil, nil, validation_result, msg: message)
end
end
end
end
|