lib/graphql/language/nodes.rb



# frozen_string_literal: true
module GraphQL
  module Language
    module Nodes
      # {AbstractNode} is the base class for all nodes in a GraphQL AST.
      #
      # It provides some APIs for working with ASTs:
      # - `children` returns all AST nodes attached to this one. Used for tree traversal.
      # - `scalars` returns all scalar (Ruby) values attached to this one. Used for comparing nodes.
      # - `to_query_string` turns an AST node into a GraphQL string
      class AbstractNode
        attr_accessor :line, :col

        # Initialize a node by extracting its position,
        # then calling the class's `initialize_node` method.
        # @param options [Hash] Initial attributes for this node
        def initialize(options={})
          if options.key?(:position_source)
            position_source = options.delete(:position_source)
            @line, @col = position_source.line_and_column
          end

          initialize_node(options)
        end

        # This is called with node-specific options
        def initialize_node(options={})
          raise NotImplementedError
        end

        # Value equality
        # @return [Boolean] True if `self` is equivalent to `other`
        def eql?(other)
          return true if equal?(other)
          other.is_a?(self.class) &&
            other.scalars.eql?(self.scalars) &&
            other.children.eql?(self.children)
        end

        # @return [Array<GraphQL::Language::Nodes::AbstractNode>] all nodes in the tree below this one
        def children
          self.class.child_attributes
            .map { |attr_name| public_send(attr_name) }
            .flatten
        end

        # @return [Array<Integer, Float, String, Boolean, Array>] Scalar values attached to this node
        def scalars
          self.class.scalar_attributes
            .map { |attr_name| public_send(attr_name) }
        end

        class << self
          # A node subclass inherits `scalar_attributes`
          # and `child_attributes` from its parent
          def inherited(subclass)
            subclass.scalar_attributes(*@scalar_attributes)
            subclass.child_attributes(*@child_attributes)
          end

          # define `attr_names` as places where scalars may be attached to this node
          def scalar_attributes(*attr_names)
            @scalar_attributes ||= []
            @scalar_attributes += attr_names
          end

          # define `attr_names` as places where child nodes may be attached to this node
          def child_attributes(*attr_names)
            @child_attributes ||= []
            @child_attributes += attr_names
          end
        end

        def position
          [line, col]
        end

        def to_query_string
          Generation.generate(self)
        end
      end

      # Base class for non-null type names and list type names
      class WrapperType < AbstractNode
        attr_accessor :of_type
        scalar_attributes :of_type

        def initialize_node(of_type: nil)
          @of_type = of_type
        end

        def children
          [].freeze
        end
      end

      # Base class for nodes whose only value is a name (no child nodes or other scalars)
      class NameOnlyNode < AbstractNode
        attr_accessor :name
        scalar_attributes :name

        def initialize_node(name: nil)
          @name = name
        end

        def children
          [].freeze
        end
      end

      # A key-value pair for a field's inputs
      class Argument < AbstractNode
        attr_accessor :name, :value
        scalar_attributes :name, :value

        # @!attribute name
        #   @return [String] the key for this argument

        # @!attribute value
        #   @return [String, Float, Integer, Boolean, Array, InputObject] The value passed for this key

        def initialize_node(name: nil, value: nil)
          @name = name
          @value = value
        end

        def children
          [value].flatten.select { |v| v.is_a?(AbstractNode) }
        end
      end

      class Directive < AbstractNode
        attr_accessor :name, :arguments
        scalar_attributes :name
        child_attributes :arguments

        def initialize_node(name: nil, arguments: [])
          @name = name
          @arguments = arguments
        end
      end

      class DirectiveDefinition < AbstractNode
        attr_accessor :name, :arguments, :locations, :description
        scalar_attributes :name
        child_attributes :arguments, :locations

        def initialize_node(name: nil, arguments: [], locations: [], description: nil)
          @name = name
          @arguments = arguments
          @locations = locations
          @description = description
        end
      end

      # This is the AST root for normal queries
      #
      # @example Deriving a document by parsing a string
      #   document = GraphQL.parse(query_string)
      #
      # @example Creating a string from a document
      #   document.to_query_string
      #   # { ... }
      #
      class Document < AbstractNode
        attr_accessor :definitions
        child_attributes :definitions

        # @!attribute definitions
        #   @return [Array<OperationDefinition, FragmentDefinition>] top-level GraphQL units: operations or fragments
        def initialize_node(definitions: [])
          @definitions = definitions
        end

        def slice_definition(name)
          GraphQL::Language::DefinitionSlice.slice(self, name)
        end
      end

      # An enum value. The string is available as {#name}.
      class Enum < NameOnlyNode; end

      # A null value literal.
      class NullValue < NameOnlyNode; end

      # A single selection in a GraphQL query.
      class Field < AbstractNode
        attr_accessor :name, :alias, :arguments, :directives, :selections
        scalar_attributes :name, :alias
        child_attributes :arguments, :directives, :selections

        # @!attribute selections
        #   @return [Array<Nodes::Field>] Selections on this object (or empty array if this is a scalar field)

        def initialize_node(name: nil, arguments: [], directives: [], selections: [], **kwargs)
          @name = name
          # oops, alias is a keyword:
          @alias = kwargs.fetch(:alias, nil)
          @arguments = arguments
          @directives = directives
          @selections = selections
        end
      end

      # A reusable fragment, defined at document-level.
      class FragmentDefinition < AbstractNode
        attr_accessor :name, :type, :directives, :selections
        scalar_attributes :name, :type
        child_attributes :directives, :selections

        # @!attribute name
        #   @return [String] the identifier for this fragment, which may be applied with `...#{name}`

        # @!attribute type
        #   @return [String] the type condition for this fragment (name of type which it may apply to)
        def initialize_node(name: nil, type: nil, directives: [], selections: [])
          @name = name
          @type = type
          @directives = directives
          @selections = selections
        end
      end

      # Application of a named fragment in a selection
      class FragmentSpread < AbstractNode
        attr_accessor :name, :directives
        scalar_attributes :name
        child_attributes :directives

        # @!attribute name
        #   @return [String] The identifier of the fragment to apply, corresponds with {FragmentDefinition#name}

        def initialize_node(name: nil, directives: [])
          @name = name
          @directives = directives
        end
      end

      # An unnamed fragment, defined directly in the query with `... {  }`
      class InlineFragment < AbstractNode
        attr_accessor :type, :directives, :selections
        scalar_attributes :type
        child_attributes :directives, :selections

        # @!attribute type
        #   @return [String, nil] Name of the type this fragment applies to, or `nil` if this fragment applies to any type

        def initialize_node(type: nil, directives: [], selections: [])
          @type = type
          @directives = directives
          @selections = selections
        end
      end

      # A collection of key-value inputs which may be a field argument
      class InputObject < AbstractNode
        attr_accessor :arguments
        child_attributes :arguments

        # @!attribute arguments
        #   @return [Array<Nodes::Argument>] A list of key-value pairs inside this input object

        def initialize_node(arguments: [])
          @arguments = arguments
        end

        # @return [Hash<String, Any>] Recursively turn this input object into a Ruby Hash
        def to_h(options={})
          arguments.inject({}) do |memo, pair|
            v = pair.value
            memo[pair.name] = serialize_value_for_hash v
            memo
          end
        end

        private

        def serialize_value_for_hash(value)
          if value.is_a? InputObject
            value.to_h
          elsif value.is_a? Array
            value.map do |v|
              serialize_value_for_hash v
            end
          elsif value.is_a? NullValue
            nil
          else
            value
          end
        end
      end


      # A list type definition, denoted with `[...]` (used for variable type definitions)
      class ListType < WrapperType; end

      # A non-null type definition, denoted with `...!` (used for variable type definitions)
      class NonNullType < WrapperType; end

      # A query, mutation or subscription.
      # May be anonymous or named.
      # May be explicitly typed (eg `mutation { ... }`) or implicitly a query (eg `{ ... }`).
      class OperationDefinition < AbstractNode
        attr_accessor :operation_type, :name, :variables, :directives, :selections
        scalar_attributes :operation_type, :name
        child_attributes :variables, :directives, :selections

        # @!attribute variables
        #   @return [Array<VariableDefinition>] Variable definitions for this operation

        # @!attribute selections
        #   @return [Array<Field>] Root-level fields on this operation

        # @!attribute operation_type
        #   @return [String, nil] The root type for this operation, or `nil` for implicit `"query"`

        # @!attribute name
        #   @return [String, nil] The name for this operation, or `nil` if unnamed

        def initialize_node(operation_type: nil, name: nil, variables: [], directives: [], selections: [])
          @operation_type = operation_type
          @name = name
          @variables = variables
          @directives = directives
          @selections = selections
        end
      end

      # A type name, used for variable definitions
      class TypeName < NameOnlyNode; end

      # An operation-level query variable
      class VariableDefinition < AbstractNode
        attr_accessor :name, :type, :default_value
        scalar_attributes :name, :type, :default_value

        # @!attribute default_value
        #   @return [String, Integer, Float, Boolean, Array, NullValue] A Ruby value to use if no other value is provided

        # @!attribute type
        #   @return [TypeName, NonNullType, ListType] The expected type of this value

        # @!attribute name
        #   @return [String] The identifier for this variable, _without_ `$`

        def initialize_node(name: nil, type: nil, default_value: nil)
          @name = name
          @type = type
          @default_value = default_value
        end
      end

      # Usage of a variable in a query. Name does _not_ include `$`.
      class VariableIdentifier < NameOnlyNode; end

      class SchemaDefinition < AbstractNode
        attr_accessor :query, :mutation, :subscription
        scalar_attributes :query, :mutation, :subscription

        def initialize_node(query: nil, mutation: nil, subscription: nil)
          @query = query
          @mutation = mutation
          @subscription = subscription
        end
      end

      class ScalarTypeDefinition < AbstractNode
        attr_accessor :name, :directives, :description
        scalar_attributes :name
        child_attributes :directives

        def initialize_node(name:, directives: [], description: nil)
          @name = name
          @directives = directives
          @description = description
        end
      end

      class ObjectTypeDefinition < AbstractNode
        attr_accessor :name, :interfaces, :fields, :directives, :description
        scalar_attributes :name
        child_attributes :interfaces, :fields, :directives

        def initialize_node(name:, interfaces:, fields:, directives: [], description: nil)
          @name = name
          @interfaces = interfaces || []
          @directives = directives
          @fields = fields
          @description = description
        end
      end

      class InputValueDefinition < AbstractNode
        attr_accessor :name, :type, :default_value, :directives,:description
        scalar_attributes :name, :type, :default_value
        child_attributes :directives

        def initialize_node(name:, type:, default_value: nil, directives: [], description: nil)
          @name = name
          @type = type
          @default_value = default_value
          @directives = directives
          @description = description
        end
      end

      class FieldDefinition < AbstractNode
        attr_accessor :name, :arguments, :type, :directives, :description
        scalar_attributes :name, :type
        child_attributes :arguments, :directives

        def initialize_node(name:, arguments:, type:, directives: [], description: nil)
          @name = name
          @arguments = arguments
          @type = type
          @directives = directives
          @description = description
        end
      end

      class InterfaceTypeDefinition < AbstractNode
        attr_accessor :name, :fields, :directives, :description
        scalar_attributes :name
        child_attributes :fields, :directives

        def initialize_node(name:, fields:, directives: [], description: nil)
          @name = name
          @fields = fields
          @directives = directives
          @description = description
        end
      end

      class UnionTypeDefinition < AbstractNode
        attr_accessor :name, :types, :directives, :description
        scalar_attributes :name
        child_attributes :types, :directives

        def initialize_node(name:, types:, directives: [], description: nil)
          @name = name
          @types = types
          @directives = directives
          @description = description
        end
      end

      class EnumTypeDefinition < AbstractNode
        attr_accessor :name, :values, :directives, :description
        scalar_attributes :name
        child_attributes :values, :directives

        def initialize_node(name:, values:, directives: [], description: nil)
          @name = name
          @values = values
          @directives = directives
          @description = description
        end
      end

      class EnumValueDefinition < AbstractNode
        attr_accessor :name, :directives, :description
        scalar_attributes :name
        child_attributes :directives

        def initialize_node(name:, directives: [], description: nil)
          @name = name
          @directives = directives
          @description = description
        end
      end

      class InputObjectTypeDefinition < AbstractNode
        attr_accessor :name, :fields, :directives, :description
        scalar_attributes :name
        child_attributes :fields

        def initialize_node(name:, fields:, directives: [], description: nil)
          @name = name
          @fields = fields
          @directives = directives
          @description = description
        end
      end
    end
  end
end