class GQLi::Validation

Validations

Attributes

errors[R]
root[R]
schema[R]

Public Class Methods

new(schema, root) click to toggle source
# File lib/gqli/validation.rb, line 8
def initialize(schema, root)
  @schema = schema
  @root = root
  @errors = []

  validate
end

Public Instance Methods

valid?() click to toggle source

Returns wether the query is valid or not

# File lib/gqli/validation.rb, line 17
def valid?
  errors.empty?
end

Protected Instance Methods

validate() click to toggle source
# File lib/gqli/validation.rb, line 23
def validate
  @errors = []

  type_name = root.class.name.split('::').last
  validate_type(type_name)
end

Private Instance Methods

non_null_type(non_null) click to toggle source
# File lib/gqli/validation.rb, line 194
def non_null_type(non_null)
  case non_null.kind
  when 'LIST'
    non_null.ofType
  else
    non_null
  end
end
remove_alias(name) click to toggle source
# File lib/gqli/validation.rb, line 45
def remove_alias(name)
  return name unless name.include?(':')

  name.split(':')[1].strip
end
type_for(field_type) click to toggle source
# File lib/gqli/validation.rb, line 179
def type_for(field_type)
  type = case field_type.type.kind
         when 'NON_NULL'
           non_null_type(field_type.type.ofType)
         when 'LIST'
           field_type.type.ofType
         when 'OBJECT', 'INTERFACE', 'INPUT_OBJECT'
           field_type.type
         when 'SCALAR'
           field_type.type
         end

  types.find { |t| t.name == type.name }
end
types() click to toggle source
# File lib/gqli/validation.rb, line 51
def types
  schema.types
end
valid_array_node?(node_type, node) click to toggle source
# File lib/gqli/validation.rb, line 123
def valid_array_node?(node_type, node)
  return false if %w[OBJECT INTERFACE].include?(node_type.kind) && node.__nodes.empty?
  true
end
valid_match_node?(parent_type, node) click to toggle source
# File lib/gqli/validation.rb, line 75
def valid_match_node?(parent_type, node)
  return if parent_type.fetch('possibleTypes', []).find { |t| t.name == node.__name.gsub('... on ', '') }
  fail "Match type '#{node.__name.gsub('... on ', '')}' invalid"
end
valid_object_node?(node_type, node) click to toggle source
# File lib/gqli/validation.rb, line 118
def valid_object_node?(node_type, node)
  return false if %w[OBJECT INTERFACE].include?(node_type.kind) && node.__nodes.empty?
  true
end
validate_directives(node) click to toggle source
# File lib/gqli/validation.rb, line 80
def validate_directives(node)
  return unless node.__params.size >= 1
  node.__params.first.tap do |k, v|
    break unless k.to_s.start_with?('@')

    fail "Directive unknown '#{k}'" unless %i[@include @skip].include?(k)
    fail "Missing arguments for directive '#{k}'" if v.nil? || !v.is_a?(::Hash) || v.empty?
    v.each do |arg, value|
      begin
        fail "Invalid argument '#{arg}' for directive '#{k}'" if arg.to_s != 'if'
        fail "Invalid value for 'if`, must be a boolean" if value != !!value
      rescue StandardError => e
        errors << e
      end
    end
  end
end
validate_hash_value(arg_type, value, for_arg) click to toggle source
# File lib/gqli/validation.rb, line 158
def validate_hash_value(arg_type, value, for_arg)
  value_type_error('Object', arg_type.name, for_arg) unless arg_type.kind == 'INPUT_OBJECT'

  type = types.find { |f| f.name == arg_type.name }
  fail "Type not found for '#{arg_type.name}'" if type.nil?

  value.each do |k, v|
    begin
      input_field = type.fetch('inputFields', []).find { |f| f.name == k.to_s }
      fail "Input field definition not found for '#{k}'" if input_field.nil?

      input_field_type = type_for(input_field)
      fail "Input field type not found for '#{k}'" if input_field_type.nil?

      validate_value_for_type(input_field_type, v, k)
    rescue StandardError => e
      errors << e
    end
  end
end
validate_nesting_node(node_type, node) click to toggle source
# File lib/gqli/validation.rb, line 114
def validate_nesting_node(node_type, node)
  fail "Invalid object for node '#{node.__name}'" unless valid_object_node?(node_type, node)
end
validate_node(parent_type, node) click to toggle source
# File lib/gqli/validation.rb, line 55
def validate_node(parent_type, node)
  validate_directives(node)

  return valid_match_node?(parent_type, node) if node.__name.start_with?('... on')

  node_name = remove_alias(node.__name)

  node_type = parent_type.fetch('fields', []).find { |f| f.name == node_name }
  fail "Node type not found for '#{node_name}'" if node_type.nil?

  validate_params(node_type, node)

  resolved_node_type = type_for(node_type)
  fail "Node type not found for '#{node_name}'" if resolved_node_type.nil?

  validate_nesting_node(resolved_node_type, node)

  node.__nodes.each { |n| validate_node(resolved_node_type, n) }
end
validate_params(node_type, node) click to toggle source
# File lib/gqli/validation.rb, line 98
def validate_params(node_type, node)
  node.__params.reject { |p, _| p.to_s.start_with?('@') }.each do |param, value|
    begin
      arg = node_type.fetch('args', []).find { |a| a.name == param.to_s }
      fail "Invalid argument '#{param}'" if arg.nil?

      arg_type = type_for(arg)
      fail "Argument type not found for '#{param}'" if arg_type.nil?

      validate_value_for_type(arg_type, value, param)
    rescue StandardError => e
      errors << e
    end
  end
end
validate_type(type) click to toggle source
# File lib/gqli/validation.rb, line 32
def validate_type(type)
  root_type = types.find { |t| t.name.casecmp(type).zero? }
  root.__nodes.each do |node|
    begin
      validate_node(root_type, node)
    rescue StandardError => e
      errors << e
    end
  end

  valid?
end
validate_value_for_type(arg_type, value, for_arg) click to toggle source
# File lib/gqli/validation.rb, line 135
def validate_value_for_type(arg_type, value, for_arg)
  case value
  when EnumValue
    if arg_type.kind == 'ENUM' && !arg_type.enumValues.map(&:name).include?(value.to_s)
      fail "Invalid value for Enum '#{arg_type.name}' for '#{for_arg}'"
    end
  when ::String
    unless arg_type.name == 'String' || arg_type.name == 'ID'
      value_type_error('String or ID', arg_type, for_arg)
    end
  when ::Integer
    value_type_error('Integer', arg_type, for_arg) unless arg_type.name == 'Int'
  when ::Float
    value_type_error('Float', arg_type, for_arg) unless arg_type.name == 'Float'
  when ::Hash
    validate_hash_value(arg_type, value, for_arg)
  when true, false
    value_type_error('Boolean', arg_type, for_arg) unless arg_type.name == 'Boolean'
  else
    value_type_error(value.class.name, arg_type, for_arg)
  end
end
value_type_error(is_type, should_be, for_arg) click to toggle source
# File lib/gqli/validation.rb, line 128
def value_type_error(is_type, should_be, for_arg)
  should_be = should_be.kind == 'ENUM' ? 'Enum' : should_be.name
  additional_message = '. Wrap the value with `__enum`.' if should_be == 'Enum'

  fail "Value is '#{is_type}', but should be '#{should_be}' for '#{for_arg}'#{additional_message}"
end