lib/graphql/schema/loader.rb in graphql-1.10.6 vs lib/graphql/schema/loader.rb in graphql-1.10.7

- old
+ new

@@ -3,42 +3,47 @@ class Schema # You can use the result of {GraphQL::Introspection::INTROSPECTION_QUERY} # to make a schema. This schema is missing some important details like # `resolve` functions, but it does include the full type system, # so you can use it to validate queries. + # + # @see GraphQL::Schema.from_introspection for a public API module Loader extend self # Create schema with the result of an introspection query. # @param introspection_result [Hash] A response from {GraphQL::Introspection::INTROSPECTION_QUERY} - # @return [GraphQL::Schema] the schema described by `input` - # @deprecated Use {GraphQL::Schema.from_introspection} instead + # @return [Class] the schema described by `input` def load(introspection_result) schema = introspection_result.fetch("data").fetch("__schema") types = {} - type_resolver = ->(type) { -> { resolve_type(types, type) } } + type_resolver = ->(type) { resolve_type(types, type) } schema.fetch("types").each do |type| next if type.fetch("name").start_with?("__") type_object = define_type(type, type_resolver) types[type["name"]] = type_object end - kargs = { orphan_types: types.values, resolve_type: NullResolveType } - [:query, :mutation, :subscription].each do |root| - type = schema["#{root}Type"] - kargs[root] = types.fetch(type.fetch("name")) if type - end + Class.new(GraphQL::Schema) do + orphan_types(types.values) - Schema.define(**kargs, raise_definition_error: true) + def self.resolve_type(*) + raise(GraphQL::RequiredImplementationMissingError, "This schema was loaded from string, so it can't resolve types for objects") + end + + [:query, :mutation, :subscription].each do |root| + type = schema["#{root}Type"] + if type + type_defn = types.fetch(type.fetch("name")) + self.public_send(root, type_defn) + end + end + end end - NullResolveType = ->(type, obj, ctx) { - raise(GraphQL::RequiredImplementationMissingError, "This schema was loaded from string, so it can't resolve types for objects") - } - NullScalarCoerce = ->(val, _ctx) { val } class << self private @@ -46,18 +51,18 @@ case kind = type.fetch("kind") when "ENUM", "INTERFACE", "INPUT_OBJECT", "OBJECT", "SCALAR", "UNION" type_name = type.fetch("name") type = types[type_name] || Schema::BUILT_IN_TYPES[type_name] if type.nil? - raise "Type not found: #{type_name.inspect} among #{types.keys.sort}" + GraphQL::Schema::LateBoundType.new(type_name) else - type.graphql_definition + type end when "LIST" - ListType.new(of_type: resolve_type(types, type.fetch("ofType"))) + Schema::List.new(resolve_type(types, type.fetch("ofType"))) when "NON_NULL" - NonNullType.new(of_type: resolve_type(types, type.fetch("ofType"))) + Schema::NonNull.new(resolve_type(types, type.fetch("ofType"))) else fail GraphQL::RequiredImplementationMissingError, "#{kind} not implemented" end end @@ -81,110 +86,109 @@ ) end end def define_type(type, type_resolver) + loader = self case type.fetch("kind") when "ENUM" - EnumType.define( - name: type["name"], - description: type["description"], - values: type["enumValues"].map { |enum| - EnumType::EnumValue.define( - name: enum["name"], - description: enum["description"], - deprecation_reason: enum["deprecationReason"], - value: enum["name"] + Class.new(GraphQL::Schema::Enum) do + graphql_name(type["name"]) + description(type["description"]) + type["enumValues"].each do |enum_value| + value( + enum_value["name"], + description: enum_value["description"], + deprecation_reason: enum_value["deprecation_reason"], ) - }) + end + end when "INTERFACE" - InterfaceType.define( - name: type["name"], - description: type["description"], - fields: Hash[(type["fields"] || []).map { |field| - [field["name"], define_type(field.merge("kind" => "FIELD"), type_resolver)] - }] - ) + Module.new do + include GraphQL::Schema::Interface + graphql_name(type["name"]) + description(type["description"]) + loader.build_fields(self, type["fields"] || [], type_resolver) + end when "INPUT_OBJECT" - InputObjectType.define( - name: type["name"], - description: type["description"], - arguments: Hash[type["inputFields"].map { |arg| - [arg["name"], define_type(arg.merge("kind" => "ARGUMENT"), type_resolver)] - }] - ) - when "OBJECT" - ObjectType.define( - name: type["name"], - description: type["description"], - interfaces: (type["interfaces"] || []).map { |interface| - type_resolver.call(interface) - }, - fields: Hash[type["fields"].map { |field| - [field["name"], define_type(field.merge("kind" => "FIELD"), type_resolver)] - }] - ) - when "FIELD" - defns = { - name: type["name"], - type: type_resolver.call(type["type"]), - description: type["description"], - } - - # Avoid passing an empty hash, which warns on Ruby 2.7 - if type["args"].any? - defns[:arguments] = Hash[type["args"].map { |arg| - [arg["name"], define_type(arg.merge("kind" => "ARGUMENT"), type_resolver)] - }] + Class.new(GraphQL::Schema::InputObject) do + graphql_name(type["name"]) + description(type["description"]) + loader.build_arguments(self, type["inputFields"], type_resolver) end - - GraphQL::Field.define(**defns) - when "ARGUMENT" - kwargs = {} - if type["defaultValue"] - kwargs[:default_value] = begin - default_value_str = type["defaultValue"] - - dummy_query_str = "query getStuff($var: InputObj = #{default_value_str}) { __typename }" - - # Returns a `GraphQL::Language::Nodes::Document`: - dummy_query_ast = GraphQL.parse(dummy_query_str) - - # Reach into the AST for the default value: - input_value_ast = dummy_query_ast.definitions.first.variables.first.default_value - - extract_default_value(default_value_str, input_value_ast) + when "OBJECT" + Class.new(GraphQL::Schema::Object) do + graphql_name(type["name"]) + description(type["description"]) + if type["interfaces"] + type["interfaces"].each do |interface_type| + implements(type_resolver.call(interface_type)) + end end + loader.build_fields(self, type["fields"], type_resolver) end - - GraphQL::Argument.define( - name: type["name"], - type: type_resolver.call(type["type"]), - description: type["description"], - method_access: false, - **kwargs - ) when "SCALAR" type_name = type.fetch("name") - if GraphQL::Schema::BUILT_IN_TYPES[type_name] - GraphQL::Schema::BUILT_IN_TYPES[type_name] + if (builtin = GraphQL::Schema::BUILT_IN_TYPES[type_name]) + builtin else - ScalarType.define( - name: type["name"], - description: type["description"], - coerce: NullScalarCoerce, - ) + Class.new(GraphQL::Schema::Scalar) do + graphql_name(type["name"]) + description(type["description"]) + end end when "UNION" - UnionType.define( - name: type["name"], - description: type["description"], - possible_types: type["possibleTypes"].map { |possible_type| - type_resolver.call(possible_type) - } - ) + Class.new(GraphQL::Schema::Union) do + graphql_name(type["name"]) + description(type["description"]) + possible_types(*(type["possibleTypes"].map { |pt| type_resolver.call(pt) })) + end else fail GraphQL::RequiredImplementationMissingError, "#{type["kind"]} not implemented" + end + end + + public + + def build_fields(type_defn, fields, type_resolver) + loader = self + fields.each do |field_hash| + type_defn.field( + field_hash["name"], + type: type_resolver.call(field_hash["type"]), + description: field_hash["description"], + null: true, + ) do + if field_hash["args"].any? + loader.build_arguments(self, field_hash["args"], type_resolver) + end + end + end + end + + def build_arguments(arg_owner, args, type_resolver) + args.each do |arg| + kwargs = { + type: type_resolver.call(arg["type"]), + description: arg["description"], + required: false, + } + + if arg["defaultValue"] + default_value_str = arg["defaultValue"] + + dummy_query_str = "query getStuff($var: InputObj = #{default_value_str}) { __typename }" + + # Returns a `GraphQL::Language::Nodes::Document`: + dummy_query_ast = GraphQL.parse(dummy_query_str) + + # Reach into the AST for the default value: + input_value_ast = dummy_query_ast.definitions.first.variables.first.default_value + + kwargs[:default_value] = extract_default_value(default_value_str, input_value_ast) + end + + arg_owner.argument(arg["name"], **kwargs) end end end end end