require "graphql/schema/catchall_middleware" require "graphql/schema/invalid_type_error" require "graphql/schema/middleware_chain" require "graphql/schema/rescue_middleware" require "graphql/schema/possible_types" require "graphql/schema/reduce_types" require "graphql/schema/timeout_middleware" require "graphql/schema/type_expression" require "graphql/schema/type_map" require "graphql/schema/validation" module GraphQL # A GraphQL schema which may be queried with {GraphQL::Query}. # # The {Schema} contains: # # - types for exposing your application # - query analyzers for assessing incoming queries (including max depth & max complexity restrictions) # - execution strategies for running incoming queries # - middleware for interacting with execution # # Schemas start with root types, {Schema#query}, {Schema#mutation} and {Schema#subscription}. # The schema will traverse the tree of fields & types, using those as starting points. # Any undiscoverable types may be provided with the `types` configuration. # # Schemas can restrict large incoming queries with `max_depth` and `max_complexity` configurations. # (These configurations can be overridden by specific calls to {Schema#execute}) # # Schemas can specify how queries should be executed against them. # `query_execution_strategy`, `mutation_execution_strategy` and `subscription_execution_strategy` # each apply to corresponding root types. # # A schema accepts a `Relay::GlobalNodeIdentification` instance for use with Relay IDs. # # @example defining a schema # MySchema = GraphQL::Schema.define do # query QueryType # middleware PermissionMiddleware # rescue_from(ActiveRecord::RecordNotFound) { "Not found" } # # If types are only connected by way of interfaces, they must be added here # orphan_types ImageType, AudioType # end # class Schema include GraphQL::Define::InstanceDefinable accepts_definitions \ :query, :mutation, :subscription, :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy, :max_depth, :max_complexity, :node_identification, :orphan_types, :resolve_type, query_analyzer: -> (schema, analyzer) { schema.query_analyzers << analyzer }, middleware: -> (schema, middleware) { schema.middleware << middleware }, rescue_from: -> (schema, err_class, &block) { schema.rescue_from(err_class, &block)} lazy_defined_attr_accessor \ :query, :mutation, :subscription, :query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy, :max_depth, :max_complexity, :orphan_types, :node_identification, :query_analyzers, :middleware DIRECTIVES = [GraphQL::Directive::SkipDirective, GraphQL::Directive::IncludeDirective] DYNAMIC_FIELDS = ["__type", "__typename", "__schema"] RESOLVE_TYPE_PROC_REQUIRED = -> (obj, ctx) { raise("Schema.resolve_type is undefined, can't resolve type for #{obj.inspect}") } attr_reader :directives, :static_validator # @!attribute node_identification # @return [GraphQL::Relay::GlobalNodeIdentification] the node identification instance for this schema, when using Relay def node_identification=(new_node_ident) new_node_ident.schema = self @node_identification = new_node_ident end # @!attribute [r] middleware # @return [Array<#call>] Middlewares suitable for MiddlewareChain, applied to fields during execution # @param query [GraphQL::ObjectType] the query root for the schema # @param mutation [GraphQL::ObjectType] the mutation root for the schema # @param subscription [GraphQL::ObjectType] the subscription root for the schema # @param max_depth [Integer] maximum query nesting (if it's greater, raise an error) # @param types [Array] additional types to include in this schema def initialize(query: nil, mutation: nil, subscription: nil, max_depth: nil, max_complexity: nil, types: []) if query warn("Schema.new is deprecated, use Schema.define instead") end @query = query @mutation = mutation @subscription = subscription @max_depth = max_depth @max_complexity = max_complexity @orphan_types = types @directives = DIRECTIVES.reduce({}) { |m, d| m[d.name] = d; m } @static_validator = GraphQL::StaticValidation::Validator.new(schema: self) @rescue_middleware = GraphQL::Schema::RescueMiddleware.new @middleware = [@rescue_middleware] @query_analyzers = [] @resolve_type_proc = RESOLVE_TYPE_PROC_REQUIRED # Default to the built-in execution strategy: @query_execution_strategy = GraphQL::Query::SerialExecution @mutation_execution_strategy = GraphQL::Query::SerialExecution @subscription_execution_strategy = GraphQL::Query::SerialExecution end def rescue_from(*args, &block) ensure_defined @rescue_middleware.rescue_from(*args, &block) end def remove_handler(*args, &block) ensure_defined @rescue_middleware.remove_handler(*args, &block) end # @return [GraphQL::Schema::TypeMap] `{ name => type }` pairs of types in this schema def types @types ||= begin ensure_defined all_types = orphan_types + [query, mutation, subscription, GraphQL::Introspection::SchemaType] GraphQL::Schema::ReduceTypes.reduce(all_types.compact) end end # Execute a query on itself. # See {Query#initialize} for arguments. # @return [Hash] query result, ready to be serialized as JSON def execute(*args) query_obj = GraphQL::Query.new(self, *args) query_obj.result end # Resolve field named `field_name` for type `parent_type`. # Handles dynamic fields `__typename`, `__type` and `__schema`, too def get_field(parent_type, field_name) ensure_defined defined_field = parent_type.get_field(field_name) if defined_field defined_field elsif field_name == "__typename" GraphQL::Introspection::TypenameField.create(parent_type) elsif field_name == "__schema" && parent_type == query GraphQL::Introspection::SchemaField.create(self) elsif field_name == "__type" && parent_type == query GraphQL::Introspection::TypeByNameField.create(self.types) else nil end end def type_from_ast(ast_node) ensure_defined GraphQL::Schema::TypeExpression.build_type(self, ast_node) end # TODO: when `resolve_type` is schema level, can this be removed? # @param type_defn [GraphQL::InterfaceType, GraphQL::UnionType] the type whose members you want to retrieve # @return [Array] types which belong to `type_defn` in this schema def possible_types(type_defn) ensure_defined @interface_possible_types ||= GraphQL::Schema::PossibleTypes.new(self) @interface_possible_types.possible_types(type_defn) end def root_type_for_operation(operation) case operation when "query" query when "mutation" mutation when "subscription" subscription else raise ArgumentError, "unknown operation type: #{operation}" end end def execution_strategy_for_operation(operation) case operation when "query" query_execution_strategy when "mutation" mutation_execution_strategy when "subscription" subscription_execution_strategy else raise ArgumentError, "unknown operation type: #{operation}" end end # Determine the GraphQL type for a given object. # This is required for unions and interfaces (include Relay's node interface) # @return [GraphQL::ObjectType] The type for exposing `object` in GraphQL def resolve_type(object, ctx) ensure_defined type_result = @resolve_type_proc.call(object, ctx) if type_result.nil? nil elsif !type_result.is_a?(GraphQL::BaseType) type_str = "#{type_result} (#{type_result.class.name})" raise "resolve_type(#{object}) returned #{type_str}, but it should return a GraphQL type" else type_result end end def resolve_type=(new_resolve_type_proc) ensure_defined @resolve_type_proc = new_resolve_type_proc end end end