lib/graphql/input_object_type.rb



# frozen_string_literal: true
module GraphQL
  # {InputObjectType}s are key-value inputs for fields.
  #
  # Input objects have _arguments_ which are identical to {GraphQL::Field} arguments.
  # They map names to types and support default values.
  # Their input types can be any input types, including {InputObjectType}s.
  #
  # @example An input type with name and number
  #   PlayerInput = GraphQL::InputObjectType.define do
  #     name("Player")
  #     argument :name, !types.String
  #     argument :number, !types.Int
  #   end
  #
  # In a `resolve` function, you can access the values by making nested lookups on `args`.
  #
  # @example Accessing input values in a resolve function
  #   resolve ->(obj, args, ctx) {
  #     args[:player][:name]    # => "Tony Gwynn"
  #     args[:player][:number]  # => 19
  #     args[:player].to_h      # { "name" => "Tony Gwynn", "number" => 19 }
  #     # ...
  #   }
  #
  class InputObjectType < GraphQL::BaseType
    accepts_definitions(
      :arguments, :mutation,
      input_field: GraphQL::Define::AssignArgument,
      argument: GraphQL::Define::AssignArgument
    )

    attr_accessor :mutation, :arguments
    ensure_defined(:mutation, :arguments, :input_fields)
    alias :input_fields :arguments

    # @!attribute mutation
    #   @return [GraphQL::Relay::Mutation, nil] The mutation this field was derived from, if it was derived from a mutation

    # @!attribute arguments
    # @return [Hash<String => GraphQL::Argument>] Map String argument names to their {GraphQL::Argument} implementations


    def initialize
      super
      @arguments = {}
    end

    def initialize_copy(other)
      super
      @arguments = other.arguments.dup
    end

    def kind
      GraphQL::TypeKinds::INPUT_OBJECT
    end

    def coerce_result(value, ctx = nil)
      if ctx.nil?
        warn_deprecated_coerce("coerce_isolated_result")
        ctx = GraphQL::Query::NullContext
      end

      # Allow the application to provide values as :symbols, and convert them to the strings
      value = value.reduce({}) { |memo, (k, v)| memo[k.to_s] = v; memo }

      result = {}

      arguments.each do |input_key, input_field_defn|
        input_value = value[input_key]
        if value.key?(input_key)
          result[input_key] = if input_value.nil?
            nil
          else
            input_field_defn.type.coerce_result(input_value, ctx)
          end
        end
      end

      result
    end

    private

    def coerce_non_null_input(value, ctx)
      input_values = {}

      arguments.each do |input_key, input_field_defn|
        field_value = value[input_key]

        if value.key?(input_key)
          input_values[input_key] = input_field_defn.type.coerce_input(field_value, ctx)
        elsif input_field_defn.default_value?
          input_values[input_key] = input_field_defn.default_value
        end
      end

      GraphQL::Query::Arguments.new(input_values, argument_definitions: arguments)
    end

    def validate_non_null_input(input, ctx)
      warden = ctx.warden
      result = GraphQL::Query::InputValidationResult.new

      if (input.to_h rescue nil).nil?
        result.add_problem(
          "Expected #{JSON.generate(input, quirks_mode: true)} to be a key, value object " \
          " responding to `to_h`."
        )
        return result
      end


      visible_arguments_map = warden.arguments(self).reduce({}) { |m, f| m[f.name] = f; m}

      # Items in the input that are unexpected
      input.each do |name, value|
        if visible_arguments_map[name].nil?
          result.add_problem("Field is not defined on #{self.name}", [name])
        end
      end

      # Items in the input that are expected, but have invalid values
      visible_arguments_map.map do |name, field|
        field_result = field.type.validate_input(input[name], ctx)
        if !field_result.valid?
          result.merge_result!(name, field_result)
        end
      end

      result
    end
  end
end