module GraphQL::Schema::BuildFromDefinition::Builder

@api private

Constants

NO_DEFAULT_VALUE
NullResolveType

Attributes

definition_default_resolve[RW]

Public Class Methods

inherited(child_class) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 157
def self.inherited(child_class)
  child_class.definition_default_resolve = self.definition_default_resolve
end
resolve_type(*args) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 121
def self.resolve_type(*args)
  self.definition_default_resolve.resolve_type(*args)
end

Public Instance Methods

args_to_kwargs(arg_owner, node) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 223
def args_to_kwargs(arg_owner, node)
  if node.respond_to?(:arguments)
    kwargs = {}
    node.arguments.each do |arg_node|
      arg_defn = arg_owner.get_argument(arg_node.name)
      kwargs[arg_defn.keyword] = args_to_kwargs(arg_defn.type.unwrap, arg_node.value)
    end
    kwargs
  elsif node.is_a?(Array)
    node.map { |n| args_to_kwargs(arg_owner, n) }
  elsif node.is_a?(Language::Nodes::Enum)
    node.name
  else
    # scalar
    node
  end
end
build(document, default_resolve:, using: {}, relay:) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 26
def build(document, default_resolve:, using: {}, relay:)
  raise InvalidDocumentError.new('Must provide a document ast.') if !document || !document.is_a?(GraphQL::Language::Nodes::Document)

  if default_resolve.is_a?(Hash)
    default_resolve = ResolveMap.new(default_resolve)
  end

  schema_defns = document.definitions.select { |d| d.is_a?(GraphQL::Language::Nodes::SchemaDefinition) }
  if schema_defns.size > 1
    raise InvalidDocumentError.new('Must provide only one schema definition.')
  end
  schema_definition = schema_defns.first
  types = {}
  directives = {}
  type_resolver = build_resolve_type(types, directives, ->(type_name) { types[type_name] ||= Schema::LateBoundType.new(type_name)})
  # Make a different type resolver because we need to coerce directive arguments
  # _while_ building the schema.
  # It will dig for a type if it encounters a custom type. This could be a problem if there are cycles.
  directive_type_resolver = nil
  directive_type_resolver = build_resolve_type(types, directives, ->(type_name) {
    types[type_name] ||= begin
      defn = document.definitions.find { |d| d.respond_to?(:name) && d.name == type_name }
      if defn
        build_definition_from_node(defn, directive_type_resolver, default_resolve)
      elsif (built_in_defn = GraphQL::Schema::BUILT_IN_TYPES[type_name])
        built_in_defn
      else
        raise "No definition for #{type_name.inspect} found in schema document or built-in types. Add a definition for it or remove it."
      end
    end
  })

  document.definitions.each do |definition|
    if definition.is_a?(GraphQL::Language::Nodes::DirectiveDefinition)
      directives[definition.name] = build_directive(definition, directive_type_resolver)
    end
  end

  directives = GraphQL::Schema.default_directives.merge(directives)

  # In case any directives referenced built-in types for their arguments:
  replace_late_bound_types_with_built_in(types)

  document.definitions.each do |definition|
    case definition
    when GraphQL::Language::Nodes::SchemaDefinition, GraphQL::Language::Nodes::DirectiveDefinition
      nil # already handled
    else
      # It's possible that this was already loaded by the directives
      prev_type = types[definition.name]
      if prev_type.nil? || prev_type.is_a?(Schema::LateBoundType)
        types[definition.name] = build_definition_from_node(definition, type_resolver, default_resolve)
      end
    end
  end

  replace_late_bound_types_with_built_in(types)

  if schema_definition
    if schema_definition.query
      raise InvalidDocumentError.new("Specified query type \"#{schema_definition.query}\" not found in document.") unless types[schema_definition.query]
      query_root_type = types[schema_definition.query]
    end

    if schema_definition.mutation
      raise InvalidDocumentError.new("Specified mutation type \"#{schema_definition.mutation}\" not found in document.") unless types[schema_definition.mutation]
      mutation_root_type = types[schema_definition.mutation]
    end

    if schema_definition.subscription
      raise InvalidDocumentError.new("Specified subscription type \"#{schema_definition.subscription}\" not found in document.") unless types[schema_definition.subscription]
      subscription_root_type = types[schema_definition.subscription]
    end
  else
    query_root_type = types['Query']
    mutation_root_type = types['Mutation']
    subscription_root_type = types['Subscription']
  end

  raise InvalidDocumentError.new('Must provide schema definition with query type or a type named Query.') unless query_root_type

  Class.new(GraphQL::Schema) do
    begin
      # Add these first so that there's some chance of resolving late-bound types
      orphan_types types.values
      query query_root_type
      mutation mutation_root_type
      subscription subscription_root_type
    rescue Schema::UnresolvedLateBoundTypeError  => err
      type_name = err.type.name
      err_backtrace =  err.backtrace
      raise InvalidDocumentError, "Type \"#{type_name}\" not found in document.", err_backtrace
    end

    if default_resolve.respond_to?(:resolve_type)
      def self.resolve_type(*args)
        self.definition_default_resolve.resolve_type(*args)
      end
    else
      def self.resolve_type(*args)
        NullResolveType.call(*args)
      end
    end

    directives directives.values

    if schema_definition
      ast_node(schema_definition)
    end

    using.each do |plugin, options|
      if options
        use(plugin, **options)
      else
        use(plugin)
      end
    end

    # Empty `orphan_types` -- this will make unreachable types ... unreachable.
    own_orphan_types.clear

    class << self
      attr_accessor :definition_default_resolve
    end

    self.definition_default_resolve = default_resolve

    def definition_default_resolve
      self.class.definition_default_resolve
    end

    def self.inherited(child_class)
      child_class.definition_default_resolve = self.definition_default_resolve
    end
  end
end
build_arguments(type_class, arguments, type_resolver) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 350
def build_arguments(type_class, arguments, type_resolver)
  builder = self

  arguments.each do |argument_defn|
    default_value_kwargs = if !argument_defn.default_value.nil?
      { default_value: builder.build_default_value(argument_defn.default_value) }
    else
      NO_DEFAULT_VALUE
    end

    type_class.argument(
      argument_defn.name,
      type: type_resolver.call(argument_defn.type),
      required: false,
      description: argument_defn.description,
      deprecation_reason: builder.build_deprecation_reason(argument_defn.directives),
      ast_node: argument_defn,
      camelize: false,
      method_access: false,
      directives: prepare_directives(argument_defn, type_resolver),
      **default_value_kwargs
    )
  end
end
build_default_value(default_value) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 333
def build_default_value(default_value)
  case default_value
  when GraphQL::Language::Nodes::Enum
    default_value.name
  when GraphQL::Language::Nodes::NullValue
    nil
  when GraphQL::Language::Nodes::InputObject
    default_value.to_h
  when Array
    default_value.map { |v| build_default_value(v) }
  else
    default_value
  end
end
build_definition_from_node(definition, type_resolver, default_resolve) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 167
def build_definition_from_node(definition, type_resolver, default_resolve)
  case definition
  when GraphQL::Language::Nodes::EnumTypeDefinition
    build_enum_type(definition, type_resolver)
  when GraphQL::Language::Nodes::ObjectTypeDefinition
    build_object_type(definition, type_resolver)
  when GraphQL::Language::Nodes::InterfaceTypeDefinition
    build_interface_type(definition, type_resolver)
  when GraphQL::Language::Nodes::UnionTypeDefinition
    build_union_type(definition, type_resolver)
  when GraphQL::Language::Nodes::ScalarTypeDefinition
    build_scalar_type(definition, type_resolver, default_resolve: default_resolve)
  when GraphQL::Language::Nodes::InputObjectTypeDefinition
    build_input_object_type(definition, type_resolver)
  end
end
build_deprecation_reason(directives) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 260
def build_deprecation_reason(directives)
  deprecated_directive = directives.find{ |d| d.name == 'deprecated' }
  return unless deprecated_directive

  reason = deprecated_directive.arguments.find{ |a| a.name == 'reason' }
  return GraphQL::Schema::Directive::DEFAULT_DEPRECATION_REASON unless reason

  reason.value
end
build_directive(directive_definition, type_resolver) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 375
def build_directive(directive_definition, type_resolver)
  builder = self
  Class.new(GraphQL::Schema::Directive) do
    graphql_name(directive_definition.name)
    description(directive_definition.description)
    repeatable(directive_definition.repeatable)
    locations(*directive_definition.locations.map { |location| location.name.to_sym })
    ast_node(directive_definition)
    builder.build_arguments(self, directive_definition.arguments, type_resolver)
  end
end
build_directives(definition, ast_node, type_resolver) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 198
def build_directives(definition, ast_node, type_resolver)
  dirs = prepare_directives(ast_node, type_resolver)
  dirs.each do |dir_class, options|
    definition.directive(dir_class, **options)
  end
end
build_enum_type(enum_type_definition, type_resolver) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 241
def build_enum_type(enum_type_definition, type_resolver)
  builder = self
  Class.new(GraphQL::Schema::Enum) do
    graphql_name(enum_type_definition.name)
    builder.build_directives(self, enum_type_definition, type_resolver)
    description(enum_type_definition.description)
    ast_node(enum_type_definition)
    enum_type_definition.values.each do |enum_value_definition|
      value(enum_value_definition.name,
        value: enum_value_definition.name,
        deprecation_reason: builder.build_deprecation_reason(enum_value_definition.directives),
        description: enum_value_definition.description,
        directives: builder.prepare_directives(enum_value_definition, type_resolver),
        ast_node: enum_value_definition,
      )
    end
  end
end
build_fields(owner, field_definitions, type_resolver, default_resolve:) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 405
        def build_fields(owner, field_definitions, type_resolver, default_resolve:)
          builder = self

          field_definitions.each do |field_definition|
            type_name = resolve_type_name(field_definition.type)
            resolve_method_name = -"resolve_field_#{field_definition.name}"
            schema_field_defn = owner.field(
              field_definition.name,
              description: field_definition.description,
              type: type_resolver.call(field_definition.type),
              null: true,
              connection: type_name.end_with?("Connection"),
              connection_extension: nil,
              deprecation_reason: build_deprecation_reason(field_definition.directives),
              ast_node: field_definition,
              method_conflict_warning: false,
              camelize: false,
              directives: prepare_directives(field_definition, type_resolver),
              resolver_method: resolve_method_name,
            )

            builder.build_arguments(schema_field_defn, field_definition.arguments, type_resolver)

            # Don't do this for interfaces
            if default_resolve
              owner.class_eval <<-RUBY, __FILE__, __LINE__
                # frozen_string_literal: true
                def #{resolve_method_name}(**args)
                  field_instance = self.class.get_field("#{field_definition.name}")
                  context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context)
                end
              RUBY
            end
          end
        end
build_input_object_type(input_object_type_definition, type_resolver) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 322
def build_input_object_type(input_object_type_definition, type_resolver)
  builder = self
  Class.new(GraphQL::Schema::InputObject) do
    graphql_name(input_object_type_definition.name)
    description(input_object_type_definition.description)
    ast_node(input_object_type_definition)
    builder.build_directives(self, input_object_type_definition, type_resolver)
    builder.build_arguments(self, input_object_type_definition.fields, type_resolver)
  end
end
build_interface_type(interface_type_definition, type_resolver) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 387
def build_interface_type(interface_type_definition, type_resolver)
  builder = self
  Module.new do
    include GraphQL::Schema::Interface
    graphql_name(interface_type_definition.name)
    description(interface_type_definition.description)
    interface_type_definition.interfaces.each do |interface_name|
      "Implements: #{interface_type_definition} -> #{interface_name}"
      interface_defn = type_resolver.call(interface_name)
      implements(interface_defn)
    end
    ast_node(interface_type_definition)
    builder.build_directives(self, interface_type_definition, type_resolver)

    builder.build_fields(self, interface_type_definition.fields, type_resolver, default_resolve: nil)
  end
end
build_object_type(object_type_definition, type_resolver) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 304
def build_object_type(object_type_definition, type_resolver)
  builder = self

  Class.new(GraphQL::Schema::Object) do
    graphql_name(object_type_definition.name)
    description(object_type_definition.description)
    ast_node(object_type_definition)
    builder.build_directives(self, object_type_definition, type_resolver)

    object_type_definition.interfaces.each do |interface_name|
      interface_defn = type_resolver.call(interface_name)
      implements(interface_defn)
    end

    builder.build_fields(self, object_type_definition.fields, type_resolver, default_resolve: true)
  end
end
build_resolve_type(lookup_hash, directives, missing_type_handler) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 441
def build_resolve_type(lookup_hash, directives, missing_type_handler)
  resolve_type_proc = nil
  resolve_type_proc = ->(ast_node) {
    case ast_node
    when GraphQL::Language::Nodes::TypeName
      type_name = ast_node.name
      if lookup_hash.key?(type_name)
        lookup_hash[type_name]
      else
        missing_type_handler.call(type_name)
      end
    when GraphQL::Language::Nodes::NonNullType
      resolve_type_proc.call(ast_node.of_type).to_non_null_type
    when GraphQL::Language::Nodes::ListType
      resolve_type_proc.call(ast_node.of_type).to_list_type
    when String
      directives[ast_node]
    else
      raise "Unexpected ast_node: #{ast_node.inspect}"
    end
  }
  resolve_type_proc
end
build_scalar_type(scalar_type_definition, type_resolver, default_resolve:) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 270
def build_scalar_type(scalar_type_definition, type_resolver, default_resolve:)
  builder = self
  Class.new(GraphQL::Schema::Scalar) do
    graphql_name(scalar_type_definition.name)
    description(scalar_type_definition.description)
    ast_node(scalar_type_definition)
    builder.build_directives(self, scalar_type_definition, type_resolver)

    if default_resolve.respond_to?(:coerce_input)
      # Put these method definitions in another method to avoid retaining `type_resolve`
      # from this method's bindiing
      builder.build_scalar_type_coerce_method(self, :coerce_input, default_resolve)
      builder.build_scalar_type_coerce_method(self, :coerce_result, default_resolve)
    end
  end
end
build_scalar_type_coerce_method(scalar_class, method_name, default_definition_resolve) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 287
def build_scalar_type_coerce_method(scalar_class, method_name, default_definition_resolve)
  scalar_class.define_singleton_method(method_name) do |val, ctx|
    default_definition_resolve.public_send(method_name, self, val, ctx)
  end
end
build_union_type(union_type_definition, type_resolver) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 293
def build_union_type(union_type_definition, type_resolver)
  builder = self
  Class.new(GraphQL::Schema::Union) do
    graphql_name(union_type_definition.name)
    description(union_type_definition.description)
    possible_types(*union_type_definition.types.map { |type_name| type_resolver.call(type_name) })
    ast_node(union_type_definition)
    builder.build_directives(self, union_type_definition, type_resolver)
  end
end
definition_default_resolve() click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 153
def definition_default_resolve
  self.class.definition_default_resolve
end
prepare_directives(ast_node, type_resolver) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 205
def prepare_directives(ast_node, type_resolver)
  dirs = {}
  ast_node.directives.each do |dir_node|
    if dir_node.name == "deprecated"
      # This is handled using `deprecation_reason`
      next
    else
      dir_class = type_resolver.call(dir_node.name)
      if dir_class.nil?
        raise ArgumentError, "No definition for @#{dir_node.name} on #{ast_node.name} at #{ast_node.line}:#{ast_node.col}"
      end
      options = args_to_kwargs(dir_class, dir_node)
      dirs[dir_class] = options
    end
  end
  dirs
end
replace_late_bound_types_with_built_in(types) click to toggle source

Modify `types`, replacing any late-bound references to built-in types with their actual definitions.

(Schema definitions are allowed to reference those built-ins without redefining them.) @return void

# File lib/graphql/schema/build_from_definition.rb, line 189
def replace_late_bound_types_with_built_in(types)
  GraphQL::Schema::BUILT_IN_TYPES.each do |scalar_name, built_in_scalar|
    existing_type = types[scalar_name]
    if existing_type.is_a?(GraphQL::Schema::LateBoundType)
      types[scalar_name] = built_in_scalar
    end
  end
end
resolve_type_name(type) click to toggle source
# File lib/graphql/schema/build_from_definition.rb, line 465
def resolve_type_name(type)
  case type
  when GraphQL::Language::Nodes::TypeName
    return type.name
  else
    resolve_type_name(type.of_type)
  end
end