lib/graphql/schema/introspection_system.rb in graphql-1.10.0.pre1 vs lib/graphql/schema/introspection_system.rb in graphql-1.10.0.pre2
- old
+ new
@@ -1,47 +1,46 @@
# frozen_string_literal: true
module GraphQL
class Schema
class IntrospectionSystem
- attr_reader :schema_type, :type_type, :typename_field
+ attr_reader :types, :possible_types
def initialize(schema)
@schema = schema
+ @class_based = !!@schema.is_a?(Class)
@built_in_namespace = GraphQL::Introspection
- @custom_namespace = schema.introspection_namespace || @built_in_namespace
+ @custom_namespace = if @class_based
+ schema.introspection || @built_in_namespace
+ else
+ schema.introspection_namespace || @built_in_namespace
+ end
- # Use to-graphql to avoid sharing with any previous instantiations
- @schema_type = load_constant(:SchemaType).to_graphql
- @type_type = load_constant(:TypeType).to_graphql
- @field_type = load_constant(:FieldType).to_graphql
- @directive_type = load_constant(:DirectiveType).to_graphql
- @enum_value_type = load_constant(:EnumValueType).to_graphql
- @input_value_type = load_constant(:InputValueType).to_graphql
- @type_kind_enum = load_constant(:TypeKindEnum).to_graphql
- @directive_location_enum = load_constant(:DirectiveLocationEnum).to_graphql
+ type_defns = [
+ load_constant(:SchemaType),
+ load_constant(:TypeType),
+ load_constant(:FieldType),
+ load_constant(:DirectiveType),
+ load_constant(:EnumValueType),
+ load_constant(:InputValueType),
+ load_constant(:TypeKindEnum),
+ load_constant(:DirectiveLocationEnum)
+ ]
+ @types = {}
+ @possible_types = {}
+ type_defns.each do |t|
+ @types[t.graphql_name] = t
+ @possible_types[t.graphql_name] = [t]
+ end
@entry_point_fields =
- if schema.disable_introspection_entry_points
+ if schema.disable_introspection_entry_points?
{}
else
get_fields_from_class(class_sym: :EntryPoints)
end
@dynamic_fields = get_fields_from_class(class_sym: :DynamicFields)
end
- def object_types
- [
- @schema_type,
- @type_type,
- @field_type,
- @directive_type,
- @enum_value_type,
- @input_value_type,
- @type_kind_enum,
- @directive_location_enum,
- ]
- end
-
def entry_points
@entry_point_fields.values
end
def entry_point(name:)
@@ -54,28 +53,97 @@
def dynamic_field(name:)
@dynamic_fields[name]
end
+ # The introspection system is prepared with a bunch of LateBoundTypes.
+ # Replace those with the objects that they refer to, since LateBoundTypes
+ # aren't handled at runtime.
+ #
+ # @api private
+ # @return void
+ def resolve_late_bindings
+ @types.each do |name, t|
+ if t.kind.fields?
+ t.fields.each do |_name, field_defn|
+ field_defn.type = resolve_late_binding(field_defn.type)
+ end
+ end
+ end
+
+ @entry_point_fields.each do |name, f|
+ f.type = resolve_late_binding(f.type)
+ end
+
+ @dynamic_fields.each do |name, f|
+ f.type = resolve_late_binding(f.type)
+ end
+ nil
+ end
+
private
+ def resolve_late_binding(late_bound_type)
+ case late_bound_type
+ when GraphQL::Schema::LateBoundType
+ @schema.get_type(late_bound_type.name)
+ when GraphQL::Schema::List, GraphQL::ListType
+ resolve_late_binding(late_bound_type.of_type).to_list_type
+ when GraphQL::Schema::NonNull, GraphQL::NonNullType
+ resolve_late_binding(late_bound_type.of_type).to_non_null_type
+ when Module
+ # It's a normal type -- no change required
+ late_bound_type
+ else
+ raise "Invariant: unexpected type: #{late_bound_type} (#{late_bound_type.class})"
+ end
+ end
+
def load_constant(class_name)
- @custom_namespace.const_get(class_name)
+ const = @custom_namespace.const_get(class_name)
+ if @class_based
+ dup_type_class(const)
+ else
+ # Use `.to_graphql` to get a freshly-made version, not shared between schemas
+ const.to_graphql
+ end
rescue NameError
# Dup the built-in so that the cached fields aren't shared
- @built_in_namespace.const_get(class_name)
+ dup_type_class(@built_in_namespace.const_get(class_name))
end
def get_fields_from_class(class_sym:)
- object_class = load_constant(class_sym)
- object_type_defn = object_class.to_graphql
- extracted_field_defns = {}
- object_type_defn.all_fields.each do |field_defn|
- inner_resolve = field_defn.resolve_proc
- resolve_with_instantiate = PerFieldProxyResolve.new(object_class: object_class, inner_resolve: inner_resolve)
- extracted_field_defns[field_defn.name] = field_defn.redefine(resolve: resolve_with_instantiate)
+ object_type_defn = load_constant(class_sym)
+
+ if object_type_defn.is_a?(Module)
+ object_type_defn.fields
+ else
+ extracted_field_defns = {}
+ object_class = object_type_defn.metadata[:type_class]
+ object_type_defn.all_fields.each do |field_defn|
+ inner_resolve = field_defn.resolve_proc
+ resolve_with_instantiate = PerFieldProxyResolve.new(object_class: object_class, inner_resolve: inner_resolve)
+ extracted_field_defns[field_defn.name] = field_defn.redefine(resolve: resolve_with_instantiate)
+ end
+ extracted_field_defns
end
- extracted_field_defns
+ end
+
+ # This is probably not 100% robust -- but it has to be good enough to avoid modifying the built-in introspection types
+ def dup_type_class(type_class)
+ type_name = type_class.graphql_name
+ Class.new(type_class) do
+ # This won't be inherited like other things will
+ graphql_name(type_name)
+
+ if type_class.kind.fields?
+ type_class.fields.each do |_name, field_defn|
+ dup_field = field_defn.dup
+ dup_field.owner = self
+ add_field(dup_field)
+ end
+ end
+ end
end
class PerFieldProxyResolve
def initialize(object_class:, inner_resolve:)
@object_class = object_class