# frozen_string_literal: true require 'i18n' require 'zlib' require 'active_model' require 'active_support' require 'active_support/core_ext/module/attribute_accessors_per_thread' require 'active_support/core_ext/string/strip' require 'rails/graphql/version' require 'rails/graphql/uri' ActiveSupport::Inflector.inflections(:en) do |inflect| inflect.acronym 'GraphiQL' inflect.acronym 'GraphQL' end module Rails # = Rails GraphQL # # This implementation tries to be as close as the GraphQL spec as possible, # meaning that this lib shares most of the same names and directions provided # by the GraphQL spec. You can use {Rails::GraphQL::SPEC_VERSION}[rdoc-ref:Rails::GraphQL] # to check which spec is being sued. # # Using ActiveSupport, define all the needed objects but doesn't load them # since it's better to trust on eager_load in order to proper load the objects. # # A very important concept is that Singleton definitions are a direct # connection to a {GraphQL Introspection}[http://spec.graphql.org/June2018/#sec-Introspection], # meaning that to query the introspection is to query everything defined and # associated with the GraphQL objects, the only exception are arguments, # directives and fields: # # * Arguments: They are strictly associated with the object that # defined it, also arguments with the same name doesn't mean they have the # same behavior or configuration. # * Directives: A directive definition belongs to the introspection # and is handled in the Singleton extent. They are handled as instance # whenever a definition or an execution uses them. # * Fields: Many other types and helper containers holds a serie of # fields, which means that fields with the same name will probably behave # differently. module GraphQL extend ActiveSupport::Autoload include ActiveSupport::Configurable # Stores the version of the GraphQL spec used for this implementation SPEC_VERSION = ::GQLParser::VERSION # Just a reusable instances of an empty array and empty hash EMPTY_ARRAY = [].freeze EMPTY_HASH = {}.freeze # Runtime registry for request execution time RuntimeRegistry = Class.new { thread_mattr_accessor :gql_runtime } # Helper class to produce a ActiveSupport-compatible versioned cache key CacheKey = Struct.new(:cache_key, :cache_version) do def inspect cache_version ? +"#{cache_key}[#{cache_version}]" : cache_key end end autoload :ToGQL autoload :Event autoload :Source autoload :Helpers autoload :Callback autoload :GlobalID autoload :Collectors autoload :Alternative autoload :Subscription autoload :Argument autoload :Directive autoload :Field autoload :Introspection autoload :Schema autoload :Type autoload_under :railties do autoload :BaseGenerator autoload :Channel autoload :Controller autoload :ControllerRuntime autoload :LogSubscriber end eager_autoload do autoload :TypeMap autoload :Request end class << self # Access to the type mapper def type_map @@type_map ||= GraphQL::TypeMap.new end # Find the key associated with the given +adapter_name+ def ar_adapter_key(adapter_name) config.ar_adapters.dig(adapter_name, :key) end # This is a little helper to require ActiveRecord adapter specific # configurations def enable_ar_adapter(adapter_name) return if (@@loaded_adapters ||= Set.new).include?(adapter_name) raise ::ArgumentError, (+<<~MSG).squish unless config.ar_adapters.key?(adapter_name) There is no GraphQL mapping for #{adapter_name} ActiveRecord adapter. MSG require(config.ar_adapters.dig(adapter_name, :path)) @@loaded_adapters << adapter_name end # Due to reloader process, adapter settings need to be reloaded def reload_ar_adapters! return unless defined?(@@loaded_adapters) adapters, @@loaded_adapters = @@loaded_adapters, Set.new adapters.map(&method(:enable_ar_adapter)) end # Turn the given object into its string representation as GraphQl # See {ToGQL}[rdoc-ref:Rails::GraphQL::ToGQL] class. def to_gql(object, **xargs) ToGQL.compile(object, **xargs) end alias to_graphql to_gql # A generic helper to not create a new array when just iterating over # something that may or may not be an array def enumerate(value) (value.is_a?(Enumerable) || value.respond_to?(:to_ary)) ? value : value.then end # Load a given +list+ of dependencies from the given +type+ def add_dependencies(type, *list, to: :base) ref = config.known_dependencies raise ArgumentError, (+<<~MSG).squish if (ref = ref[type]).nil? There are no #{type} known dependencies. MSG list = list.flatten.compact.map do |item| next item unless (item = ref[item]).nil? raise ArgumentError, (+<<~MSG).squish Unable to find #{item} as #{type} in known dependencies. MSG end type_map.add_dependencies(list, to: to) end # Returns a set instance with uniq directives from the given list. # If the same directive class is given twice, it will raise an exception, # since they must be uniq within a list of directives. # # Use the others argument to provide a list of already defined directives # so the check can be performed using a +inherited_collection+. # # If a +source+ is provided, then an +:attach+ event will be triggered # for each directive on the givem source element. def directives_to_set(list, others = nil, event = nil, **xargs) return if list.blank? if (source = xargs.delete(:source)).present? location = xargs.delete(:location) || source.try(:directive_location) event ||= GraphQL::Event.new(:attach, source, phase: :definition, **xargs) end others = others&.to_set enumerate(list).each_with_object(Set.new) do |item, result| raise ArgumentError, (+<<~MSG).squish unless item.kind_of?(GraphQL::Directive) The "#{item.class}" is not a valid directive. MSG raise DuplicatedError, (+<<~MSG).squish if others&.any?(item) || result.any?(item) A @#{item.gql_name} directive have already been provided. MSG invalid_location = location.present? && !item.locations.include?(location) raise ArgumentError, (+<<~MSG).squish if invalid_location You cannot use @#{item.gql_name} directive due to location restriction. MSG unless event.nil? begin item.assign_owner!(event.source) event.trigger_object(item) item.validate! rescue => error raise StandardError, (+<<~MSG).squish Unable to #{event.name} the @#{item.gql_name} directive: #{error.message} MSG end end result << item end end end end end require 'rails/graphql/config' require 'rails/graphql/errors' require 'rails/graphql/shortcuts' require 'rails/graphql/railtie' ActiveSupport.run_load_hooks(:graphql, Rails::GraphQL)