graphql-2.6.0/0000755000004100000410000000000015173430257013214 5ustar www-datawww-datagraphql-2.6.0/lib/0000755000004100000410000000000015173430257013762 5ustar www-datawww-datagraphql-2.6.0/lib/generators/0000755000004100000410000000000015173430257016133 5ustar www-datawww-datagraphql-2.6.0/lib/generators/graphql/0000755000004100000410000000000015173430257017571 5ustar www-datawww-datagraphql-2.6.0/lib/generators/graphql/interface_generator.rb0000644000004100000410000000112315173430257024121 0ustar www-datawww-data# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate an interface type by name, # with the specified fields. # # ``` # rails g graphql:interface NamedEntityType name:String! # ``` class InterfaceGenerator < TypeGeneratorBase desc "Create a GraphQL::InterfaceType with the given name and fields" source_root File.expand_path('../templates', __FILE__) private def graphql_type "interface" end def fields custom_fields end end end end graphql-2.6.0/lib/generators/graphql/orm_mutations_base.rb0000644000004100000410000000277115173430257024017 0ustar www-datawww-data# frozen_string_literal: true require 'rails/generators' require 'rails/generators/named_base' require_relative 'core' module Graphql module Generators # TODO: What other options should be supported? # # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name # rails g graphql:mutation CreatePostMutation class OrmMutationsBase < Rails::Generators::NamedBase include Core include Rails::Generators::ResourceHelpers desc "Create a Relay Classic mutation by name" class_option :orm, banner: "NAME", type: :string, required: true, desc: "ORM to generate the controller for" class_option :namespaced_types, type: :boolean, required: false, default: false, banner: "Namespaced", desc: "If the generated types will be namespaced" def create_mutation_file template "mutation_#{operation_type}.erb", File.join(options[:directory], "/mutations/", class_path, "#{file_name}_#{operation_type}.rb") sentinel = /class .*MutationType\s*<\s*[^\s]+?\n/m in_root do path = "#{options[:directory]}/types/mutation_type.rb" invoke "graphql:install:mutation_root" unless File.exist?(path) inject_into_file "#{options[:directory]}/types/mutation_type.rb", " field :#{file_name}_#{operation_type}, mutation: Mutations::#{class_name}#{operation_type.classify}\n", after: sentinel, verbose: false, force: false end end end end end graphql-2.6.0/lib/generators/graphql/templates/0000755000004100000410000000000015173430257021567 5ustar www-datawww-datagraphql-2.6.0/lib/generators/graphql/templates/union.erb0000644000004100000410000000040215173430257023405 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class <%= ruby_class_name %> < Types::BaseUnion <% if custom_fields.any? %> possible_types <%= normalized_possible_types.join(", ") %> <% end %> end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/base_argument.erb0000644000004100000410000000023615173430257025076 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseArgument < GraphQL::Schema::Argument end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/graphql_controller.erb0000644000004100000410000000315115173430257026162 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> class GraphqlController < ApplicationController # If accessing from outside this domain, nullify the session # This allows for outside API access while preventing CSRF attacks, # but you'll have to authenticate your user separately # protect_from_forgery with: :null_session def execute variables = prepare_variables(params[:variables]) query = params[:query] operation_name = params[:operationName] context = { # Query context goes here, for example: # current_user: current_user, } result = <%= schema_name %>.execute(query, variables: variables, context: context, operation_name: operation_name) render json: result rescue StandardError => e raise e unless Rails.env.development? handle_error_in_development(e) end private # Handle variables in form data, JSON body, or a blank value def prepare_variables(variables_param) case variables_param when String if variables_param.present? JSON.parse(variables_param) || {} else {} end when Hash variables_param when ActionController::Parameters variables_param.to_unsafe_hash # GraphQL-Ruby will validate name and type of incoming variables. when nil {} else raise ArgumentError, "Unexpected parameter: #{variables_param}" end end def handle_error_in_development(e) logger.error e.message logger.error e.backtrace.join("\n") render json: { errors: [{ message: e.message, backtrace: e.backtrace }], data: {} }, status: 500 end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/base_scalar.erb0000644000004100000410000000023215173430257024515 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseScalar < GraphQL::Schema::Scalar end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/base_interface.erb0000644000004100000410000000031515173430257025212 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types module BaseInterface include GraphQL::Schema::Interface field_class Types::BaseField end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/base_enum.erb0000644000004100000410000000022615173430257024217 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseEnum < GraphQL::Schema::Enum end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/schema.erb0000644000004100000410000000174615173430257023531 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> class <%= schema_name %> < GraphQL::Schema query(Types::QueryType) <% if options[:batch] %> # GraphQL::Batch setup: use GraphQL::Batch <% else %> # For batch-loading (see https://graphql-ruby.org/dataloader/overview.html) use GraphQL::Dataloader <% end %> # GraphQL-Ruby calls this when something goes wrong while running a query: def self.type_error(err, context) # if err.is_a?(GraphQL::InvalidNullError) # # report to your bug tracker here # return nil # end super end # Union and Interface Resolution def self.resolve_type(abstract_type, obj, ctx) # TODO: Implement this method # to return the correct GraphQL object type for `obj` raise(GraphQL::RequiredImplementationMissingError) end # Limit the size of incoming queries: max_query_string_tokens(5000) # Stop validating when it encounters this many errors: validate_max_errors(100) end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/interface.erb0000644000004100000410000000036715173430257024227 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types module <%= ruby_class_name %> include Types::BaseInterface <% normalized_fields.each do |f| %> <%= f.to_object_field %> <% end %> end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/base_resolver.erb0000644000004100000410000000024215173430257025112 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Resolvers class BaseResolver < GraphQL::Schema::Resolver end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/query_type.erb0000644000004100000410000000064115173430257024470 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class QueryType < Types::BaseObject # Add root-level fields here. # They will be entry points for queries on your schema. # TODO: remove me field :test_field, String, null: false, description: "An example field added by the generator" def test_field "Hello World!" end end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/base_edge.erb0000644000004100000410000000041615173430257024160 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseEdge < Types::BaseObject # add `node` and `cursor` fields, as well as `node_type(...)` override include GraphQL::Types::Relay::EdgeBehaviors end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/loader.erb0000644000004100000410000000101415173430257023523 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Loaders class <%= class_name %> < GraphQL::Batch::Loader # Define `initialize` to store grouping arguments, eg # # Loaders::<%= class_name %>.for(group).load(value) # # def initialize() # end # `keys` contains each key from `.load(key)`. # Find the corresponding values, then # call `fulfill(key, value)` or `fulfill(key, nil)` # for each key. def perform(keys) end end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/create_graphql_detailed_traces.erb0000644000004100000410000000055315173430257030441 0ustar www-datawww-dataclass CreateGraphqlDetailedTraces < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>] def change create_table :graphql_detailed_traces, force: true do |t| t.bigint :begin_ms, null: false t.float :duration_ms, null: false t.binary :trace_data, null: false t.string :operation_name, null: false end end end graphql-2.6.0/lib/generators/graphql/templates/scalar.erb0000644000004100000410000000072615173430257023533 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class <%= ruby_class_name %> < Types::BaseScalar def self.coerce_input(input_value, context) # Override this to prepare a client-provided GraphQL value for your Ruby code input_value end def self.coerce_result(ruby_value, context) # Override this to serialize a Ruby value for the GraphQL response ruby_value.to_s end end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/base_input_object.erb0000644000004100000410000000031315173430257025735 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseInputObject < GraphQL::Schema::InputObject argument_class Types::BaseArgument end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/mutation_delete.erb0000644000004100000410000000127415173430257025447 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Mutations class <%= class_name %>Delete < BaseMutation description "Deletes a <%= file_name %> by ID" field :<%= file_name %>, Types::<%= options[:namespaced_types] ? 'Objects::' : '' %><%= class_name %>Type, null: false argument :id, ID, required: true def resolve(id:) <%= singular_table_name %> = ::<%= orm_class.find(class_name, "id") %> raise GraphQL::ExecutionError.new "Error deleting <%= file_name %>", extensions: <%= singular_table_name %>.errors.to_hash unless <%= orm_instance.destroy %> { <%= file_name %>: <%= singular_table_name %> } end end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/mutation_create.erb0000644000004100000410000000150415173430257025444 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Mutations class <%= class_name %>Create < BaseMutation description "Creates a new <%= file_name %>" field :<%= file_name %>, Types::<%= options[:namespaced_types] ? 'Objects::' : '' %><%= class_name %>Type, null: false argument :<%= file_name %>_input, Types::<%= options[:namespaced_types] ? 'Inputs::' : '' %><%= class_name %>InputType, required: true def resolve(<%= file_name %>_input:) <%= singular_table_name %> = ::<%= orm_class.build(class_name, "**#{file_name}_input") %> raise GraphQL::ExecutionError.new "Error creating <%= file_name %>", extensions: <%= singular_table_name %>.errors.to_hash unless <%= orm_instance.save %> { <%= file_name %>: <%= singular_table_name %> } end end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/node_type.erb0000644000004100000410000000035215173430257024247 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types module NodeType include Types::BaseInterface # Add the `id` field include GraphQL::Types::Relay::NodeBehaviors end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/input.erb0000644000004100000410000000036015173430257023417 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class <%= ruby_class_name %> < Types::BaseInputObject <% normalized_fields.each do |f| %> <%= f.to_input_argument %> <% end %> end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/base_field.erb0000644000004100000410000000027715173430257024344 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseField < GraphQL::Schema::Field argument_class Types::BaseArgument end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/enum.erb0000644000004100000410000000046715173430257023234 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class <%= ruby_class_name %> < Types::BaseEnum description "<%= human_name %> enum" <% prepared_values.each do |v| %> value "<%= v[0] %>"<%= v.length > 1 ? ", value: #{v[1]}" : "" %> <% end %> end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/object.erb0000644000004100000410000000046215173430257023531 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class <%= ruby_class_name %> < Types::BaseObject <% if options.node %> implements GraphQL::Types::Relay::Node <% end %><% normalized_fields.each do |f| %> <%= f.to_object_field %> <% end %> end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/base_union.erb0000644000004100000410000000023015173430257024376 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseUnion < GraphQL::Schema::Union end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/base_connection.erb0000644000004100000410000000046715173430257025421 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseConnection < Types::BaseObject # add `nodes` and `pageInfo` fields, as well as `edge_type(...)` and `node_nullable(...)` overrides include GraphQL::Types::Relay::ConnectionBehaviors end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/base_object.erb0000644000004100000410000000027315173430257024523 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class BaseObject < GraphQL::Schema::Object field_class Types::BaseField end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/mutation.erb0000644000004100000410000000062215173430257024121 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Mutations class <%= class_name %> < BaseMutation # TODO: define return fields # field :post, Types::PostType, null: false # TODO: define arguments # argument :name, String, required: true # TODO: define resolve method # def resolve(name:) # { post: ... } # end end end <% end -%> graphql-2.6.0/lib/generators/graphql/templates/mutation_update.erb0000644000004100000410000000156715173430257025474 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Mutations class <%= class_name %>Update < BaseMutation description "Updates a <%= file_name %> by id" field :<%= file_name %>, Types::<%= options[:namespaced_types] ? 'Objects::' : '' %><%= class_name %>Type, null: false argument :id, ID, required: true argument :<%= file_name %>_input, Types::<%= options[:namespaced_types] ? 'Inputs::' : '' %><%= class_name %>InputType, required: true def resolve(id:, <%= file_name %>_input:) <%= singular_table_name %> = ::<%= orm_class.find(class_name, "id") %> raise GraphQL::ExecutionError.new "Error updating <%= file_name %>", extensions: <%= singular_table_name %>.errors.to_hash unless <%= orm_instance.update("**#{file_name}_input") %> { <%= file_name %>: <%= singular_table_name %> } end end end <% end -%> graphql-2.6.0/lib/generators/graphql/loader_generator.rb0000644000004100000410000000111515173430257023430 0ustar www-datawww-data# frozen_string_literal: true require 'rails/generators' require "rails/generators/named_base" require_relative "core" module Graphql module Generators # @example Generate a `GraphQL::Batch` loader by name. # rails g graphql:loader RecordLoader class LoaderGenerator < Rails::Generators::NamedBase include Core desc "Create a GraphQL::Batch::Loader by name" source_root File.expand_path('../templates', __FILE__) def create_loader_file template "loader.erb", "#{options[:directory]}/loaders/#{file_path}.rb" end end end end graphql-2.6.0/lib/generators/graphql/install/0000755000004100000410000000000015173430257021237 5ustar www-datawww-datagraphql-2.6.0/lib/generators/graphql/install/templates/0000755000004100000410000000000015173430257023235 5ustar www-datawww-datagraphql-2.6.0/lib/generators/graphql/install/templates/base_mutation.erb0000644000004100000410000000050715173430257026563 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Mutations class BaseMutation < GraphQL::Schema::RelayClassicMutation argument_class Types::BaseArgument field_class Types::BaseField input_object_class Types::BaseInputObject object_class Types::BaseObject end end <% end -%> graphql-2.6.0/lib/generators/graphql/install/templates/mutation_type.erb0000644000004100000410000000050415173430257026627 0ustar www-datawww-data# frozen_string_literal: true <% module_namespacing_when_supported do -%> module Types class MutationType < Types::BaseObject # TODO: remove me field :test_field, String, null: false, description: "An example field added by the generator" def test_field "Hello World" end end end <% end -%> graphql-2.6.0/lib/generators/graphql/install/mutation_root_generator.rb0000644000004100000410000000206015173430257026533 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators/base" require_relative "../core" module Graphql module Generators module Install class MutationRootGenerator < Rails::Generators::Base include Core desc "Create mutation base type, mutation root type, and adds the latter to the schema" source_root File.expand_path('../templates', __FILE__) class_option :schema, type: :string, default: nil, desc: "Name for the schema constant (default: {app_name}Schema)" class_option :skip_keeps, type: :boolean, default: false, desc: "Skip .keep files for source control" def generate create_dir("#{options[:directory]}/mutations") template("base_mutation.erb", "#{options[:directory]}/mutations/base_mutation.rb", { skip: true }) template("mutation_type.erb", "#{options[:directory]}/types/mutation_type.rb", { skip: true }) insert_root_type('mutation', 'MutationType') end end end end end graphql-2.6.0/lib/generators/graphql/field_extractor.rb0000644000004100000410000000134115173430257023273 0ustar www-datawww-data# frozen_string_literal: true require 'rails/generators/base' module Graphql module Generators module FieldExtractor def fields columns = [] columns += (klass&.columns&.map { |c| generate_column_string(c) } || []) columns + custom_fields end def generate_column_string(column) name = column.name required = column.null ? "" : "!" type = column_type_string(column) "#{name}:#{required}#{type}" end def column_type_string(column) column.name == "id" ? "ID" : column.type.to_s.camelize end def klass @klass ||= Module.const_get(name.camelize) rescue NameError @klass = nil end end end end graphql-2.6.0/lib/generators/graphql/enum_generator.rb0000644000004100000410000000125115173430257023127 0ustar www-datawww-data# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate an enum type by name, with the given values. # To add a `value:` option, add another value after a `:`. # # ``` # rails g graphql:enum ProgrammingLanguage RUBY PYTHON PERL PERL6:"PERL" # ``` class EnumGenerator < TypeGeneratorBase desc "Create a GraphQL::EnumType with the given name and values" source_root File.expand_path('../templates', __FILE__) private def graphql_type "enum" end def prepared_values custom_fields.map { |v| v.split(":", 2) } end end end end graphql-2.6.0/lib/generators/graphql/relay_generator.rb0000644000004100000410000000072615173430257023305 0ustar www-datawww-data# frozen_string_literal: true require 'rails/generators' require 'rails/generators/base' require_relative 'core' require_relative 'relay' module Graphql module Generators class RelayGenerator < Rails::Generators::Base include Core include Relay desc "Add base types and fields for Relay-style nodes and connections" source_root File.expand_path('../templates', __FILE__) def install_relay super end end end end graphql-2.6.0/lib/generators/graphql/install_generator.rb0000644000004100000410000002010015173430257023623 0ustar www-datawww-data# frozen_string_literal: true require 'rails/generators' require 'rails/generators/base' require_relative 'core' require_relative 'relay' module Graphql module Generators # Add GraphQL to a Rails app with `rails g graphql:install`. # # Setup a folder structure for GraphQL: # # ``` # - app/ # - graphql/ # - resolvers/ # - types/ # - base_argument.rb # - base_field.rb # - base_enum.rb # - base_input_object.rb # - base_interface.rb # - base_object.rb # - base_scalar.rb # - base_union.rb # - query_type.rb # - loaders/ # - mutations/ # - base_mutation.rb # - {app_name}_schema.rb # ``` # # (Add `.gitkeep`s by default, support `--skip-keeps`) # # Add a controller for serving GraphQL queries: # # ``` # app/controllers/graphql_controller.rb # ``` # # Add a route for that controller: # # ```ruby # # config/routes.rb # post "/graphql", to: "graphql#execute" # ``` # # Add ActiveRecord::QueryLogs metadata: # ```ruby # current_graphql_operation: -> { GraphQL::Current.operation_name }, # current_graphql_field: -> { GraphQL::Current.field&.path }, # current_dataloader_source: -> { GraphQL::Current.dataloader_source_class }, # ``` # # Accept a `--batch` option which adds `GraphQL::Batch` setup. # # Use `--skip-graphiql` to skip `graphiql-rails` installation. # # TODO: also add base classes class InstallGenerator < Rails::Generators::Base include Core include Relay desc "Install GraphQL folder structure and boilerplate code" source_root File.expand_path('../templates', __FILE__) class_option :schema, type: :string, default: nil, desc: "Name for the schema constant (default: {app_name}Schema)" class_option :skip_keeps, type: :boolean, default: false, desc: "Skip .keep files for source control" class_option :skip_graphiql, type: :boolean, default: false, desc: "Skip graphiql-rails installation" class_option :skip_mutation_root_type, type: :boolean, default: false, desc: "Skip creation of the mutation root type" class_option :relay, type: :boolean, default: true, desc: "Include installation of Relay conventions (nodes, connections, edges)" class_option :batch, type: :boolean, default: false, desc: "Include GraphQL::Batch installation" class_option :playground, type: :boolean, default: false, desc: "Use GraphQL Playground over Graphiql as IDE" class_option :skip_query_logs, type: :boolean, default: false, desc: "Skip ActiveRecord::QueryLogs hooks in config/application.rb" # These two options are taken from Rails' own generators' class_option :api, type: :boolean, desc: "Preconfigure smaller stack for API only apps" def create_folder_structure create_dir("#{options[:directory]}/types") template("schema.erb", schema_file_path) ["base_object", "base_argument", "base_field", "base_enum", "base_input_object", "base_interface", "base_scalar", "base_union"].each do |base_type| template("#{base_type}.erb", "#{options[:directory]}/types/#{base_type}.rb") end # All resolvers are defined as living in their own module, including this class. template("base_resolver.erb", "#{options[:directory]}/resolvers/base_resolver.rb") # Note: You can't have a schema without the query type, otherwise introspection breaks template("query_type.erb", "#{options[:directory]}/types/query_type.rb") insert_root_type('query', 'QueryType') invoke "graphql:install:mutation_root" unless options.skip_mutation_root_type? template("graphql_controller.erb", "app/controllers/graphql_controller.rb") route('post "/graphql", to: "graphql#execute"') if options[:batch] gem("graphql-batch") create_dir("#{options[:directory]}/loaders") end if options.api? say("Skipped graphiql, as this rails project is API only") say(" You may wish to use GraphiQL.app for development: https://github.com/skevy/graphiql-app") elsif !options[:skip_graphiql] # `gem(...)` uses `gsub_file(...)` under the hood, which is a no-op for `rails destroy...` (when `behavior == :revoke`). # So handle that case by calling `gsub_file` with `force: true`. if behavior == :invoke && !File.read(Rails.root.join("Gemfile")).include?("graphiql-rails") gem("graphiql-rails", group: :development) elsif behavior == :revoke gemfile_pattern = /\n\s*gem ('|")graphiql-rails('|"), :?group(:| =>) :development/ gsub_file Rails.root.join("Gemfile"), gemfile_pattern, "", { force: true } end # This is a little cheat just to get cleaner shell output: log :route, 'graphiql-rails' shell.mute do # Rails 5.2 has better support for `route`? if Rails::VERSION::STRING > "5.2" route <<-RUBY if Rails.env.development? mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" end RUBY else route <<-RUBY if Rails.env.development? mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" end RUBY end end end if options[:playground] gem("graphql_playground-rails", group: :development) log :route, 'graphql_playground-rails' shell.mute do if Rails::VERSION::STRING > "5.2" route <<-RUBY if Rails.env.development? mount GraphqlPlayground::Rails::Engine, at: "/playground", graphql_path: "/graphql" end RUBY else route <<-RUBY if Rails.env.development? mount GraphqlPlayground::Rails::Engine, at: "/playground", graphql_path: "/graphql" end RUBY end end end if options[:relay] install_relay end if !options[:skip_query_logs] config_file = "config/application.rb" current_app_rb = File.read(Rails.root.join(config_file)) existing_log_tags_pattern = /config.active_record.query_log_tags = \[\n?(\s*:[a-z_]+,?\s*\n?|\s*#[^\]]*\n)*/m existing_log_tags = existing_log_tags_pattern.match(current_app_rb) if existing_log_tags && behavior == :invoke code = <<-RUBY # GraphQL-Ruby query log tags: current_graphql_operation: -> { GraphQL::Current.operation_name }, current_graphql_field: -> { GraphQL::Current.field&.path }, current_dataloader_source: -> { GraphQL::Current.dataloader_source_class }, RUBY if !existing_log_tags.to_s.end_with?(",") code = ",\n#{code} " end # Try to insert this code _after_ any plain symbol entries in the array of query log tags: after_code = existing_log_tags_pattern else code = <<-RUBY config.active_record.query_log_tags_enabled = true config.active_record.query_log_tags = [ # Rails query log tags: :application, :controller, :action, :job, # GraphQL-Ruby query log tags: current_graphql_operation: -> { GraphQL::Current.operation_name }, current_graphql_field: -> { GraphQL::Current.field&.path }, current_dataloader_source: -> { GraphQL::Current.dataloader_source_class }, ] RUBY after_code = "class Application < Rails::Application\n" end insert_into_file(config_file, code, after: after_code) end if gemfile_modified? say "Gemfile has been modified, make sure you `bundle install`" end end private def gemfile_modified? @gemfile_modified end def gem(*args) @gemfile_modified = true super(*args) end end end end graphql-2.6.0/lib/generators/graphql/mutation_generator.rb0000644000004100000410000000214115173430257024022 0ustar www-datawww-data# frozen_string_literal: true require 'rails/generators' require 'rails/generators/named_base' require_relative 'core' module Graphql module Generators # TODO: What other options should be supported? # # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name # rails g graphql:mutation CreatePostMutation class MutationGenerator < Rails::Generators::NamedBase include Core desc "Create a Relay Classic mutation by name" source_root File.expand_path('../templates', __FILE__) def create_mutation_file template "mutation.erb", File.join(options[:directory], "/mutations/", class_path, "#{file_name}.rb") sentinel = /class .*MutationType\s*<\s*[^\s]+?\n/m in_root do path = "#{options[:directory]}/types/mutation_type.rb" invoke "graphql:install:mutation_root" unless File.exist?(path) inject_into_file "#{options[:directory]}/types/mutation_type.rb", " field :#{file_name}, mutation: Mutations::#{class_name}\n", after: sentinel, verbose: false, force: false end end end end end graphql-2.6.0/lib/generators/graphql/union_generator.rb0000644000004100000410000000154415173430257023320 0ustar www-datawww-data# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate a union type by name # with the specified member types. # # ``` # rails g graphql:union SearchResultType ImageType AudioType # ``` class UnionGenerator < TypeGeneratorBase desc "Create a GraphQL::UnionType with the given name and possible types" source_root File.expand_path('../templates', __FILE__) argument :possible_types, type: :array, default: [], banner: "type type ...", desc: "Possible types for this union (expressed as Ruby or GraphQL)" private def graphql_type "union" end def normalized_possible_types custom_fields.map { |t| self.class.normalize_type_expression(t, mode: :ruby)[0] } end end end end graphql-2.6.0/lib/generators/graphql/mutation_update_generator.rb0000644000004100000410000000107715173430257025373 0ustar www-datawww-data# frozen_string_literal: true require_relative 'orm_mutations_base' module Graphql module Generators # TODO: What other options should be supported? # # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name # rails g graphql:mutation UpdatePostMutation class MutationUpdateGenerator < OrmMutationsBase desc "Scaffold a Relay Classic ORM update mutation for the given model class" source_root File.expand_path('../templates', __FILE__) private def operation_type "update" end end end end graphql-2.6.0/lib/generators/graphql/object_generator.rb0000644000004100000410000000270115173430257023432 0ustar www-datawww-data# frozen_string_literal: true require 'generators/graphql/type_generator' require 'generators/graphql/field_extractor' module Graphql module Generators # Generate an object type by name, # with the specified fields. # # ``` # rails g graphql:object PostType name:String! # ``` # # Add the Node interface with `--node`. class ObjectGenerator < TypeGeneratorBase desc "Create a GraphQL::ObjectType with the given name and fields." \ "If the given type name matches an existing ActiveRecord model, the generated type will automatically include fields for the models database columns." source_root File.expand_path('../templates', __FILE__) include FieldExtractor class_option :node, type: :boolean, default: false, desc: "Include the Relay Node interface" def self.normalize_type_expression(type_expression, mode:, null: true) case type_expression.camelize when "Text", "Citext" ["String", null] when "Decimal" ["Float", null] when "DateTime", "Datetime" ["GraphQL::Types::ISO8601DateTime", null] when "Date" ["GraphQL::Types::ISO8601Date", null] when "Json", "Jsonb", "Hstore" ["GraphQL::Types::JSON", null] else super end end private def graphql_type "object" end end end end graphql-2.6.0/lib/generators/graphql/input_generator.rb0000644000004100000410000000236315173430257023327 0ustar www-datawww-data# frozen_string_literal: true require 'generators/graphql/type_generator' require 'generators/graphql/field_extractor' module Graphql module Generators # Generate an input type by name, # with the specified fields. # # ``` # rails g graphql:object PostType name:string! # ``` class InputGenerator < TypeGeneratorBase desc "Create a GraphQL::InputObjectType with the given name and fields" source_root File.expand_path('../templates', __FILE__) include FieldExtractor def self.normalize_type_expression(type_expression, mode:, null: true) case type_expression.camelize when "Text", "Citext" ["String", null] when "Decimal" ["Float", null] when "DateTime", "Datetime" ["GraphQL::Types::ISO8601DateTime", null] when "Date" ["GraphQL::Types::ISO8601Date", null] when "Json", "Jsonb", "Hstore" ["GraphQL::Types::JSON", null] else super end end private def graphql_type "input" end def type_ruby_name super.gsub(/Type\z/, "InputType") end def type_file_name super.gsub(/_type\z/, "_input_type") end end end end graphql-2.6.0/lib/generators/graphql/detailed_trace_generator.rb0000644000004100000410000000542615173430257025124 0ustar www-datawww-data# frozen_string_literal: true require 'rails/generators/active_record' module Graphql module Generators class DetailedTraceGenerator < ::Rails::Generators::Base include ::Rails::Generators::Migration desc "Install GraphQL::Tracing::DetailedTrace for your schema" source_root File.expand_path('../templates', __FILE__) class_option :redis, type: :boolean, default: false, desc: "Use Redis for persistence instead of ActiveRecord" def self.next_migration_number(dirname) ::ActiveRecord::Generators::Base.next_migration_number(dirname) end def install_detailed_traces schema_glob = File.expand_path("app/graphql/*_schema.rb", destination_root) schema_file = Dir.glob(schema_glob).first if !schema_file raise ArgumentError, "Failed to find schema definition file (checked: #{schema_glob.inspect})" end schema_file_match = /( *)class ([A-Za-z:]+) < GraphQL::Schema/.match(File.read(schema_file)) schema_name = schema_file_match[2] indent = schema_file_match[1] + " " if !options.redis? migration_template 'create_graphql_detailed_traces.erb', 'db/migrate/create_graphql_detailed_traces.rb' end log :add_detailed_traces_plugin sentinel = /< GraphQL::Schema\s*\n/m code = <<-RUBY #{indent}use GraphQL::Tracing::DetailedTrace#{options.redis? ? ", redis: raise(\"TODO: pass a connection to a persistent redis database\")" : ""}, limit: 50 #{indent}# When this returns true, DetailedTrace will trace the query #{indent}# Could use `query.context`, `query.selected_operation_name`, `query.query_string` here #{indent}# Could call out to Flipper, etc #{indent}def self.detailed_trace?(query) #{indent} rand <= 0.000_1 # one in ten thousand #{indent}end RUBY in_root do inject_into_file schema_file, code, after: sentinel, force: false end routes_source = File.read(File.expand_path("config/routes.rb", destination_root)) already_has_dashboard = routes_source.include?("GraphQL::Dashboard") || routes_source.include?("Schema.dashboard") || routes_source.include?("GraphQL::Pro::Routes::Lazy") if (!already_has_dashboard || behavior == :revoke) log :route, "GraphQL::Dashboard" shell.mute do route <<~RUBY # TODO: add authorization to this route and expose it in production # See https://graphql-ruby.org/pro/dashboard.html#authorizing-the-dashboard if Rails.env.development? mount GraphQL::Dashboard, at: "/graphql/dashboard", schema: #{schema_name.inspect} end RUBY end gem("google-protobuf") end end end end end graphql-2.6.0/lib/generators/graphql/relay.rb0000644000004100000410000000503415173430257021234 0ustar www-datawww-data# frozen_string_literal: true module Graphql module Generators module Relay def install_relay # Add Node, `node(id:)`, and `nodes(ids:)` template("node_type.erb", "#{options[:directory]}/types/node_type.rb") in_root do fields = <<-RUBY field :node, Types::NodeType, null: true, description: "Fetches an object given its ID." do argument :id, ID, required: true, description: "ID of the object." end def node(id:) context.schema.object_from_id(id, context) end field :nodes, [Types::NodeType, null: true], null: true, description: "Fetches a list of objects given a list of IDs." do argument :ids, [ID], required: true, description: "IDs of the objects." end def nodes(ids:) ids.map { |id| context.schema.object_from_id(id, context) } end RUBY inject_into_file "#{options[:directory]}/types/query_type.rb", fields, after: /class .*QueryType\s*<\s*[^\s]+?\n/m, force: false end # Add connections and edges template("base_connection.erb", "#{options[:directory]}/types/base_connection.rb") template("base_edge.erb", "#{options[:directory]}/types/base_edge.rb") connectionable_type_files = { "#{options[:directory]}/types/base_object.rb" => /class .*BaseObject\s*<\s*[^\s]+?\n/m, "#{options[:directory]}/types/base_union.rb" => /class .*BaseUnion\s*<\s*[^\s]+?\n/m, "#{options[:directory]}/types/base_interface.rb" => /include GraphQL::Schema::Interface\n/m, } in_root do connectionable_type_files.each do |type_class_file, sentinel| inject_into_file type_class_file, " connection_type_class(Types::BaseConnection)\n", after: sentinel, force: false inject_into_file type_class_file, " edge_type_class(Types::BaseEdge)\n", after: sentinel, force: false end end # Add object ID hooks & connection plugin schema_code = <<-RUBY # Relay-style Object Identification: # Return a string UUID for `object` def self.id_from_object(object, type_definition, query_ctx) # For example, use Rails' GlobalID library (https://github.com/rails/globalid): object.to_gid_param end # Given a string UUID, find the object def self.object_from_id(global_id, query_ctx) # For example, use Rails' GlobalID library (https://github.com/rails/globalid): GlobalID.find(global_id) end RUBY inject_into_file schema_file_path, schema_code, before: /^end\n/m, force: false end end end end graphql-2.6.0/lib/generators/graphql/scalar_generator.rb0000644000004100000410000000072315173430257023433 0ustar www-datawww-data# frozen_string_literal: true require 'generators/graphql/type_generator' module Graphql module Generators # Generate a scalar type by given name. # # ``` # rails g graphql:scalar Date # ``` class ScalarGenerator < TypeGeneratorBase desc "Create a GraphQL::ScalarType with the given name" source_root File.expand_path('../templates', __FILE__) private def graphql_type "scalar" end end end end graphql-2.6.0/lib/generators/graphql/type_generator.rb0000644000004100000410000001100615173430257023143 0ustar www-datawww-data# frozen_string_literal: true require 'rails/generators' require 'rails/generators/base' require 'graphql' require 'active_support' require 'active_support/core_ext/string/inflections' require_relative 'core' module Graphql module Generators class TypeGeneratorBase < Rails::Generators::NamedBase include Core class_option :namespaced_types, type: :boolean, required: false, default: false, banner: "Namespaced", desc: "If the generated types will be namespaced" argument :custom_fields, type: :array, default: [], banner: "name:type name:type ...", desc: "Fields for this object (type may be expressed as Ruby or GraphQL)" attr_accessor :graphql_type def create_type_file template "#{graphql_type}.erb", "#{options[:directory]}/types#{subdirectory}/#{type_file_name}.rb" end # Take a type expression in any combination of GraphQL or Ruby styles # and return it in a specified output style # TODO: nullability / list with `mode: :graphql` doesn't work # @param type_expresson [String] # @param mode [Symbol] # @param null [Boolean] # @return [(String, Boolean)] The type expression, followed by `null:` value def self.normalize_type_expression(type_expression, mode:, null: true) if type_expression.start_with?("!") normalize_type_expression(type_expression[1..-1], mode: mode, null: false) elsif type_expression.end_with?("!") normalize_type_expression(type_expression[0..-2], mode: mode, null: false) elsif type_expression.start_with?("[") && type_expression.end_with?("]") name, is_null = normalize_type_expression(type_expression[1..-2], mode: mode, null: null) ["[#{name}]", is_null] elsif type_expression.end_with?("Type") normalize_type_expression(type_expression[0..-5], mode: mode, null: null) elsif type_expression.start_with?("Types::") normalize_type_expression(type_expression[7..-1], mode: mode, null: null) elsif type_expression.start_with?("types.") normalize_type_expression(type_expression[6..-1], mode: mode, null: null) else case mode when :ruby case type_expression when "Int" ["Integer", null] when "Integer", "Float", "Boolean", "String", "ID" [type_expression, null] else ["Types::#{type_expression.camelize}Type", null] end when :graphql [type_expression.camelize, null] else raise "Unexpected normalize mode: #{mode}" end end end private # @return [String] The user-provided type name, normalized to Ruby code def type_ruby_name @type_ruby_name ||= self.class.normalize_type_expression(name, mode: :ruby)[0] end # @return [String] The user-provided type name, as a GraphQL name def type_graphql_name @type_graphql_name ||= self.class.normalize_type_expression(name, mode: :graphql)[0] end # @return [String] The user-provided type name, as a file name (without extension) def type_file_name @type_file_name ||= "#{type_graphql_name}Type".underscore end # @return [Array] User-provided fields, in `(name, Ruby type name)` pairs def normalized_fields @normalized_fields ||= fields.map { |f| name, raw_type = f.split(":", 2) type_expr, null = self.class.normalize_type_expression(raw_type, mode: :ruby) NormalizedField.new(name, type_expr, null) } end def ruby_class_name class_prefix = if options[:namespaced_types] "#{graphql_type.pluralize.camelize}::" else "" end @ruby_class_name || class_prefix + type_ruby_name.sub(/^Types::/, "") end def subdirectory if options[:namespaced_types] "/#{graphql_type.pluralize}" else "" end end class NormalizedField def initialize(name, type_expr, null) @name = name @type_expr = type_expr @null = null end def to_object_field "field :#{@name}, #{@type_expr}#{@null ? '' : ', null: false'}" end def to_input_argument "argument :#{@name}, #{@type_expr}, required: false" end end end end end graphql-2.6.0/lib/generators/graphql/core.rb0000644000004100000410000000316215173430257021050 0ustar www-datawww-data# frozen_string_literal: true require 'rails/generators/base' module Graphql module Generators module Core def self.included(base) base.send( :class_option, :directory, type: :string, default: "app/graphql", desc: "Directory where generated files should be saved" ) end def insert_root_type(type, name) log :add_root_type, type sentinel = /< GraphQL::Schema\s*\n/m in_root do if File.exist?(schema_file_path) inject_into_file schema_file_path, " #{type}(Types::#{name})\n", after: sentinel, verbose: false, force: false end end end def schema_file_path "#{options[:directory]}/#{schema_name.underscore}.rb" end def create_dir(dir) empty_directory(dir) if !options[:skip_keeps] create_file("#{dir}/.keep") end end def module_namespacing_when_supported if defined?(module_namespacing) module_namespacing { yield } else yield end end private def schema_name @schema_name ||= begin if options[:schema] options[:schema] else "#{parent_name}Schema" end end end def parent_name require File.expand_path("config/application", destination_root) if Rails.application.class.respond_to?(:module_parent_name) Rails.application.class.module_parent_name else Rails.application.class.parent_name end end end end end graphql-2.6.0/lib/generators/graphql/mutation_delete_generator.rb0000644000004100000410000000107715173430257025353 0ustar www-datawww-data# frozen_string_literal: true require_relative 'orm_mutations_base' module Graphql module Generators # TODO: What other options should be supported? # # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name # rails g graphql:mutation DeletePostMutation class MutationDeleteGenerator < OrmMutationsBase desc "Scaffold a Relay Classic ORM delete mutation for the given model class" source_root File.expand_path('../templates', __FILE__) private def operation_type "delete" end end end end graphql-2.6.0/lib/generators/graphql/mutation_create_generator.rb0000644000004100000410000000107715173430257025354 0ustar www-datawww-data# frozen_string_literal: true require_relative 'orm_mutations_base' module Graphql module Generators # TODO: What other options should be supported? # # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name # rails g graphql:mutation CreatePostMutation class MutationCreateGenerator < OrmMutationsBase desc "Scaffold a Relay Classic ORM create mutation for the given model class" source_root File.expand_path('../templates', __FILE__) private def operation_type "create" end end end end graphql-2.6.0/lib/graphql.rb0000644000004100000410000001171715173430257015754 0ustar www-datawww-data# frozen_string_literal: true require "delegate" require "json" require "set" require "singleton" require "forwardable" require "fiber/storage" if RUBY_VERSION < "3.2.0" require "graphql/autoload" module GraphQL extend Autoload # Load all `autoload`-configured classes, and also eager-load dependents who have autoloads of their own. def self.eager_load! super Query.eager_load! Types.eager_load! Schema.eager_load! end class Error < StandardError end # This error is raised when GraphQL-Ruby encounters a situation # that it *thought* would never happen. Please report this bug! class InvariantError < Error def initialize(message) message += " This is probably a bug in GraphQL-Ruby, please report this error on GitHub: https://github.com/rmosolgo/graphql-ruby/issues/new?template=bug_report.md" super(message) end end class RequiredImplementationMissingError < Error end class << self def default_parser @default_parser ||= GraphQL::Language::Parser end attr_writer :default_parser end # Turn a query string or schema definition into an AST # @param graphql_string [String] a GraphQL query string or schema definition # @return [GraphQL::Language::Nodes::Document] def self.parse(graphql_string, trace: GraphQL::Tracing::NullTrace, filename: nil, max_tokens: nil) default_parser.parse(graphql_string, trace: trace, filename: filename, max_tokens: max_tokens) end # Read the contents of `filename` and parse them as GraphQL # @param filename [String] Path to a `.graphql` file containing IDL or query # @return [GraphQL::Language::Nodes::Document] def self.parse_file(filename) content = File.read(filename) default_parser.parse(content, filename: filename) end # @return [Array] def self.scan(graphql_string) default_parser.scan(graphql_string) end def self.parse_with_racc(string, filename: nil, trace: GraphQL::Tracing::NullTrace) warn "`GraphQL.parse_with_racc` is deprecated; GraphQL-Ruby no longer uses racc for parsing. Call `GraphQL.parse` or `GraphQL::Language::Parser.parse` instead." GraphQL::Language::Parser.parse(string, filename: filename, trace: trace) end def self.scan_with_ruby(graphql_string) GraphQL::Language::Lexer.tokenize(graphql_string) end NOT_CONFIGURED = Object.new.freeze private_constant :NOT_CONFIGURED module EmptyObjects EMPTY_HASH = {}.freeze EMPTY_ARRAY = [].freeze end class << self # If true, the parser should raise when an integer or float is followed immediately by an identifier (instead of a space or punctuation) attr_accessor :reject_numbers_followed_by_names end self.reject_numbers_followed_by_names = false autoload :ExecutionError, "graphql/execution_error" autoload :RuntimeTypeError, "graphql/runtime_type_error" autoload :UnresolvedTypeError, "graphql/unresolved_type_error" autoload :InvalidNullError, "graphql/invalid_null_error" autoload :AnalysisError, "graphql/analysis_error" autoload :CoercionError, "graphql/coercion_error" autoload :InvalidNameError, "graphql/invalid_name_error" autoload :IntegerDecodingError, "graphql/integer_decoding_error" autoload :IntegerEncodingError, "graphql/integer_encoding_error" autoload :StringEncodingError, "graphql/string_encoding_error" autoload :DateEncodingError, "graphql/date_encoding_error" autoload :DurationEncodingError, "graphql/duration_encoding_error" autoload :TypeKinds, "graphql/type_kinds" autoload :NameValidator, "graphql/name_validator" autoload :Language, "graphql/language" autoload :Analysis, "graphql/analysis" autoload :Tracing, "graphql/tracing" autoload :Dig, "graphql/dig" autoload :Execution, "graphql/execution" autoload :Pagination, "graphql/pagination" autoload :Schema, "graphql/schema" autoload :Query, "graphql/query" autoload :Dataloader, "graphql/dataloader" autoload :Types, "graphql/types" autoload :StaticValidation, "graphql/static_validation" autoload :Execution, "graphql/execution" autoload :Introspection, "graphql/introspection" autoload :Relay, "graphql/relay" autoload :Subscriptions, "graphql/subscriptions" autoload :ParseError, "graphql/parse_error" autoload :Backtrace, "graphql/backtrace" autoload :RuntimeError, "graphql/runtime_error" autoload :UnauthorizedError, "graphql/unauthorized_error" autoload :UnauthorizedEnumValueError, "graphql/unauthorized_enum_value_error" autoload :UnauthorizedFieldError, "graphql/unauthorized_field_error" autoload :LoadApplicationObjectFailedError, "graphql/load_application_object_failed_error" autoload :Testing, "graphql/testing" autoload :Current, "graphql/current" if defined?(::Rails::Engine) # This needs to be defined before Rails runs `add_routing_paths`, # otherwise GraphQL::Dashboard's routes won't have been gathered for loading # when that initializer runs. require 'graphql/dashboard' end end require "graphql/version" require "graphql/railtie" if defined? Rails::Railtie graphql-2.6.0/lib/graphql/0000755000004100000410000000000015173430257015420 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/backtrace/0000755000004100000410000000000015173430257017337 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/backtrace/traced_error.rb0000644000004100000410000000332515173430257022342 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Backtrace # When {Backtrace} is enabled, raised errors are wrapped with {TracedError}. class TracedError < GraphQL::Error # @return [Array] Printable backtrace of GraphQL error context attr_reader :graphql_backtrace # @return [GraphQL::Query::Context] The context at the field where the error was raised attr_reader :context MESSAGE_TEMPLATE = <<-MESSAGE Unhandled error during GraphQL execution: %{cause_message} %{cause_backtrace} %{cause_backtrace_more} Use #cause to access the original exception (including #cause.backtrace). GraphQL Backtrace: %{graphql_table} MESSAGE # This many lines of the original Ruby backtrace # are included in the message CAUSE_BACKTRACE_PREVIEW_LENGTH = 10 def initialize(err, current_ctx) @context = current_ctx backtrace = Backtrace.new(current_ctx, value: err) @graphql_backtrace = backtrace.to_a cause_backtrace_preview = err.backtrace.first(CAUSE_BACKTRACE_PREVIEW_LENGTH).join("\n ") cause_backtrace_remainder_length = err.backtrace.length - CAUSE_BACKTRACE_PREVIEW_LENGTH cause_backtrace_more = if cause_backtrace_remainder_length < 0 "" elsif cause_backtrace_remainder_length == 1 "... and 1 more line\n" else "... and #{cause_backtrace_remainder_length} more lines\n" end message = MESSAGE_TEMPLATE % { cause_message: err.message, cause_backtrace: cause_backtrace_preview, cause_backtrace_more: cause_backtrace_more, graphql_table: backtrace.inspect, } super(message) end end end end graphql-2.6.0/lib/graphql/backtrace/table.rb0000644000004100000410000001261015173430257020753 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Backtrace # A class for turning a context into a human-readable table or array class Table MIN_COL_WIDTH = 4 MAX_COL_WIDTH = 100 HEADERS = [ "Loc", "Field", "Object", "Arguments", "Result", ] def initialize(context, value:) @context = context @override_value = value end # @return [String] A table layout of backtrace with metadata def to_table @to_table ||= render_table(rows) end # @return [Array] An array of position + field name entries def to_backtrace @to_backtrace ||= begin backtrace = rows.map { |r| "#{r[0]}: #{r[1]}" } # skip the header entry backtrace.shift backtrace end end private def rows @rows ||= begin query = @context.query query_ctx = @context runtime_inst = query_ctx.namespace(:interpreter_runtime)[:runtime] result = runtime_inst.instance_variable_get(:@response) rows = [] result_path = [] last_part = nil path = @context.current_path path.each do |path_part| value = value_at(runtime_inst, result_path) if result_path.empty? name = query.selected_operation.operation_type || "query" if (n = query.selected_operation_name) name += " #{n}" end args = query.variables else name = result.graphql_field.path args = result.graphql_arguments end object = result.graphql_parent ? result.graphql_parent.graphql_application_value : result.graphql_application_value object = object.object.inspect rows << [ result.ast_node.position.join(":"), name, "#{object}", args.to_h.inspect, inspect_result(value), ] result_path << path_part if path_part == path.last last_part = path_part else result = result[path_part] end end object = result.graphql_application_value.object.inspect ast_node = nil result.graphql_selections.each do |s| found_ast_node = find_ast_node(s, last_part) if found_ast_node ast_node = found_ast_node break end end if ast_node field_defn = query.get_field(result.graphql_result_type, ast_node.name) args = query.arguments_for(ast_node, field_defn).to_h field_path = field_defn.path if ast_node.alias field_path += " as #{ast_node.alias}" end rows << [ ast_node.position.join(":"), field_path, "#{object}", args.inspect, inspect_result(@override_value) ] end rows << HEADERS rows.reverse! rows end end def find_ast_node(node, last_part) return nil unless node return node if node.respond_to?(:alias) && node.respond_to?(:name) && (node.alias == last_part || node.name == last_part) return nil unless node.respond_to?(:selections) return nil if node.selections.nil? || node.selections.empty? node.selections.each do |child| child_ast_node = find_ast_node(child, last_part) return child_ast_node if child_ast_node end nil end # @return [String] def render_table(rows) max = Array.new(HEADERS.length, MIN_COL_WIDTH) rows.each do |row| row.each_with_index do |col, idx| col_len = col.length max_len = max[idx] if col_len > max_len if col_len > MAX_COL_WIDTH max[idx] = MAX_COL_WIDTH else max[idx] = col_len end end end end table = "".dup last_col_idx = max.length - 1 rows.each do |row| table << row.map.each_with_index do |col, idx| max_len = max[idx] if idx < last_col_idx col = col.ljust(max_len) end if col.length > max_len col = col[0, max_len - 3] + "..." end col end.join(" | ") table << "\n" end table end def value_at(runtime, path) response = runtime.final_result path.each do |key| response && (response = response[key]) end response end def inspect_result(obj) case obj when Hash "{" + obj.map do |key, val| "#{key}: #{inspect_truncated(val)}" end.join(", ") + "}" when Array "[" + obj.map { |v| inspect_truncated(v) }.join(", ") + "]" else inspect_truncated(obj) end end def inspect_truncated(obj) case obj when Hash "{...}" when Array "[...]" when GraphQL::Execution::Lazy "(unresolved)" else "#{obj.inspect}" end end end end end graphql-2.6.0/lib/graphql/execution_error.rb0000644000004100000410000000353215173430257021164 0ustar www-datawww-data# frozen_string_literal: true module GraphQL # If a field's resolve function returns a {ExecutionError}, # the error will be inserted into the response's `"errors"` key # and the field will resolve to `nil`. class ExecutionError < GraphQL::RuntimeError # @return [GraphQL::Language::Nodes::Field] the field where the error occurred def ast_node ast_nodes&.first end def ast_node=(new_node) @ast_nodes = [new_node] end attr_accessor :ast_nodes # @return [String] an array describing the JSON-path into the execution # response which corresponds to this error. attr_accessor :path # @return [Hash] Optional data for error objects # @deprecated Use `extensions` instead of `options`. The GraphQL spec # recommends that any custom entries in an error be under the # `extensions` key. attr_accessor :options # @return [Hash] Optional custom data for error objects which will be added # under the `extensions` key. attr_accessor :extensions def initialize(message, ast_node: nil, ast_nodes: nil, options: nil, extensions: nil) @ast_nodes = ast_nodes || [ast_node] @options = options @extensions = extensions super(message) end def finalize_graphql_result(query, result_data, key) result_data[key] = nil end # @return [Hash] An entry for the response's "errors" key def to_h hash = { "message" => message, } if ast_node hash["locations"] = @ast_nodes.map { |a| { "line" => a.line, "column" => a.col } } end if path hash["path"] = path end if options hash.merge!(options) end if extensions hash["extensions"] = extensions.each_with_object({}) { |(key, value), ext| ext[key.to_s] = value } end hash end end end graphql-2.6.0/lib/graphql/analysis_error.rb0000644000004100000410000000014715173430257021003 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class AnalysisError < GraphQL::ExecutionError end end graphql-2.6.0/lib/graphql/name_validator.rb0000644000004100000410000000047715173430257020742 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class NameValidator VALID_NAME_REGEX = /^[_a-zA-Z][_a-zA-Z0-9]*$/ def self.validate!(name) name = name.is_a?(String) ? name : name.to_s raise GraphQL::InvalidNameError.new(name, VALID_NAME_REGEX) unless name.match?(VALID_NAME_REGEX) end end end graphql-2.6.0/lib/graphql/relay/0000755000004100000410000000000015173430257016534 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/relay/range_add.rb0000644000004100000410000000406215173430257020767 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Relay # This provides some isolation from `GraphQL::Relay` internals. # # Given a list of items and a new item, it will provide a connection and an edge. # # The connection doesn't receive outside arguments, so the list of items # should be ordered and paginated before providing it here. # # @example Adding a comment to list of comments # post = Post.find(args[:post_id]) # comments = post.comments # new_comment = comments.build(body: args[:body]) # new_comment.save! # # range_add = GraphQL::Relay::RangeAdd.new( # parent: post, # collection: comments, # item: new_comment, # context: context, # ) # # response = { # post: post, # comments_connection: range_add.connection, # new_comment_edge: range_add.edge, # } class RangeAdd attr_reader :edge, :connection, :parent # @param collection [Object] The list of items to wrap in a connection # @param item [Object] The newly-added item (will be wrapped in `edge_class`) # @param context [GraphQL::Query::Context] The surrounding `ctx`, will be passed to the connection # @param parent [Object] The owner of `collection`, will be passed to the connection if provided # @param edge_class [Class] The class to wrap `item` with (defaults to the connection's edge class) def initialize(collection:, item:, context:, parent: nil, edge_class: nil) conn_class = context.schema.connections.wrapper_for(collection) # The rest will be added by ConnectionExtension @connection = conn_class.new(collection, parent: parent, context: context, edge_class: edge_class) # Check if this connection supports it, to support old versions of GraphQL-Pro @edge = if @connection.respond_to?(:range_add_edge) @connection.range_add_edge(item) else @connection.edge_class.new(item, @connection) end @parent = parent end end end end graphql-2.6.0/lib/graphql/rake_task.rb0000644000004100000410000001164615173430257017721 0ustar www-datawww-data# frozen_string_literal: true require "fileutils" require "rake" require "graphql/rake_task/validate" module GraphQL # A rake task for dumping a schema as IDL or JSON. # # By default, schemas are looked up by name as constants using `schema_name:`. # You can provide a `load_schema` function to return your schema another way. # # Use `load_context:` and `visible?` to dump schemas under certain visibility constraints. # # @example Dump a Schema to .graphql + .json files # require "graphql/rake_task" # GraphQL::RakeTask.new(schema_name: "MySchema") # # # $ rake graphql:schema:dump # # Schema IDL dumped to ./schema.graphql # # Schema JSON dumped to ./schema.json # # @example Invoking the task from Ruby # require "rake" # Rake::Task["graphql:schema:dump"].invoke # # @example Providing arguments to build the introspection query # require "graphql/rake_task" # GraphQL::RakeTask.new(schema_name: "MySchema", include_is_one_of: true) class RakeTask include Rake::DSL DEFAULT_OPTIONS = { namespace: "graphql", dependencies: nil, schema_name: nil, load_schema: ->(task) { Object.const_get(task.schema_name) }, load_context: ->(task) { {} }, directory: ".", idl_outfile: "schema.graphql", json_outfile: "schema.json", include_deprecated_args: true, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false } # @return [String] Namespace for generated tasks attr_writer :namespace def rake_namespace @namespace end # @return [Array] attr_accessor :dependencies # @return [String] By default, used to find the schema as a constant. # @see {#load_schema} for loading a schema another way attr_accessor :schema_name # @return [<#call(task)>] A proc for loading the target GraphQL schema attr_accessor :load_schema # @return [<#call(task)>] A callable for loading the query context attr_accessor :load_context # @return [String] target for IDL task attr_accessor :idl_outfile # @return [String] target for JSON task attr_accessor :json_outfile # @return [String] directory for IDL & JSON files attr_accessor :directory # @return [Boolean] Options for additional fields in the introspection query JSON response # @see GraphQL::Schema.as_json attr_accessor :include_deprecated_args, :include_schema_description, :include_is_repeatable, :include_specified_by_url, :include_is_one_of # Set the parameters of this task by passing keyword arguments # or assigning attributes inside the block def initialize(options = {}) all_options = DEFAULT_OPTIONS.merge(options) all_options.each do |k, v| self.public_send("#{k}=", v) end if block_given? yield(self) end define_task end private # Use the provided `method_name` to generate a string from the specified schema # then write it to `file`. def write_outfile(method_name, file) schema = @load_schema.call(self) context = @load_context.call(self) result = case method_name when :to_json schema.to_json( include_is_one_of: include_is_one_of, include_deprecated_args: include_deprecated_args, include_is_repeatable: include_is_repeatable, include_specified_by_url: include_specified_by_url, include_schema_description: include_schema_description, context: context ) when :to_definition schema.to_definition(context: context) else raise ArgumentError, "Unexpected schema dump method: #{method_name.inspect}" end dir = File.dirname(file) FileUtils.mkdir_p(dir) if !result.end_with?("\n") result += "\n" end File.write(file, result) end def idl_path File.join(@directory, @idl_outfile) end def json_path File.join(@directory, @json_outfile) end def load_rails_environment_if_defined if Rake::Task.task_defined?('environment') Rake::Task['environment'].invoke end end # Use the Rake DSL to add tasks def define_task namespace(@namespace) do namespace("schema") do desc("Dump the schema to IDL in #{idl_path}") task :idl => @dependencies do load_rails_environment_if_defined write_outfile(:to_definition, idl_path) puts "Schema IDL dumped into #{idl_path}" end desc("Dump the schema to JSON in #{json_path}") task :json => @dependencies do load_rails_environment_if_defined write_outfile(:to_json, json_path) puts "Schema JSON dumped into #{json_path}" end desc("Dump the schema to JSON and IDL") task :dump => [:idl, :json] end end end end end graphql-2.6.0/lib/graphql/testing/0000755000004100000410000000000015173430257017075 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/testing/helpers.rb0000644000004100000410000001470015173430257021066 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Testing module Helpers # @param schema_class [Class] # @return [Module] A helpers module which always uses the given schema def self.for(schema_class) SchemaHelpers.for(schema_class) end class Error < GraphQL::Error end class TypeNotVisibleError < Error def initialize(type_name:) message = "`#{type_name}` should be `visible?` this field resolution and `context`, but it was not" super(message) end end class FieldNotVisibleError < Error def initialize(type_name:, field_name:) message = "`#{type_name}.#{field_name}` should be `visible?` for this resolution, but it was not" super(message) end end class TypeNotDefinedError < Error def initialize(type_name:) message = "No type named `#{type_name}` is defined; choose another type name or define this type." super(message) end end class FieldNotDefinedError < Error def initialize(type_name:, field_name:) message = "`#{type_name}` has no field named `#{field_name}`; pick another name or define this field." super(message) end end def run_graphql_field(schema, field_path, object, arguments: {}, context: {}, ast_node: nil, lookahead: nil, visibility_profile: nil) type_name, *field_names = field_path.split(".") dummy_query = GraphQL::Query.new(schema, "{ __typename }", context: context, visibility_profile: visibility_profile) query_context = dummy_query.context dataloader = query_context.dataloader object_type = dummy_query.types.type(type_name) # rubocop:disable Development/ContextIsPassedCop if object_type graphql_result = object field_names.each do |field_name| inner_object = graphql_result dataloader.run_isolated { graphql_result = object_type.wrap(inner_object, query_context) } if graphql_result.nil? return nil end visible_field = dummy_query.types.field(object_type, field_name) # rubocop:disable Development/ContextIsPassedCop if visible_field dataloader.run_isolated { query_context[:current_field] = visible_field field_args = visible_field.coerce_arguments(graphql_result, arguments, query_context) field_args = schema.sync_lazy(field_args) if !visible_field.extras.empty? extra_args = {} visible_field.extras.each do |extra| extra_args[extra] = case extra when :ast_node ast_node ||= GraphQL::Language::Nodes::Field.new(name: visible_field.graphql_name) when :lookahead lookahead ||= begin ast_node ||= GraphQL::Language::Nodes::Field.new(name: visible_field.graphql_name) Execution::Lookahead.new( query: dummy_query, ast_nodes: [ast_node], field: visible_field, ) end else raise ArgumentError, "This extra isn't supported in `run_graphql_field` yet: `#{extra.inspect}`. Open an issue on GitHub to request it: https://github.com/rmosolgo/graphql-ruby/issues/new" end end field_args = field_args.merge_extras(extra_args) end graphql_result = visible_field.resolve(graphql_result, field_args.keyword_arguments, query_context) graphql_result = schema.sync_lazy(graphql_result) } object_type = visible_field.type.unwrap elsif object_type.all_field_definitions.any? { |f| f.graphql_name == field_name } raise FieldNotVisibleError.new(field_name: field_name, type_name: type_name) else raise FieldNotDefinedError.new(type_name: type_name, field_name: field_name) end end graphql_result else unfiltered_type = schema.use_visibility_profile? ? schema.visibility.get_type(type_name) : schema.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop if unfiltered_type raise TypeNotVisibleError.new(type_name: type_name) else raise TypeNotDefinedError.new(type_name: type_name) end end end def with_resolution_context(schema, type:, object:, context:{}, visibility_profile: nil) resolution_context = ResolutionAssertionContext.new( self, schema: schema, type_name: type, object: object, context: context, visibility_profile: visibility_profile, ) yield(resolution_context) end class ResolutionAssertionContext def initialize(test, type_name:, object:, schema:, context:, visibility_profile:) @test = test @type_name = type_name @object = object @schema = schema @context = context @visibility_profile = visibility_profile end attr_reader :visibility_profile def run_graphql_field(field_name, arguments: {}) if @schema @test.run_graphql_field(@schema, "#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context, visibility_profile: @visibility_profile) else @test.run_graphql_field("#{@type_name}.#{field_name}", @object, arguments: arguments, context: @context, visibility_profile: @visibility_profile) end end end module SchemaHelpers include Helpers def run_graphql_field(field_path, object, arguments: {}, context: {}, visibility_profile: nil) super(@@schema_class_for_helpers, field_path, object, arguments: arguments, context: context, visibility_profile: visibility_profile) end def with_resolution_context(*args, **kwargs, &block) # schema will be added later super(nil, *args, **kwargs, &block) end def self.for(schema_class) Module.new do include SchemaHelpers @@schema_class_for_helpers = schema_class end end end end end end graphql-2.6.0/lib/graphql/testing/mock_action_cable.rb0000644000004100000410000000703515173430257023043 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Testing # A stub implementation of ActionCable. # Any methods to support the mock backend have `mock` in the name. # # @example Configuring your schema to use MockActionCable in the test environment # class MySchema < GraphQL::Schema # # Use MockActionCable in test: # use GraphQL::Subscriptions::ActionCableSubscriptions, # action_cable: Rails.env.test? ? GraphQL::Testing::MockActionCable : ActionCable # end # # @example Clearing old data before each test # setup do # GraphQL::Testing::MockActionCable.clear_mocks # end # # @example Using MockActionCable in a test case # # Create a channel to use in the test, pass it to GraphQL # mock_channel = GraphQL::Testing::MockActionCable.get_mock_channel # ActionCableTestSchema.execute("subscription { newsFlash { text } }", context: { channel: mock_channel }) # # # Trigger a subscription update # ActionCableTestSchema.subscriptions.trigger(:news_flash, {}, {text: "After yesterday's rain, someone stopped on Rio Road to help a box turtle across five lanes of traffic"}) # # # Check messages on the channel # expected_msg = { # result: { # "data" => { # "newsFlash" => { # "text" => "After yesterday's rain, someone stopped on Rio Road to help a box turtle across five lanes of traffic" # } # } # }, # more: true, # } # assert_equal [expected_msg], mock_channel.mock_broadcasted_messages # class MockActionCable class MockChannel def initialize @mock_broadcasted_messages = [] end # @return [Array] Payloads "sent" to this channel by GraphQL-Ruby attr_reader :mock_broadcasted_messages # Called by ActionCableSubscriptions. Implements a Rails API. def stream_from(stream_name, coder: nil, &block) # Rails uses `coder`, we don't block ||= ->(msg) { @mock_broadcasted_messages << msg } MockActionCable.mock_stream_for(stream_name).add_mock_channel(self, block) end end # Used by mock code # @api private class MockStream def initialize @mock_channels = {} end def add_mock_channel(channel, handler) @mock_channels[channel] = handler end def mock_broadcast(message) @mock_channels.each do |channel, handler| handler && handler.call(message) end end end class << self # Call this before each test run to make sure that MockActionCable's data is empty def clear_mocks @mock_streams = {} end # Implements Rails API def server self end # Implements Rails API def broadcast(stream_name, message) stream = @mock_streams[stream_name] stream && stream.mock_broadcast(message) end # Used by mock code def mock_stream_for(stream_name) @mock_streams[stream_name] ||= MockStream.new end # Use this as `context[:channel]` to simulate an ActionCable channel # # @return [GraphQL::Testing::MockActionCable::MockChannel] def get_mock_channel MockChannel.new end # @return [Array] Streams that currently have subscribers def mock_stream_names @mock_streams.keys end end end end end graphql-2.6.0/lib/graphql/introspection.rb0000644000004100000410000000536415173430257020655 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Introspection def self.query(include_deprecated_args: false, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false) # The introspection query to end all introspection queries, copied from # https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js <<-QUERY.gsub(/\n{2,}/, "\n") query IntrospectionQuery { __schema { #{include_schema_description ? "description" : ""} queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations #{include_is_repeatable ? "isRepeatable" : ""} args#{include_deprecated_args ? '(includeDeprecated: true)' : ''} { ...InputValue } } } } fragment FullType on __Type { kind name description #{include_specified_by_url ? "specifiedByURL" : ""} #{include_is_one_of ? "isOneOf" : ""} fields(includeDeprecated: true) { name description args#{include_deprecated_args ? '(includeDeprecated: true)' : ''} { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields#{include_deprecated_args ? '(includeDeprecated: true)' : ''} { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue #{include_deprecated_args ? 'isDeprecated' : ''} #{include_deprecated_args ? 'deprecationReason' : ''} } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } } QUERY end end end require "graphql/introspection/base_object" require "graphql/introspection/input_value_type" require "graphql/introspection/enum_value_type" require "graphql/introspection/type_kind_enum" require "graphql/introspection/type_type" require "graphql/introspection/field_type" require "graphql/introspection/directive_location_enum" require "graphql/introspection/directive_type" require "graphql/introspection/schema_type" require "graphql/introspection/introspection_query" require "graphql/introspection/dynamic_fields" require "graphql/introspection/entry_points" graphql-2.6.0/lib/graphql/type_kinds.rb0000644000004100000410000000603215173430257020117 0ustar www-datawww-data# frozen_string_literal: true module GraphQL # Type kinds are the basic categories which a type may belong to (`Object`, `Scalar`, `Union`...) module TypeKinds # These objects are singletons, eg `GraphQL::TypeKinds::UNION`, `GraphQL::TypeKinds::SCALAR`. class TypeKind attr_reader :name, :description def initialize(name, abstract: false, leaf: false, fields: false, wraps: false, input: false, description: nil) @name = name @abstract = abstract @fields = fields @wraps = wraps @input = input @leaf = leaf @composite = fields? || abstract? @description = description freeze end # Does this TypeKind have multiple possible implementers? # @deprecated Use `abstract?` instead of `resolves?`. def resolves?; @abstract; end # Is this TypeKind abstract? def abstract?; @abstract; end # Does this TypeKind have queryable fields? def fields?; @fields; end # Does this TypeKind modify another type? def wraps?; @wraps; end # Is this TypeKind a valid query input? def input?; @input; end def to_s; @name; end # Is this TypeKind a primitive value? def leaf?; @leaf; end # Is this TypeKind composed of many values? def composite?; @composite; end def scalar? self == TypeKinds::SCALAR end def object? self == TypeKinds::OBJECT end def interface? self == TypeKinds::INTERFACE end def union? self == TypeKinds::UNION end def enum? self == TypeKinds::ENUM end def input_object? self == TypeKinds::INPUT_OBJECT end def list? self == TypeKinds::LIST end def non_null? self == TypeKinds::NON_NULL end end TYPE_KINDS = [ SCALAR = TypeKind.new("SCALAR", input: true, leaf: true, description: 'Indicates this type is a scalar.'), OBJECT = TypeKind.new("OBJECT", fields: true, description: 'Indicates this type is an object. `fields` and `interfaces` are valid fields.'), INTERFACE = TypeKind.new("INTERFACE", abstract: true, fields: true, description: 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.'), UNION = TypeKind.new("UNION", abstract: true, description: 'Indicates this type is a union. `possibleTypes` is a valid field.'), ENUM = TypeKind.new("ENUM", input: true, leaf: true, description: 'Indicates this type is an enum. `enumValues` is a valid field.'), INPUT_OBJECT = TypeKind.new("INPUT_OBJECT", input: true, description: 'Indicates this type is an input object. `inputFields` is a valid field.'), LIST = TypeKind.new("LIST", wraps: true, description: 'Indicates this type is a list. `ofType` is a valid field.'), NON_NULL = TypeKind.new("NON_NULL", wraps: true, description: 'Indicates this type is a non-null. `ofType` is a valid field.'), ] end end graphql-2.6.0/lib/graphql/parse_error.rb0000644000004100000410000000077515173430257020301 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class ParseError < GraphQL::Error attr_reader :line, :col, :query def initialize(message, line, col, query, filename: nil) if filename message += " (#{filename})" end super(message) @line = line @col = col @query = query end def to_h locations = line ? [{ "line" => line, "column" => col }] : [] { "message" => message, "locations" => locations, } end end end graphql-2.6.0/lib/graphql/pagination.rb0000644000004100000410000000044415173430257020100 0ustar www-datawww-data# frozen_string_literal: true require "graphql/pagination/array_connection" require "graphql/pagination/active_record_relation_connection" require "graphql/pagination/connections" require "graphql/pagination/mongoid_relation_connection" require "graphql/pagination/sequel_dataset_connection" graphql-2.6.0/lib/graphql/dashboard/0000755000004100000410000000000015173430257017347 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/statics_controller.rb0000644000004100000410000000145215173430257023613 0ustar www-datawww-data# frozen_string_literal: true module Graphql class Dashboard < Rails::Engine class StaticsController < ApplicationController skip_forgery_protection # Use an explicit list of files to avoid any chance of reading other files from disk STATICS = {} [ "icon.png", "header-icon.png", "charts.min.css", "dashboard.css", "dashboard.js", "bootstrap-5.3.3.min.css", "bootstrap-5.3.3.min.js", ].each do |static_file| STATICS[static_file] = File.expand_path("../statics/#{static_file}", __FILE__) end def show expires_in 1.year, public: true if (filepath = STATICS[params[:id]]) render file: filepath else head :not_found end end end end end graphql-2.6.0/lib/graphql/dashboard/views/0000755000004100000410000000000015173430257020504 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/views/layouts/0000755000004100000410000000000015173430257022204 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/views/layouts/graphql/0000755000004100000410000000000015173430257023642 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/views/layouts/graphql/dashboard/0000755000004100000410000000000015173430257025571 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb0000644000004100000410000001334415173430257031536 0ustar www-datawww-data "> GraphQL Dashboard <%= content_for?(:title) ? " · #{content_for(:title)}" : "" %> " media="screen"> " media="screen"> " media="screen"> <%= csrf_meta_tags %>
<% flash.each do |flash_type, flash_message| %>
<% end %>
<%= yield %>

GraphQL-Ruby v<%= GraphQL::VERSION %> · <%= schema_class %>

graphql-2.6.0/lib/graphql/dashboard/views/graphql/0000755000004100000410000000000015173430257022142 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/0000755000004100000410000000000015173430257024071 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/landings/0000755000004100000410000000000015173430257025670 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb0000644000004100000410000000072515173430257030311 0ustar www-datawww-data<% content_for(:title, "Landing") %>

Welcome to the GraphQL-Ruby Dashboard

Click the links above to see data about your schema (<%= schema_class %>).

graphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/limiters/0000755000004100000410000000000015173430257025721 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/0000755000004100000410000000000015173430257027551 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb0000644000004100000410000000540215173430257032167 0ustar www-datawww-data<% content_for(:title, @title) %> <% if @install_path %>

<%= @title %>

It looks like this limiter isn't installed yet. Install it now.

<% else %>

<%= @title %>

<%= link_to("This Hour", graphql_dashboard.limiters_limiter_path(params[:name], chart: "hour"), class: "btn btn-sm btn-outline-primary #{@chart_mode == "hour" ? "active" : "inactive"}", params: { chart: "hour" }) %> <%= link_to("Today", graphql_dashboard.limiters_limiter_path(params[:name], chart: "day"), class: "btn btn-sm btn-outline-primary #{@chart_mode == "day" ? "active" : "inactive"}", params: { chart: "day" }) %> <%= link_to("This Month", graphql_dashboard.limiters_limiter_path(params[:name], chart: "month"), class: "btn btn-sm btn-outline-primary #{@chart_mode == "month" ? "active" : "inactive"}", params: { chart: "month" }) %>
<%= form_tag graphql_dashboard.limiters_limiter_path(params[:name], chart: @chart_mode), method: "patch" do %> <%= submit_tag "#{@current_soft ? "Disable" : "Enable"} Soft Limiting", class: "btn btn-sm btn-outline-warning" %> <% end %>
<% @histogram.columns.each_with_index do |col, col_idx| %> <% col.values.each_with_index do |value, val_idx| %> <% end %> <% end %>
Date Limited Requests Unlimited Requests
<%= col.label %> <%= value.formatted_value %> <%= value.label %>: <%= value.formatted_value %>
<%= col.label %>
<%= content_tag "style", nonce: @csp_nonce do %> <% @histogram.columns.each_with_index do |col, col_idx| %> <% col_max = @histogram.max_column_value.to_f %> <% col.values.each_with_index do |val, val_idx| %> #data-<%= col_idx %>-<%= val_idx %> { --size: <%= val.value / col_max %>} <% end %> <% end %> <% end %> <% end %> graphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb0000644000004100000410000000063415173430257030370 0ustar www-datawww-data<% content_for(:title, "Operation Store") %>

<%= @component_header_html %>

<%= @component_message_html %>

graphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/0000755000004100000410000000000015173430257027000 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/0000755000004100000410000000000015173430257030301 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb0000644000004100000410000000335515173430257033053 0ustar www-datawww-data<% content_for(:title, "Subscriptions - Topics") %>

<%= pluralize(@all_topics_count, "Subscription Topic") %>

<%= button_tag "Clear All", class: "btn btn-outline-danger", data: { subscriptions_delete_all: graphql_dashboard.subscriptions_clear_all_path } %>
<% if @all_topics_count == 0 %> <% else %> <% @topics.each do |topic| %> <% end %> <% end %>
Name # Subscriptions Last Triggered At
There aren't any subscriptions right now.
<%= link_to(topic.name, graphql_dashboard.subscriptions_topic_path(name: topic.name)) %> <%= topic.subscriptions_count %> <%= topic.last_triggered_at || "--" %>
<% if @page > 1 %> <%= link_to("« prev", graphql_dashboard.subscriptions_topics_path(per_page: params[:per_page], page: @page - 1), class: "btn btn-outline-secondary") %> <% else %> <% end %>
<% if @has_next_page %> <%= link_to("next »", graphql_dashboard.subscriptions_topics_path(per_page: params[:per_page], page: @page + 1), class: "btn btn-outline-secondary") %> <% else %> <% end %>
graphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb0000644000004100000410000000246315173430257032723 0ustar www-datawww-data<%= content_for(:title, "Subscriptions - #{params[:name]}") %>

Topic: <%= params[:name] %>

Last triggered: <%= @topic_last_triggered_at || "none" %>

<%= pluralize(@subscriptions_count, "Subscription") %>

<% if @show_broadcast_subscribers_count %><% end %> <% @subscriptions.each do |subscription| %> <% if @show_broadcast_subscribers_count %><% end %> <% end %>
Subscription ID Created At Subscribed? Broadcast?Subscribers
<%= link_to(subscription[:id], graphql_dashboard.subscriptions_subscription_path(subscription[:id])) %> <%= subscription[:created_at] %> <%= subscription[:still_subscribed] ? "YES" : "NO" %> <%= subscription[:is_broadcast] ? "YES" : "NO" %><%= subscription[:subscribers_count] %>
graphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/0000755000004100000410000000000015173430257031707 5ustar www-datawww-data././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootgraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erbgraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.er0000644000004100000410000000267515173430257034174 0ustar www-datawww-data<% content_for(:title, "Subscription #{params[:id]}") %>

Subscription: <%= params[:id] %>

<% if @query_data.nil? %>

This subscription was not found or is no longer active.

<% else %>

Created at <%= @query_data[:created_at] %>, last triggered at <%= @query_data[:last_triggered_at] || "--" %>

Subscribed? <%= @still_subscribed ? "YES" : "NO" %>

Broadcast? <%= @is_broadcast ? "YES" : "NO" %> <% if @is_broadcast %> <% if @subscribers_count.nil? %> This subscription may have multiple subscribers. <% else %> (<%= pluralize(@subscribers_count, "subscriber") %>) <% end %> <% end %>

Context:

<%= @query_data[:context].inspect %>

Variables:

<%= @query_data[:variables].inspect %>

Operation Name:

<%= @query_data[:operation_name].inspect %>

Query String:

<%= textarea_tag "_source", @query_data[:query_string], class: "graphql-highlight form-control", disabled: true, rows: @query_data[:query_string].count("\n") + 1 %>
<% end %> graphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/0000755000004100000410000000000015173430257027205 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/0000755000004100000410000000000015173430257030466 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb0000644000004100000410000000343715173430257033241 0ustar www-datawww-data<% content_for(:title, "Profiles") %>

Detailed Profiles

<%= button_tag "Delete All Traces", class: "btn btn-sm btn-outline-danger", data: { perfetto_delete_all: graphql_dashboard.delete_all_detailed_traces_traces_path } %>
<% if @traces.empty? %> <% end %> <% @traces.each do |trace| %> <% end %>
Operation Duration (ms) Timestamp Open in Perfetto UI
No traces saved yet. Read about saving traces <%= link_to "in the docs", "https://graphql-ruby.org/queries/tracing#detailed-profiles" %>.
<%= trace.operation_name %> <%= trace.duration_ms.round(2) %> <%= Time.at(trace.begin_ms / 1000.0).strftime("%Y-%m-%d %H:%M:%S.%L") %> <%= link_to "View ↗", "#", data: { perfetto_open: trace.operation_name, perfetto_path: graphql_dashboard.detailed_traces_trace_path(trace.id) } %> <%= link_to "Delete", "#", data: { perfetto_delete: graphql_dashboard.detailed_traces_trace_path(trace.id) }, class: "text-danger" %>
<% if @last && @traces.size >= @last %> <%= link_to("Previous >", graphql_dashboard.detailed_traces_traces_path(last: @last, before: @traces.last.begin_ms), class: "btn btn-outline-primary") %> <% end %>
graphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/operation_store/0000755000004100000410000000000015173430257027305 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/0000755000004100000410000000000015173430257030746 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb0000644000004100000410000000136215173430257033332 0ustar www-datawww-data<% content_for(:title, "Edit #{@client.name}") %>

Edit <%= @client.name %>

<%= render partial: "graphql/dashboard/operation_store/clients/form" %>

Delete <%= @client.name %>

If you delete this client, it will no longer be able to use stored operations.

There is no way to undo this action.

<%= form_tag(graphql_dashboard.operation_store_client_path(name: @client.name), method: "delete") do %> <%= submit_tag "Permanently Delete #{@client.name.inspect}", class: "btn btn-outline-danger" %> <% end %>
graphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb0000644000004100000410000000554315173430257033521 0ustar www-datawww-data<% content_for(:title, "Clients") %>

<%= pluralize(@clients_page.total_count, "Client") %>

<%= link_to("New Client", graphql_dashboard.new_operation_store_client_path, class: "btn btn-outline-primary") %>
<% if @clients_page.total_count == 0 %> <% else %> <% @clients_page.items.each do |client| %> <% end %> <% end %>
<%= link_to("Name", graphql_dashboard.operation_store_clients_path, params: { order_by: "name", order_dir: ((@order_by == "name" && @order_dir != :desc) ? "desc" : "asc" )}) %> Operations Created At Last Updated <%= link_to("Last Used At", graphql_dashboard.operation_store_clients_path, params: { order_by: "last_used_at", order_dir: ((@order_by == "last_used_at" && @order_dir != :desc) ? "desc": "asc")}) %>
To get started, create a <%= link_to "new client", graphql_dashboard.new_operation_store_client_path %>, then <%= link_to "sync operations", "https://graphql-ruby.org/operation_store/client_workflow.html" %> to your schema.
<%= link_to(client.name, graphql_dashboard.edit_operation_store_client_path(name: client.name)) %> <%= link_to(graphql_dashboard.operation_store_client_operations_path(client_name: client.name)) do %> <%= client.operations_count %><% if client.archived_operations_count > 0 %> (<%= client.archived_operations_count %> archived)<% end %> <% end %> <%= client.created_at %> <% if client.operations_count == 0 %> — <% else %> <%= client.last_synced_at %> <% end %> <%= client.last_used_at || "—" %>
<% if @clients_page.prev_page %> <%= link_to("« prev", graphql_dashboard.operation_store_clients_path(per_page: params[:per_page], page: @clients_page.prev_page), class: "btn btn-outline-secondary") %> <% else %> <% end %>
<% if @clients_page.next_page %> <%= link_to("next »", graphql_dashboard.operation_store_clients_path(per_page: params[:per_page], page: @clients_page.next_page), class: "btn btn-outline-secondary") %> <% else %> <% end %>
graphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb0000644000004100000410000000213115173430257033502 0ustar www-datawww-data<%= form_tag((@client.persisted? ? graphql_dashboard.operation_store_client_path(name: @client.name) : graphql_dashboard.operation_store_clients_path), method: (@client.persisted? ? "patch" : "post")) do %>
<%= text_field_tag "client[name]", @client.name, class: "form-control", disabled: @client.persisted? %>
a unique identifier for this owner of persisted operations
<%= textarea_tag "client[secret]", @client.secret, class: "form-control" %>
authentication credential for sync transactions
<%= submit_tag "Save", class: "btn btn-outline-primary" %>
<%= link_to "Back", graphql_dashboard.operation_store_clients_path, class: "btn btn-outline-secondary" %>
<% end %> graphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb0000644000004100000410000000027615173430257033201 0ustar www-datawww-data<% content_for(:title, "New Client") %>

New Client

<%= render partial: "graphql/dashboard/operation_store/clients/form" %> graphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/0000755000004100000410000000000015173430257032145 5ustar www-datawww-data././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootgraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erbgraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html0000644000004100000410000000275015173430257034146 0ustar www-datawww-data<% content_for(:title, "Index#{@search_term ? " - #{@search_term}" : ""}") %>

Schema Index

<%= pluralize(@index_entries_page.total_count, @search_term ? "result" : "entry") %>

<%= text_field_tag "q", @search_term, class: "form-control", placeholder: "Find types, fields, arguments, or enum values" %>
<% @index_entries_page.items.each do |entry| %> <% end %>
Name # Usages Last Used At
<%= link_to(entry.name, graphql_dashboard.operation_store_index_entry_path(name: entry.name)) %> <%= entry.references_count %><% if entry.archived_references_count.nil? %>(missing data - call `YourSchema.operation_store.reindex` to repair index)<% elsif entry.archived_references_count > 0 %> (<%= entry.archived_references_count %> archived)<% end %> <%= entry.last_used_at %>
<%= # render_partial("_pagination") %> ././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootgraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erbgraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.0000644000004100000410000000173415173430257034076 0ustar www-datawww-data<% name = @chain.pop %> <% content_for(:title, "Index - #{@entry.name}") %>
<%= link_to("Index", graphql_dashboard.operation_store_index_entries_path) %> <% @chain.each do |c| %> > <%= link_to(c.split(".").last, graphql_dashboard.operation_store_index_entry_path(name: c)) %> <% end %> > <%= name.split(".").last %>

<%= name %>

Used By: <% if @operations.any? %>

    <% @operations.each do |operation| %>
  • <%= link_to(operation.name, graphql_dashboard.operation_store_operation_path(digest: operation.digest)) %><% if operation.is_archived %> (archived)<% end %>
  • <% end %>
<% else %> none <% end %>

Last used at: <%= @entry.last_used_at || "—" %>

graphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/0000755000004100000410000000000015173430257031470 5ustar www-datawww-data././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootgraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erbgraphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.er0000644000004100000410000000720015173430257034071 0ustar www-datawww-data
<% if @client_operations %> <%= content_for(:title, "#{params[:client_name]} Operations") %>

<%= params[:client_name] %>

<% else %> <%= content_for(:title, "Operations") %>
<% end %>
<% if @client_operations %> <% else %> <% end %> <% if @operations_page.total_count == 0 %> <% else %> <% @operations_page.items.each do |operation| %> <% if @client_operations %> <% else %> <% end %> <% end %> <% end %>
<%= link_to "Name", graphql_dashboard.operation_store_operations_path({ order_by: "name", order_dir: params[:order_dir] == "asc" ? "desc" : "asc" }) %>Alias# ClientsDigest <%= link_to "Last Used At", graphql_dashboard.operation_store_operations_path({ order_by: "last_used_at", order_dir: params[:order_dir] == "asc" ? "desc" : "asc" }) %>
<% if @is_archived %> <%= link_to "Archived operations", "https://graphql-ruby.org/operation_store/server_management.html#archiving-and-deleting-data" %> will appear here. <% else %> Add your first stored operations with <%= link_to "sync", "https://graphql-ruby.org/operation_store/client_workflow.html" %>. <% end %>
<%= link_to(operation.name, graphql_dashboard.operation_store_operation_path(digest: operation.digest)) %><%= operation.operation_alias %><%= operation.clients_count %><%= operation.digest %> <%= operation.last_used_at %> <%= check_box_tag("value", (@client_operations ? operation.operation_alias : operation.digest), class: "archive-check form-check-input") %>
graphql-2.6.0/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb0000644000004100000410000000404015173430257034103 0ustar www-datawww-data<% content_for(:title, "View #{params[:digest]}") %> <% if @operation.nil? %>

No stored operation found for <%= params[:digest] %>

<% else %>

<%= @operation.name %> <% if @operation.is_archived %> (archived)<% end %>

Aliases

<% if @client_operations.empty? %>

None

<% else %>
    <% @client_operations.each do |cl_op| %>
  • <%= cl_op.operation_alias %> <%= link_to(cl_op.client_name, graphql_dashboard.operation_store_client_operations_path(client_name: cl_op.client_name)) %> <%= cl_op.is_archived ? " (archived)" : "" %>
  • <% end %>
<% end %>

Last Used At

<%= @operation.last_used_at %>

Source

<%= textarea_tag "_source", @graphql_source, class: "graphql-highlight form-control", disabled: true, rows: @graphql_source.count("\n") + 1 %>

References

    <% @entries.each do |entry| %>
  • <%= link_to(entry.name, graphql_dashboard.operation_store_index_entry_path(name: entry.name)) %>
  • <% end %>

Digest

<%= @operation.digest %>

Minified Source

<%= textarea_tag "_source", @operation.body, class: "graphql-highlight form-control", disabled: true, rows: @operation.body.count("\n") + 1 %>
<% end %> graphql-2.6.0/lib/graphql/dashboard/landings_controller.rb0000644000004100000410000000025715173430257023742 0ustar www-datawww-data# frozen_string_literal: true module Graphql class Dashboard < Rails::Engine class LandingsController < ApplicationController def show end end end end graphql-2.6.0/lib/graphql/dashboard/statics/0000755000004100000410000000000015173430257021021 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dashboard/statics/icon.png0000644000004100000410000001156015173430257022462 0ustar www-datawww-dataPNG  IHDR``w87IDATx^] U( I'fvOc PQq, pvղ,+듣bD3]z #rȲD]BHB@[=ݙ鮞ib}{fnݺI$bĈ#F1bĈ#F1^XWΙS`o75[7XUOJKJ[, >'sd}It"rAߎNgo67%>8;o`Ӽ%>PdV;` lGiշ)vW9190dK{ }q،{ܝO_{#7*'mw=k)o=Of248' 75vLj ;:/^m?yj 8nIy}e(jGyO%z9l[~0]29y7 ѡb|(8ȿ9R`cПAb׏DAJƾa}aλ)3c8 GAb;嵷ON[f^->ǩ}ϐ`6WҖ7pEz8]適mBQM+ӍHm~BVd`9ލyc4`rB*0zּ~4r2)lk^oc?,8Εs6Ёq#VɒtyaB7=P@9-k|m܍&ͨgi,7v8Ej,q.t PN[y'}B5 ݰ4P }[ZlmW_-s8~**SA< X̸֗g\7C's$v;_ apd8)-~zh8p.(I+jQL$8tܜKqɁ/0pr-o?nP-XK|}B ތ!!2V-ĺΙC=SfL)@Yc 7^G0Zٺ-qDe0@qWiv8ǁyE:/5BQRO`{(;lFIRzC@݁{<Ef;\ QB1f(@'jIT[{6pf̶V0+y-g \hɼlC|W2&X<\JP:@;^+8bqϔSvQRԂӾ~:9k`ڍ=jYL Y8i!Z&EYHya t,QAJ_&ſ}_d7uc 4p;A~Jw4J b'ⶶCv&: ]P,l!I}ȌyLHnb\,aJ@:8AQf`߁kk%oyLp`#bnĽv|=SxϠlVvF4!tdDZ@r,#.blH_씲hSŁ[6P0K6vc!~N+la#李9-{/Ά@?CQ i= < ʂ`,%U<ǻL)]g/M~UVaR5rXVy:;q<cXvNm+La)bsqTȟy(c##dgnǠw\r ;99yz4v82s]*'b ]FnL5oX/h΂'f2wLkp|R|jOAJ,`8l[/ǘ(_4t 6(Bs}z8[7*]7Q+ut?۽ CZm|^. |ǽ B&U`IyqI;;TzMA_p.q_"xt p7>Cgfxf۱.$@0&c*5pO1\J>8yZ$9c4@;j!rwmqF7{Jg* ÕL-mƒdL_,KٶH?8$2ʞF^rN(]s5+4>JQW 0~Dl?Z@)zKY( D⠰imImUowj&0PVb:wբ!v8y=S`k0&#>USӫK#d:g1jxb1~9#6QyhIVF;]V1B^j L\Lb phżWa]r{r=Hc$byJ&*ڟ,}KS3X>7ۍsz0~>v%$fL9s`< G]<fLn.ENTZZV9(5 :|L2 \ KJ`>&\8T֭r@F4Qiigja P>S('6 ZHP`UClK+;1tU+D,R=>?BoBjR\Opy"|vA[OKa.,8L=S!bV|̗J߆WHlPuޓ)@e̖YĢ֫>&9`C?)3*L7r;(`[%{A w~T^MB3 aLL8TV\]-UO$L5h ?E14k*X-pNO 6 X@h'nx0> f&Bj䄔לwӀv2I6z@l <n8y7  ',c4_"|֖Wh{~jyB%Y*߀xdPq'{*s-C9>`UZJw޳QPOyBHNx5ØG=K봽,2E z<+}Dzw/ UbHB9 9lf3f4?+A\鸗baW]ǧia";zSbAjV.xs, zw㿍qX޿ ^0'U({wagQcPNCpi-g_7Pa[כ\hZJm 3gɢM(IR_n,Jη|@_F۟a|_'*hX3ϓEn\&OyMC'lqeJÇB<|yoet. &%.v:CȨ{滂LӣTK)̥A<+^8Z$;DQ62wV@%$w_K).MT^=Um vކ~B_y$26}`~=Op'af Rע^\ d9J=Wٹ9-[Jǡu1RgưR9^9|j>Μpr9% ܁%Vop7󥰸neh¢L͆Uh:k| E`++0,pBcwrԮTo]'a#K >bԁu; 4QԳk-*Zo'f1@-+u$Cc~}KG@[|2K'+_'ű{Gy(F Pſ|׿ FŲkᇤXםpsw *ަ/#F1bĈ#F1bĘXNIENDB`graphql-2.6.0/lib/graphql/dashboard/statics/dashboard.css0000644000004100000410000000104015173430257023455 0ustar www-datawww-data#header-icon { max-height: 2em; } .graphql-highlight { font-family:'Courier New', Courier, monospace; width: 100%; white-space: pre-wrap; } #limiter-histogram .column { max-height: 300px; } #limiter-histogram .column td { --color-1: var(--bs-gray); --color-2: var(--bs-red); opacity: 0.6; } #limiter-histogram .column td:hover { opacity: 1; } #limiter-histogram .column tbody tr th[scope=row] { width: 150px; transform: rotate(-75deg) translateY(55px) translateX(-50px); left: auto; --labels-align-inline: end; } graphql-2.6.0/lib/graphql/dashboard/statics/charts.min.css0000644000004100000410000022472515173430257023615 0ustar www-datawww-data@property --color-1{syntax:"";initial-value:transparent;inherits:true}@property --color-2{syntax:"";initial-value:transparent;inherits:true}@property --color-3{syntax:"";initial-value:transparent;inherits:true}@property --color-4{syntax:"";initial-value:transparent;inherits:true}@property --color-5{syntax:"";initial-value:transparent;inherits:true}@property --color-6{syntax:"";initial-value:transparent;inherits:true}@property --color-7{syntax:"";initial-value:transparent;inherits:true}@property --color-8{syntax:"";initial-value:transparent;inherits:true}@property --color-9{syntax:"";initial-value:transparent;inherits:true}@property --color-10{syntax:"";initial-value:transparent;inherits:true}@property --color{syntax:"";inherits:true}@property --chart-bg-color{syntax:"";inherits:true}@property --aspect-ratio{syntax:"";initial-value:auto;inherits:true}@property --labels-size{syntax:"";initial-value:0;inherits:true}@property --labels-align-block{syntax:"";inherits:true}@property --labels-align-inline{syntax:"";inherits:true}@property --primary-axis-width{syntax:"";initial-value:1px;inherits:true}@property --secondary-axes-width{syntax:"";initial-value:1px;inherits:true}@property --data-axes-width{syntax:"";initial-value:1px;inherits:true}@property --legend-border-width{syntax:"";initial-value:1px;inherits:true}@property --primary-axis-style{syntax:"";initial-value:solid;inherits:true}@property --secondary-axes-style{syntax:"";initial-value:solid;inherits:true}@property --data-axes-style{syntax:"";initial-value:solid;inherits:true}@property --legend-border-style{syntax:"";initial-value:solid;inherits:true}@property --primary-axis-color{syntax:"";initial-value:transparent;inherits:true}@property --secondary-axes-color{syntax:"";initial-value:transparent;inherits:true}@property --data-axes-color{syntax:"";initial-value:transparent;inherits:true}@property --legend-border-color{syntax:"";initial-value:transparent;inherits:true}@property --start{syntax:"";inherits:true}@property --end{syntax:"";inherits:true}@property --size{syntax:"";inherits:true}@property --line-size{syntax:"";inherits:true}.charts-css{--color-1:rgba(240,50,50,.75);--color-2:rgba(255,180,50,.75);--color-3:rgba(255,220,90,.75);--color-4:rgba(100,210,80,.75);--color-5:rgba(90,165,255,.75);--color-6:rgba(170,90,240,.75);--color-7:hsla(0,0%,71%,.75);--color-8:hsla(0,0%,43%,.75);--color-9:hsla(40,26%,55%,.75);--color-10:rgba(130,50,20,.75);--chart-bg-color:#f5f5f5;--primary-axis-color:#000;--primary-axis-style:solid;--primary-axis-width:1px;--secondary-axes-color:rgba(0,0,0,.15);--secondary-axes-style:solid;--secondary-axes-width:1px;--data-axes-color:rgba(0,0,0,.15);--data-axes-style:solid;--data-axes-width:1px;--legend-border-color:#c8c8c8;--legend-border-style:solid;--legend-border-width:1px;border:0;display:block;height:100%;margin:0 auto;padding:0;position:relative;-webkit-print-color-adjust:exact;print-color-adjust:exact;width:100%}.charts-css,.charts-css *,.charts-css ::after,.charts-css ::before,.charts-css::after,.charts-css::before{-webkit-box-sizing:border-box;box-sizing:border-box}table.charts-css{background-color:transparent;border-collapse:collapse;border-spacing:0;empty-cells:show;overflow:initial}table.charts-css caption,table.charts-css colgroup,table.charts-css tbody,table.charts-css td,table.charts-css th,table.charts-css thead,table.charts-css tr{background-color:transparent;border:0;display:block;margin:0;padding:0}.charts-css.area.show-labels th.hide-label,.charts-css.area.show-labels tr.hide-label th,.charts-css.area:not(.show-labels) tbody tr th,.charts-css.bar.show-labels th.hide-label,.charts-css.bar.show-labels tr.hide-label th,.charts-css.bar:not(.show-labels) tbody tr th,.charts-css.column.show-labels th.hide-label,.charts-css.column.show-labels tr.hide-label th,.charts-css.column:not(.show-labels) tbody tr th,.charts-css.hide-data .data,.charts-css.hide-data .data:not(:focus):not(:focus-within),.charts-css.line.show-labels th.hide-label,.charts-css.line.show-labels tr.hide-label th,.charts-css.line:not(.show-labels) tbody tr th,.charts-css.pie tbody tr th,.charts-css.polar tbody tr,.charts-css.radar tbody tr,.charts-css.radial tbody tr,.charts-css:not(.show-heading) caption,table.charts-css colgroup,table.charts-css tfoot,table.charts-css thead{clip:rect(0,0,0,0);border:0;-webkit-clip-path:inset(50%);clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}table.charts-css tbody{position:relative}ol.charts-css,ul.charts-css{list-style-type:none}ol.charts-css li,ul.charts-css li{border:0;margin:0;padding:0}.charts-css.show-heading caption{display:block;width:100%}.charts-css.area tbody tr td:nth-of-type(10n+1)::before,.charts-css.bar tbody tr:nth-of-type(10n+1) td,.charts-css.bar.multiple tbody tr td:nth-of-type(10n+1),.charts-css.column tbody tr:nth-of-type(10n+1) td,.charts-css.column.multiple tbody tr td:nth-of-type(10n+1),.charts-css.line tbody tr td:nth-of-type(10n+1)::before{background:var(--color,var(--color-1))}.charts-css.pie tbody tr:nth-of-type(10n+1) td,.charts-css.pie.multiple tbody tr td:nth-of-type(10n+1){--c:var(--color,var(--color-1,transparent))}.charts-css.area tbody tr td:nth-of-type(10n+2)::before,.charts-css.bar tbody tr:nth-of-type(10n+2) td,.charts-css.bar.multiple tbody tr td:nth-of-type(10n+2),.charts-css.column tbody tr:nth-of-type(10n+2) td,.charts-css.column.multiple tbody tr td:nth-of-type(10n+2),.charts-css.line tbody tr td:nth-of-type(10n+2)::before{background:var(--color,var(--color-2))}.charts-css.pie tbody tr:nth-of-type(10n+2) td,.charts-css.pie.multiple tbody tr td:nth-of-type(10n+2){--c:var(--color,var(--color-2,transparent))}.charts-css.area tbody tr td:nth-of-type(10n+3)::before,.charts-css.bar tbody tr:nth-of-type(10n+3) td,.charts-css.bar.multiple tbody tr td:nth-of-type(10n+3),.charts-css.column tbody tr:nth-of-type(10n+3) td,.charts-css.column.multiple tbody tr td:nth-of-type(10n+3),.charts-css.line tbody tr td:nth-of-type(10n+3)::before{background:var(--color,var(--color-3))}.charts-css.pie tbody tr:nth-of-type(10n+3) td,.charts-css.pie.multiple tbody tr td:nth-of-type(10n+3){--c:var(--color,var(--color-3,transparent))}.charts-css.area tbody tr td:nth-of-type(10n+4)::before,.charts-css.bar tbody tr:nth-of-type(10n+4) td,.charts-css.bar.multiple tbody tr td:nth-of-type(10n+4),.charts-css.column tbody tr:nth-of-type(10n+4) td,.charts-css.column.multiple tbody tr td:nth-of-type(10n+4),.charts-css.line tbody tr td:nth-of-type(10n+4)::before{background:var(--color,var(--color-4))}.charts-css.pie tbody tr:nth-of-type(10n+4) td,.charts-css.pie.multiple tbody tr td:nth-of-type(10n+4){--c:var(--color,var(--color-4,transparent))}.charts-css.area tbody tr td:nth-of-type(10n+5)::before,.charts-css.bar tbody tr:nth-of-type(10n+5) td,.charts-css.bar.multiple tbody tr td:nth-of-type(10n+5),.charts-css.column tbody tr:nth-of-type(10n+5) td,.charts-css.column.multiple tbody tr td:nth-of-type(10n+5),.charts-css.line tbody tr td:nth-of-type(10n+5)::before{background:var(--color,var(--color-5))}.charts-css.pie tbody tr:nth-of-type(10n+5) td,.charts-css.pie.multiple tbody tr td:nth-of-type(10n+5){--c:var(--color,var(--color-5,transparent))}.charts-css.area tbody tr td:nth-of-type(10n+6)::before,.charts-css.bar tbody tr:nth-of-type(10n+6) td,.charts-css.bar.multiple tbody tr td:nth-of-type(10n+6),.charts-css.column tbody tr:nth-of-type(10n+6) td,.charts-css.column.multiple tbody tr td:nth-of-type(10n+6),.charts-css.line tbody tr td:nth-of-type(10n+6)::before{background:var(--color,var(--color-6))}.charts-css.pie tbody tr:nth-of-type(10n+6) td,.charts-css.pie.multiple tbody tr td:nth-of-type(10n+6){--c:var(--color,var(--color-6,transparent))}.charts-css.area tbody tr td:nth-of-type(10n+7)::before,.charts-css.bar tbody tr:nth-of-type(10n+7) td,.charts-css.bar.multiple tbody tr td:nth-of-type(10n+7),.charts-css.column tbody tr:nth-of-type(10n+7) td,.charts-css.column.multiple tbody tr td:nth-of-type(10n+7),.charts-css.line tbody tr td:nth-of-type(10n+7)::before{background:var(--color,var(--color-7))}.charts-css.pie tbody tr:nth-of-type(10n+7) td,.charts-css.pie.multiple tbody tr td:nth-of-type(10n+7){--c:var(--color,var(--color-7,transparent))}.charts-css.area tbody tr td:nth-of-type(10n+8)::before,.charts-css.bar tbody tr:nth-of-type(10n+8) td,.charts-css.bar.multiple tbody tr td:nth-of-type(10n+8),.charts-css.column tbody tr:nth-of-type(10n+8) td,.charts-css.column.multiple tbody tr td:nth-of-type(10n+8),.charts-css.line tbody tr td:nth-of-type(10n+8)::before{background:var(--color,var(--color-8))}.charts-css.pie tbody tr:nth-of-type(10n+8) td,.charts-css.pie.multiple tbody tr td:nth-of-type(10n+8){--c:var(--color,var(--color-8,transparent))}.charts-css.area tbody tr td:nth-of-type(10n+9)::before,.charts-css.bar tbody tr:nth-of-type(10n+9) td,.charts-css.bar.multiple tbody tr td:nth-of-type(10n+9),.charts-css.column tbody tr:nth-of-type(10n+9) td,.charts-css.column.multiple tbody tr td:nth-of-type(10n+9),.charts-css.line tbody tr td:nth-of-type(10n+9)::before{background:var(--color,var(--color-9))}.charts-css.pie tbody tr:nth-of-type(10n+9) td,.charts-css.pie.multiple tbody tr td:nth-of-type(10n+9){--c:var(--color,var(--color-9,transparent))}.charts-css.area tbody tr td:nth-of-type(10n+10)::before,.charts-css.bar tbody tr:nth-of-type(10n+10) td,.charts-css.bar.multiple tbody tr td:nth-of-type(10n+10),.charts-css.column tbody tr:nth-of-type(10n+10) td,.charts-css.column.multiple tbody tr td:nth-of-type(10n+10),.charts-css.line tbody tr td:nth-of-type(10n+10)::before{background:var(--color,var(--color-10))}.charts-css.pie tbody tr:nth-of-type(10n+10) td,.charts-css.pie.multiple tbody tr td:nth-of-type(10n+10){--c:var(--color,var(--color-10,transparent))}.charts-css .data{display:-webkit-box;display:-ms-flexbox;display:flex}.charts-css.show-data-on-hover .data{opacity:0;-webkit-transition-duration:.3s;transition-duration:.3s}.charts-css.pie.show-data-on-hover tbody:hover .data,.charts-css.polar.show-data-on-hover tbody:hover .data,.charts-css.radar.show-data-on-hover tbody:hover .data,.charts-css.radial.show-data-on-hover tbody:hover .data,.charts-css.show-data-on-hover tr:hover .data{opacity:1;-webkit-transition-duration:.3s;transition-duration:.3s}.charts-css.bar.data-center tbody tr td,.charts-css.column.data-center tbody tr td{--data-position:center}.charts-css.bar.data-end.reverse tbody tr td,.charts-css.bar.data-outside.reverse tbody tr td,.charts-css.bar.data-start:not(.reverse) tbody tr td,.charts-css.column.data-end:not(.reverse) tbody tr td,.charts-css.column.data-outside:not(.reverse) tbody tr td,.charts-css.column.data-start.reverse tbody tr td{--data-position:flex-start}.charts-css.bar.data-end:not(.reverse) tbody tr td,.charts-css.bar.data-outside:not(.reverse) tbody tr td,.charts-css.bar.data-start.reverse tbody tr td,.charts-css.column.data-end.reverse tbody tr td,.charts-css.column.data-outside.reverse tbody tr td,.charts-css.column.data-start:not(.reverse) tbody tr td{--data-position:flex-end}.charts-css.bar.data-outside:not(.reverse) tbody tr td .data{-webkit-transform:translateX(100%);transform:translateX(100%)}.charts-css.bar.data-outside.reverse tbody tr td .data{-webkit-transform:translateX(-100%);transform:translateX(-100%)}.charts-css.column.data-outside:not(.reverse) tbody tr td .data,.charts-css.column:not(.reverse) tbody tr td .data.outside{-webkit-transform:translateY(-100%);transform:translateY(-100%)}.charts-css.column.data-outside.reverse tbody tr td .data,.charts-css.column.reverse tbody tr td .data.outside{-webkit-transform:translateY(100%);transform:translateY(100%)}.charts-css.area.reverse tbody tr td .data.inside,.charts-css.area.reverse tbody tr td.inside .data,.charts-css.area:not(.reverse) tbody tr td .data.inside,.charts-css.area:not(.reverse) tbody tr td.inside .data,.charts-css.bar.reverse tbody tr td .data.inside,.charts-css.bar.reverse tbody tr td.inside .data,.charts-css.bar:not(.reverse) tbody tr td .data.inside,.charts-css.bar:not(.reverse) tbody tr td.inside .data,.charts-css.column.reverse tbody tr td .data.inside,.charts-css.column.reverse tbody tr td.inside .data,.charts-css.column:not(.reverse) tbody tr td .data.inside,.charts-css.column:not(.reverse) tbody tr td.inside .data,.charts-css.line.reverse tbody tr td .data.inside,.charts-css.line.reverse tbody tr td.inside .data,.charts-css.line:not(.reverse) tbody tr td .data.inside,.charts-css.line:not(.reverse) tbody tr td.inside .data{-webkit-transform:unset;transform:unset}.charts-css.bar{--labels-size:80px}.charts-css.area:not(.show-labels),.charts-css.bar:not(.show-labels),.charts-css.column:not(.show-labels),.charts-css.line:not(.show-labels){--labels-size:0}.charts-css.bar.show-labels tbody tr th{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-box-align:var(--labels-align-block,center);-ms-flex-align:var(--labels-align-block,center);align-items:var(--labels-align-block,center);display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;gap:5px}.charts-css.bar.show-labels.reverse.reverse-labels tbody tr th,.charts-css.bar.show-labels:not(.reverse):not(.reverse-labels) tbody tr th{-webkit-box-pack:var(--labels-align-inline,flex-start);-ms-flex-pack:var(--labels-align-inline,flex-start);justify-content:var(--labels-align-inline,flex-start)}.charts-css.bar.show-labels.reverse:not(.reverse-labels) tbody tr th,.charts-css.bar.show-labels:not(.reverse).reverse-labels tbody tr th{-webkit-box-pack:var(--labels-align-inline,flex-end);-ms-flex-pack:var(--labels-align-inline,flex-end);justify-content:var(--labels-align-inline,flex-end)}.charts-css.area,.charts-css.column,.charts-css.line{--labels-size:1.5rem}.charts-css.area.show-labels tbody tr th,.charts-css.column.show-labels tbody tr th,.charts-css.line.show-labels tbody tr th{-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-box-align:var(--labels-align-inline,center);-ms-flex-align:var(--labels-align-inline,center);align-items:var(--labels-align-inline,center);display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.charts-css.area.show-labels.reverse.reverse-labels tbody tr th,.charts-css.area.show-labels:not(.reverse):not(.reverse-labels) tbody tr th,.charts-css.column.show-labels.reverse.reverse-labels tbody tr th,.charts-css.column.show-labels:not(.reverse):not(.reverse-labels) tbody tr th,.charts-css.line.show-labels.reverse.reverse-labels tbody tr th,.charts-css.line.show-labels:not(.reverse):not(.reverse-labels) tbody tr th{-webkit-box-pack:var(--labels-align-block,flex-end);-ms-flex-pack:var(--labels-align-block,flex-end);justify-content:var(--labels-align-block,flex-end)}.charts-css.area.show-labels.reverse:not(.reverse-labels) tbody tr th,.charts-css.area.show-labels:not(.reverse).reverse-labels tbody tr th,.charts-css.column.show-labels.reverse:not(.reverse-labels) tbody tr th,.charts-css.column.show-labels:not(.reverse).reverse-labels tbody tr th,.charts-css.line.show-labels.reverse:not(.reverse-labels) tbody tr th,.charts-css.line.show-labels:not(.reverse).reverse-labels tbody tr th{-webkit-box-pack:var(--labels-align-block,flex-start);-ms-flex-pack:var(--labels-align-block,flex-start);justify-content:var(--labels-align-block,flex-start)}.charts-css.area.labels-align-inline-start tbody tr th,.charts-css.bar.labels-align-inline-start tbody tr th,.charts-css.column.labels-align-inline-start tbody tr th,.charts-css.line.labels-align-inline-start tbody tr th{--labels-align-inline:flex-start}.charts-css.area.labels-align-inline-end tbody tr th,.charts-css.bar.labels-align-inline-end tbody tr th,.charts-css.column.labels-align-inline-end tbody tr th,.charts-css.line.labels-align-inline-end tbody tr th{--labels-align-inline:flex-end}.charts-css.area.labels-align-inline-center tbody tr th,.charts-css.bar.labels-align-inline-center tbody tr th,.charts-css.column.labels-align-inline-center tbody tr th,.charts-css.line.labels-align-inline-center tbody tr th{--labels-align-inline:center}.charts-css.area.labels-align-block-start tbody tr th,.charts-css.bar.labels-align-block-start tbody tr th,.charts-css.column.labels-align-block-start tbody tr th,.charts-css.line.labels-align-block-start tbody tr th{--labels-align-block:flex-start}.charts-css.area.labels-align-block-end tbody tr th,.charts-css.bar.labels-align-block-end tbody tr th,.charts-css.column.labels-align-block-end tbody tr th,.charts-css.line.labels-align-block-end tbody tr th{--labels-align-block:flex-end}.charts-css.area.labels-align-block-center tbody tr th,.charts-css.bar.labels-align-block-center tbody tr th,.charts-css.column.labels-align-block-center tbody tr th,.charts-css.line.labels-align-block-center tbody tr th{--labels-align-block:center}.charts-css.area.show-primary-axis:not(.reverse) tbody tr,.charts-css.column.show-primary-axis:not(.reverse) tbody tr,.charts-css.line.show-primary-axis:not(.reverse) tbody tr{-webkit-border-after:var(--primary-axis-width) var(--primary-axis-style) var(--primary-axis-color);border-block-end:var(--primary-axis-width) var(--primary-axis-style) var(--primary-axis-color)}.charts-css.area.show-primary-axis.reverse tbody tr,.charts-css.column.show-primary-axis.reverse tbody tr,.charts-css.line.show-primary-axis.reverse tbody tr{-webkit-border-before:var(--primary-axis-width) var(--primary-axis-style) var(--primary-axis-color);border-block-start:var(--primary-axis-width) var(--primary-axis-style) var(--primary-axis-color)}.charts-css.area.show-1-secondary-axes:not(.reverse) tbody tr,.charts-css.column.show-1-secondary-axes:not(.reverse) tbody tr,.charts-css.line.show-1-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,left top,left bottom,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 1)}.charts-css.area.show-1-secondary-axes.reverse tbody tr,.charts-css.column.show-1-secondary-axes.reverse tbody tr,.charts-css.line.show-1-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left bottom,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(0deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 1)}.charts-css.area.show-2-secondary-axes:not(.reverse) tbody tr,.charts-css.column.show-2-secondary-axes:not(.reverse) tbody tr,.charts-css.line.show-2-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,left top,left bottom,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 2)}.charts-css.area.show-2-secondary-axes.reverse tbody tr,.charts-css.column.show-2-secondary-axes.reverse tbody tr,.charts-css.line.show-2-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left bottom,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(0deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 2)}.charts-css.area.show-3-secondary-axes:not(.reverse) tbody tr,.charts-css.column.show-3-secondary-axes:not(.reverse) tbody tr,.charts-css.line.show-3-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,left top,left bottom,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 3)}.charts-css.area.show-3-secondary-axes.reverse tbody tr,.charts-css.column.show-3-secondary-axes.reverse tbody tr,.charts-css.line.show-3-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left bottom,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(0deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 3)}.charts-css.area.show-4-secondary-axes:not(.reverse) tbody tr,.charts-css.column.show-4-secondary-axes:not(.reverse) tbody tr,.charts-css.line.show-4-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,left top,left bottom,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 4)}.charts-css.area.show-4-secondary-axes.reverse tbody tr,.charts-css.column.show-4-secondary-axes.reverse tbody tr,.charts-css.line.show-4-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left bottom,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(0deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 4)}.charts-css.area.show-5-secondary-axes:not(.reverse) tbody tr,.charts-css.column.show-5-secondary-axes:not(.reverse) tbody tr,.charts-css.line.show-5-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,left top,left bottom,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 5)}.charts-css.area.show-5-secondary-axes.reverse tbody tr,.charts-css.column.show-5-secondary-axes.reverse tbody tr,.charts-css.line.show-5-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left bottom,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(0deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 5)}.charts-css.area.show-6-secondary-axes:not(.reverse) tbody tr,.charts-css.column.show-6-secondary-axes:not(.reverse) tbody tr,.charts-css.line.show-6-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,left top,left bottom,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 6)}.charts-css.area.show-6-secondary-axes.reverse tbody tr,.charts-css.column.show-6-secondary-axes.reverse tbody tr,.charts-css.line.show-6-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left bottom,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(0deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 6)}.charts-css.area.show-7-secondary-axes:not(.reverse) tbody tr,.charts-css.column.show-7-secondary-axes:not(.reverse) tbody tr,.charts-css.line.show-7-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,left top,left bottom,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 7)}.charts-css.area.show-7-secondary-axes.reverse tbody tr,.charts-css.column.show-7-secondary-axes.reverse tbody tr,.charts-css.line.show-7-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left bottom,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(0deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 7)}.charts-css.area.show-8-secondary-axes:not(.reverse) tbody tr,.charts-css.column.show-8-secondary-axes:not(.reverse) tbody tr,.charts-css.line.show-8-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,left top,left bottom,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 8)}.charts-css.area.show-8-secondary-axes.reverse tbody tr,.charts-css.column.show-8-secondary-axes.reverse tbody tr,.charts-css.line.show-8-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left bottom,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(0deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 8)}.charts-css.area.show-9-secondary-axes:not(.reverse) tbody tr,.charts-css.column.show-9-secondary-axes:not(.reverse) tbody tr,.charts-css.line.show-9-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,left top,left bottom,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 9)}.charts-css.area.show-9-secondary-axes.reverse tbody tr,.charts-css.column.show-9-secondary-axes.reverse tbody tr,.charts-css.line.show-9-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left bottom,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(0deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 9)}.charts-css.area.show-10-secondary-axes:not(.reverse) tbody tr,.charts-css.column.show-10-secondary-axes:not(.reverse) tbody tr,.charts-css.line.show-10-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,left top,left bottom,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 10)}.charts-css.area.show-10-secondary-axes.reverse tbody tr,.charts-css.column.show-10-secondary-axes.reverse tbody tr,.charts-css.line.show-10-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left bottom,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(0deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:100% calc(100% / 10)}.charts-css.area.show-data-axes tbody tr,.charts-css.area.show-dataset-axes tbody tr td,.charts-css.column.show-data-axes tbody tr,.charts-css.column.show-dataset-axes tbody tr td,.charts-css.line.show-data-axes tbody tr,.charts-css.line.show-dataset-axes tbody tr td{-webkit-border-end:var(--data-axes-width) var(--data-axes-style) var(--data-axes-color);border-inline-end:var(--data-axes-width) var(--data-axes-style) var(--data-axes-color)}.charts-css.area.show-data-axes.reverse-data tbody tr:last-of-type,.charts-css.area.show-data-axes:not(.reverse-data) tbody tr:first-of-type,.charts-css.area.show-dataset-axes.reverse-data tbody tr:last-of-type td,.charts-css.area.show-dataset-axes:not(.reverse-data) tbody tr:first-of-type td,.charts-css.column.show-data-axes.reverse-data tbody tr:last-of-type,.charts-css.column.show-data-axes:not(.reverse-data) tbody tr:first-of-type,.charts-css.column.show-dataset-axes.reverse-data tbody tr:last-of-type td,.charts-css.column.show-dataset-axes:not(.reverse-data) tbody tr:first-of-type td,.charts-css.line.show-data-axes.reverse-data tbody tr:last-of-type,.charts-css.line.show-data-axes:not(.reverse-data) tbody tr:first-of-type,.charts-css.line.show-dataset-axes.reverse-data tbody tr:last-of-type td,.charts-css.line.show-dataset-axes:not(.reverse-data) tbody tr:first-of-type td{-webkit-border-start:var(--data-axes-width) var(--data-axes-style) var(--data-axes-color);border-inline-start:var(--data-axes-width) var(--data-axes-style) var(--data-axes-color)}.charts-css.bar.show-primary-axis:not(.reverse) tbody tr{-webkit-border-start:var(--primary-axis-width) var(--primary-axis-style) var(--primary-axis-color);border-inline-start:var(--primary-axis-width) var(--primary-axis-style) var(--primary-axis-color)}.charts-css.bar.show-primary-axis.reverse tbody tr{-webkit-border-end:var(--primary-axis-width) var(--primary-axis-style) var(--primary-axis-color);border-inline-end:var(--primary-axis-width) var(--primary-axis-style) var(--primary-axis-color)}.charts-css.bar.show-1-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,right top,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(-90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 1) 100%}.charts-css.bar.show-1-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left top,right top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 1) 100%}.charts-css.bar.show-2-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,right top,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(-90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 2) 100%}.charts-css.bar.show-2-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left top,right top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 2) 100%}.charts-css.bar.show-3-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,right top,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(-90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 3) 100%}.charts-css.bar.show-3-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left top,right top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 3) 100%}.charts-css.bar.show-4-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,right top,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(-90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 4) 100%}.charts-css.bar.show-4-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left top,right top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 4) 100%}.charts-css.bar.show-5-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,right top,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(-90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 5) 100%}.charts-css.bar.show-5-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left top,right top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 5) 100%}.charts-css.bar.show-6-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,right top,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(-90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 6) 100%}.charts-css.bar.show-6-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left top,right top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 6) 100%}.charts-css.bar.show-7-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,right top,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(-90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 7) 100%}.charts-css.bar.show-7-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left top,right top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 7) 100%}.charts-css.bar.show-8-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,right top,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(-90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 8) 100%}.charts-css.bar.show-8-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left top,right top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 8) 100%}.charts-css.bar.show-9-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,right top,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(-90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 9) 100%}.charts-css.bar.show-9-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left top,right top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 9) 100%}.charts-css.bar.show-10-secondary-axes:not(.reverse) tbody tr{background-image:-webkit-gradient(linear,right top,left top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(-90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 10) 100%}.charts-css.bar.show-10-secondary-axes.reverse tbody tr{background-image:-webkit-gradient(linear,left top,right top,from(var(--secondary-axes-color)),to(transparent));background-image:linear-gradient(90deg,var(--secondary-axes-color) var(--secondary-axes-width),transparent var(--secondary-axes-width));background-size:calc(100% / 10) 100%}.charts-css.bar.show-data-axes tbody tr,.charts-css.bar.show-dataset-axes tbody tr td{-webkit-border-after:var(--data-axes-width) var(--data-axes-style) var(--data-axes-color);border-block-end:var(--data-axes-width) var(--data-axes-style) var(--data-axes-color)}.charts-css.bar.show-data-axes.reverse-data tbody tr:last-of-type,.charts-css.bar.show-data-axes:not(.reverse-data) tbody tr:first-of-type,.charts-css.bar.show-dataset-axes.reverse-data tbody tr:last-of-type td,.charts-css.bar.show-dataset-axes:not(.reverse-data) tbody tr:first-of-type td{-webkit-border-before:var(--data-axes-width) var(--data-axes-style) var(--data-axes-color);border-block-start:var(--data-axes-width) var(--data-axes-style) var(--data-axes-color)}.charts-css.pie.show-primary-axis tbody,.charts-css.polar.show-primary-axis tbody,.charts-css.radar.show-primary-axis tbody,.charts-css.radial.show-primary-axis tbody{border:var(--primary-axis-width) var(--primary-axis-style) var(--primary-axis-color)}.charts-css.pie.show-1-secondary-axes tbody::after,.charts-css.polar.show-1-secondary-axes tbody::after,.charts-css.radar.show-1-secondary-axes tbody::after,.charts-css.radial.show-1-secondary-axes tbody::after{background:repeating-radial-gradient(closest-side,transparent 0,transparent calc(100% / 2 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 2 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 2),transparent calc(100% / 2 + var(--secondary-axes-width)),transparent calc(100% / 2 + var(--secondary-axes-width)));border-radius:50%;bottom:0;content:"";height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:2}.charts-css.pie.show-2-secondary-axes tbody::after,.charts-css.polar.show-2-secondary-axes tbody::after,.charts-css.radar.show-2-secondary-axes tbody::after,.charts-css.radial.show-2-secondary-axes tbody::after{background:repeating-radial-gradient(closest-side,transparent 0,transparent calc(100% / 3 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 3 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 3),transparent calc(100% / 3 + var(--secondary-axes-width)),transparent calc(100% / 3 + var(--secondary-axes-width)));border-radius:50%;bottom:0;content:"";height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:2}.charts-css.pie.show-3-secondary-axes tbody::after,.charts-css.polar.show-3-secondary-axes tbody::after,.charts-css.radar.show-3-secondary-axes tbody::after,.charts-css.radial.show-3-secondary-axes tbody::after{background:repeating-radial-gradient(closest-side,transparent 0,transparent calc(100% / 4 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 4 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 4),transparent calc(100% / 4 + var(--secondary-axes-width)),transparent calc(100% / 4 + var(--secondary-axes-width)));border-radius:50%;bottom:0;content:"";height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:2}.charts-css.pie.show-4-secondary-axes tbody::after,.charts-css.polar.show-4-secondary-axes tbody::after,.charts-css.radar.show-4-secondary-axes tbody::after,.charts-css.radial.show-4-secondary-axes tbody::after{background:repeating-radial-gradient(closest-side,transparent 0,transparent calc(100% / 5 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 5 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 5),transparent calc(100% / 5 + var(--secondary-axes-width)),transparent calc(100% / 5 + var(--secondary-axes-width)));border-radius:50%;bottom:0;content:"";height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:2}.charts-css.pie.show-5-secondary-axes tbody::after,.charts-css.polar.show-5-secondary-axes tbody::after,.charts-css.radar.show-5-secondary-axes tbody::after,.charts-css.radial.show-5-secondary-axes tbody::after{background:repeating-radial-gradient(closest-side,transparent 0,transparent calc(100% / 6 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 6 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 6),transparent calc(100% / 6 + var(--secondary-axes-width)),transparent calc(100% / 6 + var(--secondary-axes-width)));border-radius:50%;bottom:0;content:"";height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:2}.charts-css.pie.show-6-secondary-axes tbody::after,.charts-css.polar.show-6-secondary-axes tbody::after,.charts-css.radar.show-6-secondary-axes tbody::after,.charts-css.radial.show-6-secondary-axes tbody::after{background:repeating-radial-gradient(closest-side,transparent 0,transparent calc(100% / 7 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 7 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 7),transparent calc(100% / 7 + var(--secondary-axes-width)),transparent calc(100% / 7 + var(--secondary-axes-width)));border-radius:50%;bottom:0;content:"";height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:2}.charts-css.pie.show-7-secondary-axes tbody::after,.charts-css.polar.show-7-secondary-axes tbody::after,.charts-css.radar.show-7-secondary-axes tbody::after,.charts-css.radial.show-7-secondary-axes tbody::after{background:repeating-radial-gradient(closest-side,transparent 0,transparent calc(100% / 8 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 8 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 8),transparent calc(100% / 8 + var(--secondary-axes-width)),transparent calc(100% / 8 + var(--secondary-axes-width)));border-radius:50%;bottom:0;content:"";height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:2}.charts-css.pie.show-8-secondary-axes tbody::after,.charts-css.polar.show-8-secondary-axes tbody::after,.charts-css.radar.show-8-secondary-axes tbody::after,.charts-css.radial.show-8-secondary-axes tbody::after{background:repeating-radial-gradient(closest-side,transparent 0,transparent calc(100% / 9 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 9 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 9),transparent calc(100% / 9 + var(--secondary-axes-width)),transparent calc(100% / 9 + var(--secondary-axes-width)));border-radius:50%;bottom:0;content:"";height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:2}.charts-css.pie.show-9-secondary-axes tbody::after,.charts-css.polar.show-9-secondary-axes tbody::after,.charts-css.radar.show-9-secondary-axes tbody::after,.charts-css.radial.show-9-secondary-axes tbody::after{background:repeating-radial-gradient(closest-side,transparent 0,transparent calc(100% / 10 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 10 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 10),transparent calc(100% / 10 + var(--secondary-axes-width)),transparent calc(100% / 10 + var(--secondary-axes-width)));border-radius:50%;bottom:0;content:"";height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:2}.charts-css.pie.show-10-secondary-axes tbody::after,.charts-css.polar.show-10-secondary-axes tbody::after,.charts-css.radar.show-10-secondary-axes tbody::after,.charts-css.radial.show-10-secondary-axes tbody::after{background:repeating-radial-gradient(closest-side,transparent 0,transparent calc(100% / 11 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 11 - var(--secondary-axes-width)),var(--secondary-axes-color) calc(100% / 11),transparent calc(100% / 11 + var(--secondary-axes-width)),transparent calc(100% / 11 + var(--secondary-axes-width)));border-radius:50%;bottom:0;content:"";height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:2}.charts-css.legend{border:var(--legend-border-width) var(--legend-border-style) var(--legend-border-color);font-size:1rem;list-style:none;padding:1rem}.charts-css.legend li{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;line-height:2}.charts-css.legend li::before{-webkit-margin-end:.5rem;border-style:solid;border-width:2px;content:"";display:inline-block;margin-inline-end:.5rem;vertical-align:middle}.charts-css.legend li:nth-child(10n+1)::before{background-color:var(--color-1,transparent);border-color:var(--border-color-1,var(--border-color,#000))}.charts-css.legend li:nth-child(10n+2)::before{background-color:var(--color-2,transparent);border-color:var(--border-color-2,var(--border-color,#000))}.charts-css.legend li:nth-child(10n+3)::before{background-color:var(--color-3,transparent);border-color:var(--border-color-3,var(--border-color,#000))}.charts-css.legend li:nth-child(10n+4)::before{background-color:var(--color-4,transparent);border-color:var(--border-color-4,var(--border-color,#000))}.charts-css.legend li:nth-child(10n+5)::before{background-color:var(--color-5,transparent);border-color:var(--border-color-5,var(--border-color,#000))}.charts-css.legend li:nth-child(10n+6)::before{background-color:var(--color-6,transparent);border-color:var(--border-color-6,var(--border-color,#000))}.charts-css.legend li:nth-child(10n+7)::before{background-color:var(--color-7,transparent);border-color:var(--border-color-7,var(--border-color,#000))}.charts-css.legend li:nth-child(10n+8)::before{background-color:var(--color-8,transparent);border-color:var(--border-color-8,var(--border-color,#000))}.charts-css.legend li:nth-child(10n+9)::before{background-color:var(--color-9,transparent);border-color:var(--border-color-9,var(--border-color,#000))}.charts-css.legend li:nth-child(10n+10)::before{background-color:var(--color-10,transparent);border-color:var(--border-color-10,var(--border-color,#000))}.charts-css:not(.legend-inline){-webkit-box-orient:vertical;-ms-flex-direction:column;flex-direction:column;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.charts-css.legend-inline,.charts-css:not(.legend-inline){-webkit-box-direction:normal;display:-webkit-box;display:-ms-flexbox;display:flex}.charts-css.legend-inline{-webkit-box-orient:horizontal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap}.charts-css.legend-inline li{-webkit-margin-end:1rem;margin-inline-end:1rem}.charts-css.legend-circle li::before{border-radius:50%;height:1rem;width:1rem}.charts-css.legend-ellipse li::before{border-radius:50%;height:1rem;width:2rem}.charts-css.legend-rhombus li::before,.charts-css.legend-square li::before{border-radius:3px;height:1rem;width:1rem}.charts-css.legend-rhombus li::before{-webkit-transform:rotate(45deg) scale(.85);transform:rotate(45deg) scale(.85)}.charts-css.legend-rectangle li::before{border-radius:3px;height:1rem;width:2rem}.charts-css.legend-line li::before{border-radius:2px;-webkit-box-sizing:content-box;box-sizing:content-box;height:3px;width:2rem}.charts-css .tooltip{background-color:#555;border-radius:6px;bottom:50%;color:#fff;font-size:.9rem;left:50%;opacity:0;padding:5px 10px;position:absolute;text-align:center;-webkit-transform:translateX(-50%);transform:translateX(-50%);-webkit-transition:opacity .3s;transition:opacity .3s;visibility:hidden;width:-webkit-max-content;width:-moz-max-content;width:max-content;z-index:1}.charts-css .tooltip::after{border:5px solid transparent;border-top-color:#555;content:"";left:50%;margin-left:-5px;position:absolute;top:100%}.charts-css td:hover .tooltip{opacity:1;visibility:visible}.charts-css.bar tbody{-webkit-box-pack:justify;-ms-flex-pack:justify;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;aspect-ratio:var(--aspect-ratio,auto);display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:space-between;width:100%}.charts-css.area tbody tr,.charts-css.bar tbody tr,.charts-css.column tbody tr,.charts-css.line tbody tr{-webkit-box-pack:start;-ms-flex-pack:start;-webkit-box-flex:1;-ms-flex-positive:1;-ms-flex-negative:1;-ms-flex-preferred-size:0;display:-webkit-box;display:-ms-flexbox;display:flex;flex-basis:0;flex-grow:1;flex-shrink:1;justify-content:flex-start;overflow-wrap:anywhere;position:relative}.charts-css.bar tbody tr th{bottom:0;left:0;position:absolute;right:0;top:0;width:var(--labels-size)}.charts-css.bar tbody tr td{-webkit-box-align:center;-ms-flex-align:center;-webkit-padding-before:10px;-webkit-padding-after:10px;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;height:100%;min-height:1rem;padding-block-end:10px;padding-block-start:10px;position:relative;width:calc(100% * var(--end, var(--size, 1)))}.charts-css.bar:not(.reverse) tbody tr td{-webkit-box-pack:var(--data-position,flex-end);-ms-flex-pack:var(--data-position,flex-end);justify-content:var(--data-position,flex-end)}.charts-css.bar:not(.reverse) tbody tr td .data.outside{-webkit-transform:translateX(100%);transform:translateX(100%);white-space:nowrap}.charts-css.bar.reverse tbody tr td{-webkit-box-pack:var(--data-position,flex-start);-ms-flex-pack:var(--data-position,flex-start);justify-content:var(--data-position,flex-start)}.charts-css.bar.reverse tbody tr td .data.outside{-webkit-transform:translateX(-100%);transform:translateX(-100%);white-space:nowrap}.charts-css.area.reverse tbody tr,.charts-css.area:not(.reverse) tbody tr td .data,.charts-css.bar:not(.reverse) tbody tr,.charts-css.column.reverse tbody tr,.charts-css.line.reverse tbody tr,.charts-css.line:not(.reverse) tbody tr td .data{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.charts-css.area.reverse tbody tr td .data,.charts-css.area:not(.reverse) tbody tr,.charts-css.bar.reverse tbody tr,.charts-css.column:not(.reverse) tbody tr,.charts-css.line.reverse tbody tr td .data,.charts-css.line:not(.reverse) tbody tr{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.charts-css.bar.reverse-labels.reverse tbody tr,.charts-css.bar:not(.reverse-labels):not(.reverse) tbody tr{-webkit-margin-start:var(--labels-size);margin-inline-start:var(--labels-size)}.charts-css.bar:not(.reverse-labels):not(.reverse) tbody tr th{-webkit-margin-end:auto;-webkit-margin-start:calc(-1 * var(--labels-size) - var(--primary-axis-width));margin-inline-end:auto;margin-inline-start:calc(-1 * var(--labels-size) - var(--primary-axis-width))}.charts-css.bar.reverse-labels:not(.reverse) tbody tr,.charts-css.bar:not(.reverse-labels).reverse tbody tr{-webkit-margin-end:var(--labels-size);margin-inline-end:var(--labels-size)}.charts-css.bar:not(.reverse-labels).reverse tbody tr th{-webkit-margin-start:auto;-webkit-margin-end:calc(-1 * var(--labels-size) - var(--primary-axis-width));margin-inline-end:calc(-1 * var(--labels-size) - var(--primary-axis-width));margin-inline-start:auto}.charts-css.bar.reverse-labels:not(.reverse) tbody tr th{-webkit-margin-start:auto;-webkit-margin-end:calc(-1 * var(--labels-size));margin-inline-end:calc(-1 * var(--labels-size));margin-inline-start:auto}.charts-css.bar.reverse-labels.reverse tbody tr th{-webkit-margin-end:auto;-webkit-margin-start:calc(-1 * var(--labels-size));margin-inline-end:auto;margin-inline-start:calc(-1 * var(--labels-size))}.charts-css.bar:not(.stacked) tbody tr td,.charts-css.column:not(.stacked) tbody tr td{-webkit-box-flex:1;-ms-flex-positive:1;-ms-flex-negative:1;-ms-flex-preferred-size:0;flex-basis:0;flex-grow:1;flex-shrink:1}.charts-css.bar.stacked tbody tr td,.charts-css.column.stacked tbody tr td{-webkit-box-flex:unset;-ms-flex-positive:unset;-ms-flex-negative:unset;-ms-flex-preferred-size:unset;flex-basis:unset;flex-grow:unset;flex-shrink:unset}.charts-css.area:not(.reverse) tbody tr th,.charts-css.bar.stacked.reverse-datasets tbody tr,.charts-css.column.stacked.reverse-datasets tbody tr,.charts-css.column:not(.reverse) tbody tr th,.charts-css.line:not(.reverse) tbody tr th{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.charts-css.bar:not(.reverse-data) tbody,.charts-css.bar:not(.reverse-datasets):not(.stacked) tbody tr,.charts-css.column.reverse-datasets.stacked:not(.reverse) tbody tr,.charts-css.column:not(.reverse-datasets).stacked.reverse tbody tr{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.charts-css.bar.reverse-data tbody,.charts-css.bar.reverse-datasets:not(.stacked) tbody tr,.charts-css.column.reverse-datasets.stacked.reverse tbody tr,.charts-css.column:not(.reverse-datasets).stacked:not(.reverse) tbody tr{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.charts-css.area:not(.reverse-data) tbody,.charts-css.area:not(.reverse-datasets) tbody tr,.charts-css.bar.reverse-datasets.stacked.reverse tbody tr,.charts-css.bar:not(.reverse-datasets).stacked:not(.reverse) tbody tr,.charts-css.column.reverse-labels.reverse-data tbody,.charts-css.column:not(.reverse-datasets):not(.stacked) tbody tr,.charts-css.column:not(.reverse-labels):not(.reverse-data) tbody,.charts-css.line:not(.reverse-data) tbody,.charts-css.line:not(.reverse-datasets) tbody tr{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.charts-css.area.reverse-data tbody,.charts-css.area.reverse-datasets tbody tr,.charts-css.bar.reverse-datasets.stacked:not(.reverse) tbody tr,.charts-css.bar:not(.reverse-datasets).stacked.reverse tbody tr,.charts-css.column.reverse-datasets:not(.stacked) tbody tr,.charts-css.column.reverse-labels:not(.reverse-data) tbody,.charts-css.column:not(.reverse-labels).reverse-data tbody,.charts-css.line.reverse-data tbody,.charts-css.line.reverse-datasets tbody tr{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.charts-css.bar.data-spacing-1 tbody tr{-webkit-padding-before:1px;-webkit-padding-after:1px;padding-block-end:1px;padding-block-start:1px}.charts-css.bar.data-spacing-2 tbody tr{-webkit-padding-before:2px;-webkit-padding-after:2px;padding-block-end:2px;padding-block-start:2px}.charts-css.bar.data-spacing-3 tbody tr{-webkit-padding-before:3px;-webkit-padding-after:3px;padding-block-end:3px;padding-block-start:3px}.charts-css.bar.data-spacing-4 tbody tr{-webkit-padding-before:4px;-webkit-padding-after:4px;padding-block-end:4px;padding-block-start:4px}.charts-css.bar.data-spacing-5 tbody tr{-webkit-padding-before:5px;-webkit-padding-after:5px;padding-block-end:5px;padding-block-start:5px}.charts-css.bar.data-spacing-6 tbody tr{-webkit-padding-before:6px;-webkit-padding-after:6px;padding-block-end:6px;padding-block-start:6px}.charts-css.bar.data-spacing-7 tbody tr{-webkit-padding-before:7px;-webkit-padding-after:7px;padding-block-end:7px;padding-block-start:7px}.charts-css.bar.data-spacing-8 tbody tr{-webkit-padding-before:8px;-webkit-padding-after:8px;padding-block-end:8px;padding-block-start:8px}.charts-css.bar.data-spacing-9 tbody tr{-webkit-padding-before:9px;-webkit-padding-after:9px;padding-block-end:9px;padding-block-start:9px}.charts-css.bar.data-spacing-10 tbody tr{-webkit-padding-before:10px;-webkit-padding-after:10px;padding-block-end:10px;padding-block-start:10px}.charts-css.bar.data-spacing-11 tbody tr{-webkit-padding-before:11px;-webkit-padding-after:11px;padding-block-end:11px;padding-block-start:11px}.charts-css.bar.data-spacing-12 tbody tr{-webkit-padding-before:12px;-webkit-padding-after:12px;padding-block-end:12px;padding-block-start:12px}.charts-css.bar.data-spacing-13 tbody tr{-webkit-padding-before:13px;-webkit-padding-after:13px;padding-block-end:13px;padding-block-start:13px}.charts-css.bar.data-spacing-14 tbody tr{-webkit-padding-before:14px;-webkit-padding-after:14px;padding-block-end:14px;padding-block-start:14px}.charts-css.bar.data-spacing-15 tbody tr{-webkit-padding-before:15px;-webkit-padding-after:15px;padding-block-end:15px;padding-block-start:15px}.charts-css.bar.data-spacing-16 tbody tr{-webkit-padding-before:16px;-webkit-padding-after:16px;padding-block-end:16px;padding-block-start:16px}.charts-css.bar.data-spacing-17 tbody tr{-webkit-padding-before:17px;-webkit-padding-after:17px;padding-block-end:17px;padding-block-start:17px}.charts-css.bar.data-spacing-18 tbody tr{-webkit-padding-before:18px;-webkit-padding-after:18px;padding-block-end:18px;padding-block-start:18px}.charts-css.bar.data-spacing-19 tbody tr{-webkit-padding-before:19px;-webkit-padding-after:19px;padding-block-end:19px;padding-block-start:19px}.charts-css.bar.data-spacing-20 tbody tr{-webkit-padding-before:20px;-webkit-padding-after:20px;padding-block-end:20px;padding-block-start:20px}.charts-css.bar.datasets-spacing-1 tbody tr td{-webkit-margin-before:1px;-webkit-margin-after:1px;margin-block-end:1px;margin-block-start:1px}.charts-css.bar.datasets-spacing-2 tbody tr td{-webkit-margin-before:2px;-webkit-margin-after:2px;margin-block-end:2px;margin-block-start:2px}.charts-css.bar.datasets-spacing-3 tbody tr td{-webkit-margin-before:3px;-webkit-margin-after:3px;margin-block-end:3px;margin-block-start:3px}.charts-css.bar.datasets-spacing-4 tbody tr td{-webkit-margin-before:4px;-webkit-margin-after:4px;margin-block-end:4px;margin-block-start:4px}.charts-css.bar.datasets-spacing-5 tbody tr td{-webkit-margin-before:5px;-webkit-margin-after:5px;margin-block-end:5px;margin-block-start:5px}.charts-css.bar.datasets-spacing-6 tbody tr td{-webkit-margin-before:6px;-webkit-margin-after:6px;margin-block-end:6px;margin-block-start:6px}.charts-css.bar.datasets-spacing-7 tbody tr td{-webkit-margin-before:7px;-webkit-margin-after:7px;margin-block-end:7px;margin-block-start:7px}.charts-css.bar.datasets-spacing-8 tbody tr td{-webkit-margin-before:8px;-webkit-margin-after:8px;margin-block-end:8px;margin-block-start:8px}.charts-css.bar.datasets-spacing-9 tbody tr td{-webkit-margin-before:9px;-webkit-margin-after:9px;margin-block-end:9px;margin-block-start:9px}.charts-css.bar.datasets-spacing-10 tbody tr td{-webkit-margin-before:10px;-webkit-margin-after:10px;margin-block-end:10px;margin-block-start:10px}.charts-css.bar.datasets-spacing-11 tbody tr td{-webkit-margin-before:11px;-webkit-margin-after:11px;margin-block-end:11px;margin-block-start:11px}.charts-css.bar.datasets-spacing-12 tbody tr td{-webkit-margin-before:12px;-webkit-margin-after:12px;margin-block-end:12px;margin-block-start:12px}.charts-css.bar.datasets-spacing-13 tbody tr td{-webkit-margin-before:13px;-webkit-margin-after:13px;margin-block-end:13px;margin-block-start:13px}.charts-css.bar.datasets-spacing-14 tbody tr td{-webkit-margin-before:14px;-webkit-margin-after:14px;margin-block-end:14px;margin-block-start:14px}.charts-css.bar.datasets-spacing-15 tbody tr td{-webkit-margin-before:15px;-webkit-margin-after:15px;margin-block-end:15px;margin-block-start:15px}.charts-css.bar.datasets-spacing-16 tbody tr td{-webkit-margin-before:16px;-webkit-margin-after:16px;margin-block-end:16px;margin-block-start:16px}.charts-css.bar.datasets-spacing-17 tbody tr td{-webkit-margin-before:17px;-webkit-margin-after:17px;margin-block-end:17px;margin-block-start:17px}.charts-css.bar.datasets-spacing-18 tbody tr td{-webkit-margin-before:18px;-webkit-margin-after:18px;margin-block-end:18px;margin-block-start:18px}.charts-css.bar.datasets-spacing-19 tbody tr td{-webkit-margin-before:19px;-webkit-margin-after:19px;margin-block-end:19px;margin-block-start:19px}.charts-css.bar.datasets-spacing-20 tbody tr td{-webkit-margin-before:20px;-webkit-margin-after:20px;margin-block-end:20px;margin-block-start:20px}.charts-css.area tbody,.charts-css.column tbody,.charts-css.line tbody{-webkit-box-pack:justify;-ms-flex-pack:justify;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;aspect-ratio:var(--aspect-ratio,21/9);display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:space-between;width:100%}.charts-css.area tbody tr th,.charts-css.column tbody tr th,.charts-css.line tbody tr th{bottom:0;height:var(--labels-size);left:0;position:absolute;right:0;top:0}.charts-css.column tbody tr td{-webkit-box-pack:center;-ms-flex-pack:center;display:-webkit-box;display:-ms-flexbox;display:flex;height:calc(100% * var(--end, var(--size, 1)));justify-content:center;position:relative;width:100%}.charts-css.column:not(.reverse) tbody tr td{-webkit-box-align:var(--data-position,flex-start);-ms-flex-align:var(--data-position,flex-start);align-items:var(--data-position,flex-start)}.charts-css.column.reverse tbody tr td{-webkit-box-align:var(--data-position,flex-end);-ms-flex-align:var(--data-position,flex-end);align-items:var(--data-position,flex-end)}.charts-css.area.reverse tbody tr td,.charts-css.area:not(.reverse) tbody tr td,.charts-css.column.reverse tbody tr td,.charts-css.column:not(.reverse) tbody tr td,.charts-css.line.reverse tbody tr td,.charts-css.line:not(.reverse) tbody tr td{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.charts-css.area.reverse tbody tr th,.charts-css.column.reverse tbody tr th,.charts-css.line.reverse tbody tr th{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.charts-css.area.reverse-labels.reverse tbody tr,.charts-css.area:not(.reverse-labels):not(.reverse) tbody tr,.charts-css.column.reverse-labels.reverse tbody tr,.charts-css.column:not(.reverse-labels):not(.reverse) tbody tr,.charts-css.line.reverse-labels.reverse tbody tr,.charts-css.line:not(.reverse-labels):not(.reverse) tbody tr{-webkit-margin-after:var(--labels-size);margin-block-end:var(--labels-size)}.charts-css.area:not(.reverse-labels):not(.reverse) tbody tr th,.charts-css.column:not(.reverse-labels):not(.reverse) tbody tr th,.charts-css.line:not(.reverse-labels):not(.reverse) tbody tr th{-webkit-margin-before:auto;-webkit-margin-after:calc(-1 * var(--labels-size) - var(--primary-axis-width));margin-block-end:calc(-1 * var(--labels-size) - var(--primary-axis-width));margin-block-start:auto}.charts-css.area.reverse-labels:not(.reverse) tbody tr,.charts-css.area:not(.reverse-labels).reverse tbody tr,.charts-css.column.reverse-labels:not(.reverse) tbody tr,.charts-css.column:not(.reverse-labels).reverse tbody tr,.charts-css.line.reverse-labels:not(.reverse) tbody tr,.charts-css.line:not(.reverse-labels).reverse tbody tr{-webkit-margin-before:var(--labels-size);margin-block-start:var(--labels-size)}.charts-css.area:not(.reverse-labels).reverse tbody tr th,.charts-css.column:not(.reverse-labels).reverse tbody tr th,.charts-css.line:not(.reverse-labels).reverse tbody tr th{-webkit-margin-after:auto;-webkit-margin-before:calc(-1 * var(--labels-size) - var(--primary-axis-width));margin-block-end:auto;margin-block-start:calc(-1 * var(--labels-size) - var(--primary-axis-width))}.charts-css.area.reverse-labels:not(.reverse) tbody tr th,.charts-css.column.reverse-labels:not(.reverse) tbody tr th,.charts-css.line.reverse-labels:not(.reverse) tbody tr th{-webkit-margin-after:auto;-webkit-margin-before:calc(-1 * var(--labels-size));margin-block-end:auto;margin-block-start:calc(-1 * var(--labels-size))}.charts-css.area.reverse-labels.reverse tbody tr th,.charts-css.column.reverse-labels.reverse tbody tr th,.charts-css.line.reverse-labels.reverse tbody tr th{-webkit-margin-before:auto;-webkit-margin-after:calc(-1 * var(--labels-size));margin-block-end:calc(-1 * var(--labels-size));margin-block-start:auto}.charts-css.column.data-spacing-1 tbody tr{-webkit-padding-start:1px;-webkit-padding-end:1px;padding-inline-end:1px;padding-inline-start:1px}.charts-css.column.data-spacing-2 tbody tr{-webkit-padding-start:2px;-webkit-padding-end:2px;padding-inline-end:2px;padding-inline-start:2px}.charts-css.column.data-spacing-3 tbody tr{-webkit-padding-start:3px;-webkit-padding-end:3px;padding-inline-end:3px;padding-inline-start:3px}.charts-css.column.data-spacing-4 tbody tr{-webkit-padding-start:4px;-webkit-padding-end:4px;padding-inline-end:4px;padding-inline-start:4px}.charts-css.column.data-spacing-5 tbody tr{-webkit-padding-start:5px;-webkit-padding-end:5px;padding-inline-end:5px;padding-inline-start:5px}.charts-css.column.data-spacing-6 tbody tr{-webkit-padding-start:6px;-webkit-padding-end:6px;padding-inline-end:6px;padding-inline-start:6px}.charts-css.column.data-spacing-7 tbody tr{-webkit-padding-start:7px;-webkit-padding-end:7px;padding-inline-end:7px;padding-inline-start:7px}.charts-css.column.data-spacing-8 tbody tr{-webkit-padding-start:8px;-webkit-padding-end:8px;padding-inline-end:8px;padding-inline-start:8px}.charts-css.column.data-spacing-9 tbody tr{-webkit-padding-start:9px;-webkit-padding-end:9px;padding-inline-end:9px;padding-inline-start:9px}.charts-css.column.data-spacing-10 tbody tr{-webkit-padding-start:10px;-webkit-padding-end:10px;padding-inline-end:10px;padding-inline-start:10px}.charts-css.column.data-spacing-11 tbody tr{-webkit-padding-start:11px;-webkit-padding-end:11px;padding-inline-end:11px;padding-inline-start:11px}.charts-css.column.data-spacing-12 tbody tr{-webkit-padding-start:12px;-webkit-padding-end:12px;padding-inline-end:12px;padding-inline-start:12px}.charts-css.column.data-spacing-13 tbody tr{-webkit-padding-start:13px;-webkit-padding-end:13px;padding-inline-end:13px;padding-inline-start:13px}.charts-css.column.data-spacing-14 tbody tr{-webkit-padding-start:14px;-webkit-padding-end:14px;padding-inline-end:14px;padding-inline-start:14px}.charts-css.column.data-spacing-15 tbody tr{-webkit-padding-start:15px;-webkit-padding-end:15px;padding-inline-end:15px;padding-inline-start:15px}.charts-css.column.data-spacing-16 tbody tr{-webkit-padding-start:16px;-webkit-padding-end:16px;padding-inline-end:16px;padding-inline-start:16px}.charts-css.column.data-spacing-17 tbody tr{-webkit-padding-start:17px;-webkit-padding-end:17px;padding-inline-end:17px;padding-inline-start:17px}.charts-css.column.data-spacing-18 tbody tr{-webkit-padding-start:18px;-webkit-padding-end:18px;padding-inline-end:18px;padding-inline-start:18px}.charts-css.column.data-spacing-19 tbody tr{-webkit-padding-start:19px;-webkit-padding-end:19px;padding-inline-end:19px;padding-inline-start:19px}.charts-css.column.data-spacing-20 tbody tr{-webkit-padding-start:20px;-webkit-padding-end:20px;padding-inline-end:20px;padding-inline-start:20px}.charts-css.column.datasets-spacing-1 tbody tr td{-webkit-margin-start:1px;-webkit-margin-end:1px;margin-inline-end:1px;margin-inline-start:1px}.charts-css.column.datasets-spacing-2 tbody tr td{-webkit-margin-start:2px;-webkit-margin-end:2px;margin-inline-end:2px;margin-inline-start:2px}.charts-css.column.datasets-spacing-3 tbody tr td{-webkit-margin-start:3px;-webkit-margin-end:3px;margin-inline-end:3px;margin-inline-start:3px}.charts-css.column.datasets-spacing-4 tbody tr td{-webkit-margin-start:4px;-webkit-margin-end:4px;margin-inline-end:4px;margin-inline-start:4px}.charts-css.column.datasets-spacing-5 tbody tr td{-webkit-margin-start:5px;-webkit-margin-end:5px;margin-inline-end:5px;margin-inline-start:5px}.charts-css.column.datasets-spacing-6 tbody tr td{-webkit-margin-start:6px;-webkit-margin-end:6px;margin-inline-end:6px;margin-inline-start:6px}.charts-css.column.datasets-spacing-7 tbody tr td{-webkit-margin-start:7px;-webkit-margin-end:7px;margin-inline-end:7px;margin-inline-start:7px}.charts-css.column.datasets-spacing-8 tbody tr td{-webkit-margin-start:8px;-webkit-margin-end:8px;margin-inline-end:8px;margin-inline-start:8px}.charts-css.column.datasets-spacing-9 tbody tr td{-webkit-margin-start:9px;-webkit-margin-end:9px;margin-inline-end:9px;margin-inline-start:9px}.charts-css.column.datasets-spacing-10 tbody tr td{-webkit-margin-start:10px;-webkit-margin-end:10px;margin-inline-end:10px;margin-inline-start:10px}.charts-css.column.datasets-spacing-11 tbody tr td{-webkit-margin-start:11px;-webkit-margin-end:11px;margin-inline-end:11px;margin-inline-start:11px}.charts-css.column.datasets-spacing-12 tbody tr td{-webkit-margin-start:12px;-webkit-margin-end:12px;margin-inline-end:12px;margin-inline-start:12px}.charts-css.column.datasets-spacing-13 tbody tr td{-webkit-margin-start:13px;-webkit-margin-end:13px;margin-inline-end:13px;margin-inline-start:13px}.charts-css.column.datasets-spacing-14 tbody tr td{-webkit-margin-start:14px;-webkit-margin-end:14px;margin-inline-end:14px;margin-inline-start:14px}.charts-css.column.datasets-spacing-15 tbody tr td{-webkit-margin-start:15px;-webkit-margin-end:15px;margin-inline-end:15px;margin-inline-start:15px}.charts-css.column.datasets-spacing-16 tbody tr td{-webkit-margin-start:16px;-webkit-margin-end:16px;margin-inline-end:16px;margin-inline-start:16px}.charts-css.column.datasets-spacing-17 tbody tr td{-webkit-margin-start:17px;-webkit-margin-end:17px;margin-inline-end:17px;margin-inline-start:17px}.charts-css.column.datasets-spacing-18 tbody tr td{-webkit-margin-start:18px;-webkit-margin-end:18px;margin-inline-end:18px;margin-inline-start:18px}.charts-css.column.datasets-spacing-19 tbody tr td{-webkit-margin-start:19px;-webkit-margin-end:19px;margin-inline-end:19px;margin-inline-start:19px}.charts-css.column.datasets-spacing-20 tbody tr td{-webkit-margin-start:20px;-webkit-margin-end:20px;margin-inline-end:20px;margin-inline-start:20px}.charts-css.area tbody tr td,.charts-css.line tbody tr td{-webkit-box-orient:vertical;-webkit-box-direction:normal;bottom:0;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-flow:column;flex-flow:column;height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:0}.charts-css.area tbody tr td::before,.charts-css.line tbody tr td::before{bottom:0;content:"";left:0;position:absolute;right:0;top:0;z-index:-1}.charts-css.area tbody tr td::after,.charts-css.line tbody tr td::after,.charts-css.pie tbody tr td::after{content:"";width:100%}.charts-css.area.reverse:not(.reverse-data) tbody tr td,.charts-css.area:not(.reverse):not(.reverse-data) tbody tr td,.charts-css.line.reverse:not(.reverse-data) tbody tr td,.charts-css.line:not(.reverse):not(.reverse-data) tbody tr td{-webkit-box-pack:end;-ms-flex-pack:end;-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end;justify-content:flex-end}.charts-css.area:not(.reverse):not(.reverse-data) tbody tr td::before{-webkit-clip-path:polygon(0 calc(100% * (1 - var(--start, var(--end, var(--size))))),100% calc(100% * (1 - var(--end, var(--size)))),100% 100%,0 100%);clip-path:polygon(0 calc(100% * (1 - var(--start, var(--end, var(--size))))),100% calc(100% * (1 - var(--end, var(--size)))),100% 100%,0 100%)}.charts-css.area.reverse:not(.reverse-data) tbody tr td .data,.charts-css.area:not(.reverse):not(.reverse-data) tbody tr td .data,.charts-css.line.reverse:not(.reverse-data) tbody tr td .data,.charts-css.line:not(.reverse):not(.reverse-data) tbody tr td .data{-webkit-transform:translateX(50%);transform:translateX(50%)}.charts-css.area:not(.reverse).reverse-data tbody tr td::after,.charts-css.area:not(.reverse):not(.reverse-data) tbody tr td::after,.charts-css.line:not(.reverse).reverse-data tbody tr td::after,.charts-css.line:not(.reverse):not(.reverse-data) tbody tr td::after{height:calc(100% * var(--end, var(--size)))}.charts-css.area.reverse.reverse-data tbody tr td,.charts-css.area:not(.reverse).reverse-data tbody tr td,.charts-css.line.reverse.reverse-data tbody tr td,.charts-css.line:not(.reverse).reverse-data tbody tr td{-webkit-box-pack:end;-ms-flex-pack:end;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;justify-content:flex-end}.charts-css.area:not(.reverse).reverse-data tbody tr td::before{-webkit-clip-path:polygon(0 calc(100% * (1 - var(--end, var(--size)))),100% calc(100% * (1 - var(--start, var(--end, var(--size))))),100% 100%,0 100%);clip-path:polygon(0 calc(100% * (1 - var(--end, var(--size)))),100% calc(100% * (1 - var(--start, var(--end, var(--size))))),100% 100%,0 100%)}.charts-css.area.reverse.reverse-data tbody tr td .data,.charts-css.area:not(.reverse).reverse-data tbody tr td .data,.charts-css.line.reverse.reverse-data tbody tr td .data,.charts-css.line:not(.reverse).reverse-data tbody tr td .data{-webkit-transform:translateX(-50%);transform:translateX(-50%)}.charts-css.area.reverse:not(.reverse-data) tbody tr td::before{-webkit-clip-path:polygon(0 0,100% 0,100% calc(100% * var(--end, var(--size))),0 calc(100% * var(--start, var(--end, var(--size)))));clip-path:polygon(0 0,100% 0,100% calc(100% * var(--end, var(--size))),0 calc(100% * var(--start, var(--end, var(--size)))))}.charts-css.area.reverse.reverse-data tbody tr td::after,.charts-css.area.reverse:not(.reverse-data) tbody tr td::after,.charts-css.line.reverse.reverse-data tbody tr td::after,.charts-css.line.reverse:not(.reverse-data) tbody tr td::after{height:calc(100% * (1 - var(--end, var(--size))))}.charts-css.area.reverse.reverse-data tbody tr td::before{-webkit-clip-path:polygon(0 0,100% 0,100% calc(100% * var(--start, var(--end, var(--size)))),0 calc(100% * var(--end, var(--size))));clip-path:polygon(0 0,100% 0,100% calc(100% * var(--start, var(--end, var(--size)))),0 calc(100% * var(--end, var(--size))))}.charts-css.line{--line-size:3px}.charts-css.line:not(.reverse):not(.reverse-data) tbody tr td::before{-webkit-clip-path:polygon(0 calc(100% * (1 - var(--start, var(--end, var(--size))))),100% calc(100% * (1 - var(--end, var(--size)))),100% calc(100% * (1 - var(--end, var(--size))) - var(--line-size)),0 calc(100% * (1 - var(--start, var(--end, var(--size)))) - var(--line-size)));clip-path:polygon(0 calc(100% * (1 - var(--start, var(--end, var(--size))))),100% calc(100% * (1 - var(--end, var(--size)))),100% calc(100% * (1 - var(--end, var(--size))) - var(--line-size)),0 calc(100% * (1 - var(--start, var(--end, var(--size)))) - var(--line-size)))}.charts-css.line:not(.reverse).reverse-data tbody tr td::before{-webkit-clip-path:polygon(0 calc(100% * (1 - var(--end, var(--size)))),100% calc(100% * (1 - var(--start, var(--end, var(--size))))),100% calc(100% * (1 - var(--start, var(--end, var(--size)))) - var(--line-size)),0 calc(100% * (1 - var(--end, var(--size))) - var(--line-size)));clip-path:polygon(0 calc(100% * (1 - var(--end, var(--size)))),100% calc(100% * (1 - var(--start, var(--end, var(--size))))),100% calc(100% * (1 - var(--start, var(--end, var(--size)))) - var(--line-size)),0 calc(100% * (1 - var(--end, var(--size))) - var(--line-size)))}.charts-css.line.reverse:not(.reverse-data) tbody tr td::before{-webkit-clip-path:polygon(0 calc(100% * var(--start, var(--end, var(--size))) - var(--line-size)),100% calc(100% * var(--end, var(--size)) - var(--line-size)),100% calc(100% * var(--end, var(--size))),0 calc(100% * var(--start, var(--end, var(--size)))));clip-path:polygon(0 calc(100% * var(--start, var(--end, var(--size))) - var(--line-size)),100% calc(100% * var(--end, var(--size)) - var(--line-size)),100% calc(100% * var(--end, var(--size))),0 calc(100% * var(--start, var(--end, var(--size)))))}.charts-css.line.reverse.reverse-data tbody tr td::before{-webkit-clip-path:polygon(0 calc(100% * var(--end, var(--size)) - var(--line-size)),100% calc(100% * var(--start, var(--end, var(--size))) - var(--line-size)),100% calc(100% * var(--start, var(--end, var(--size)))),0 calc(100% * var(--end, var(--size))));clip-path:polygon(0 calc(100% * var(--end, var(--size)) - var(--line-size)),100% calc(100% * var(--start, var(--end, var(--size))) - var(--line-size)),100% calc(100% * var(--start, var(--end, var(--size)))),0 calc(100% * var(--end, var(--size))))}.charts-css.pie tbody,.charts-css.polar tbody,.charts-css.radar tbody,.charts-css.radial tbody{aspect-ratio:1;background-color:var(--chart-bg-color);border-radius:50%;display:block;width:100%}.charts-css.pie tbody tr td{-webkit-box-pack:center;-ms-flex-pack:center;background:conic-gradient(transparent 0 calc(1turn * var(--start)),var(--c,transparent) calc(1turn * var(--start, 0)) calc(1turn * var(--end)),transparent calc(1turn * var(--end)) 1turn);border-radius:50%;display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:center}.charts-css.pie tbody tr td,.charts-css.pie tbody tr td::before{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}.charts-css.pie tbody tr td::before{content:""}.charts-css.pie tbody tr td .data{-webkit-box-pack:center;-ms-flex-pack:center;border-radius:50%;bottom:0;display:-webkit-box;display:-ms-flexbox;display:flex;height:100%;justify-content:center;left:0;position:absolute;right:0;top:0;-webkit-transform:rotate(calc(.5turn * var(--start, 0) + .5turn * var(--end, 0)));transform:rotate(calc(.5turn * var(--start, 0) + .5turn * var(--end, 0)));width:100%}graphql-2.6.0/lib/graphql/dashboard/statics/dashboard.js0000644000004100000410000001047615173430257023316 0ustar www-datawww-datafunction detectTheme() { var storedTheme = localStorage.getItem("graphql_dashboard:theme") var preferredTheme = !!window.matchMedia('(prefers-color-scheme: dark)').matches ? "dark" : "light" setTheme(storedTheme || preferredTheme) } function toggleTheme() { var nextTheme = document.documentElement.getAttribute("data-bs-theme") == "dark" ? "light" : "dark" setTheme(nextTheme) } function setTheme(theme) { localStorage.setItem("graphql_dashboard:theme", theme) document.documentElement.setAttribute("data-bs-theme", theme) var icon = theme == "dark" ? "🌙" : "🌞" var toggle = document.getElementById("themeToggle") if (toggle) { toggle.innerText = icon } else { document.addEventListener("DOMContentLoaded", function(_ev) { document.getElementById("themeToggle").innerText = icon }) } } detectTheme() var perfettoUrl = "https://ui.perfetto.dev" async function openOnPerfetto(operationName, tracePath) { var resp = await fetch(tracePath); var blob = await resp.blob(); var nextPerfettoData = await blob.arrayBuffer(); nextPerfettoWindow = window.open(perfettoUrl) var messageHandler = function(event) { if (event.origin == perfettoUrl && event.data == "PONG") { clearInterval(perfettoWaiting) window.removeEventListener("message", messageHandler) nextPerfettoWindow.postMessage({ perfetto: { buffer: nextPerfettoData, title: operationName + " - GraphQL", filename: "perfetto-" + operationName + ".dump", } }, perfettoUrl) } } window.addEventListener("message", messageHandler, false) perfettoWaiting = setInterval(function() { nextPerfettoWindow.postMessage("PING", perfettoUrl) }, 100) } function getCsrfToken() { return document.querySelector("meta[name='csrf-token']").content } function deleteTrace(tracePath) { if (confirm("Are you sure you want to permanently delete this trace?")) { fetch(tracePath, { method: "DELETE", headers: { "X-CSRF-Token": getCsrfToken() } }).then(function(_response) { window.location.reload() }) } } function deleteAllTraces(path) { if (confirm("Are you sure you want to permanently delete ALL traces?")) { fetch(path, { method: "DELETE", headers: { "X-CSRF-Token": getCsrfToken() } }).then(function(_response) { window.location.reload() }) } } function deleteAllSubscriptions(path) { if (confirm("This will:\n\n- Remove all subscriptions from the database\n- Stop updates to all current subscribers\n\nAre you sure?")) { fetch(path, { method: "POST", headers: { "X-CSRF-Token": getCsrfToken() } }).then(function(_response) { window.location.reload() }) } } function sendArchive(clientName) { var values = [] document.querySelectorAll(".archive-check:checked").forEach(function(el) { values.push(el.value) }) if (values.length == 0) { return } var mode = window.location.pathname.includes("/archived") ? "/unarchive" : "/archive" if (mode == "/archive") { if (!confirm("Are you sure you want to archive these operations? They won't be usable by clients while archived.")) { return } } else { if (!confirm("Are you sure you want to reactivate these operations? They'll be available to clients again.")) { return } } var url = window.location.pathname.replace("/archived", "") url += mode var data if (clientName) { data = { operation_aliases: values } } else { data = { digests: values } } fetch(url, { method: "POST", body: JSON.stringify(data), headers: { "X-CSRF-Token": getCsrfToken(), "Content-Type": "application/json", }}).then(function(_response) { window.location.reload() }) } document.addEventListener("click", function(event) { var dataset = event.target.dataset if (dataset.perfettoOpen) { openOnPerfetto(dataset.perfettoOpen, dataset.perfettoPath) } else if (dataset.perfettoDelete) { deleteTrace(dataset.perfettoDelete, event) } else if (dataset.perfettoDeleteAll) { deleteAllTraces(dataset.perfettoDeleteAll) } else if (dataset.subscriptionsDeleteAll) { deleteAllSubscriptions(dataset.subscriptionsDeleteAll) } else if (event.target.id == "themeToggle") { toggleTheme() } else if (dataset.archiveClient || dataset.archiveAll) { sendArchive(dataset.archiveClient) } }) graphql-2.6.0/lib/graphql/dashboard/statics/header-icon.png0000644000004100000410000001654215173430257023715 0ustar www-datawww-dataPNG  IHDR`V0sRGBPeXIfMM*i&`V 1 ^DATx] tTչ{sf&I޼ >BT JZJ}WW{k_Voj(&>JTDArA Bw& $3}[H2 f9P(0D! QP^iFBNYBK$NW7,aX%,O`*Ō3Ź,~h 7"-ǕcZ-Mh;97פXճ`΀ Ezw} b JyAj)#z2OKy{cTb``R;tk?p% KRa9 @{R==/ٿ#Xk4 ֦53 uh#gnQsrp"*$h0GMG*RMP~s=IGa5#.K!08z|ͯޖweE 8LE=gUeAdUi38g>]X[ !.ʝ*,ՌU&LS\\Y $W !,b6i2]oӪ^5b+,B^lPI6c"\K@o@! xnc\l`wG 3!SFx.<8 5+ GR̂}LCS etLpt6`G9t>C>LIc[ 9 |a>Fl,r6aQgĞQ_1906Q(G20(q\ TjSHj4~7/TBL:Q6̞ajT[a ]蠟X+}`=Mx-TI-3Dg^^P| 0޹8rCss]h,cUǙ'111167i/7AdH6~DWUnJM:G}8#܌kt~? I_erYNZc.{e] Rv6v$eol#c:)> 0spG%%mrQ[K̞|fl\&pfA[U+))uI^OA}x|iӌ6{@E_ 6 ~9w[hobݘej ~9aTղ(gA9Yirr@ 9q^ s+Q1e_p's%V,j]]kȘv0F_n1c}[jj:P~0c4 .kꀰqGPCϢ_%s|䨘1poAK׮>:*`.HɆ)͕G+ڗĦ5BpEPYv}Vs@xAKusx܀c|ň<3&&ފܱY0!7JNt?uToy.p$ n3P}2Kiw  +Z4:w¨bw ^foVH#٭BLIaPRaJ< <Ų*5_*Z|ь;iE˹ %uua}{ءVŴgg VuSoLĥ/ybI̶pI2oCriT/T:Vh5˗ :>N2֥B"0 X0>/a&U>S3=SŀnM𥹍 Xeض:MJد@ F4 hCWnXքWnjظ*8-S.!URB511J24D6tO45DDR!t ,Ș DD2m|j kWֺE;ºa;P\z@]6>l!.c dXAXH 2 {¯p~[Z?X@Jk6!ߋujX@i 稂KZhׄ;/,bRQ?zezM-Sj3LvK(y_D@z2wec^ v@`3$i~mmܘMvV}^RWͨEBфiiխl9yOncBL 0d`?-8H; < (j.hXOt]1 &;BOD'wJchrآ{%g;s`]̆F`᜵HMU T@,ܚvp#IZj0\JxBotWШ+X_߉!: 5 >+Q䶾1܅Q ?x%~77Ѱ 1 dc˒Nr%v7y5MRamĎ{dk@0&<7u\35ɣH#']/9'|tw;a‡CІ!>:%1cjOkx; %6EA+Rk5şF g3hʷb]1ov FbG+]G٧U>nfZr@c _3/uߥp:^Ao+V"VUb׃`,l{S3=d46l_3ָ͚ "A]`SഃDMZ{0{ %0{,L3HBn ΘϽϵ${{kߩӿ5< oݍ$402OpBnIytdAͯWr)X02%ojP"ºZ6Ϝ0}6i>ヨZ kǿ( @A| Fj '7A JS@E_wX^S0`M=rRX8`鴖rH vg%te2pȀ=ȶ4OёH}TqJ!x`Z6M ?p7\\,Hw3k5gt)SrSxk^zY-xXޘ;X?*&$v+.J'%D|󃪙\1 RgrGŵx`F0Sf&lOƁ =Hq)N Tօ.3 "Ec{~=m$C< lj\3bV5r+B>q{&LO|VQ鈯&1[.PYxQn+s_N0HY#{9eM8[4_e?8$$!RlIګ0@كQ`Hտn4R+ Fڙy$n$m-mM}7ˎ6v^%VzIe< >_L-^rp]|70lA #&}8筷bT0,|y!}#z']2л[6]څ3L4eGa;)kYT$xESX3y;ڥ ;2 \{MIW Q߅1~|Aq1WɛĢE\v;pMy=28)+s pgj~>ҿg̜0 A_!< P}5~oΛT< cR ?Sޏؓ<` .t_lµ$ǔ=N_-lj; ms7!X ?=Wt6@ȖYxN1CI c ]=Ȏݷ`d4ri19[KpSaw >v\>.1xǁ2. Ja?a*1M(90-JYQ18[ՄƁ}^^Z4ꐘ,1C34RT? ~Vg@! 왊c a_ CNO{!1fi$Br`ق2@v8.`7*G1P'h*Tx? C(,Ϩ )qMƕ_hLێ0R_L5 `Z (ݚK ;%0`i7lNr\M>{]2k"5^UDF- QoCg՜7I,x)ꓼL8ɽgBxzk+!vL} #9hg5QE)`F]f5_AR,; jch&pfE3V6+qvӸm \;zk%fZ$t5} PQ J|"y8nr%5dS> yAl;vǼlٻק'>e!gK68= Q]_@4go Rdkz>+e#h>~G]Z| 1EYom̈wKh454bB;A`&Bȭ8?[e. Bqnѿ񹟛>̂}.k49seH`E[ | |i[q)7?;╬hVKg!go %(.No$FQxgYpȗ%K^3€*_KgԃX( !in5ޒjkx3.L(1"k3H^\~(0kskp5@^qyv% H>ÆC >Q?''+cNt>I>7`w,4޾} !vmNJ:10ȴ,Gu5/AЦ)R;xJl_/hߎ-g4tbjw<5@ -:i-,AP.Ŀcħ>Tk~оJ>p 3L/uNPGbazZ#dB ^VȚ_ay>GWcNfy]LA| Vj0K[7y72;-sOqgKSY&2> ;0 .^f# x0V hZe.>0/unxaCY1__pugV7c)?*Xn JРiZRGXŇ1^z%_ѓ.-- Mo7ژ.%Tάcg1?.h*_ RVG\{o!A63\ ~=b.&f]X<킦W+0ЬoLx{]G/D4Y5OCGB洸84JYQ=4hA01=S{y $}aajoabx>-X]Rߧ xZ^jjpM ` [z\Z{볂ܢ fO*Y*x$Ӛ0ΰ5㴬' A (1B_ gG9+@Li΄e xY' LmFU?l?/L,9kt[.HnKZ4Do=[ǹ6àBSgO|e6Fe7-_OLM;1uPs! Q`C(0D_s3IENDB`graphql-2.6.0/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js0000644000004100000410000023552215173430257024633 0ustar www-datawww-data/*! * Bootstrap v5.3.3 (https://getbootstrap.com/) * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),s=t=>{t.dispatchEvent(new Event(i))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,a=t=>{if(!o(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},l=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),c=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?c(t.parentNode):null},h=()=>{},d=t=>{t.offsetHeight},u=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,f=[],p=()=>"rtl"===document.documentElement.dir,m=t=>{var e;e=()=>{const e=u();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(f.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of f)t()})),f.push(e)):e()},g=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,_=(t,e,n=!0)=>{if(!n)return void g(t);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),g(t))};e.addEventListener(i,a),setTimeout((()=>{r||s(e)}),o)},b=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,A={};let E=1;const T={mouseenter:"mouseover",mouseleave:"mouseout"},C=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${E++}`||t.uidEvent||E++}function x(t){const e=O(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function k(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function L(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=I(t);return C.has(o)||(o=t),[n,s,o]}function S(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=L(e,i,n);if(e in T){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=x(t),c=l[a]||(l[a]={}),h=k(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=O(r,e.replace(v,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return P(s,{delegateTarget:r}),n.oneOff&&N.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return P(n,{delegateTarget:t}),i.oneOff&&N.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function D(t,e,i,n,s){const o=k(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function $(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&D(t,e,i,r.callable,r.delegationSelector)}function I(t){return t=t.replace(y,""),T[t]||t}const N={on(t,e,i,n){S(t,e,i,n,!1)},one(t,e,i,n){S(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=L(e,i,n),a=r!==e,l=x(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))$(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(w,"");a&&!e.includes(s)||D(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;D(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=u();let s=null,o=!0,r=!0,a=!1;e!==I(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=P(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function P(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function j(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function M(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const F={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${M(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${M(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=j(t.dataset[n])}return e},getDataAttribute:(t,e)=>j(t.getAttribute(`data-bs-${M(e)}`))};class H{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=o(e)?F.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...o(e)?F.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],r=o(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(r))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${r}" but expected type "${s}".`)}var i}}class W extends H{constructor(t,i){super(),(t=r(t))&&(this._element=t,this._config=this._getConfig(i),e.set(this._element,this.constructor.DATA_KEY,this))}dispose(){e.remove(this._element,this.constructor.DATA_KEY),N.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){_(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return e.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.3"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const B=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e?e.split(",").map((t=>n(t))).join(","):null},z={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!l(t)&&a(t)))},getSelectorFromElement(t){const e=B(t);return e&&z.findOne(e)?e:null},getElementFromSelector(t){const e=B(t);return e?z.findOne(e):null},getMultipleElementsFromSelector(t){const e=B(t);return e?z.find(e):[]}},R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;N.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),l(this))return;const s=z.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},q=".bs.alert",V=`close${q}`,K=`closed${q}`;class Q extends W{static get NAME(){return"alert"}close(){if(N.trigger(this._element,V).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),N.trigger(this._element,K),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Q.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(Q,"close"),m(Q);const X='[data-bs-toggle="button"]';class Y extends W{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=Y.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}N.on(document,"click.bs.button.data-api",X,(t=>{t.preventDefault();const e=t.target.closest(X);Y.getOrCreateInstance(e).toggle()})),m(Y);const U=".bs.swipe",G=`touchstart${U}`,J=`touchmove${U}`,Z=`touchend${U}`,tt=`pointerdown${U}`,et=`pointerup${U}`,it={endCallback:null,leftCallback:null,rightCallback:null},nt={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class st extends H{constructor(t,e){super(),this._element=t,t&&st.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return it}static get DefaultType(){return nt}static get NAME(){return"swipe"}dispose(){N.off(this._element,U)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),g(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&g(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(N.on(this._element,tt,(t=>this._start(t))),N.on(this._element,et,(t=>this._end(t))),this._element.classList.add("pointer-event")):(N.on(this._element,G,(t=>this._start(t))),N.on(this._element,J,(t=>this._move(t))),N.on(this._element,Z,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const ot=".bs.carousel",rt=".data-api",at="next",lt="prev",ct="left",ht="right",dt=`slide${ot}`,ut=`slid${ot}`,ft=`keydown${ot}`,pt=`mouseenter${ot}`,mt=`mouseleave${ot}`,gt=`dragstart${ot}`,_t=`load${ot}${rt}`,bt=`click${ot}${rt}`,vt="carousel",yt="active",wt=".active",At=".carousel-item",Et=wt+At,Tt={ArrowLeft:ht,ArrowRight:ct},Ct={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Ot={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class xt extends W{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=z.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===vt&&this.cycle()}static get Default(){return Ct}static get DefaultType(){return Ot}static get NAME(){return"carousel"}next(){this._slide(at)}nextWhenVisible(){!document.hidden&&a(this._element)&&this.next()}prev(){this._slide(lt)}pause(){this._isSliding&&s(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?N.one(this._element,ut,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void N.one(this._element,ut,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?at:lt;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&N.on(this._element,ft,(t=>this._keydown(t))),"hover"===this._config.pause&&(N.on(this._element,pt,(()=>this.pause())),N.on(this._element,mt,(()=>this._maybeEnableCycle()))),this._config.touch&&st.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of z.find(".carousel-item img",this._element))N.on(t,gt,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(ct)),rightCallback:()=>this._slide(this._directionToOrder(ht)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new st(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=Tt[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=z.findOne(wt,this._indicatorsElement);e.classList.remove(yt),e.removeAttribute("aria-current");const i=z.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(yt),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===at,s=e||b(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>N.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(dt).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),d(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(yt),i.classList.remove(yt,c,l),this._isSliding=!1,r(ut)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return z.findOne(Et,this._element)}_getItems(){return z.find(At,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===ct?lt:at:t===ct?at:lt}_orderToDirection(t){return p()?t===lt?ct:ht:t===lt?ht:ct}static jQueryInterface(t){return this.each((function(){const e=xt.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}N.on(document,bt,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=z.getElementFromSelector(this);if(!e||!e.classList.contains(vt))return;t.preventDefault();const i=xt.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===F.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),N.on(window,_t,(()=>{const t=z.find('[data-bs-ride="carousel"]');for(const e of t)xt.getOrCreateInstance(e)})),m(xt);const kt=".bs.collapse",Lt=`show${kt}`,St=`shown${kt}`,Dt=`hide${kt}`,$t=`hidden${kt}`,It=`click${kt}.data-api`,Nt="show",Pt="collapse",jt="collapsing",Mt=`:scope .${Pt} .${Pt}`,Ft='[data-bs-toggle="collapse"]',Ht={parent:null,toggle:!0},Wt={parent:"(null|element)",toggle:"boolean"};class Bt extends W{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=z.find(Ft);for(const t of i){const e=z.getSelectorFromElement(t),i=z.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return Ht}static get DefaultType(){return Wt}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Bt.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(N.trigger(this._element,Lt).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(Pt),this._element.classList.add(jt),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(jt),this._element.classList.add(Pt,Nt),this._element.style[e]="",N.trigger(this._element,St)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(N.trigger(this._element,Dt).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,d(this._element),this._element.classList.add(jt),this._element.classList.remove(Pt,Nt);for(const t of this._triggerArray){const e=z.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(jt),this._element.classList.add(Pt),N.trigger(this._element,$t)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(Nt)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=r(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(Ft);for(const e of t){const t=z.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=z.find(Mt,this._config.parent);return z.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Bt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}N.on(document,It,Ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of z.getMultipleElementsFromSelector(this))Bt.getOrCreateInstance(t,{toggle:!1}).toggle()})),m(Bt);var zt="top",Rt="bottom",qt="right",Vt="left",Kt="auto",Qt=[zt,Rt,qt,Vt],Xt="start",Yt="end",Ut="clippingParents",Gt="viewport",Jt="popper",Zt="reference",te=Qt.reduce((function(t,e){return t.concat([e+"-"+Xt,e+"-"+Yt])}),[]),ee=[].concat(Qt,[Kt]).reduce((function(t,e){return t.concat([e,e+"-"+Xt,e+"-"+Yt])}),[]),ie="beforeRead",ne="read",se="afterRead",oe="beforeMain",re="main",ae="afterMain",le="beforeWrite",ce="write",he="afterWrite",de=[ie,ne,se,oe,re,ae,le,ce,he];function ue(t){return t?(t.nodeName||"").toLowerCase():null}function fe(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function pe(t){return t instanceof fe(t).Element||t instanceof Element}function me(t){return t instanceof fe(t).HTMLElement||t instanceof HTMLElement}function ge(t){return"undefined"!=typeof ShadowRoot&&(t instanceof fe(t).ShadowRoot||t instanceof ShadowRoot)}const _e={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];me(s)&&ue(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});me(n)&&ue(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function be(t){return t.split("-")[0]}var ve=Math.max,ye=Math.min,we=Math.round;function Ae(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function Ee(){return!/^((?!chrome|android).)*safari/i.test(Ae())}function Te(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&me(t)&&(s=t.offsetWidth>0&&we(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&we(n.height)/t.offsetHeight||1);var r=(pe(t)?fe(t):window).visualViewport,a=!Ee()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function Ce(t){var e=Te(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Oe(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&ge(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function xe(t){return fe(t).getComputedStyle(t)}function ke(t){return["table","td","th"].indexOf(ue(t))>=0}function Le(t){return((pe(t)?t.ownerDocument:t.document)||window.document).documentElement}function Se(t){return"html"===ue(t)?t:t.assignedSlot||t.parentNode||(ge(t)?t.host:null)||Le(t)}function De(t){return me(t)&&"fixed"!==xe(t).position?t.offsetParent:null}function $e(t){for(var e=fe(t),i=De(t);i&&ke(i)&&"static"===xe(i).position;)i=De(i);return i&&("html"===ue(i)||"body"===ue(i)&&"static"===xe(i).position)?e:i||function(t){var e=/firefox/i.test(Ae());if(/Trident/i.test(Ae())&&me(t)&&"fixed"===xe(t).position)return null;var i=Se(t);for(ge(i)&&(i=i.host);me(i)&&["html","body"].indexOf(ue(i))<0;){var n=xe(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Ie(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function Ne(t,e,i){return ve(t,ye(e,i))}function Pe(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function je(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const Me={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=be(i.placement),l=Ie(a),c=[Vt,qt].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return Pe("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:je(t,Qt))}(s.padding,i),d=Ce(o),u="y"===l?zt:Vt,f="y"===l?Rt:qt,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=$e(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,A=Ne(v,w,y),E=l;i.modifiersData[n]=((e={})[E]=A,e.centerOffset=A-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Oe(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Fe(t){return t.split("-")[1]}var He={top:"auto",right:"auto",bottom:"auto",left:"auto"};function We(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=t.isFixed,u=r.x,f=void 0===u?0:u,p=r.y,m=void 0===p?0:p,g="function"==typeof h?h({x:f,y:m}):{x:f,y:m};f=g.x,m=g.y;var _=r.hasOwnProperty("x"),b=r.hasOwnProperty("y"),v=Vt,y=zt,w=window;if(c){var A=$e(i),E="clientHeight",T="clientWidth";A===fe(i)&&"static"!==xe(A=Le(i)).position&&"absolute"===a&&(E="scrollHeight",T="scrollWidth"),(s===zt||(s===Vt||s===qt)&&o===Yt)&&(y=Rt,m-=(d&&A===w&&w.visualViewport?w.visualViewport.height:A[E])-n.height,m*=l?1:-1),s!==Vt&&(s!==zt&&s!==Rt||o!==Yt)||(v=qt,f-=(d&&A===w&&w.visualViewport?w.visualViewport.width:A[T])-n.width,f*=l?1:-1)}var C,O=Object.assign({position:a},c&&He),x=!0===h?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:we(i*s)/s||0,y:we(n*s)/s||0}}({x:f,y:m},fe(i)):{x:f,y:m};return f=x.x,m=x.y,l?Object.assign({},O,((C={})[y]=b?"0":"",C[v]=_?"0":"",C.transform=(w.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",C)):Object.assign({},O,((e={})[y]=b?m+"px":"",e[v]=_?f+"px":"",e.transform="",e))}const Be={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:be(e.placement),variation:Fe(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,We(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,We(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var ze={passive:!0};const Re={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=fe(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,ze)})),a&&l.addEventListener("resize",i.update,ze),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,ze)})),a&&l.removeEventListener("resize",i.update,ze)}},data:{}};var qe={left:"right",right:"left",bottom:"top",top:"bottom"};function Ve(t){return t.replace(/left|right|bottom|top/g,(function(t){return qe[t]}))}var Ke={start:"end",end:"start"};function Qe(t){return t.replace(/start|end/g,(function(t){return Ke[t]}))}function Xe(t){var e=fe(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ye(t){return Te(Le(t)).left+Xe(t).scrollLeft}function Ue(t){var e=xe(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ge(t){return["html","body","#document"].indexOf(ue(t))>=0?t.ownerDocument.body:me(t)&&Ue(t)?t:Ge(Se(t))}function Je(t,e){var i;void 0===e&&(e=[]);var n=Ge(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=fe(n),r=s?[o].concat(o.visualViewport||[],Ue(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Je(Se(r)))}function Ze(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function ti(t,e,i){return e===Gt?Ze(function(t,e){var i=fe(t),n=Le(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=Ee();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+Ye(t),y:l}}(t,i)):pe(e)?function(t,e){var i=Te(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):Ze(function(t){var e,i=Le(t),n=Xe(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ve(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ve(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ye(t),l=-n.scrollTop;return"rtl"===xe(s||i).direction&&(a+=ve(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Le(t)))}function ei(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?be(s):null,r=s?Fe(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case zt:e={x:a,y:i.y-n.height};break;case Rt:e={x:a,y:i.y+i.height};break;case qt:e={x:i.x+i.width,y:l};break;case Vt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?Ie(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case Xt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Yt:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ii(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.strategy,r=void 0===o?t.strategy:o,a=i.boundary,l=void 0===a?Ut:a,c=i.rootBoundary,h=void 0===c?Gt:c,d=i.elementContext,u=void 0===d?Jt:d,f=i.altBoundary,p=void 0!==f&&f,m=i.padding,g=void 0===m?0:m,_=Pe("number"!=typeof g?g:je(g,Qt)),b=u===Jt?Zt:Jt,v=t.rects.popper,y=t.elements[p?b:u],w=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=Je(Se(t)),i=["absolute","fixed"].indexOf(xe(t).position)>=0&&me(t)?$e(t):t;return pe(i)?e.filter((function(t){return pe(t)&&Oe(t,i)&&"body"!==ue(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=ti(t,i,n);return e.top=ve(s.top,e.top),e.right=ye(s.right,e.right),e.bottom=ye(s.bottom,e.bottom),e.left=ve(s.left,e.left),e}),ti(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(pe(y)?y:y.contextElement||Le(t.elements.popper),l,h,r),A=Te(t.elements.reference),E=ei({reference:A,element:v,strategy:"absolute",placement:s}),T=Ze(Object.assign({},v,E)),C=u===Jt?T:A,O={top:w.top-C.top+_.top,bottom:C.bottom-w.bottom+_.bottom,left:w.left-C.left+_.left,right:C.right-w.right+_.right},x=t.modifiersData.offset;if(u===Jt&&x){var k=x[s];Object.keys(O).forEach((function(t){var e=[qt,Rt].indexOf(t)>=0?1:-1,i=[zt,Rt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function ni(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?ee:l,h=Fe(n),d=h?a?te:te.filter((function(t){return Fe(t)===h})):Qt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ii(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[be(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const si={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=be(g),b=l||(_!==g&&p?function(t){if(be(t)===Kt)return[];var e=Ve(t);return[Qe(t),e,Qe(e)]}(g):[Ve(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(be(i)===Kt?ni(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,A=new Map,E=!0,T=v[0],C=0;C=0,S=L?"width":"height",D=ii(e,{placement:O,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),$=L?k?qt:Vt:k?Rt:zt;y[S]>w[S]&&($=Ve($));var I=Ve($),N=[];if(o&&N.push(D[x]<=0),a&&N.push(D[$]<=0,D[I]<=0),N.every((function(t){return t}))){T=O,E=!1;break}A.set(O,N)}if(E)for(var P=function(t){var e=v.find((function(e){var i=A.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},j=p?3:1;j>0&&"break"!==P(j);j--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function oi(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function ri(t){return[zt,qt,Rt,Vt].some((function(e){return t[e]>=0}))}const ai={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ii(e,{elementContext:"reference"}),a=ii(e,{altBoundary:!0}),l=oi(r,n),c=oi(a,s,o),h=ri(l),d=ri(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},li={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=ee.reduce((function(t,i){return t[i]=function(t,e,i){var n=be(t),s=[Vt,zt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[Vt,qt].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},ci={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=ei({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},hi={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ii(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=be(e.placement),b=Fe(e.placement),v=!b,y=Ie(_),w="x"===y?"y":"x",A=e.modifiersData.popperOffsets,E=e.rects.reference,T=e.rects.popper,C="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,O="number"==typeof C?{mainAxis:C,altAxis:C}:Object.assign({mainAxis:0,altAxis:0},C),x=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,k={x:0,y:0};if(A){if(o){var L,S="y"===y?zt:Vt,D="y"===y?Rt:qt,$="y"===y?"height":"width",I=A[y],N=I+g[S],P=I-g[D],j=f?-T[$]/2:0,M=b===Xt?E[$]:T[$],F=b===Xt?-T[$]:-E[$],H=e.elements.arrow,W=f&&H?Ce(H):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},z=B[S],R=B[D],q=Ne(0,E[$],W[$]),V=v?E[$]/2-j-q-z-O.mainAxis:M-q-z-O.mainAxis,K=v?-E[$]/2+j+q+R+O.mainAxis:F+q+R+O.mainAxis,Q=e.elements.arrow&&$e(e.elements.arrow),X=Q?"y"===y?Q.clientTop||0:Q.clientLeft||0:0,Y=null!=(L=null==x?void 0:x[y])?L:0,U=I+K-Y,G=Ne(f?ye(N,I+V-Y-X):N,I,f?ve(P,U):P);A[y]=G,k[y]=G-I}if(a){var J,Z="x"===y?zt:Vt,tt="x"===y?Rt:qt,et=A[w],it="y"===w?"height":"width",nt=et+g[Z],st=et-g[tt],ot=-1!==[zt,Vt].indexOf(_),rt=null!=(J=null==x?void 0:x[w])?J:0,at=ot?nt:et-E[it]-T[it]-rt+O.altAxis,lt=ot?et+E[it]+T[it]-rt-O.altAxis:st,ct=f&&ot?function(t,e,i){var n=Ne(t,e,i);return n>i?i:n}(at,et,lt):Ne(f?at:nt,et,f?lt:st);A[w]=ct,k[w]=ct-et}e.modifiersData[n]=k}},requiresIfExists:["offset"]};function di(t,e,i){void 0===i&&(i=!1);var n,s,o=me(e),r=me(e)&&function(t){var e=t.getBoundingClientRect(),i=we(e.width)/t.offsetWidth||1,n=we(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=Le(e),l=Te(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==ue(e)||Ue(a))&&(c=(n=e)!==fe(n)&&me(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:Xe(n)),me(e)?((h=Te(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=Ye(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function ui(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var fi={placement:"bottom",modifiers:[],strategy:"absolute"};function pi(){for(var t=arguments.length,e=new Array(t),i=0;iNumber.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(F.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...g(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=z.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>a(t)));i.length&&b(i,e,t===Ti,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=qi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=z.find(Ni);for(const i of e){const e=qi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Ei,Ti].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Ii)?this:z.prev(this,Ii)[0]||z.next(this,Ii)[0]||z.findOne(Ii,t.delegateTarget.parentNode),o=qi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}N.on(document,Si,Ii,qi.dataApiKeydownHandler),N.on(document,Si,Pi,qi.dataApiKeydownHandler),N.on(document,Li,qi.clearMenus),N.on(document,Di,qi.clearMenus),N.on(document,Li,Ii,(function(t){t.preventDefault(),qi.getOrCreateInstance(this).toggle()})),m(qi);const Vi="backdrop",Ki="show",Qi=`mousedown.bs.${Vi}`,Xi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Yi={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Ui extends H{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Xi}static get DefaultType(){return Yi}static get NAME(){return Vi}show(t){if(!this._config.isVisible)return void g(t);this._append();const e=this._getElement();this._config.isAnimated&&d(e),e.classList.add(Ki),this._emulateAnimation((()=>{g(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Ki),this._emulateAnimation((()=>{this.dispose(),g(t)}))):g(t)}dispose(){this._isAppended&&(N.off(this._element,Qi),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=r(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),N.on(t,Qi,(()=>{g(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){_(t,this._getElement(),this._config.isAnimated)}}const Gi=".bs.focustrap",Ji=`focusin${Gi}`,Zi=`keydown.tab${Gi}`,tn="backward",en={autofocus:!0,trapElement:null},nn={autofocus:"boolean",trapElement:"element"};class sn extends H{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return en}static get DefaultType(){return nn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),N.off(document,Gi),N.on(document,Ji,(t=>this._handleFocusin(t))),N.on(document,Zi,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,N.off(document,Gi))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=z.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===tn?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?tn:"forward")}}const on=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",rn=".sticky-top",an="padding-right",ln="margin-right";class cn{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,an,(e=>e+t)),this._setElementAttributes(on,an,(e=>e+t)),this._setElementAttributes(rn,ln,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,an),this._resetElementAttributes(on,an),this._resetElementAttributes(rn,ln)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&F.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=F.getDataAttribute(t,e);null!==i?(F.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(o(t))e(t);else for(const i of z.find(t,this._element))e(i)}}const hn=".bs.modal",dn=`hide${hn}`,un=`hidePrevented${hn}`,fn=`hidden${hn}`,pn=`show${hn}`,mn=`shown${hn}`,gn=`resize${hn}`,_n=`click.dismiss${hn}`,bn=`mousedown.dismiss${hn}`,vn=`keydown.dismiss${hn}`,yn=`click${hn}.data-api`,wn="modal-open",An="show",En="modal-static",Tn={backdrop:!0,focus:!0,keyboard:!0},Cn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class On extends W{constructor(t,e){super(t,e),this._dialog=z.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new cn,this._addEventListeners()}static get Default(){return Tn}static get DefaultType(){return Cn}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||N.trigger(this._element,pn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(wn),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(N.trigger(this._element,dn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(An),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){N.off(window,hn),N.off(this._dialog,hn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Ui({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=z.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),d(this._element),this._element.classList.add(An),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,N.trigger(this._element,mn,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){N.on(this._element,vn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),N.on(window,gn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),N.on(this._element,bn,(t=>{N.one(this._element,_n,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(wn),this._resetAdjustments(),this._scrollBar.reset(),N.trigger(this._element,fn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(N.trigger(this._element,un).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(En)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(En),this._queueCallback((()=>{this._element.classList.remove(En),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=p()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=p()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=On.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}N.on(document,yn,'[data-bs-toggle="modal"]',(function(t){const e=z.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),N.one(e,pn,(t=>{t.defaultPrevented||N.one(e,fn,(()=>{a(this)&&this.focus()}))}));const i=z.findOne(".modal.show");i&&On.getInstance(i).hide(),On.getOrCreateInstance(e).toggle(this)})),R(On),m(On);const xn=".bs.offcanvas",kn=".data-api",Ln=`load${xn}${kn}`,Sn="show",Dn="showing",$n="hiding",In=".offcanvas.show",Nn=`show${xn}`,Pn=`shown${xn}`,jn=`hide${xn}`,Mn=`hidePrevented${xn}`,Fn=`hidden${xn}`,Hn=`resize${xn}`,Wn=`click${xn}${kn}`,Bn=`keydown.dismiss${xn}`,zn={backdrop:!0,keyboard:!0,scroll:!1},Rn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class qn extends W{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return zn}static get DefaultType(){return Rn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||N.trigger(this._element,Nn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new cn).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Dn),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Sn),this._element.classList.remove(Dn),N.trigger(this._element,Pn,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(N.trigger(this._element,jn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add($n),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(Sn,$n),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new cn).reset(),N.trigger(this._element,Fn)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Ui({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():N.trigger(this._element,Mn)}:null})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_addEventListeners(){N.on(this._element,Bn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():N.trigger(this._element,Mn))}))}static jQueryInterface(t){return this.each((function(){const e=qn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}N.on(document,Wn,'[data-bs-toggle="offcanvas"]',(function(t){const e=z.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this))return;N.one(e,Fn,(()=>{a(this)&&this.focus()}));const i=z.findOne(In);i&&i!==e&&qn.getInstance(i).hide(),qn.getOrCreateInstance(e).toggle(this)})),N.on(window,Ln,(()=>{for(const t of z.find(In))qn.getOrCreateInstance(t).show()})),N.on(window,Hn,(()=>{for(const t of z.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&qn.getOrCreateInstance(t).hide()})),R(qn),m(qn);const Vn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],dd:[],div:[],dl:[],dt:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Kn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Qn=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Xn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Kn.has(i)||Boolean(Qn.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Yn={allowList:Vn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
"},Un={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Gn={entry:"(string|element|function|null)",selector:"(string|element)"};class Jn extends H{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Yn}static get DefaultType(){return Un}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Gn)}_setContent(t,e,i){const n=z.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?o(e)?this._putElementInTemplate(r(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Xn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return g(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const Zn=new Set(["sanitize","allowList","sanitizeFn"]),ts="fade",es="show",is=".modal",ns="hide.bs.modal",ss="hover",os="focus",rs={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},as={allowList:Vn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},ls={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class cs extends W{constructor(t,e){if(void 0===vi)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return as}static get DefaultType(){return ls}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),N.off(this._element.closest(is),ns,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=N.trigger(this._element,this.constructor.eventName("show")),e=(c(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),N.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.on(t,"mouseover",h);this._queueCallback((()=>{N.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!N.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.off(t,"mouseover",h);this._activeTrigger.click=!1,this._activeTrigger[os]=!1,this._activeTrigger[ss]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),N.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(ts,es),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(ts),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Jn({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(ts)}_isShown(){return this.tip&&this.tip.classList.contains(es)}_createPopper(t){const e=g(this._config.placement,[this,t,this._element]),i=rs[e.toUpperCase()];return bi(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return g(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...g(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)N.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===ss?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===ss?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");N.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?os:ss]=!0,e._enter()})),N.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?os:ss]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},N.on(this._element.closest(is),ns,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=F.getDataAttributes(this._element);for(const t of Object.keys(e))Zn.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=cs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(cs);const hs={...cs.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},ds={...cs.DefaultType,content:"(null|string|element|function)"};class us extends cs{static get Default(){return hs}static get DefaultType(){return ds}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=us.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(us);const fs=".bs.scrollspy",ps=`activate${fs}`,ms=`click${fs}`,gs=`load${fs}.data-api`,_s="active",bs="[href]",vs=".nav-link",ys=`${vs}, .nav-item > ${vs}, .list-group-item`,ws={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},As={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Es extends W{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return ws}static get DefaultType(){return As}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=r(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(N.off(this._config.target,ms),N.on(this._config.target,ms,bs,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=z.find(bs,this._config.target);for(const e of t){if(!e.hash||l(e))continue;const t=z.findOne(decodeURI(e.hash),this._element);a(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(_s),this._activateParents(t),N.trigger(this._element,ps,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))z.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(_s);else for(const e of z.parents(t,".nav, .list-group"))for(const t of z.prev(e,ys))t.classList.add(_s)}_clearActiveClass(t){t.classList.remove(_s);const e=z.find(`${bs}.${_s}`,t);for(const t of e)t.classList.remove(_s)}static jQueryInterface(t){return this.each((function(){const e=Es.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(window,gs,(()=>{for(const t of z.find('[data-bs-spy="scroll"]'))Es.getOrCreateInstance(t)})),m(Es);const Ts=".bs.tab",Cs=`hide${Ts}`,Os=`hidden${Ts}`,xs=`show${Ts}`,ks=`shown${Ts}`,Ls=`click${Ts}`,Ss=`keydown${Ts}`,Ds=`load${Ts}`,$s="ArrowLeft",Is="ArrowRight",Ns="ArrowUp",Ps="ArrowDown",js="Home",Ms="End",Fs="active",Hs="fade",Ws="show",Bs=".dropdown-toggle",zs=`:not(${Bs})`,Rs='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',qs=`.nav-link${zs}, .list-group-item${zs}, [role="tab"]${zs}, ${Rs}`,Vs=`.${Fs}[data-bs-toggle="tab"], .${Fs}[data-bs-toggle="pill"], .${Fs}[data-bs-toggle="list"]`;class Ks extends W{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),N.on(this._element,Ss,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?N.trigger(e,Cs,{relatedTarget:t}):null;N.trigger(t,xs,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(Fs),this._activate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),N.trigger(t,ks,{relatedTarget:e})):t.classList.add(Ws)}),t,t.classList.contains(Hs)))}_deactivate(t,e){t&&(t.classList.remove(Fs),t.blur(),this._deactivate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),N.trigger(t,Os,{relatedTarget:e})):t.classList.remove(Ws)}),t,t.classList.contains(Hs)))}_keydown(t){if(![$s,Is,Ns,Ps,js,Ms].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!l(t)));let i;if([js,Ms].includes(t.key))i=e[t.key===js?0:e.length-1];else{const n=[Is,Ps].includes(t.key);i=b(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Ks.getOrCreateInstance(i).show())}_getChildren(){return z.find(qs,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=z.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=z.findOne(t,i);s&&s.classList.toggle(n,e)};n(Bs,Fs),n(".dropdown-menu",Ws),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(Fs)}_getInnerElement(t){return t.matches(qs)?t:z.findOne(qs,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Ks.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(document,Ls,Rs,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this)||Ks.getOrCreateInstance(this).show()})),N.on(window,Ds,(()=>{for(const t of z.find(Vs))Ks.getOrCreateInstance(t)})),m(Ks);const Qs=".bs.toast",Xs=`mouseover${Qs}`,Ys=`mouseout${Qs}`,Us=`focusin${Qs}`,Gs=`focusout${Qs}`,Js=`hide${Qs}`,Zs=`hidden${Qs}`,to=`show${Qs}`,eo=`shown${Qs}`,io="hide",no="show",so="showing",oo={animation:"boolean",autohide:"boolean",delay:"number"},ro={animation:!0,autohide:!0,delay:5e3};class ao extends W{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return ro}static get DefaultType(){return oo}static get NAME(){return"toast"}show(){N.trigger(this._element,to).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(io),d(this._element),this._element.classList.add(no,so),this._queueCallback((()=>{this._element.classList.remove(so),N.trigger(this._element,eo),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(N.trigger(this._element,Js).defaultPrevented||(this._element.classList.add(so),this._queueCallback((()=>{this._element.classList.add(io),this._element.classList.remove(so,no),N.trigger(this._element,Zs)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(no),super.dispose()}isShown(){return this._element.classList.contains(no)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){N.on(this._element,Xs,(t=>this._onInteraction(t,!0))),N.on(this._element,Ys,(t=>this._onInteraction(t,!1))),N.on(this._element,Us,(t=>this._onInteraction(t,!0))),N.on(this._element,Gs,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=ao.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(ao),m(ao),{Alert:Q,Button:Y,Carousel:xt,Collapse:Bt,Dropdown:qi,Modal:On,Offcanvas:qn,Popover:us,ScrollSpy:Es,Tab:Ks,Toast:ao,Tooltip:cs}})); //# sourceMappingURL=bootstrap.bundle.min.js.map graphql-2.6.0/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css0000644000004100000410000070654315173430257025015 0ustar www-datawww-data@charset "UTF-8";/*! * Bootstrap v5.3.3 (https://getbootstrap.com/) * Copyright 2011-2024 The Bootstrap Authors * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */:root,[data-bs-theme=light]{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-primary-text-emphasis:#052c65;--bs-secondary-text-emphasis:#2b2f32;--bs-success-text-emphasis:#0a3622;--bs-info-text-emphasis:#055160;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#cfe2ff;--bs-secondary-bg-subtle:#e2e3e5;--bs-success-bg-subtle:#d1e7dd;--bs-info-bg-subtle:#cff4fc;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#9ec5fe;--bs-secondary-border-subtle:#c4c8cb;--bs-success-border-subtle:#a3cfbb;--bs-info-border-subtle:#9eeaf9;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--bs-light-border-subtle:#e9ecef;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33,37,41;--bs-body-bg:#fff;--bs-body-bg-rgb:255,255,255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0,0,0;--bs-secondary-color:rgba(33, 37, 41, 0.75);--bs-secondary-color-rgb:33,37,41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233,236,239;--bs-tertiary-color:rgba(33, 37, 41, 0.5);--bs-tertiary-color-rgb:33,37,41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:inherit;--bs-link-color:#0d6efd;--bs-link-color-rgb:13,110,253;--bs-link-decoration:underline;--bs-link-hover-color:#0a58ca;--bs-link-hover-color-rgb:10,88,202;--bs-code-color:#d63384;--bs-highlight-color:#212529;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.25rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg:0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset:inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width:0.25rem;--bs-focus-ring-opacity:0.25;--bs-focus-ring-color:rgba(13, 110, 253, 0.25);--bs-form-valid-color:#198754;--bs-form-valid-border-color:#198754;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222,226,230;--bs-body-bg:#212529;--bs-body-bg-rgb:33,37,41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255,255,255;--bs-secondary-color:rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb:222,226,230;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52,58,64;--bs-tertiary-color:rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb:222,226,230;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43,48,53;--bs-primary-text-emphasis:#6ea8fe;--bs-secondary-text-emphasis:#a7acb1;--bs-success-text-emphasis:#75b798;--bs-info-text-emphasis:#6edff6;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#031633;--bs-secondary-bg-subtle:#161719;--bs-success-bg-subtle:#051b11;--bs-info-bg-subtle:#032830;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#084298;--bs-secondary-border-subtle:#41464b;--bs-success-border-subtle:#0f5132;--bs-info-border-subtle:#087990;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-color:#6ea8fe;--bs-link-hover-color:#8bb9fe;--bs-link-color-rgb:110,168,254;--bs-link-hover-color-rgb:139,185,254;--bs-code-color:#e685b5;--bs-highlight-color:#dee2e6;--bs-highlight-bg:#664d03;--bs-border-color:#495057;--bs-border-color-translucent:rgba(255, 255, 255, 0.15);--bs-form-valid-color:#75b798;--bs-form-valid-border-color:#75b798;--bs-form-invalid-color:#ea868f;--bs-form-invalid-border-color:#ea868f}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;border:0;border-top:var(--bs-border-width) solid;opacity:.25}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2;color:var(--bs-heading-color)}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.1875em;color:var(--bs-highlight-color);background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1));text-decoration:underline}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.1875rem .375rem;font-size:.875em;color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.25rem}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-secondary-color);text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:var(--bs-body-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:var(--bs-secondary-color)}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{--bs-gutter-x:1.5rem;--bs-gutter-y:0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}:root{--bs-breakpoint-xs:0;--bs-breakpoint-sm:576px;--bs-breakpoint-md:768px;--bs-breakpoint-lg:992px;--bs-breakpoint-xl:1200px;--bs-breakpoint-xxl:1400px}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.66666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.66666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.66666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.66666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.66666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.33333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.66666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-color-type:initial;--bs-table-bg-type:initial;--bs-table-color-state:initial;--bs-table-bg-state:initial;--bs-table-color:var(--bs-emphasis-color);--bs-table-bg:var(--bs-body-bg);--bs-table-border-color:var(--bs-border-color);--bs-table-accent-bg:transparent;--bs-table-striped-color:var(--bs-emphasis-color);--bs-table-striped-bg:rgba(var(--bs-emphasis-color-rgb), 0.05);--bs-table-active-color:var(--bs-emphasis-color);--bs-table-active-bg:rgba(var(--bs-emphasis-color-rgb), 0.1);--bs-table-hover-color:var(--bs-emphasis-color);--bs-table-hover-bg:rgba(var(--bs-emphasis-color-rgb), 0.075);width:100%;margin-bottom:1rem;vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem .5rem;color:var(--bs-table-color-state,var(--bs-table-color-type,var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:var(--bs-border-width);box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state,var(--bs-table-bg-type,var(--bs-table-accent-bg)))}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(var(--bs-border-width) * 2) solid currentcolor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:var(--bs-border-width) 0}.table-bordered>:not(caption)>*>*{border-width:0 var(--bs-border-width)}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-striped-columns>:not(caption)>tr>:nth-child(2n){--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-active{--bs-table-color-state:var(--bs-table-active-color);--bs-table-bg-state:var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state:var(--bs-table-hover-color);--bs-table-bg-state:var(--bs-table-hover-bg)}.table-primary{--bs-table-color:#000;--bs-table-bg:#cfe2ff;--bs-table-border-color:#a6b5cc;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color:#000;--bs-table-bg:#e2e3e5;--bs-table-border-color:#b5b6b7;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color:#000;--bs-table-bg:#d1e7dd;--bs-table-border-color:#a7b9b1;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color:#000;--bs-table-bg:#cff4fc;--bs-table-border-color:#a6c3ca;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color:#000;--bs-table-bg:#fff3cd;--bs-table-border-color:#ccc2a4;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color:#000;--bs-table-bg:#f8d7da;--bs-table-border-color:#c6acae;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color:#000;--bs-table-bg:#f8f9fa;--bs-table-border-color:#c6c7c8;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color:#fff;--bs-table-bg:#212529;--bs-table-border-color:#4d5154;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + var(--bs-border-width));padding-bottom:calc(.375rem + var(--bs-border-width));margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + var(--bs-border-width));padding-bottom:calc(.5rem + var(--bs-border-width));font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + var(--bs-border-width));padding-bottom:calc(.25rem + var(--bs-border-width));font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:var(--bs-secondary-color)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-body-bg);background-clip:padding-box;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:var(--bs-body-bg);border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::-moz-placeholder{color:var(--bs-secondary-color);opacity:1}.form-control::placeholder{color:var(--bs-secondary-color);opacity:1}.form-control:disabled{background-color:var(--bs-secondary-bg);opacity:1}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:var(--bs-border-width);border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:var(--bs-secondary-bg)}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:var(--bs-body-color);background-color:transparent;border:solid transparent;border-width:var(--bs-border-width) 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2));padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2))}textarea.form-control-sm{min-height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-control-color{width:3rem;height:calc(1.5em + .75rem + calc(var(--bs-border-width) * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color::-webkit-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + calc(var(--bs-border-width) * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(var(--bs-border-width) * 2))}.form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-body-bg);background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon,none);background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:var(--bs-secondary-bg)}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{padding-right:1.5em;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:-1.5em;margin-left:0}.form-check-input{--bs-form-check-bg:var(--bs-body-bg);flex-shrink:0;width:1em;height:1em;margin-top:.25em;vertical-align:top;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:center;background-size:contain;border:var(--bs-border-width) solid var(--bs-border-color);-webkit-print-color-adjust:exact;color-adjust:exact;print-color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.5rem;padding:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;-webkit-appearance:none;appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;-moz-appearance:none;appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:var(--bs-secondary-bg);border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(var(--bs-border-width) * 2));min-height:calc(3.5rem + calc(var(--bs-border-width) * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;height:100%;padding:1rem .75rem;overflow:hidden;text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:var(--bs-border-width) solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control-plaintext::-moz-placeholder,.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control-plaintext::placeholder,.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control-plaintext:not(:-moz-placeholder-shown),.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:-webkit-autofill,.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label,.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:not(:-moz-placeholder-shown)~label::after{position:absolute;inset:1rem 0.375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control-plaintext~label::after,.form-floating>.form-control:focus~label::after,.form-floating>.form-control:not(:placeholder-shown)~label::after,.form-floating>.form-select~label::after{position:absolute;inset:1rem 0.375rem;z-index:-1;height:1.5em;content:"";background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius)}.form-floating>.form-control:-webkit-autofill~label{color:rgba(var(--bs-body-color-rgb),.65);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control-plaintext~label{border-width:var(--bs-border-width) 0}.form-floating>.form-control:disabled~label,.form-floating>:disabled~label{color:#6c757d}.form-floating>.form-control:disabled~label::after,.form-floating>:disabled~label::after{background-color:var(--bs-secondary-bg)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-floating,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-floating:focus-within,.input-group>.form-select:focus{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:var(--bs-tertiary-bg);border:var(--bs-border-width) solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select,.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select,.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(var(--bs-border-width) * -1);border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-valid-color)}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-success);border-radius:var(--bs-border-radius)}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:var(--bs-form-valid-border-color);padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:var(--bs-form-valid-border-color)}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-control-color.is-valid,.was-validated .form-control-color:valid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:var(--bs-form-valid-border-color)}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:var(--bs-form-valid-color)}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-valid,.input-group>.form-floating:not(:focus-within).is-valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-control:not(:focus):valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.was-validated .input-group>.form-select:not(:focus):valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-invalid-color)}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-danger);border-radius:var(--bs-border-radius)}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:var(--bs-form-invalid-border-color);padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:var(--bs-form-invalid-border-color)}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-control-color.is-invalid,.was-validated .form-control-color:invalid{width:calc(3rem + calc(1.5em + .75rem))}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:var(--bs-form-invalid-border-color)}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:var(--bs-form-invalid-color)}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-invalid,.input-group>.form-floating:not(:focus-within).is-invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-control:not(:focus):invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.was-validated .input-group>.form-select:not(:focus):invalid{z-index:4}.btn{--bs-btn-padding-x:0.75rem;--bs-btn-padding-y:0.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight:400;--bs-btn-line-height:1.5;--bs-btn-color:var(--bs-body-color);--bs-btn-bg:transparent;--bs-btn-border-width:var(--bs-border-width);--bs-btn-border-color:transparent;--bs-btn-border-radius:var(--bs-border-radius);--bs-btn-hover-border-color:transparent;--bs-btn-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.15),0 1px 1px rgba(0, 0, 0, 0.075);--bs-btn-disabled-opacity:0.65;--bs-btn-focus-box-shadow:0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,.btn.active,.btn.show,.btn:first-child:active,:not(.btn-check)+.btn:active{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible,.btn:first-child:active:focus-visible,:not(.btn-check)+.btn:active:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked:focus-visible+.btn{box-shadow:var(--bs-btn-focus-box-shadow)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0b5ed7;--bs-btn-hover-border-color:#0a58ca;--bs-btn-focus-shadow-rgb:49,132,253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0a58ca;--bs-btn-active-border-color:#0a53be;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#0d6efd;--bs-btn-disabled-border-color:#0d6efd}.btn-secondary{--bs-btn-color:#fff;--bs-btn-bg:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5c636a;--bs-btn-hover-border-color:#565e64;--bs-btn-focus-shadow-rgb:130,138,145;--bs-btn-active-color:#fff;--bs-btn-active-bg:#565e64;--bs-btn-active-border-color:#51585e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6c757d;--bs-btn-disabled-border-color:#6c757d}.btn-success{--bs-btn-color:#fff;--bs-btn-bg:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#157347;--bs-btn-hover-border-color:#146c43;--bs-btn-focus-shadow-rgb:60,153,110;--bs-btn-active-color:#fff;--bs-btn-active-bg:#146c43;--bs-btn-active-border-color:#13653f;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#198754;--bs-btn-disabled-border-color:#198754}.btn-info{--bs-btn-color:#000;--bs-btn-bg:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#31d2f2;--bs-btn-hover-border-color:#25cff2;--bs-btn-focus-shadow-rgb:11,172,204;--bs-btn-active-color:#000;--bs-btn-active-bg:#3dd5f3;--bs-btn-active-border-color:#25cff2;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#0dcaf0;--bs-btn-disabled-border-color:#0dcaf0}.btn-warning{--bs-btn-color:#000;--bs-btn-bg:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffca2c;--bs-btn-hover-border-color:#ffc720;--bs-btn-focus-shadow-rgb:217,164,6;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffcd39;--bs-btn-active-border-color:#ffc720;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#ffc107;--bs-btn-disabled-border-color:#ffc107}.btn-danger{--bs-btn-color:#fff;--bs-btn-bg:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#bb2d3b;--bs-btn-hover-border-color:#b02a37;--bs-btn-focus-shadow-rgb:225,83,97;--bs-btn-active-color:#fff;--bs-btn-active-bg:#b02a37;--bs-btn-active-border-color:#a52834;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#dc3545;--bs-btn-disabled-border-color:#dc3545}.btn-light{--bs-btn-color:#000;--bs-btn-bg:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#d3d4d5;--bs-btn-hover-border-color:#c6c7c8;--bs-btn-focus-shadow-rgb:211,212,213;--bs-btn-active-color:#000;--bs-btn-active-bg:#c6c7c8;--bs-btn-active-border-color:#babbbc;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#f8f9fa;--bs-btn-disabled-border-color:#f8f9fa}.btn-dark{--bs-btn-color:#fff;--bs-btn-bg:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#424649;--bs-btn-hover-border-color:#373b3e;--bs-btn-focus-shadow-rgb:66,70,73;--bs-btn-active-color:#fff;--bs-btn-active-bg:#4d5154;--bs-btn-active-border-color:#373b3e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#212529;--bs-btn-disabled-border-color:#212529}.btn-outline-primary{--bs-btn-color:#0d6efd;--bs-btn-border-color:#0d6efd;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#0d6efd;--bs-btn-hover-border-color:#0d6efd;--bs-btn-focus-shadow-rgb:13,110,253;--bs-btn-active-color:#fff;--bs-btn-active-bg:#0d6efd;--bs-btn-active-border-color:#0d6efd;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#0d6efd;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0d6efd;--bs-gradient:none}.btn-outline-secondary{--bs-btn-color:#6c757d;--bs-btn-border-color:#6c757d;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6c757d;--bs-btn-hover-border-color:#6c757d;--bs-btn-focus-shadow-rgb:108,117,125;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6c757d;--bs-btn-active-border-color:#6c757d;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6c757d;--bs-gradient:none}.btn-outline-success{--bs-btn-color:#198754;--bs-btn-border-color:#198754;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#198754;--bs-btn-hover-border-color:#198754;--bs-btn-focus-shadow-rgb:25,135,84;--bs-btn-active-color:#fff;--bs-btn-active-bg:#198754;--bs-btn-active-border-color:#198754;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#198754;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#198754;--bs-gradient:none}.btn-outline-info{--bs-btn-color:#0dcaf0;--bs-btn-border-color:#0dcaf0;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#0dcaf0;--bs-btn-hover-border-color:#0dcaf0;--bs-btn-focus-shadow-rgb:13,202,240;--bs-btn-active-color:#000;--bs-btn-active-bg:#0dcaf0;--bs-btn-active-border-color:#0dcaf0;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#0dcaf0;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#0dcaf0;--bs-gradient:none}.btn-outline-warning{--bs-btn-color:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#ffc107;--bs-btn-hover-border-color:#ffc107;--bs-btn-focus-shadow-rgb:255,193,7;--bs-btn-active-color:#000;--bs-btn-active-bg:#ffc107;--bs-btn-active-border-color:#ffc107;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#ffc107;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#ffc107;--bs-gradient:none}.btn-outline-danger{--bs-btn-color:#dc3545;--bs-btn-border-color:#dc3545;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#dc3545;--bs-btn-hover-border-color:#dc3545;--bs-btn-focus-shadow-rgb:220,53,69;--bs-btn-active-color:#fff;--bs-btn-active-bg:#dc3545;--bs-btn-active-border-color:#dc3545;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#dc3545;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#dc3545;--bs-gradient:none}.btn-outline-light{--bs-btn-color:#f8f9fa;--bs-btn-border-color:#f8f9fa;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#f8f9fa;--bs-btn-hover-border-color:#f8f9fa;--bs-btn-focus-shadow-rgb:248,249,250;--bs-btn-active-color:#000;--bs-btn-active-bg:#f8f9fa;--bs-btn-active-border-color:#f8f9fa;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#f8f9fa;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#f8f9fa;--bs-gradient:none}.btn-outline-dark{--bs-btn-color:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#212529;--bs-btn-hover-border-color:#212529;--bs-btn-focus-shadow-rgb:33,37,41;--bs-btn-active-color:#fff;--bs-btn-active-bg:#212529;--bs-btn-active-border-color:#212529;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#212529;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#212529;--bs-gradient:none}.btn-link{--bs-btn-font-weight:400;--bs-btn-color:var(--bs-link-color);--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-link-hover-color);--bs-btn-hover-border-color:transparent;--bs-btn-active-color:var(--bs-link-hover-color);--bs-btn-active-border-color:transparent;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-border-color:transparent;--bs-btn-box-shadow:0 0 0 #000;--bs-btn-focus-shadow-rgb:49,132,253;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-group-lg>.btn,.btn-lg{--bs-btn-padding-y:0.5rem;--bs-btn-padding-x:1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius:var(--bs-border-radius-lg)}.btn-group-sm>.btn,.btn-sm{--bs-btn-padding-y:0.25rem;--bs-btn-padding-x:0.5rem;--bs-btn-font-size:0.875rem;--bs-btn-border-radius:var(--bs-border-radius-sm)}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropdown-center,.dropend,.dropstart,.dropup,.dropup-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex:1000;--bs-dropdown-min-width:10rem;--bs-dropdown-padding-x:0;--bs-dropdown-padding-y:0.5rem;--bs-dropdown-spacer:0.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color:var(--bs-body-color);--bs-dropdown-bg:var(--bs-body-bg);--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-border-radius:var(--bs-border-radius);--bs-dropdown-border-width:var(--bs-border-width);--bs-dropdown-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-divider-margin-y:0.5rem;--bs-dropdown-box-shadow:var(--bs-box-shadow);--bs-dropdown-link-color:var(--bs-body-color);--bs-dropdown-link-hover-color:var(--bs-body-color);--bs-dropdown-link-hover-bg:var(--bs-tertiary-bg);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:var(--bs-tertiary-color);--bs-dropdown-item-padding-x:1rem;--bs-dropdown-item-padding-y:0.25rem;--bs-dropdown-header-color:#6c757d;--bs-dropdown-header-padding-x:1rem;--bs-dropdown-header-padding-y:0.5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0;border-radius:var(--bs-dropdown-item-border-radius,0)}.dropdown-item:focus,.dropdown-item:hover{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color:#dee2e6;--bs-dropdown-bg:#343a40;--bs-dropdown-border-color:var(--bs-border-color-translucent);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color:#dee2e6;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-divider-bg:var(--bs-border-color-translucent);--bs-dropdown-link-hover-bg:rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#0d6efd;--bs-dropdown-link-disabled-color:#adb5bd;--bs-dropdown-header-color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:var(--bs-border-radius)}.btn-group>.btn-group:not(:first-child),.btn-group>:not(.btn-check:first-child)+.btn{margin-left:calc(var(--bs-border-width) * -1)}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:calc(var(--bs-border-width) * -1)}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x:1rem;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-link-color);--bs-nav-link-hover-color:var(--bs-link-hover-color);--bs-nav-link-disabled-color:var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);text-decoration:none;background:0 0;border:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width:var(--bs-border-width);--bs-nav-tabs-border-color:var(--bs-border-color);--bs-nav-tabs-border-radius:var(--bs-border-radius);--bs-nav-tabs-link-hover-border-color:var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);--bs-nav-tabs-link-active-color:var(--bs-emphasis-color);--bs-nav-tabs-link-active-bg:var(--bs-body-bg);--bs-nav-tabs-link-active-border-color:var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg);border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius:var(--bs-border-radius);--bs-nav-pills-link-active-color:#fff;--bs-nav-pills-link-active-bg:#0d6efd}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap:1rem;--bs-nav-underline-border-width:0.125rem;--bs-nav-underline-link-active-color:var(--bs-emphasis-color);gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{padding-right:0;padding-left:0;border-bottom:var(--bs-nav-underline-border-width) solid transparent}.nav-underline .nav-link:focus,.nav-underline .nav-link:hover{border-bottom-color:currentcolor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{font-weight:700;color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentcolor}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x:0;--bs-navbar-padding-y:0.5rem;--bs-navbar-color:rgba(var(--bs-emphasis-color-rgb), 0.65);--bs-navbar-hover-color:rgba(var(--bs-emphasis-color-rgb), 0.8);--bs-navbar-disabled-color:rgba(var(--bs-emphasis-color-rgb), 0.3);--bs-navbar-active-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-padding-y:0.3125rem;--bs-navbar-brand-margin-end:1rem;--bs-navbar-brand-font-size:1.25rem;--bs-navbar-brand-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-hover-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-nav-link-padding-x:0.5rem;--bs-navbar-toggler-padding-y:0.25rem;--bs-navbar-toggler-padding-x:0.75rem;--bs-navbar-toggler-font-size:1.25rem;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color:rgba(var(--bs-emphasis-color-rgb), 0.15);--bs-navbar-toggler-border-radius:var(--bs-border-radius);--bs-navbar-toggler-focus-width:0.25rem;--bs-navbar-toggler-transition:box-shadow 0.15s ease-in-out;position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);text-decoration:none;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-navbar-color);--bs-nav-link-hover-color:var(--bs-navbar-hover-color);--bs-nav-link-disabled-color:var(--bs-navbar-disabled-color);display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:focus,.navbar-text a:hover{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:transparent;border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition)}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:transparent!important;border:0!important;transform:none!important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color:rgba(255, 255, 255, 0.55);--bs-navbar-hover-color:rgba(255, 255, 255, 0.75);--bs-navbar-disabled-color:rgba(255, 255, 255, 0.25);--bs-navbar-active-color:#fff;--bs-navbar-brand-color:#fff;--bs-navbar-brand-hover-color:#fff;--bs-navbar-toggler-border-color:rgba(255, 255, 255, 0.1);--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y:1rem;--bs-card-spacer-x:1rem;--bs-card-title-spacer-y:0.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width:var(--bs-border-width);--bs-card-border-color:var(--bs-border-color-translucent);--bs-card-border-radius:var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-card-cap-padding-y:0.5rem;--bs-card-cap-padding-x:1rem;--bs-card-cap-bg:rgba(var(--bs-body-color-rgb), 0.03);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg:var(--bs-body-bg);--bs-card-img-overlay-padding:1rem;--bs-card-group-margin:0.75rem;position:relative;display:flex;flex-direction:column;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width) solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));margin-bottom:0;color:var(--bs-card-subtitle-color)}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion{--bs-accordion-color:var(--bs-body-color);--bs-accordion-bg:var(--bs-body-bg);--bs-accordion-transition:color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out,border-radius 0.15s ease;--bs-accordion-border-color:var(--bs-border-color);--bs-accordion-border-width:var(--bs-border-width);--bs-accordion-border-radius:var(--bs-border-radius);--bs-accordion-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-accordion-btn-padding-x:1.25rem;--bs-accordion-btn-padding-y:1rem;--bs-accordion-btn-color:var(--bs-body-color);--bs-accordion-btn-bg:var(--bs-accordion-bg);--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width:1.25rem;--bs-accordion-btn-icon-transform:rotate(-180deg);--bs-accordion-btn-icon-transition:transform 0.2s ease-in-out;--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23052c65' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");--bs-accordion-btn-focus-box-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-accordion-body-padding-x:1.25rem;--bs-accordion-body-padding-y:1rem;--bs-accordion-active-color:var(--bs-primary-text-emphasis);--bs-accordion-active-bg:var(--bs-primary-bg-subtle)}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);font-size:1rem;color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);border:0;border-radius:0;overflow-anchor:none;transition:var(--bs-accordion-transition)}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed)::after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button::after{flex-shrink:0;width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);margin-left:auto;content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition)}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;outline:0;box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:first-of-type{border-top-left-radius:var(--bs-accordion-border-radius);border-top-right-radius:var(--bs-accordion-border-radius)}.accordion-item:first-of-type>.accordion-header .accordion-button{border-top-left-radius:var(--bs-accordion-inner-border-radius);border-top-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-item:last-of-type>.accordion-header .accordion-button.collapsed{border-bottom-right-radius:var(--bs-accordion-inner-border-radius);border-bottom-left-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:last-of-type>.accordion-collapse{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush>.accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush>.accordion-item:first-child{border-top:0}.accordion-flush>.accordion-item:last-child{border-bottom:0}.accordion-flush>.accordion-item>.accordion-header .accordion-button,.accordion-flush>.accordion-item>.accordion-header .accordion-button.collapsed{border-radius:0}.accordion-flush>.accordion-item>.accordion-collapse{border-radius:0}[data-bs-theme=dark] .accordion-button::after{--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x:0;--bs-breadcrumb-padding-y:0;--bs-breadcrumb-margin-bottom:1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color:var(--bs-secondary-color);--bs-breadcrumb-item-padding-x:0.5rem;--bs-breadcrumb-item-active-color:var(--bs-secondary-color);display:flex;flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x:0.75rem;--bs-pagination-padding-y:0.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color:var(--bs-link-color);--bs-pagination-bg:var(--bs-body-bg);--bs-pagination-border-width:var(--bs-border-width);--bs-pagination-border-color:var(--bs-border-color);--bs-pagination-border-radius:var(--bs-border-radius);--bs-pagination-hover-color:var(--bs-link-hover-color);--bs-pagination-hover-bg:var(--bs-tertiary-bg);--bs-pagination-hover-border-color:var(--bs-border-color);--bs-pagination-focus-color:var(--bs-link-hover-color);--bs-pagination-focus-bg:var(--bs-secondary-bg);--bs-pagination-focus-box-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-pagination-active-color:#fff;--bs-pagination-active-bg:#0d6efd;--bs-pagination-active-border-color:#0d6efd;--bs-pagination-disabled-color:var(--bs-secondary-color);--bs-pagination-disabled-bg:var(--bs-secondary-bg);--bs-pagination-disabled-border-color:var(--bs-border-color);display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);text-decoration:none;background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.active>.page-link,.page-link.active{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.disabled>.page-link,.page-link.disabled{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(var(--bs-border-width) * -1)}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x:1.5rem;--bs-pagination-padding-y:0.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius:var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x:0.5rem;--bs-pagination-padding-y:0.25rem;--bs-pagination-font-size:0.875rem;--bs-pagination-border-radius:var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x:0.65em;--bs-badge-padding-y:0.35em;--bs-badge-font-size:0.75em;--bs-badge-font-weight:700;--bs-badge-color:#fff;--bs-badge-border-radius:var(--bs-border-radius);display:inline-block;padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius)}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg:transparent;--bs-alert-padding-x:1rem;--bs-alert-padding-y:1rem;--bs-alert-margin-bottom:1rem;--bs-alert-color:inherit;--bs-alert-border-color:transparent;--bs-alert-border:var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius:var(--bs-border-radius);--bs-alert-link-color:inherit;position:relative;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius)}.alert-heading{color:inherit}.alert-link{font-weight:700;color:var(--bs-alert-link-color)}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{--bs-alert-color:var(--bs-primary-text-emphasis);--bs-alert-bg:var(--bs-primary-bg-subtle);--bs-alert-border-color:var(--bs-primary-border-subtle);--bs-alert-link-color:var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color:var(--bs-secondary-text-emphasis);--bs-alert-bg:var(--bs-secondary-bg-subtle);--bs-alert-border-color:var(--bs-secondary-border-subtle);--bs-alert-link-color:var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color:var(--bs-success-text-emphasis);--bs-alert-bg:var(--bs-success-bg-subtle);--bs-alert-border-color:var(--bs-success-border-subtle);--bs-alert-link-color:var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color:var(--bs-info-text-emphasis);--bs-alert-bg:var(--bs-info-bg-subtle);--bs-alert-border-color:var(--bs-info-border-subtle);--bs-alert-link-color:var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color:var(--bs-warning-text-emphasis);--bs-alert-bg:var(--bs-warning-bg-subtle);--bs-alert-border-color:var(--bs-warning-border-subtle);--bs-alert-link-color:var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color:var(--bs-danger-text-emphasis);--bs-alert-bg:var(--bs-danger-bg-subtle);--bs-alert-border-color:var(--bs-danger-border-subtle);--bs-alert-link-color:var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color:var(--bs-light-text-emphasis);--bs-alert-bg:var(--bs-light-bg-subtle);--bs-alert-border-color:var(--bs-light-border-subtle);--bs-alert-link-color:var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color:var(--bs-dark-text-emphasis);--bs-alert-bg:var(--bs-dark-bg-subtle);--bs-alert-border-color:var(--bs-dark-border-subtle);--bs-alert-link-color:var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress,.progress-stacked{--bs-progress-height:1rem;--bs-progress-font-size:0.75rem;--bs-progress-bg:var(--bs-secondary-bg);--bs-progress-border-radius:var(--bs-border-radius);--bs-progress-box-shadow:var(--bs-box-shadow-inset);--bs-progress-bar-color:#fff;--bs-progress-bar-bg:#0d6efd;--bs-progress-bar-transition:width 0.6s ease;display:flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius)}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color:var(--bs-body-color);--bs-list-group-bg:var(--bs-body-bg);--bs-list-group-border-color:var(--bs-border-color);--bs-list-group-border-width:var(--bs-border-width);--bs-list-group-border-radius:var(--bs-border-radius);--bs-list-group-item-padding-x:1rem;--bs-list-group-item-padding-y:0.5rem;--bs-list-group-action-color:var(--bs-secondary-color);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-tertiary-bg);--bs-list-group-action-active-color:var(--bs-body-color);--bs-list-group-action-active-bg:var(--bs-secondary-bg);--bs-list-group-disabled-color:var(--bs-secondary-color);--bs-list-group-disabled-bg:var(--bs-body-bg);--bs-list-group-active-color:#fff;--bs-list-group-active-bg:#0d6efd;--bs-list-group-active-border-color:#0d6efd;display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:var(--bs-list-group-border-radius)}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);text-decoration:none;background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{--bs-list-group-color:var(--bs-primary-text-emphasis);--bs-list-group-bg:var(--bs-primary-bg-subtle);--bs-list-group-border-color:var(--bs-primary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-primary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-primary-border-subtle);--bs-list-group-active-color:var(--bs-primary-bg-subtle);--bs-list-group-active-bg:var(--bs-primary-text-emphasis);--bs-list-group-active-border-color:var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color:var(--bs-secondary-text-emphasis);--bs-list-group-bg:var(--bs-secondary-bg-subtle);--bs-list-group-border-color:var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-secondary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-secondary-border-subtle);--bs-list-group-active-color:var(--bs-secondary-bg-subtle);--bs-list-group-active-bg:var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color:var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color:var(--bs-success-text-emphasis);--bs-list-group-bg:var(--bs-success-bg-subtle);--bs-list-group-border-color:var(--bs-success-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-success-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-success-border-subtle);--bs-list-group-active-color:var(--bs-success-bg-subtle);--bs-list-group-active-bg:var(--bs-success-text-emphasis);--bs-list-group-active-border-color:var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color:var(--bs-info-text-emphasis);--bs-list-group-bg:var(--bs-info-bg-subtle);--bs-list-group-border-color:var(--bs-info-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-info-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-info-border-subtle);--bs-list-group-active-color:var(--bs-info-bg-subtle);--bs-list-group-active-bg:var(--bs-info-text-emphasis);--bs-list-group-active-border-color:var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color:var(--bs-warning-text-emphasis);--bs-list-group-bg:var(--bs-warning-bg-subtle);--bs-list-group-border-color:var(--bs-warning-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-warning-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-warning-border-subtle);--bs-list-group-active-color:var(--bs-warning-bg-subtle);--bs-list-group-active-bg:var(--bs-warning-text-emphasis);--bs-list-group-active-border-color:var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color:var(--bs-danger-text-emphasis);--bs-list-group-bg:var(--bs-danger-bg-subtle);--bs-list-group-border-color:var(--bs-danger-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-danger-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-danger-border-subtle);--bs-list-group-active-color:var(--bs-danger-bg-subtle);--bs-list-group-active-bg:var(--bs-danger-text-emphasis);--bs-list-group-active-border-color:var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color:var(--bs-light-text-emphasis);--bs-list-group-bg:var(--bs-light-bg-subtle);--bs-list-group-border-color:var(--bs-light-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-light-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-light-border-subtle);--bs-list-group-active-color:var(--bs-light-bg-subtle);--bs-list-group-active-bg:var(--bs-light-text-emphasis);--bs-list-group-active-border-color:var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color:var(--bs-dark-text-emphasis);--bs-list-group-bg:var(--bs-dark-bg-subtle);--bs-list-group-border-color:var(--bs-dark-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-dark-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-dark-border-subtle);--bs-list-group-active-color:var(--bs-dark-bg-subtle);--bs-list-group-active-bg:var(--bs-dark-text-emphasis);--bs-list-group-active-border-color:var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color:#000;--bs-btn-close-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");--bs-btn-close-opacity:0.5;--bs-btn-close-hover-opacity:0.75;--bs-btn-close-focus-shadow:0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-btn-close-focus-opacity:1;--bs-btn-close-disabled-opacity:0.25;--bs-btn-close-white-filter:invert(1) grayscale(100%) brightness(200%);box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:var(--bs-btn-close-color);background:transparent var(--bs-btn-close-bg) center/1em auto no-repeat;border:0;border-radius:.375rem;opacity:var(--bs-btn-close-opacity)}.btn-close:hover{color:var(--bs-btn-close-color);text-decoration:none;opacity:var(--bs-btn-close-hover-opacity)}.btn-close:focus{outline:0;box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity)}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{filter:var(--bs-btn-close-white-filter)}[data-bs-theme=dark] .btn-close{filter:var(--bs-btn-close-white-filter)}.toast{--bs-toast-zindex:1090;--bs-toast-padding-x:0.75rem;--bs-toast-padding-y:0.5rem;--bs-toast-spacing:1.5rem;--bs-toast-max-width:350px;--bs-toast-font-size:0.875rem;--bs-toast-color: ;--bs-toast-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-border-width:var(--bs-border-width);--bs-toast-border-color:var(--bs-border-color-translucent);--bs-toast-border-radius:var(--bs-border-radius);--bs-toast-box-shadow:var(--bs-box-shadow);--bs-toast-header-color:var(--bs-secondary-color);--bs-toast-header-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-header-border-color:var(--bs-border-color-translucent);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex:1090;position:absolute;z-index:var(--bs-toast-zindex);width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;align-items:center;padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width))}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex:1055;--bs-modal-width:500px;--bs-modal-padding:1rem;--bs-modal-margin:0.5rem;--bs-modal-color: ;--bs-modal-bg:var(--bs-body-bg);--bs-modal-border-color:var(--bs-border-color-translucent);--bs-modal-border-width:var(--bs-border-width);--bs-modal-border-radius:var(--bs-border-radius-lg);--bs-modal-box-shadow:var(--bs-box-shadow-sm);--bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));--bs-modal-header-padding-x:1rem;--bs-modal-header-padding-y:1rem;--bs-modal-header-padding:1rem 1rem;--bs-modal-header-border-color:var(--bs-border-color);--bs-modal-header-border-width:var(--bs-border-width);--bs-modal-title-line-height:1.5;--bs-modal-footer-gap:0.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color:var(--bs-border-color);--bs-modal-footer-border-width:var(--bs-border-width);position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - var(--bs-modal-margin) * 2)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);outline:0}.modal-backdrop{--bs-backdrop-zindex:1050;--bs-backdrop-bg:#000;--bs-backdrop-opacity:0.5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;flex-shrink:0;align-items:center;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5) calc(var(--bs-modal-header-padding-x) * .5);margin:calc(-.5 * var(--bs-modal-header-padding-y)) calc(-.5 * var(--bs-modal-header-padding-x)) calc(-.5 * var(--bs-modal-header-padding-y)) auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:1 1 auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media (min-width:576px){.modal{--bs-modal-margin:1.75rem;--bs-modal-box-shadow:var(--bs-box-shadow)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{--bs-modal-width:800px}}@media (min-width:1200px){.modal-xl{--bs-modal-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-footer,.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-footer,.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-footer,.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-footer,.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-footer,.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-footer,.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex:1080;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:0.5rem;--bs-tooltip-padding-y:0.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:0.875rem;--bs-tooltip-color:var(--bs-body-bg);--bs-tooltip-bg:var(--bs-emphasis-color);--bs-tooltip-border-radius:var(--bs-border-radius);--bs-tooltip-opacity:0.9;--bs-tooltip-arrow-width:0.8rem;--bs-tooltip-arrow-height:0.4rem;z-index:var(--bs-tooltip-zindex);display:block;margin:var(--bs-tooltip-margin);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{display:block;width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height)}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-top-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * .5) 0;border-right-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5) 0 calc(var(--bs-tooltip-arrow-width) * .5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg)}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg);border-radius:var(--bs-tooltip-border-radius)}.popover{--bs-popover-zindex:1070;--bs-popover-max-width:276px;--bs-popover-font-size:0.875rem;--bs-popover-bg:var(--bs-body-bg);--bs-popover-border-width:var(--bs-border-width);--bs-popover-border-color:var(--bs-border-color-translucent);--bs-popover-border-radius:var(--bs-border-radius-lg);--bs-popover-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow:var(--bs-box-shadow);--bs-popover-header-padding-x:1rem;--bs-popover-header-padding-y:0.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color:inherit;--bs-popover-header-bg:var(--bs-secondary-bg);--bs-popover-body-padding-x:1rem;--bs-popover-body-padding-y:1rem;--bs-popover-body-color:var(--bs-body-color);--bs-popover-arrow-width:1rem;--bs-popover-arrow-height:0.5rem;--bs-popover-arrow-border:var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid;border-width:0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-top>.popover-arrow::before{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-end>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * .5) 0}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::before{border-width:0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-start>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5) 0 calc(var(--bs-popover-arrow-width) * .5) var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}[data-bs-theme=dark] .carousel .carousel-control-next-icon,[data-bs-theme=dark] .carousel .carousel-control-prev-icon,[data-bs-theme=dark].carousel .carousel-control-next-icon,[data-bs-theme=dark].carousel .carousel-control-prev-icon{filter:invert(1) grayscale(100)}[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target],[data-bs-theme=dark].carousel .carousel-indicators [data-bs-target]{background-color:#000}[data-bs-theme=dark] .carousel .carousel-caption,[data-bs-theme=dark].carousel .carousel-caption{color:#000}.spinner-border,.spinner-grow{display:inline-block;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name)}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-border-width:0.25em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:transparent}.spinner-border-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem;--bs-spinner-border-width:0.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed:1.5s}}.offcanvas,.offcanvas-lg,.offcanvas-md,.offcanvas-sm,.offcanvas-xl,.offcanvas-xxl{--bs-offcanvas-zindex:1045;--bs-offcanvas-width:400px;--bs-offcanvas-height:30vh;--bs-offcanvas-padding-x:1rem;--bs-offcanvas-padding-y:1rem;--bs-offcanvas-color:var(--bs-body-color);--bs-offcanvas-bg:var(--bs-body-bg);--bs-offcanvas-border-width:var(--bs-border-width);--bs-offcanvas-border-color:var(--bs-border-color-translucent);--bs-offcanvas-box-shadow:var(--bs-box-shadow-sm);--bs-offcanvas-transition:transform 0.3s ease-in-out;--bs-offcanvas-title-line-height:1.5}@media (max-width:575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:575.98px) and (prefers-reduced-motion:reduce){.offcanvas-sm{transition:none}}@media (max-width:575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-sm.show:not(.hiding),.offcanvas-sm.showing{transform:none}.offcanvas-sm.hiding,.offcanvas-sm.show,.offcanvas-sm.showing{visibility:visible}}@media (min-width:576px){.offcanvas-sm{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:767.98px) and (prefers-reduced-motion:reduce){.offcanvas-md{transition:none}}@media (max-width:767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-md.show:not(.hiding),.offcanvas-md.showing{transform:none}.offcanvas-md.hiding,.offcanvas-md.show,.offcanvas-md.showing{visibility:visible}}@media (min-width:768px){.offcanvas-md{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:991.98px) and (prefers-reduced-motion:reduce){.offcanvas-lg{transition:none}}@media (max-width:991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-lg.show:not(.hiding),.offcanvas-lg.showing{transform:none}.offcanvas-lg.hiding,.offcanvas-lg.show,.offcanvas-lg.showing{visibility:visible}}@media (min-width:992px){.offcanvas-lg{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:1199.98px) and (prefers-reduced-motion:reduce){.offcanvas-xl{transition:none}}@media (max-width:1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xl.show:not(.hiding),.offcanvas-xl.showing{transform:none}.offcanvas-xl.hiding,.offcanvas-xl.show,.offcanvas-xl.showing{visibility:visible}}@media (min-width:1200px){.offcanvas-xl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}@media (max-width:1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media (max-width:1399.98px) and (prefers-reduced-motion:reduce){.offcanvas-xxl{transition:none}}@media (max-width:1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xxl.show:not(.hiding),.offcanvas-xxl.showing{transform:none}.offcanvas-xxl.hiding,.offcanvas-xxl.show,.offcanvas-xxl.showing{visibility:visible}}@media (min-width:1400px){.offcanvas-xxl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:transparent!important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:transparent!important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.show:not(.hiding),.offcanvas.showing{transform:none}.offcanvas.hiding,.offcanvas.show,.offcanvas.showing{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5) calc(var(--bs-offcanvas-padding-x) * .5);margin:calc(-.5 * var(--bs-offcanvas-padding-y)) calc(-.5 * var(--bs-offcanvas-padding-x)) calc(-.5 * var(--bs-offcanvas-padding-y)) auto}.offcanvas-title{margin-bottom:0;line-height:var(--bs-offcanvas-title-line-height)}.offcanvas-body{flex-grow:1;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);overflow-y:auto}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentcolor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.text-bg-primary{color:#fff!important;background-color:RGBA(var(--bs-primary-rgb),var(--bs-bg-opacity,1))!important}.text-bg-secondary{color:#fff!important;background-color:RGBA(var(--bs-secondary-rgb),var(--bs-bg-opacity,1))!important}.text-bg-success{color:#fff!important;background-color:RGBA(var(--bs-success-rgb),var(--bs-bg-opacity,1))!important}.text-bg-info{color:#000!important;background-color:RGBA(var(--bs-info-rgb),var(--bs-bg-opacity,1))!important}.text-bg-warning{color:#000!important;background-color:RGBA(var(--bs-warning-rgb),var(--bs-bg-opacity,1))!important}.text-bg-danger{color:#fff!important;background-color:RGBA(var(--bs-danger-rgb),var(--bs-bg-opacity,1))!important}.text-bg-light{color:#000!important;background-color:RGBA(var(--bs-light-rgb),var(--bs-bg-opacity,1))!important}.text-bg-dark{color:#fff!important;background-color:RGBA(var(--bs-dark-rgb),var(--bs-bg-opacity,1))!important}.link-primary{color:RGBA(var(--bs-primary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important}.link-primary:focus,.link-primary:hover{color:RGBA(10,88,202,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(10,88,202,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(10,88,202,var(--bs-link-underline-opacity,1))!important}.link-secondary{color:RGBA(var(--bs-secondary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important}.link-secondary:focus,.link-secondary:hover{color:RGBA(86,94,100,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(86,94,100,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(86,94,100,var(--bs-link-underline-opacity,1))!important}.link-success{color:RGBA(var(--bs-success-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important}.link-success:focus,.link-success:hover{color:RGBA(20,108,67,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(20,108,67,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(20,108,67,var(--bs-link-underline-opacity,1))!important}.link-info{color:RGBA(var(--bs-info-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important}.link-info:focus,.link-info:hover{color:RGBA(61,213,243,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(61,213,243,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(61,213,243,var(--bs-link-underline-opacity,1))!important}.link-warning{color:RGBA(var(--bs-warning-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important}.link-warning:focus,.link-warning:hover{color:RGBA(255,205,57,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(255,205,57,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(255,205,57,var(--bs-link-underline-opacity,1))!important}.link-danger{color:RGBA(var(--bs-danger-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important}.link-danger:focus,.link-danger:hover{color:RGBA(176,42,55,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(176,42,55,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(176,42,55,var(--bs-link-underline-opacity,1))!important}.link-light{color:RGBA(var(--bs-light-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important}.link-light:focus,.link-light:hover{color:RGBA(249,250,251,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(249,250,251,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(249,250,251,var(--bs-link-underline-opacity,1))!important}.link-dark{color:RGBA(var(--bs-dark-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important}.link-dark:focus,.link-dark:hover{color:RGBA(26,30,33,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(26,30,33,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(26,30,33,var(--bs-link-underline-opacity,1))!important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-body-emphasis:focus,.link-body-emphasis:hover{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,.75))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,0.75))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,0.75))!important}.focus-ring:focus{outline:0;box-shadow:var(--bs-focus-ring-x,0) var(--bs-focus-ring-y,0) var(--bs-focus-ring-blur,0) var(--bs-focus-ring-width) var(--bs-focus-ring-color)}.icon-link{display:inline-flex;gap:.375rem;align-items:center;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,0.5));text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,0.5));text-underline-offset:0.25em;-webkit-backface-visibility:hidden;backface-visibility:hidden}.icon-link>.bi{flex-shrink:0;width:1em;height:1em;fill:currentcolor;transition:.2s ease-in-out transform}@media (prefers-reduced-motion:reduce){.icon-link>.bi{transition:none}}.icon-link-hover:focus-visible>.bi,.icon-link-hover:hover>.bi{transform:var(--bs-icon-link-transform,translate3d(.25em,0,0))}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption),.visually-hidden:not(caption){position:absolute!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:var(--bs-border-width);min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.object-fit-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-none{-o-object-fit:none!important;object-fit:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.overflow-x-auto{overflow-x:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.overflow-x-visible{overflow-x:visible!important}.overflow-x-scroll{overflow-x:scroll!important}.overflow-y-auto{overflow-y:auto!important}.overflow-y-hidden{overflow-y:hidden!important}.overflow-y-visible{overflow-y:visible!important}.overflow-y-scroll{overflow-y:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-inline-grid{display:inline-grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:var(--bs-box-shadow)!important}.shadow-sm{box-shadow:var(--bs-box-shadow-sm)!important}.shadow-lg{box-shadow:var(--bs-box-shadow-lg)!important}.shadow-none{box-shadow:none!important}.focus-ring-primary{--bs-focus-ring-color:rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color:rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color:rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color:rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color:rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color:rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color:rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color:rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-0{border:0!important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-top-0{border-top:0!important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-primary{--bs-border-opacity:1;border-color:rgba(var(--bs-primary-rgb),var(--bs-border-opacity))!important}.border-secondary{--bs-border-opacity:1;border-color:rgba(var(--bs-secondary-rgb),var(--bs-border-opacity))!important}.border-success{--bs-border-opacity:1;border-color:rgba(var(--bs-success-rgb),var(--bs-border-opacity))!important}.border-info{--bs-border-opacity:1;border-color:rgba(var(--bs-info-rgb),var(--bs-border-opacity))!important}.border-warning{--bs-border-opacity:1;border-color:rgba(var(--bs-warning-rgb),var(--bs-border-opacity))!important}.border-danger{--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb),var(--bs-border-opacity))!important}.border-light{--bs-border-opacity:1;border-color:rgba(var(--bs-light-rgb),var(--bs-border-opacity))!important}.border-dark{--bs-border-opacity:1;border-color:rgba(var(--bs-dark-rgb),var(--bs-border-opacity))!important}.border-black{--bs-border-opacity:1;border-color:rgba(var(--bs-black-rgb),var(--bs-border-opacity))!important}.border-white{--bs-border-opacity:1;border-color:rgba(var(--bs-white-rgb),var(--bs-border-opacity))!important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle)!important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle)!important}.border-success-subtle{border-color:var(--bs-success-border-subtle)!important}.border-info-subtle{border-color:var(--bs-info-border-subtle)!important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle)!important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle)!important}.border-light-subtle{border-color:var(--bs-light-border-subtle)!important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle)!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.border-opacity-10{--bs-border-opacity:0.1}.border-opacity-25{--bs-border-opacity:0.25}.border-opacity-50{--bs-border-opacity:0.5}.border-opacity-75{--bs-border-opacity:0.75}.border-opacity-100{--bs-border-opacity:1}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.row-gap-0{row-gap:0!important}.row-gap-1{row-gap:.25rem!important}.row-gap-2{row-gap:.5rem!important}.row-gap-3{row-gap:1rem!important}.row-gap-4{row-gap:1.5rem!important}.row-gap-5{row-gap:3rem!important}.column-gap-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-lighter{font-weight:lighter!important}.fw-light{font-weight:300!important}.fw-normal{font-weight:400!important}.fw-medium{font-weight:500!important}.fw-semibold{font-weight:600!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-body-secondary{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-body-tertiary{--bs-text-opacity:1;color:var(--bs-tertiary-color)!important}.text-body-emphasis{--bs-text-opacity:1;color:var(--bs-emphasis-color)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis)!important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis)!important}.text-success-emphasis{color:var(--bs-success-text-emphasis)!important}.text-info-emphasis{color:var(--bs-info-text-emphasis)!important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis)!important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis)!important}.text-light-emphasis{color:var(--bs-light-text-emphasis)!important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis)!important}.link-opacity-10{--bs-link-opacity:0.1}.link-opacity-10-hover:hover{--bs-link-opacity:0.1}.link-opacity-25{--bs-link-opacity:0.25}.link-opacity-25-hover:hover{--bs-link-opacity:0.25}.link-opacity-50{--bs-link-opacity:0.5}.link-opacity-50-hover:hover{--bs-link-opacity:0.5}.link-opacity-75{--bs-link-opacity:0.75}.link-opacity-75-hover:hover{--bs-link-opacity:0.75}.link-opacity-100{--bs-link-opacity:1}.link-opacity-100-hover:hover{--bs-link-opacity:1}.link-offset-1{text-underline-offset:0.125em!important}.link-offset-1-hover:hover{text-underline-offset:0.125em!important}.link-offset-2{text-underline-offset:0.25em!important}.link-offset-2-hover:hover{text-underline-offset:0.25em!important}.link-offset-3{text-underline-offset:0.375em!important}.link-offset-3-hover:hover{text-underline-offset:0.375em!important}.link-underline-primary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-secondary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-success{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important}.link-underline-info{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important}.link-underline-warning{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important}.link-underline-danger{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important}.link-underline-light{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important}.link-underline-dark{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important}.link-underline{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-underline-opacity-0{--bs-link-underline-opacity:0}.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity:0}.link-underline-opacity-10{--bs-link-underline-opacity:0.1}.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity:0.1}.link-underline-opacity-25{--bs-link-underline-opacity:0.25}.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity:0.25}.link-underline-opacity-50{--bs-link-underline-opacity:0.5}.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity:0.5}.link-underline-opacity-75{--bs-link-underline-opacity:0.75}.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity:0.75}.link-underline-opacity-100{--bs-link-underline-opacity:1}.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-body-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-bg-rgb),var(--bs-bg-opacity))!important}.bg-body-tertiary{--bs-bg-opacity:1;background-color:rgba(var(--bs-tertiary-bg-rgb),var(--bs-bg-opacity))!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle)!important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle)!important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle)!important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle)!important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle)!important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle)!important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle)!important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle)!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:var(--bs-border-radius)!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:var(--bs-border-radius-sm)!important}.rounded-2{border-radius:var(--bs-border-radius)!important}.rounded-3{border-radius:var(--bs-border-radius-lg)!important}.rounded-4{border-radius:var(--bs-border-radius-xl)!important}.rounded-5{border-radius:var(--bs-border-radius-xxl)!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:var(--bs-border-radius-pill)!important}.rounded-top{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-0{border-top-left-radius:0!important;border-top-right-radius:0!important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm)!important;border-top-right-radius:var(--bs-border-radius-sm)!important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg)!important;border-top-right-radius:var(--bs-border-radius-lg)!important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl)!important;border-top-right-radius:var(--bs-border-radius-xl)!important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl)!important;border-top-right-radius:var(--bs-border-radius-xxl)!important}.rounded-top-circle{border-top-left-radius:50%!important;border-top-right-radius:50%!important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill)!important;border-top-right-radius:var(--bs-border-radius-pill)!important}.rounded-end{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-0{border-top-right-radius:0!important;border-bottom-right-radius:0!important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm)!important;border-bottom-right-radius:var(--bs-border-radius-sm)!important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg)!important;border-bottom-right-radius:var(--bs-border-radius-lg)!important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl)!important;border-bottom-right-radius:var(--bs-border-radius-xl)!important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-right-radius:var(--bs-border-radius-xxl)!important}.rounded-end-circle{border-top-right-radius:50%!important;border-bottom-right-radius:50%!important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill)!important;border-bottom-right-radius:var(--bs-border-radius-pill)!important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-0{border-bottom-right-radius:0!important;border-bottom-left-radius:0!important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm)!important;border-bottom-left-radius:var(--bs-border-radius-sm)!important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg)!important;border-bottom-left-radius:var(--bs-border-radius-lg)!important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl)!important;border-bottom-left-radius:var(--bs-border-radius-xl)!important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-left-radius:var(--bs-border-radius-xxl)!important}.rounded-bottom-circle{border-bottom-right-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill)!important;border-bottom-left-radius:var(--bs-border-radius-pill)!important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-0{border-bottom-left-radius:0!important;border-top-left-radius:0!important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm)!important;border-top-left-radius:var(--bs-border-radius-sm)!important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg)!important;border-top-left-radius:var(--bs-border-radius-lg)!important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl)!important;border-top-left-radius:var(--bs-border-radius-xl)!important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl)!important;border-top-left-radius:var(--bs-border-radius-xxl)!important}.rounded-start-circle{border-bottom-left-radius:50%!important;border-top-left-radius:50%!important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill)!important;border-top-left-radius:var(--bs-border-radius-pill)!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.z-n1{z-index:-1!important}.z-0{z-index:0!important}.z-1{z-index:1!important}.z-2{z-index:2!important}.z-3{z-index:3!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.object-fit-sm-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-sm-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-sm-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-sm-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-sm-none{-o-object-fit:none!important;object-fit:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-inline-grid{display:inline-grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.row-gap-sm-0{row-gap:0!important}.row-gap-sm-1{row-gap:.25rem!important}.row-gap-sm-2{row-gap:.5rem!important}.row-gap-sm-3{row-gap:1rem!important}.row-gap-sm-4{row-gap:1.5rem!important}.row-gap-sm-5{row-gap:3rem!important}.column-gap-sm-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-sm-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-sm-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-sm-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-sm-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-sm-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.object-fit-md-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-md-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-md-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-md-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-md-none{-o-object-fit:none!important;object-fit:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-inline-grid{display:inline-grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.row-gap-md-0{row-gap:0!important}.row-gap-md-1{row-gap:.25rem!important}.row-gap-md-2{row-gap:.5rem!important}.row-gap-md-3{row-gap:1rem!important}.row-gap-md-4{row-gap:1.5rem!important}.row-gap-md-5{row-gap:3rem!important}.column-gap-md-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-md-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-md-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-md-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-md-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-md-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.object-fit-lg-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-lg-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-lg-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-lg-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-lg-none{-o-object-fit:none!important;object-fit:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-inline-grid{display:inline-grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.row-gap-lg-0{row-gap:0!important}.row-gap-lg-1{row-gap:.25rem!important}.row-gap-lg-2{row-gap:.5rem!important}.row-gap-lg-3{row-gap:1rem!important}.row-gap-lg-4{row-gap:1.5rem!important}.row-gap-lg-5{row-gap:3rem!important}.column-gap-lg-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-lg-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-lg-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-lg-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-lg-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-lg-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.object-fit-xl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xl-none{-o-object-fit:none!important;object-fit:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-inline-grid{display:inline-grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.row-gap-xl-0{row-gap:0!important}.row-gap-xl-1{row-gap:.25rem!important}.row-gap-xl-2{row-gap:.5rem!important}.row-gap-xl-3{row-gap:1rem!important}.row-gap-xl-4{row-gap:1.5rem!important}.row-gap-xl-5{row-gap:3rem!important}.column-gap-xl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xl-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-xl-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-xl-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xl-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xl-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.object-fit-xxl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xxl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xxl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xxl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xxl-none{-o-object-fit:none!important;object-fit:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-inline-grid{display:inline-grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.row-gap-xxl-0{row-gap:0!important}.row-gap-xxl-1{row-gap:.25rem!important}.row-gap-xxl-2{row-gap:.5rem!important}.row-gap-xxl-3{row-gap:1rem!important}.row-gap-xxl-4{row-gap:1.5rem!important}.row-gap-xxl-5{row-gap:3rem!important}.column-gap-xxl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xxl-1{-moz-column-gap:0.25rem!important;column-gap:.25rem!important}.column-gap-xxl-2{-moz-column-gap:0.5rem!important;column-gap:.5rem!important}.column-gap-xxl-3{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xxl-4{-moz-column-gap:1.5rem!important;column-gap:1.5rem!important}.column-gap-xxl-5{-moz-column-gap:3rem!important;column-gap:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-inline-grid{display:inline-grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} /*# sourceMappingURL=bootstrap.min.css.map */ graphql-2.6.0/lib/graphql/dashboard/application_controller.rb0000644000004100000410000000333115173430257024442 0ustar www-datawww-data# frozen_string_literal: true require "action_controller" module Graphql class Dashboard < Rails::Engine class ApplicationController < ActionController::Base protect_from_forgery with: :exception prepend_view_path(File.expand_path("../views", __FILE__)) content_security_policy do |policy| policy.default_src(:self) if policy.default_src(*policy.default_src).blank? policy.connect_src(:self) if policy.connect_src(*policy.connect_src).blank? policy.base_uri(:none) if policy.base_uri(*policy.base_uri).blank? policy.font_src(:self) if policy.font_src(*policy.font_src).blank? policy.img_src(:self, :data) if policy.img_src(*policy.img_src).blank? policy.object_src(:none) if policy.object_src(*policy.object_src).blank? policy.script_src(:self) if policy.script_src(*policy.script_src).blank? policy.style_src(:self) if policy.style_src(*policy.style_src).blank? policy.form_action(:self) if policy.form_action(*policy.form_action).blank? policy.frame_ancestors(:none) if policy.frame_ancestors(*policy.frame_ancestors).blank? end def schema_class @schema_class ||= begin schema_param = request.query_parameters["schema"] || params[:schema] case schema_param when Class schema_param when String schema_param.constantize else raise "Missing `params[:schema]`, please provide a class or string to `mount GraphQL::Dashboard, schema: ...`" end end end helper_method :schema_class end end end ActiveSupport.run_load_hooks(:graphql_dashboard_application_controller, GraphQL::Dashboard::ApplicationController) graphql-2.6.0/lib/graphql/dashboard/detailed_traces.rb0000644000004100000410000000265715173430257023022 0ustar www-datawww-data# frozen_string_literal: true require_relative "./installable" module Graphql class Dashboard < Rails::Engine module DetailedTraces class TracesController < Graphql::Dashboard::ApplicationController include Installable def index @last = params[:last]&.to_i || 50 @before = params[:before]&.to_i @traces = schema_class.detailed_trace.traces(last: @last, before: @before) end def show trace = schema_class.detailed_trace.find_trace(params[:id].to_i) send_data(trace.trace_data) end def destroy schema_class.detailed_trace.delete_trace(params[:id]) flash[:success] = "Trace deleted." head :no_content end def delete_all schema_class.detailed_trace.delete_all_traces flash[:success] = "Deleted all traces." head :no_content end private def feature_installed? !!schema_class.detailed_trace end INSTALLABLE_COMPONENT_HEADER_HTML = "Detailed traces aren't installed yet." INSTALLABLE_COMPONENT_MESSAGE_HTML = <<~HTML.html_safe GraphQL-Ruby can instrument production traffic and save tracing artifacts here for later review.
Read more in the detailed tracing docs. HTML end end end end graphql-2.6.0/lib/graphql/dashboard/operation_store.rb0000644000004100000410000001614715173430257023121 0ustar www-datawww-data# frozen_string_literal: true require_relative "./installable" module Graphql class Dashboard < Rails::Engine module OperationStore class BaseController < Dashboard::ApplicationController include Installable private def feature_installed? schema_class.respond_to?(:operation_store) && schema_class.operation_store.is_a?(GraphQL::Pro::OperationStore) end INSTALLABLE_COMPONENT_HEADER_HTML = "OperationStore isn't installed for this schema yet.".html_safe INSTALLABLE_COMPONENT_MESSAGE_HTML = <<-HTML.html_safe Learn more about improving performance and security with stored operations in the OperationStore docs. HTML end class ClientsController < BaseController def index @order_by = params[:order_by] || "name" @order_dir = params[:order_dir].presence || "asc" clients_page = schema_class.operation_store.all_clients( page: params[:page]&.to_i || 1, per_page: params[:per_page]&.to_i || 25, order_by: @order_by, order_dir: @order_dir, ) @clients_page = clients_page end def new @client = init_client(secret: SecureRandom.hex(32)) end def create client_params = params.require(:client).permit(:name, :secret) schema_class.operation_store.upsert_client(client_params[:name], client_params[:secret]) flash[:success] = "Created #{client_params[:name].inspect}" redirect_to graphql_dashboard.operation_store_clients_path end def edit @client = schema_class.operation_store.get_client(params[:name]) end def update client_name = params[:name] client_secret = params.require(:client).permit(:secret)[:secret] schema_class.operation_store.upsert_client(client_name, client_secret) flash[:success] = "Updated #{client_name.inspect}" redirect_to graphql_dashboard.operation_store_clients_path end def destroy client_name = params[:name] schema_class.operation_store.delete_client(client_name) flash[:success] = "Deleted #{client_name.inspect}" redirect_to graphql_dashboard.operation_store_clients_path end private def init_client(name: nil, secret: nil) GraphQL::Pro::OperationStore::ClientRecord.new( name: name, secret: secret, created_at: nil, operations_count: 0, archived_operations_count: 0, last_synced_at: nil, last_used_at: nil, ) end end class OperationsController < BaseController def index @client_operations = client_name = params[:client_name] per_page = params[:per_page]&.to_i || 25 page = params[:page]&.to_i || 1 @is_archived = params[:archived_status] == :archived order_by = params[:order_by] || "name" order_dir = params[:order_dir]&.to_sym || :asc if @client_operations @operations_page = schema_class.operation_store.get_client_operations_by_client( client_name, page: page, per_page: per_page, is_archived: @is_archived, order_by: order_by, order_dir: order_dir, ) opposite_archive_mode_count = schema_class.operation_store.get_client_operations_by_client( client_name, page: 1, per_page: 1, is_archived: !@is_archived, order_by: order_by, order_dir: order_dir, ).total_count else @operations_page = schema_class.operation_store.all_operations( page: page, per_page: per_page, is_archived: @is_archived, order_by: order_by, order_dir: order_dir, ) opposite_archive_mode_count = schema_class.operation_store.all_operations( page: 1, per_page: 1, is_archived: !@is_archived, order_by: order_by, order_dir: order_dir, ).total_count end if @is_archived @archived_operations_count = @operations_page.total_count @unarchived_operations_count = opposite_archive_mode_count else @archived_operations_count = opposite_archive_mode_count @unarchived_operations_count = @operations_page.total_count end end def show digest = params[:digest] @operation = schema_class.operation_store.get_operation_by_digest(digest) if @operation # Parse & re-format the query document = GraphQL.parse(@operation.body) @graphql_source = document.to_query_string @client_operations = schema_class.operation_store.get_client_operations_by_digest(digest) @entries = schema_class.operation_store.get_index_entries_by_digest(digest) end end def update is_archived = case params[:modification] when :archive true when :unarchive false else raise ArgumentError, "Unexpected modification: #{params[:modification].inspect}" end if (client_name = params[:client_name]) operation_aliases = params[:operation_aliases] schema_class.operation_store.archive_client_operations( client_name: client_name, operation_aliases: operation_aliases, is_archived: is_archived ) flash[:success] = "#{is_archived ? "Archived" : "Activated"} #{operation_aliases.size} #{"operation".pluralize(operation_aliases.size)}" else digests = params[:digests] schema_class.operation_store.archive_operations( digests: digests, is_archived: is_archived ) flash[:success] = "#{is_archived ? "Archived" : "Activated"} #{digests.size} #{"operation".pluralize(digests.size)}" end head :no_content end end class IndexEntriesController < BaseController def index @search_term = if request.params["q"] && request.params["q"].length > 0 request.params["q"] else nil end @index_entries_page = schema_class.operation_store.all_index_entries( search_term: @search_term, page: params[:page]&.to_i || 1, per_page: params[:per_page]&.to_i || 25, ) end def show name = params[:name] @entry = schema_class.operation_store.index.get_entry(name) @chain = schema_class.operation_store.index.index_entry_chain(name) @operations = schema_class.operation_store.get_operations_by_index_entry(name) end end end end end graphql-2.6.0/lib/graphql/dashboard/subscriptions.rb0000644000004100000410000000763215173430257022613 0ustar www-datawww-data# frozen_string_literal: true require_relative "./installable" module Graphql class Dashboard < Rails::Engine module Subscriptions class BaseController < Graphql::Dashboard::ApplicationController include Installable def feature_installed? defined?(GraphQL::Pro::Subscriptions) && schema_class.subscriptions.is_a?(GraphQL::Pro::Subscriptions) end INSTALLABLE_COMPONENT_HEADER_HTML = "GraphQL-Pro Subscriptions aren't installed on this schema yet.".html_safe INSTALLABLE_COMPONENT_MESSAGE_HTML = <<-HTML.html_safe Deliver live updates over Pusher or Ably with GraphQL-Pro's subscription integrations. HTML end class TopicsController < BaseController def show topic_name = params[:name] all_subscription_ids = [] schema_class.subscriptions.each_subscription_id(topic_name) do |sid| all_subscription_ids << sid end page = params[:page]&.to_i || 1 limit = params[:per_page]&.to_i || 20 offset = limit * (page - 1) subscription_ids = all_subscription_ids[offset, limit] subs = schema_class.subscriptions.read_subscriptions(subscription_ids) show_broadcast_subscribers_count = schema_class.subscriptions.show_broadcast_subscribers_count? subs.each do |sub| sub[:is_broadcast] = is_broadcast = schema_class.subscriptions.broadcast_subscription_id?(sub[:id]) if is_broadcast && show_broadcast_subscribers_count sub[:subscribers_count] = sub_count =schema_class.subscriptions.count_broadcast_subscribed(sub[:id]) sub[:still_subscribed] = sub_count > 0 else sub[:still_subscribed] = schema_class.subscriptions.still_subscribed?(sub[:id]) sub[:subscribers_count] = nil end end @topic_last_triggered_at = schema_class.subscriptions.topic_last_triggered_at(topic_name) @subscriptions = subs @subscriptions_count = all_subscription_ids.size @show_broadcast_subscribers_count = show_broadcast_subscribers_count @has_next_page = all_subscription_ids.size > offset + limit ? page + 1 : false end def index page = params[:page]&.to_i || 1 per_page = params[:per_page]&.to_i || 20 offset = per_page * (page - 1) limit = per_page topics, all_topics_count, has_next_page = schema_class.subscriptions.topics(offset: offset, limit: limit) @topics = topics @all_topics_count = all_topics_count @has_next_page = has_next_page @page = page end end class SubscriptionsController < BaseController def show subscription_id = params[:id] subscriptions = schema_class.subscriptions query_data = subscriptions.read_subscription(subscription_id) is_broadcast = subscriptions.broadcast_subscription_id?(subscription_id) if is_broadcast && subscriptions.show_broadcast_subscribers_count? subscribers_count = subscriptions.count_broadcast_subscribed(subscription_id) is_still_subscribed = subscribers_count > 0 else subscribers_count = nil is_still_subscribed = subscriptions.still_subscribed?(subscription_id) end @query_data = query_data @still_subscribed = is_still_subscribed @is_broadcast = is_broadcast @subscribers_count = subscribers_count end def clear_all schema_class.subscriptions.clear flash[:success] = "All subscription data cleared." head :no_content end end end end end graphql-2.6.0/lib/graphql/dashboard/limiters.rb0000644000004100000410000000652315173430257021532 0ustar www-datawww-data# frozen_string_literal: true require_relative "./installable" module Graphql class Dashboard < Rails::Engine module Limiters class LimitersController < Dashboard::ApplicationController include Installable FALLBACK_CSP_NONCE_GENERATOR = ->(_req) { SecureRandom.hex(32) } def show name = params[:name] @title = case name when "runtime" "Runtime Limiter" when "active_operations" "Active Operation Limiter" when "mutations" "Mutation Limiter" else raise ArgumentError, "Unknown limiter name: #{name}" end limiter = limiter_for(name) if limiter.nil? @install_path = "http://graphql-ruby.org/limiters/#{name}" else @chart_mode = params[:chart] || "day" @current_soft = limiter.soft_limit_enabled? @histogram = limiter.dashboard_histogram(@chart_mode) # These configs may have already been defined by the application; provide overrides here if not. request.content_security_policy_nonce_generator ||= FALLBACK_CSP_NONCE_GENERATOR nonce_dirs = request.content_security_policy_nonce_directives || [] if !nonce_dirs.include?("style-src") nonce_dirs += ["style-src"] request.content_security_policy_nonce_directives = nonce_dirs end @csp_nonce = request.content_security_policy_nonce end end def update name = params[:name] limiter = limiter_for(name) if limiter limiter.toggle_soft_limit flash[:success] = if limiter.soft_limit_enabled? "Enabled soft limiting -- over-limit traffic will be logged but not rejected." else "Disabled soft limiting -- over-limit traffic will be rejected." end else flash[:warning] = "No limiter configured for #{name.inspect}" end redirect_to graphql_dashboard.limiters_limiter_path(name, chart: params[:chart]) end private def limiter_for(name) case name when "runtime" schema_class.enterprise_runtime_limiter when "active_operations" schema_class.enterprise_active_operation_limiter when "mutations" schema_class.enterprise_mutation_limiter else raise ArgumentError, "Unknown limiter: #{name}" end end def feature_installed? defined?(GraphQL::Enterprise::Limiter) && ( schema_class.enterprise_active_operation_limiter || schema_class.enterprise_runtime_limiter || (schema_class.respond_to?(:enterprise_mutation_limiter) && schema_class.enterprise_mutation_limiter) ) end INSTALLABLE_COMPONENT_HEADER_HTML = "Rate limiters aren't installed on this schema yet." INSTALLABLE_COMPONENT_MESSAGE_HTML = <<-HTML.html_safe Check out the docs to get started with GraphQL-Enterprise's runtime limiter or active operation limiter. HTML end end end end graphql-2.6.0/lib/graphql/dashboard/installable.rb0000644000004100000410000000124315173430257022166 0ustar www-datawww-data# frozen_string_literal: true module Graphql class Dashboard < Rails::Engine module Installable def self.included(child_module) child_module.before_action(:check_installed) end def feature_installed? raise "Implement #{self.class}#feature_installed? to check whether this should render `not_installed` or not." end def check_installed if !feature_installed? @component_header_html = self.class::INSTALLABLE_COMPONENT_HEADER_HTML @component_message_html = self.class::INSTALLABLE_COMPONENT_MESSAGE_HTML render "graphql/dashboard/not_installed" end end end end end graphql-2.6.0/lib/graphql/tracing/0000755000004100000410000000000015173430257017047 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/tracing/perfetto_trace/0000755000004100000410000000000015173430257022055 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/tracing/perfetto_trace/trace.proto0000644000004100000410000000701015173430257024236 0ustar www-datawww-data// This is an abbreviated version of the full Perfetto schema. // Most of them are for OS or Chrome traces and we'll never use them. // Full doc: https://github.com/google/perfetto/tree/main/protos/perfetto // // Build it with // protoc --ruby_out=lib/graphql/tracing/perfetto_trace --proto_path=lib/graphql/tracing/perfetto_trace trace.proto syntax = "proto2"; package perfetto_trace.protos; option ruby_package = "GraphQL::Tracing::PerfettoTrace"; message Trace { repeated TracePacket packet = 1; } message TracePacket { optional uint64 timestamp = 8; oneof data { TrackEvent track_event = 11; TrackDescriptor track_descriptor = 60; } oneof optional_trusted_packet_sequence_id { uint32 trusted_packet_sequence_id = 10; } optional InternedData interned_data = 12; optional bool first_packet_on_sequence = 87; optional bool previous_packet_dropped = 42; optional uint32 sequence_flags = 13; } message TrackEvent { repeated uint64 category_iids = 3; repeated string categories = 22; oneof name_field { uint64 name_iid = 10; string name = 23; } enum Type { TYPE_UNSPECIFIED = 0; TYPE_SLICE_BEGIN = 1; TYPE_SLICE_END = 2; TYPE_INSTANT = 3; TYPE_COUNTER = 4; } optional Type type = 9; optional uint64 track_uuid = 11; oneof counter_value_field { int64 counter_value = 30; double double_counter_value = 44; } repeated uint64 extra_counter_track_uuids = 31; repeated int64 extra_counter_values = 12; repeated uint64 extra_double_counter_track_uuids = 45; repeated double extra_double_counter_values = 46; repeated fixed64 flow_ids = 47; repeated fixed64 terminating_flow_ids = 48; repeated DebugAnnotation debug_annotations = 4; } message DebugAnnotation { oneof name_field { uint64 name_iid = 1; string name = 10; } oneof value { bool bool_value = 2; uint64 uint_value = 3; int64 int_value = 4; double double_value = 5; string string_value = 6; uint64 string_value_iid = 17; } repeated DebugAnnotation dict_entries = 11; repeated DebugAnnotation array_values = 12; uint64 string_value_iid = 17; } message TrackDescriptor { optional uint64 uuid = 1; optional uint64 parent_uuid = 5; oneof static_or_dynamic_name { string name = 2; } optional CounterDescriptor counter = 8; enum ChildTracksOrdering { UNKNOWN = 0; LEXICOGRAPHIC = 1; CHRONOLOGICAL = 2; EXPLICIT = 3; } optional ChildTracksOrdering child_ordering = 11; optional int32 sibling_order_rank = 12; } message CounterDescriptor { enum BuiltinCounterType { COUNTER_UNSPECIFIED = 0; COUNTER_THREAD_TIME_NS = 1; COUNTER_THREAD_INSTRUCTION_COUNT = 2; } enum Unit { UNIT_UNSPECIFIED = 0; UNIT_TIME_NS = 1; UNIT_COUNT = 2; UNIT_SIZE_BYTES = 3; } optional BuiltinCounterType type = 1; repeated string categories = 2; optional Unit unit = 3; optional string unit_name = 6; optional int64 unit_multiplier = 4; optional bool is_incremental = 5; } message InternedData { repeated EventCategory event_categories = 1; repeated EventName event_names = 2; repeated DebugAnnotationName debug_annotation_names = 3; repeated InternedString debug_annotation_string_values = 29; } message InternedString { optional uint64 iid = 1; optional bytes str = 2; } message EventCategory { optional uint64 iid = 1; optional string name = 2; } message EventName { optional uint64 iid = 1; optional string name = 2; } message DebugAnnotationName { optional uint64 iid = 1; optional string name = 2; } graphql-2.6.0/lib/graphql/tracing/perfetto_trace/trace_pb.rb0000644000004100000410000001537215173430257024171 0ustar www-datawww-data# frozen_string_literal: true # Generated by the protocol buffer compiler. DO NOT EDIT! # source: trace.proto require 'google/protobuf' descriptor_data = "\n\x0btrace.proto\x12\x15perfetto_trace.protos\";\n\x05Trace\x12\x32\n\x06packet\x18\x01 \x03(\x0b\x32\".perfetto_trace.protos.TracePacket\"\x8a\x03\n\x0bTracePacket\x12\x11\n\ttimestamp\x18\x08 \x01(\x04\x12\x38\n\x0btrack_event\x18\x0b \x01(\x0b\x32!.perfetto_trace.protos.TrackEventH\x00\x12\x42\n\x10track_descriptor\x18< \x01(\x0b\x32&.perfetto_trace.protos.TrackDescriptorH\x00\x12$\n\x1atrusted_packet_sequence_id\x18\n \x01(\rH\x01\x12:\n\rinterned_data\x18\x0c \x01(\x0b\x32#.perfetto_trace.protos.InternedData\x12 \n\x18\x66irst_packet_on_sequence\x18W \x01(\x08\x12\x1f\n\x17previous_packet_dropped\x18* \x01(\x08\x12\x16\n\x0esequence_flags\x18\r \x01(\rB\x06\n\x04\x64\x61taB%\n#optional_trusted_packet_sequence_id\"\xf2\x04\n\nTrackEvent\x12\x15\n\rcategory_iids\x18\x03 \x03(\x04\x12\x12\n\ncategories\x18\x16 \x03(\t\x12\x12\n\x08name_iid\x18\n \x01(\x04H\x00\x12\x0e\n\x04name\x18\x17 \x01(\tH\x00\x12\x34\n\x04type\x18\t \x01(\x0e\x32&.perfetto_trace.protos.TrackEvent.Type\x12\x12\n\ntrack_uuid\x18\x0b \x01(\x04\x12\x17\n\rcounter_value\x18\x1e \x01(\x03H\x01\x12\x1e\n\x14\x64ouble_counter_value\x18, \x01(\x01H\x01\x12!\n\x19\x65xtra_counter_track_uuids\x18\x1f \x03(\x04\x12\x1c\n\x14\x65xtra_counter_values\x18\x0c \x03(\x03\x12(\n extra_double_counter_track_uuids\x18- \x03(\x04\x12#\n\x1b\x65xtra_double_counter_values\x18. \x03(\x01\x12\x10\n\x08\x66low_ids\x18/ \x03(\x06\x12\x1c\n\x14terminating_flow_ids\x18\x30 \x03(\x06\x12\x41\n\x11\x64\x65\x62ug_annotations\x18\x04 \x03(\x0b\x32&.perfetto_trace.protos.DebugAnnotation\"j\n\x04Type\x12\x14\n\x10TYPE_UNSPECIFIED\x10\x00\x12\x14\n\x10TYPE_SLICE_BEGIN\x10\x01\x12\x12\n\x0eTYPE_SLICE_END\x10\x02\x12\x10\n\x0cTYPE_INSTANT\x10\x03\x12\x10\n\x0cTYPE_COUNTER\x10\x04\x42\x0c\n\nname_fieldB\x15\n\x13\x63ounter_value_field\"\xd5\x02\n\x0f\x44\x65\x62ugAnnotation\x12\x12\n\x08name_iid\x18\x01 \x01(\x04H\x00\x12\x0e\n\x04name\x18\n \x01(\tH\x00\x12\x14\n\nbool_value\x18\x02 \x01(\x08H\x01\x12\x14\n\nuint_value\x18\x03 \x01(\x04H\x01\x12\x13\n\tint_value\x18\x04 \x01(\x03H\x01\x12\x16\n\x0c\x64ouble_value\x18\x05 \x01(\x01H\x01\x12\x16\n\x0cstring_value\x18\x06 \x01(\tH\x01\x12\x1a\n\x10string_value_iid\x18\x11 \x01(\x04H\x01\x12<\n\x0c\x64ict_entries\x18\x0b \x03(\x0b\x32&.perfetto_trace.protos.DebugAnnotation\x12<\n\x0c\x61rray_values\x18\x0c \x03(\x0b\x32&.perfetto_trace.protos.DebugAnnotationB\x0c\n\nname_fieldB\x07\n\x05value\"\xe1\x02\n\x0fTrackDescriptor\x12\x0c\n\x04uuid\x18\x01 \x01(\x04\x12\x13\n\x0bparent_uuid\x18\x05 \x01(\x04\x12\x0e\n\x04name\x18\x02 \x01(\tH\x00\x12\x39\n\x07\x63ounter\x18\x08 \x01(\x0b\x32(.perfetto_trace.protos.CounterDescriptor\x12R\n\x0e\x63hild_ordering\x18\x0b \x01(\x0e\x32:.perfetto_trace.protos.TrackDescriptor.ChildTracksOrdering\x12\x1a\n\x12sibling_order_rank\x18\x0c \x01(\x05\"V\n\x13\x43hildTracksOrdering\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x11\n\rLEXICOGRAPHIC\x10\x01\x12\x11\n\rCHRONOLOGICAL\x10\x02\x12\x0c\n\x08\x45XPLICIT\x10\x03\x42\x18\n\x16static_or_dynamic_name\"\xb9\x03\n\x11\x43ounterDescriptor\x12I\n\x04type\x18\x01 \x01(\x0e\x32;.perfetto_trace.protos.CounterDescriptor.BuiltinCounterType\x12\x12\n\ncategories\x18\x02 \x03(\t\x12;\n\x04unit\x18\x03 \x01(\x0e\x32-.perfetto_trace.protos.CounterDescriptor.Unit\x12\x11\n\tunit_name\x18\x06 \x01(\t\x12\x17\n\x0funit_multiplier\x18\x04 \x01(\x03\x12\x16\n\x0eis_incremental\x18\x05 \x01(\x08\"o\n\x12\x42uiltinCounterType\x12\x17\n\x13\x43OUNTER_UNSPECIFIED\x10\x00\x12\x1a\n\x16\x43OUNTER_THREAD_TIME_NS\x10\x01\x12$\n COUNTER_THREAD_INSTRUCTION_COUNT\x10\x02\"S\n\x04Unit\x12\x14\n\x10UNIT_UNSPECIFIED\x10\x00\x12\x10\n\x0cUNIT_TIME_NS\x10\x01\x12\x0e\n\nUNIT_COUNT\x10\x02\x12\x13\n\x0fUNIT_SIZE_BYTES\x10\x03\"\xa0\x02\n\x0cInternedData\x12>\n\x10\x65vent_categories\x18\x01 \x03(\x0b\x32$.perfetto_trace.protos.EventCategory\x12\x35\n\x0b\x65vent_names\x18\x02 \x03(\x0b\x32 .perfetto_trace.protos.EventName\x12J\n\x16\x64\x65\x62ug_annotation_names\x18\x03 \x03(\x0b\x32*.perfetto_trace.protos.DebugAnnotationName\x12M\n\x1e\x64\x65\x62ug_annotation_string_values\x18\x1d \x03(\x0b\x32%.perfetto_trace.protos.InternedString\"*\n\x0eInternedString\x12\x0b\n\x03iid\x18\x01 \x01(\x04\x12\x0b\n\x03str\x18\x02 \x01(\x0c\"*\n\rEventCategory\x12\x0b\n\x03iid\x18\x01 \x01(\x04\x12\x0c\n\x04name\x18\x02 \x01(\t\"&\n\tEventName\x12\x0b\n\x03iid\x18\x01 \x01(\x04\x12\x0c\n\x04name\x18\x02 \x01(\t\"0\n\x13\x44\x65\x62ugAnnotationName\x12\x0b\n\x03iid\x18\x01 \x01(\x04\x12\x0c\n\x04name\x18\x02 \x01(\tB\"\xea\x02\x1fGraphQL::Tracing::PerfettoTrace" pool = Google::Protobuf::DescriptorPool.generated_pool pool.add_serialized_file(descriptor_data) module GraphQL module Tracing module PerfettoTrace Trace = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("perfetto_trace.protos.Trace").msgclass TracePacket = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("perfetto_trace.protos.TracePacket").msgclass TrackEvent = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("perfetto_trace.protos.TrackEvent").msgclass TrackEvent::Type = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("perfetto_trace.protos.TrackEvent.Type").enummodule DebugAnnotation = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("perfetto_trace.protos.DebugAnnotation").msgclass TrackDescriptor = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("perfetto_trace.protos.TrackDescriptor").msgclass TrackDescriptor::ChildTracksOrdering = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("perfetto_trace.protos.TrackDescriptor.ChildTracksOrdering").enummodule CounterDescriptor = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("perfetto_trace.protos.CounterDescriptor").msgclass CounterDescriptor::BuiltinCounterType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("perfetto_trace.protos.CounterDescriptor.BuiltinCounterType").enummodule CounterDescriptor::Unit = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("perfetto_trace.protos.CounterDescriptor.Unit").enummodule InternedData = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("perfetto_trace.protos.InternedData").msgclass InternedString = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("perfetto_trace.protos.InternedString").msgclass EventCategory = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("perfetto_trace.protos.EventCategory").msgclass EventName = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("perfetto_trace.protos.EventName").msgclass DebugAnnotationName = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("perfetto_trace.protos.DebugAnnotationName").msgclass end end end graphql-2.6.0/lib/graphql/tracing/prometheus_trace.rb0000644000004100000410000000556415173430257022757 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/monitor_trace" module GraphQL module Tracing # A tracer for reporting GraphQL-Ruby times to Prometheus. # # The PrometheusExporter server must be run with a custom type collector that extends `GraphQL::Tracing::PrometheusTracing::GraphQLCollector`. # # @example Adding this trace to your schema # require 'prometheus_exporter/client' # # class MySchema < GraphQL::Schema # trace_with GraphQL::Tracing::PrometheusTrace # end # # @example Running a custom type collector # # lib/graphql_collector.rb # if defined?(PrometheusExporter::Server) # require 'graphql/tracing' # # class GraphQLCollector < GraphQL::Tracing::PrometheusTrace::GraphQLCollector # end # end # # # Then run: # # bundle exec prometheus_exporter -a lib/graphql_collector.rb PrometheusTrace = MonitorTrace.create_module("prometheus") module PrometheusTrace if defined?(PrometheusExporter::Server) autoload :GraphQLCollector, "graphql/tracing/prometheus_trace/graphql_collector" end def initialize(client: PrometheusExporter::Client.default, keys_whitelist: [:execute_field], collector_type: "graphql", **rest) @prometheus_client = client @prometheus_keys_whitelist = keys_whitelist.map(&:to_sym) # handle previous string keys @prometheus_collector_type = collector_type setup_prometheus_monitor(**rest) super end attr_reader :prometheus_collector_type, :prometheus_client, :prometheus_keys_whitelist class PrometheusMonitor < MonitorTrace::Monitor def instrument(keyword, object) if active?(keyword) start = gettime result = yield duration = gettime - start send_json(duration, keyword, object) result else yield end end def active?(keyword) @trace.prometheus_keys_whitelist.include?(keyword) end def gettime ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) end def send_json(duration, keyword, object) event_name = name_for(keyword, object) @trace.prometheus_client.send_json( type: @trace.prometheus_collector_type, duration: duration, platform_key: event_name, key: keyword ) end include MonitorTrace::Monitor::GraphQLPrefixNames class Event < MonitorTrace::Monitor::Event def start @start_time = @monitor.gettime end def finish if @monitor.active?(keyword) duration = @monitor.gettime - @start_time @monitor.send_json(duration, keyword, object) end end end end end end end graphql-2.6.0/lib/graphql/tracing/monitor_trace.rb0000644000004100000410000002211715173430257022244 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Tracing # This module is the basis for Ruby-level integration with third-party monitoring platforms. # Platform-specific traces include this module and implement an adapter. # # @see ActiveSupportNotificationsTrace Integration via ActiveSupport::Notifications, an alternative approach. module MonitorTrace class Monitor def initialize(trace:, set_transaction_name:, **_rest) @trace = trace @set_transaction_name = set_transaction_name @platform_field_key_cache = Hash.new { |h, k| h[k] = platform_field_key(k) }.compare_by_identity @platform_authorized_key_cache = Hash.new { |h, k| h[k] = platform_authorized_key(k) }.compare_by_identity @platform_resolve_type_key_cache = Hash.new { |h, k| h[k] = platform_resolve_type_key(k) }.compare_by_identity @platform_source_class_key_cache = Hash.new { |h, source_cls| h[source_cls] = platform_source_class_key(source_cls) }.compare_by_identity end def instrument(keyword, object, &block) raise "Implement #{self.class}#instrument to measure the block" end def start_event(keyword, object) ev = self.class::Event.new(self, keyword, object) ev.start ev end # Get the transaction name based on the operation type and name if possible, or fall back to a user provided # one. Useful for anonymous queries. def transaction_name(query) selected_op = query.selected_operation txn_name = if selected_op op_type = selected_op.operation_type op_name = selected_op.name || fallback_transaction_name(query.context) || "anonymous" "#{op_type}.#{op_name}" else "query.anonymous" end "GraphQL/#{txn_name}" end def fallback_transaction_name(context) context[:tracing_fallback_transaction_name] end def name_for(keyword, object) case keyword when :execute_field @platform_field_key_cache[object] when :authorized @platform_authorized_key_cache[object] when :resolve_type @platform_resolve_type_key_cache[object] when :dataloader_source @platform_source_class_key_cache[object.class] when :parse then self.class::PARSE_NAME when :lex then self.class::LEX_NAME when :execute then self.class::EXECUTE_NAME when :analyze then self.class::ANALYZE_NAME when :validate then self.class::VALIDATE_NAME else raise "No name for #{keyword.inspect}" end end class Event def initialize(monitor, keyword, object) @monitor = monitor @keyword = keyword @object = object end attr_reader :keyword, :object def start raise "Implement #{self.class}#start to begin a new event (#{inspect})" end def finish raise "Implement #{self.class}#finish to end this event (#{inspect})" end end module GraphQLSuffixNames PARSE_NAME = "parse.graphql" LEX_NAME = "lex.graphql" VALIDATE_NAME = "validate.graphql" EXECUTE_NAME = "execute.graphql" ANALYZE_NAME = "analyze.graphql" def platform_field_key(field) "#{field.path}.graphql" end def platform_authorized_key(type) "#{type.graphql_name}.authorized.graphql" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type.graphql" end def platform_source_class_key(source_class) "#{source_class.name.gsub("::", "_")}.fetch.graphql" end end module GraphQLPrefixNames PARSE_NAME = "graphql.parse" LEX_NAME = "graphql.lex" VALIDATE_NAME = "graphql.validate" EXECUTE_NAME = "graphql.execute" ANALYZE_NAME = "graphql.analyze" def platform_field_key(field) "graphql.#{field.path}" end def platform_authorized_key(type) "graphql.authorized.#{type.graphql_name}" end def platform_resolve_type_key(type) "graphql.resolve_type.#{type.graphql_name}" end def platform_source_class_key(source_class) "graphql.fetch.#{source_class.name.gsub("::", "_")}" end end end def self.create_module(monitor_name) if !monitor_name.match?(/[a-z]+/) raise ArgumentError, "monitor name must be [a-z]+, not: #{monitor_name.inspect}" end trace_module = Module.new code = MODULE_TEMPLATE % { monitor: monitor_name, monitor_class: monitor_name.capitalize + "Monitor", } trace_module.module_eval(code, __FILE__, __LINE__ + 5) # rubocop:disable Development/NoEvalCop This is build-time with a validated string trace_module end MODULE_TEMPLATE = <<~RUBY # @param set_transaction_name [Boolean] If `true`, use the GraphQL operation name as the request name on the monitoring platform # @param trace_scalars [Boolean] If `true`, leaf fields will be traced too (Scalars _and_ Enums) # @param trace_authorized [Boolean] If `false`, skip tracing `authorized?` calls # @param trace_resolve_type [Boolean] If `false`, skip tracing `resolve_type?` calls def initialize(...) setup_%{monitor}_monitor(...) super end def setup_%{monitor}_monitor(trace_scalars: false, trace_authorized: true, trace_resolve_type: true, set_transaction_name: false, **kwargs) @trace_scalars = trace_scalars @trace_authorized = trace_authorized @trace_resolve_type = trace_resolve_type @set_transaction_name = set_transaction_name @%{monitor} = %{monitor_class}.new(trace: self, set_transaction_name: @set_transaction_name, **kwargs) end def parse(query_string:) @%{monitor}.instrument(:parse, query_string) do super end end def lex(query_string:) @%{monitor}.instrument(:lex, query_string) do super end end def validate(query:, validate:) @%{monitor}.instrument(:validate, query) do super end end def begin_analyze_multiplex(multiplex, analyzers) begin_%{monitor}_event(:analyze, nil) super end def end_analyze_multiplex(multiplex, analyzers) finish_%{monitor}_event super end def execute_multiplex(multiplex:) @%{monitor}.instrument(:execute, multiplex) do super end end def begin_execute_field(field, object, arguments, query) return_type = field.type.unwrap trace_field = if return_type.kind.scalar? || return_type.kind.enum? (field.trace.nil? && @trace_scalars) || field.trace else true end if trace_field begin_%{monitor}_event(:execute_field, field) end super end def end_execute_field(field, object, arguments, query, result) finish_%{monitor}_event super end def dataloader_fiber_yield(source) Fiber[PREVIOUS_EV_KEY] = finish_%{monitor}_event super end def dataloader_fiber_resume(source) prev_ev = Fiber[PREVIOUS_EV_KEY] if prev_ev begin_%{monitor}_event(prev_ev.keyword, prev_ev.object) end super end def begin_authorized(type, object, context) @trace_authorized && begin_%{monitor}_event(:authorized, type) super end def end_authorized(type, object, context, result) finish_%{monitor}_event super end def begin_resolve_type(type, value, context) @trace_resolve_type && begin_%{monitor}_event(:resolve_type, type) super end def end_resolve_type(type, value, context, resolved_type) finish_%{monitor}_event super end def begin_dataloader_source(source) begin_%{monitor}_event(:dataloader_source, source) super end def end_dataloader_source(source) finish_%{monitor}_event super end CURRENT_EV_KEY = :__graphql_%{monitor}_trace_event PREVIOUS_EV_KEY = :__graphql_%{monitor}_trace_previous_event private def begin_%{monitor}_event(keyword, object) Fiber[CURRENT_EV_KEY] = @%{monitor}.start_event(keyword, object) end def finish_%{monitor}_event if ev = Fiber[CURRENT_EV_KEY] ev.finish # Use `false` to prevent grabbing an event from a parent fiber Fiber[CURRENT_EV_KEY] = false ev end end RUBY end end end graphql-2.6.0/lib/graphql/tracing/new_relic_trace.rb0000644000004100000410000000443615173430257022530 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/monitor_trace" module GraphQL module Tracing # A tracer for reporting GraphQL-Ruby time to New Relic # # @example Installing the tracer # class MySchema < GraphQL::Schema # trace_with GraphQL::Tracing::NewRelicTrace # # # Optional, use the operation name to set the new relic transaction name: # # trace_with GraphQL::Tracing::NewRelicTrace, set_transaction_name: true # end # # @example Installing without trace events for `authorized?` or `resolve_type` calls # trace_with GraphQL::Tracing::NewRelicTrace, trace_authorized: false, trace_resolve_type: false NewRelicTrace = MonitorTrace.create_module("newrelic") module NewRelicTrace class NewrelicMonitor < MonitorTrace::Monitor PARSE_NAME = "GraphQL/parse" LEX_NAME = "GraphQL/lex" VALIDATE_NAME = "GraphQL/validate" EXECUTE_NAME = "GraphQL/execute" ANALYZE_NAME = "GraphQL/analyze" def instrument(keyword, payload, &block) if keyword == :execute query = payload.queries.first set_this_txn_name = query.context[:set_new_relic_transaction_name] if set_this_txn_name || (set_this_txn_name.nil? && @set_transaction_name) NewRelic::Agent.set_transaction_name(transaction_name(query)) end end ::NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(name_for(keyword, payload), &block) end def platform_source_class_key(source_class) "GraphQL/Source/#{source_class.name}" end def platform_field_key(field) "GraphQL/#{field.owner.graphql_name}/#{field.graphql_name}" end def platform_authorized_key(type) "GraphQL/Authorized/#{type.graphql_name}" end def platform_resolve_type_key(type) "GraphQL/ResolveType/#{type.graphql_name}" end class Event < MonitorTrace::Monitor::Event def start name = @monitor.name_for(keyword, object) @nr_ev = NewRelic::Agent::Tracer.start_transaction_or_segment(partial_name: name, category: :web) end def finish @nr_ev.finish end end end end end end graphql-2.6.0/lib/graphql/tracing/appsignal_trace.rb0000644000004100000410000000360115173430257022530 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/monitor_trace" module GraphQL module Tracing # Instrumentation for reporting GraphQL-Ruby times to Appsignal. # # @example Installing the tracer # class MySchema < GraphQL::Schema # trace_with GraphQL::Tracing::AppsignalTrace # end AppsignalTrace = MonitorTrace.create_module("appsignal") module AppsignalTrace # @param set_action_name [Boolean] If true, the GraphQL operation name will be used as the transaction name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_appsignal_action_name]`. def initialize(set_action_name: false, **rest) rest[:set_transaction_name] ||= set_action_name setup_appsignal_monitor(**rest) super end class AppsignalMonitor < MonitorTrace::Monitor def instrument(keyword, object) if keyword == :execute query = object.queries.first set_this_txn_name = query.context[:set_appsignal_action_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name) Appsignal::Transaction.current.set_action(transaction_name(query)) end end Appsignal.instrument(name_for(keyword, object)) do yield end end include MonitorTrace::Monitor::GraphQLSuffixNames class Event < GraphQL::Tracing::MonitorTrace::Monitor::Event def start Appsignal::Transaction.current.start_event end def finish Appsignal::Transaction.current.finish_event( @monitor.name_for(@keyword, @object), "", "" ) end end end end end end graphql-2.6.0/lib/graphql/tracing/platform_trace.rb0000644000004100000410000001016415173430257022400 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Tracing module PlatformTrace def initialize(trace_scalars: false, **_options) @trace_scalars = trace_scalars @platform_key_cache = Hash.new { |h, mod| h[mod] = mod::KeyCache.new } super end module BaseKeyCache def initialize @platform_field_key_cache = Hash.new { |h, k| h[k] = platform_field_key(k) } @platform_authorized_key_cache = Hash.new { |h, k| h[k] = platform_authorized_key(k) } @platform_resolve_type_key_cache = Hash.new { |h, k| h[k] = platform_resolve_type_key(k) } end attr_reader :platform_field_key_cache, :platform_authorized_key_cache, :platform_resolve_type_key_cache end def platform_execute_field_lazy(*args, &block) platform_execute_field(*args, &block) end def platform_authorized_lazy(key, &block) platform_authorized(key, &block) end def platform_resolve_type_lazy(key, &block) platform_resolve_type(key, &block) end def self.included(child_class) key_methods_class = Class.new { include(child_class) include(BaseKeyCache) } child_class.const_set(:KeyCache, key_methods_class) # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time [:execute_field, :execute_field_lazy].each do |field_trace_method| if !child_class.method_defined?(field_trace_method) child_class.module_eval <<-RUBY, __FILE__, __LINE__ def #{field_trace_method}(query:, field:, ast_node:, arguments:, object:) return_type = field.type.unwrap trace_field = if return_type.kind.scalar? || return_type.kind.enum? (field.trace.nil? && @trace_scalars) || field.trace else true end platform_key = if trace_field @platform_key_cache[#{child_class}].platform_field_key_cache[field] else nil end if platform_key && trace_field platform_#{field_trace_method}(platform_key) do super end else super end end RUBY end end [:authorized, :authorized_lazy].each do |auth_trace_method| if !child_class.method_defined?(auth_trace_method) child_class.module_eval <<-RUBY, __FILE__, __LINE__ def #{auth_trace_method}(type:, query:, object:) platform_key = @platform_key_cache[#{child_class}].platform_authorized_key_cache[type] platform_#{auth_trace_method}(platform_key) do super end end RUBY end end [:resolve_type, :resolve_type_lazy].each do |rt_trace_method| if !child_class.method_defined?(rt_trace_method) child_class.module_eval <<-RUBY, __FILE__, __LINE__ def #{rt_trace_method}(query:, type:, object:) platform_key = @platform_key_cache[#{child_class}].platform_resolve_type_key_cache[type] platform_#{rt_trace_method}(platform_key) do super end end RUBY end # rubocop:enable Development/NoEvalCop end end private # Get the transaction name based on the operation type and name if possible, or fall back to a user provided # one. Useful for anonymous queries. def transaction_name(query) selected_op = query.selected_operation txn_name = if selected_op op_type = selected_op.operation_type op_name = selected_op.name || fallback_transaction_name(query.context) || "anonymous" "#{op_type}.#{op_name}" else "query.anonymous" end "GraphQL/#{txn_name}" end def fallback_transaction_name(context) context[:tracing_fallback_transaction_name] end end end end graphql-2.6.0/lib/graphql/tracing/data_dog_trace.rb0000644000004100000410000000503015173430257022312 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/monitor_trace" module GraphQL module Tracing # A tracer for reporting to DataDog # @example Adding this tracer to your schema # class MySchema < GraphQL::Schema # trace_with GraphQL::Tracing::DataDogTrace # end # @example Skipping `resolve_type` and `authorized` events # trace_with GraphQL::Tracing::DataDogTrace, trace_authorized: false, trace_resolve_type: false DataDogTrace = MonitorTrace.create_module("datadog") module DataDogTrace class DatadogMonitor < MonitorTrace::Monitor def initialize(set_transaction_name:, service: nil, tracer: nil, **_rest) super if tracer.nil? tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer end @tracer = tracer @service_name = service @has_prepare_span = @trace.respond_to?(:prepare_span) end attr_reader :tracer, :service_name def instrument(keyword, object) trace_key = name_for(keyword, object) @tracer.trace(trace_key, service: @service_name, type: 'custom') do |span| span.set_tag('component', 'graphql') op_name = keyword.respond_to?(:name) ? keyword.name : keyword.to_s span.set_tag('operation', op_name) if keyword == :execute operations = object.queries.map(&:selected_operation_name).join(', ') first_query = object.queries.first resource = if operations.empty? fallback_transaction_name(first_query && first_query.context) else operations end span.resource = resource if resource span.set_tag("selected_operation_name", first_query.selected_operation_name) span.set_tag("selected_operation_type", first_query.selected_operation&.operation_type) span.set_tag("query_string", first_query.query_string) end if @has_prepare_span @trace.prepare_span(keyword, object, span) end yield end end include MonitorTrace::Monitor::GraphQLSuffixNames class Event < MonitorTrace::Monitor::Event def start name = @monitor.name_for(keyword, object) @dd_span = @monitor.tracer.trace(name, service: @monitor.service_name, type: 'custom') end def finish @dd_span.finish end end end end end end graphql-2.6.0/lib/graphql/tracing/appsignal_tracing.rb0000644000004100000410000000337615173430257023072 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/platform_tracing" module GraphQL module Tracing class AppsignalTracing < PlatformTracing self.platform_keys = { "lex" => "lex.graphql", "parse" => "parse.graphql", "validate" => "validate.graphql", "analyze_query" => "analyze.graphql", "analyze_multiplex" => "analyze.graphql", "execute_multiplex" => "execute.graphql", "execute_query" => "execute.graphql", "execute_query_lazy" => "execute.graphql", } # @param set_action_name [Boolean] If true, the GraphQL operation name will be used as the transaction name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_appsignal_action_name]`. def initialize(options = {}) @set_action_name = options.fetch(:set_action_name, false) super end def platform_trace(platform_key, key, data) if key == "execute_query" set_this_txn_name = data[:query].context[:set_appsignal_action_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_action_name) Appsignal::Transaction.current.set_action(transaction_name(data[:query])) end end Appsignal.instrument(platform_key) do yield end end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}.graphql" end def platform_authorized_key(type) "#{type.graphql_name}.authorized.graphql" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type.graphql" end end end end graphql-2.6.0/lib/graphql/tracing/appoptics_tracing.rb0000644000004100000410000001350715173430257023113 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/platform_tracing" module GraphQL module Tracing # This class uses the AppopticsAPM SDK from the appoptics_apm gem to create # traces for GraphQL. # # There are 4 configurations available. They can be set in the # appoptics_apm config file or in code. Please see: # {https://docs.appoptics.com/kb/apm_tracing/ruby/configure} # # AppOpticsAPM::Config[:graphql][:enabled] = true|false # AppOpticsAPM::Config[:graphql][:transaction_name] = true|false # AppOpticsAPM::Config[:graphql][:sanitize_query] = true|false # AppOpticsAPM::Config[:graphql][:remove_comments] = true|false class AppOpticsTracing < GraphQL::Tracing::PlatformTracing # These GraphQL events will show up as 'graphql.prep' spans PREP_KEYS = ['lex', 'parse', 'validate', 'analyze_query', 'analyze_multiplex'].freeze # These GraphQL events will show up as 'graphql.execute' spans EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze def initialize(...) warn "GraphQL::Tracing::AppOptics tracing is deprecated; update to SolarWindsAPM instead, which uses OpenTelemetry." super end # During auto-instrumentation this version of AppOpticsTracing is compared # with the version provided in the appoptics_apm gem, so that the newer # version of the class can be used def self.version Gem::Version.new('1.0.0') end self.platform_keys = { 'lex' => 'lex', 'parse' => 'parse', 'validate' => 'validate', 'analyze_query' => 'analyze_query', 'analyze_multiplex' => 'analyze_multiplex', 'execute_multiplex' => 'execute_multiplex', 'execute_query' => 'execute_query', 'execute_query_lazy' => 'execute_query_lazy' } def platform_trace(platform_key, _key, data) return yield if !defined?(AppOpticsAPM) || gql_config[:enabled] == false layer = span_name(platform_key) kvs = metadata(data, layer) kvs[:Key] = platform_key if (PREP_KEYS + EXEC_KEYS).include?(platform_key) transaction_name(kvs[:InboundQuery]) if kvs[:InboundQuery] && layer == 'graphql.execute' ::AppOpticsAPM::SDK.trace(layer, kvs) do kvs.clear # we don't have to send them twice yield end end def platform_field_key(type, field) "graphql.#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "graphql.authorized.#{type.graphql_name}" end def platform_resolve_type_key(type) "graphql.resolve_type.#{type.graphql_name}" end private def gql_config ::AppOpticsAPM::Config[:graphql] ||= {} end def transaction_name(query) return if gql_config[:transaction_name] == false || ::AppOpticsAPM::SDK.get_transaction_name split_query = query.strip.split(/\W+/, 3) split_query[0] = 'query' if split_query[0].empty? name = "graphql.#{split_query[0..1].join('.')}" ::AppOpticsAPM::SDK.set_transaction_name(name) end def multiplex_transaction_name(names) return if gql_config[:transaction_name] == false || ::AppOpticsAPM::SDK.get_transaction_name name = "graphql.multiplex.#{names.join('.')}" name = "#{name[0..251]}..." if name.length > 254 ::AppOpticsAPM::SDK.set_transaction_name(name) end def span_name(key) return 'graphql.prep' if PREP_KEYS.include?(key) return 'graphql.execute' if EXEC_KEYS.include?(key) key[/^graphql\./] ? key : "graphql.#{key}" end # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def metadata(data, layer) data.keys.map do |key| case key when :context graphql_context(data[key], layer) when :query graphql_query(data[key]) when :query_string graphql_query_string(data[key]) when :multiplex graphql_multiplex(data[key]) when :path [key, data[key].join(".")] else [key, data[key]] end end.tap { _1.flatten!(2) }.each_slice(2).to_h.merge(Spec: 'graphql') end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength def graphql_context(context, layer) context.errors && context.errors.each do |err| AppOpticsAPM::API.log_exception(layer, err) end [[:Path, context.path.join('.')]] end def graphql_query(query) return [] unless query query_string = query.query_string query_string = remove_comments(query_string) if gql_config[:remove_comments] != false query_string = sanitize(query_string) if gql_config[:sanitize_query] != false [[:InboundQuery, query_string], [:Operation, query.selected_operation_name]] end def graphql_query_string(query_string) query_string = remove_comments(query_string) if gql_config[:remove_comments] != false query_string = sanitize(query_string) if gql_config[:sanitize_query] != false [:InboundQuery, query_string] end def graphql_multiplex(data) names = data.queries.map(&:operations).map!(&:keys).tap(&:flatten!).tap(&:compact!) multiplex_transaction_name(names) if names.size > 1 [:Operations, names.join(', ')] end def sanitize(query) return unless query # remove arguments query.gsub(/"[^"]*"/, '"?"') # strings .gsub(/-?[0-9]*\.?[0-9]+e?[0-9]*/, '?') # ints + floats .gsub(/\[[^\]]*\]/, '[?]') # arrays end def remove_comments(query) return unless query query.gsub(/#[^\n\r]*/, '') end end end end graphql-2.6.0/lib/graphql/tracing/appoptics_trace.rb0000644000004100000410000002063015173430257022555 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/platform_trace" module GraphQL module Tracing # This class uses the AppopticsAPM SDK from the appoptics_apm gem to create # traces for GraphQL. # # There are 4 configurations available. They can be set in the # appoptics_apm config file or in code. Please see: # {https://docs.appoptics.com/kb/apm_tracing/ruby/configure} # # AppOpticsAPM::Config[:graphql][:enabled] = true|false # AppOpticsAPM::Config[:graphql][:transaction_name] = true|false # AppOpticsAPM::Config[:graphql][:sanitize_query] = true|false # AppOpticsAPM::Config[:graphql][:remove_comments] = true|false module AppOpticsTrace # These GraphQL events will show up as 'graphql.prep' spans PREP_KEYS = ['lex', 'parse', 'validate', 'analyze_query', 'analyze_multiplex'].freeze # These GraphQL events will show up as 'graphql.execute' spans EXEC_KEYS = ['execute_multiplex', 'execute_query', 'execute_query_lazy'].freeze # During auto-instrumentation this version of AppOpticsTracing is compared # with the version provided in the appoptics_apm gem, so that the newer # version of the class can be used def self.version Gem::Version.new('1.0.0') end # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time [ 'lex', 'parse', 'validate', 'analyze_query', 'analyze_multiplex', 'execute_multiplex', 'execute_query', 'execute_query_lazy', ].each do |trace_method| module_eval <<-RUBY, __FILE__, __LINE__ def #{trace_method}(**data) return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false layer = span_name("#{trace_method}") kvs = metadata(data, layer) kvs[:Key] = "#{trace_method}" if (PREP_KEYS + EXEC_KEYS).include?("#{trace_method}") transaction_name(kvs[:InboundQuery]) if kvs[:InboundQuery] && layer == 'graphql.execute' ::AppOpticsAPM::SDK.trace(layer, kvs) do kvs.clear # we don't have to send them twice super end end RUBY end # rubocop:enable Development/NoEvalCop def execute_field(query:, field:, ast_node:, arguments:, object:) return_type = field.type.unwrap trace_field = if return_type.kind.scalar? || return_type.kind.enum? (field.trace.nil? && @trace_scalars) || field.trace else true end platform_key = if trace_field @platform_key_cache[AppOpticsTrace].platform_field_key_cache[field] else nil end if platform_key && trace_field return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false layer = platform_key kvs = metadata({query: query, field: field, ast_node: ast_node, arguments: arguments, object: object}, layer) ::AppOpticsAPM::SDK.trace(layer, kvs) do kvs.clear # we don't have to send them twice super end else super end end def execute_field_lazy(query:, field:, ast_node:, arguments:, object:) # rubocop:disable Development/TraceCallsSuperCop execute_field(query: query, field: field, ast_node: ast_node, arguments: arguments, object: object) end def authorized(**data) return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false layer = @platform_key_cache[AppOpticsTrace].platform_authorized_key_cache[data[:type]] kvs = metadata(data, layer) ::AppOpticsAPM::SDK.trace(layer, kvs) do kvs.clear # we don't have to send them twice super end end def authorized_lazy(**data) return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false layer = @platform_key_cache[AppOpticsTrace].platform_authorized_key_cache[data[:type]] kvs = metadata(data, layer) ::AppOpticsAPM::SDK.trace(layer, kvs) do kvs.clear # we don't have to send them twice super end end def resolve_type(**data) return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false layer = @platform_key_cache[AppOpticsTrace].platform_resolve_type_key_cache[data[:type]] kvs = metadata(data, layer) ::AppOpticsAPM::SDK.trace(layer, kvs) do kvs.clear # we don't have to send them twice super end end def resolve_type_lazy(**data) return super if !defined?(AppOpticsAPM) || gql_config[:enabled] == false layer = @platform_key_cache[AppOpticsTrace].platform_resolve_type_key_cache[data[:type]] kvs = metadata(data, layer) ::AppOpticsAPM::SDK.trace(layer, kvs) do kvs.clear # we don't have to send them twice super end end include PlatformTrace def platform_field_key(field) "graphql.#{field.owner.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "graphql.authorized.#{type.graphql_name}" end def platform_resolve_type_key(type) "graphql.resolve_type.#{type.graphql_name}" end private def gql_config ::AppOpticsAPM::Config[:graphql] ||= {} end def transaction_name(query) return if gql_config[:transaction_name] == false || ::AppOpticsAPM::SDK.get_transaction_name split_query = query.strip.split(/\W+/, 3) split_query[0] = 'query' if split_query[0].empty? name = "graphql.#{split_query[0..1].join('.')}" ::AppOpticsAPM::SDK.set_transaction_name(name) end def multiplex_transaction_name(names) return if gql_config[:transaction_name] == false || ::AppOpticsAPM::SDK.get_transaction_name name = "graphql.multiplex.#{names.join('.')}" name = "#{name[0..251]}..." if name.length > 254 ::AppOpticsAPM::SDK.set_transaction_name(name) end def span_name(key) return 'graphql.prep' if PREP_KEYS.include?(key) return 'graphql.execute' if EXEC_KEYS.include?(key) key[/^graphql\./] ? key : "graphql.#{key}" end # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def metadata(data, layer) data.keys.map do |key| case key when :context graphql_context(data[key], layer) when :query graphql_query(data[key]) when :query_string graphql_query_string(data[key]) when :multiplex graphql_multiplex(data[key]) when :path [key, data[key].join(".")] else [key, data[key]] end end.tap { _1.flatten!(2) }.each_slice(2).to_h.merge(Spec: 'graphql') end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength def graphql_context(context, layer) context.errors && context.errors.each do |err| AppOpticsAPM::API.log_exception(layer, err) end [[:Path, context.path.join('.')]] end def graphql_query(query) return [] unless query query_string = query.query_string query_string = remove_comments(query_string) if gql_config[:remove_comments] != false query_string = sanitize(query_string) if gql_config[:sanitize_query] != false [[:InboundQuery, query_string], [:Operation, query.selected_operation_name]] end def graphql_query_string(query_string) query_string = remove_comments(query_string) if gql_config[:remove_comments] != false query_string = sanitize(query_string) if gql_config[:sanitize_query] != false [:InboundQuery, query_string] end def graphql_multiplex(data) names = data.queries.map(&:operations).map!(&:keys).tap(&:flatten!).tap(&:compact!) multiplex_transaction_name(names) if names.size > 1 [:Operations, names.join(', ')] end def sanitize(query) return unless query # remove arguments query.gsub(/"[^"]*"/, '"?"') # strings .gsub(/-?[0-9]*\.?[0-9]+e?[0-9]*/, '?') # ints + floats .gsub(/\[[^\]]*\]/, '[?]') # arrays end def remove_comments(query) return unless query query.gsub(/#[^\n\r]*/, '') end end end end graphql-2.6.0/lib/graphql/tracing/active_support_notifications_tracing.rb0000644000004100000410000000123615173430257027105 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/notifications_tracing" module GraphQL module Tracing # This implementation forwards events to ActiveSupport::Notifications # with a `graphql` suffix. # # @see KEYS for event names module ActiveSupportNotificationsTracing # A cache of frequently-used keys to avoid needless string allocations KEYS = NotificationsTracing::KEYS NOTIFICATIONS_ENGINE = NotificationsTracing.new(ActiveSupport::Notifications) if defined?(ActiveSupport::Notifications) def self.trace(key, metadata, &blk) NOTIFICATIONS_ENGINE.trace(key, metadata, &blk) end end end end graphql-2.6.0/lib/graphql/tracing/new_relic_tracing.rb0000644000004100000410000000347315173430257023061 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/platform_tracing" module GraphQL module Tracing class NewRelicTracing < PlatformTracing self.platform_keys = { "lex" => "GraphQL/lex", "parse" => "GraphQL/parse", "validate" => "GraphQL/validate", "analyze_query" => "GraphQL/analyze", "analyze_multiplex" => "GraphQL/analyze", "execute_multiplex" => "GraphQL/execute", "execute_query" => "GraphQL/execute", "execute_query_lazy" => "GraphQL/execute", } # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_new_relic_transaction_name]`. def initialize(options = {}) @set_transaction_name = options.fetch(:set_transaction_name, false) super end def platform_trace(platform_key, key, data) if key == "execute_query" set_this_txn_name = data[:query].context[:set_new_relic_transaction_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name) NewRelic::Agent.set_transaction_name(transaction_name(data[:query])) end end NewRelic::Agent::MethodTracerHelpers.trace_execution_scoped(platform_key) do yield end end def platform_field_key(type, field) "GraphQL/#{type.graphql_name}/#{field.graphql_name}" end def platform_authorized_key(type) "GraphQL/Authorize/#{type.graphql_name}" end def platform_resolve_type_key(type) "GraphQL/ResolveType/#{type.graphql_name}" end end end end graphql-2.6.0/lib/graphql/tracing/statsd_trace.rb0000644000004100000410000000250115173430257022052 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/monitor_trace" module GraphQL module Tracing # A tracer for reporting GraphQL-Ruby times to Statsd. # Passing any Statsd client that implements `.time(name) { ... }` # and `.timing(name, ms)` will work. # # @example Installing this tracer # # eg: # # $statsd = Statsd.new 'localhost', 9125 # class MySchema < GraphQL::Schema # use GraphQL::Tracing::StatsdTrace, statsd: $statsd # end StatsdTrace = MonitorTrace.create_module("statsd") module StatsdTrace class StatsdMonitor < MonitorTrace::Monitor def initialize(statsd:, **_rest) @statsd = statsd super end attr_reader :statsd def instrument(keyword, object) @statsd.time(name_for(keyword, object)) do yield end end include MonitorTrace::Monitor::GraphQLPrefixNames class Event < MonitorTrace::Monitor::Event def start @start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) end def finish elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start_time @monitor.statsd.timing(@monitor.name_for(keyword, object), elapsed) end end end end end end graphql-2.6.0/lib/graphql/tracing/prometheus_tracing.rb0000644000004100000410000000402315173430257023275 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/platform_tracing" module GraphQL module Tracing class PrometheusTracing < PlatformTracing DEFAULT_WHITELIST = ['execute_field', 'execute_field_lazy'].freeze DEFAULT_COLLECTOR_TYPE = 'graphql'.freeze self.platform_keys = { 'lex' => "graphql.lex", 'parse' => "graphql.parse", 'validate' => "graphql.validate", 'analyze_query' => "graphql.analyze", 'analyze_multiplex' => "graphql.analyze", 'execute_multiplex' => "graphql.execute", 'execute_query' => "graphql.execute", 'execute_query_lazy' => "graphql.execute", 'execute_field' => "graphql.execute", 'execute_field_lazy' => "graphql.execute" } def initialize(opts = {}) @client = opts[:client] || PrometheusExporter::Client.default @keys_whitelist = opts[:keys_whitelist] || DEFAULT_WHITELIST @collector_type = opts[:collector_type] || DEFAULT_COLLECTOR_TYPE super opts end def platform_trace(platform_key, key, _data, &block) return yield unless @keys_whitelist.include?(key) instrument_execution(platform_key, key, &block) end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "#{type.graphql_name}.authorized" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type" end private def instrument_execution(platform_key, key, &block) start = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC result = block.call duration = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start observe platform_key, key, duration result end def observe(platform_key, key, duration) @client.send_json( type: @collector_type, duration: duration, platform_key: platform_key, key: key ) end end end end graphql-2.6.0/lib/graphql/tracing/perfetto_trace.rb0000644000004100000410000007360015173430257022410 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Tracing # This produces a trace file for inspecting in the [Perfetto Trace Viewer](https://ui.perfetto.dev). # # To get the file, call {#write} on the trace. # # Use "trace modes" to configure this to run on command or on a sample of traffic. # # @example Writing trace output # # result = MySchema.execute(...) # result.query.trace.write(file: "tmp/trace.dump") # # @example Running this instrumenter when `trace: true` is present in the request # # class MySchema < GraphQL::Schema # # Only run this tracer when `context[:trace_mode]` is `:trace` # trace_with GraphQL::Tracing::Perfetto, mode: :trace # end # # # In graphql_controller.rb: # # context[:trace_mode] = params[:trace] ? :trace : nil # result = MySchema.execute(query_str, context: context, variables: variables, ...) # if context[:trace_mode] == :trace # result.trace.write(file: ...) # end # module PerfettoTrace # TODOs: # - Make debug annotations visible on both parts when dataloader is involved PROTOBUF_AVAILABLE = begin require "google/protobuf" true rescue LoadError false end if PROTOBUF_AVAILABLE require "graphql/tracing/perfetto_trace/trace_pb" end def self.included(_trace_class) if !PROTOBUF_AVAILABLE raise "#{self} can't be used because the `google-protobuf` gem wasn't available. Add it to your project, then try again." end end DATALOADER_CATEGORY_IIDS = [5] FIELD_EXECUTE_CATEGORY_IIDS = [6] ACTIVE_SUPPORT_NOTIFICATIONS_CATEGORY_IIDS = [7] AUTHORIZED_CATEGORY_IIDS = [8] RESOLVE_TYPE_CATEGORY_IIDS = [9] DA_OBJECT_IID = 10 DA_RESULT_IID = 11 DA_ARGUMENTS_IID = 12 DA_FETCH_KEYS_IID = 13 DA_STR_VAL_NIL_IID = 14 REVERSE_DEBUG_NAME_LOOKUP = { DA_OBJECT_IID => "object", DA_RESULT_IID => "result", DA_ARGUMENTS_IID => "arguments", DA_FETCH_KEYS_IID => "fetch keys", } DEBUG_INSPECT_CATEGORY_IIDS = [15] DA_DEBUG_INSPECT_CLASS_IID = 16 DEBUG_INSPECT_EVENT_NAME_IID = 17 DA_DEBUG_INSPECT_FOR_IID = 18 # @param active_support_notifications_pattern [String, RegExp, false] A filter for `ActiveSupport::Notifications`, if it's present. Or `false` to skip subscribing. def initialize(active_support_notifications_pattern: nil, save_profile: false, **_rest) super @active_support_notifications_pattern = active_support_notifications_pattern @save_profile = save_profile query = if @multiplex @multiplex.queries.first else @query # could still be nil in some initializations end @detailed_trace = query&.schema&.detailed_trace || DetailedTrace @create_debug_annotations = if (ctx = query&.context).nil? || (ctx_debug = ctx[:detailed_trace_debug]).nil? @detailed_trace.debug? else ctx_debug end @arguments_filter = if (ctx = query&.context) && (dtf = ctx[:detailed_trace_filter]) dtf elsif defined?(ActiveSupport::ParameterFilter) fp = if defined?(Rails) && Rails.application && (app_config = Rails.application.config.filter_parameters).present? && !app_config.empty? app_config elsif ActiveSupport.respond_to?(:filter_parameters) ActiveSupport.filter_parameters else EmptyObjects::EMPTY_ARRAY end ActiveSupport::ParameterFilter.new(fp, mask: ArgumentsFilter::FILTERED) else ArgumentsFilter.new end Fiber[:graphql_flow_stack] = nil @sequence_id = object_id @pid = Process.pid @flow_ids = Hash.new { |h, source_inst| h[source_inst] = [] }.compare_by_identity @new_interned_event_names = {} @interned_event_name_iids = Hash.new { |h, k| new_id = 100 + h.size @new_interned_event_names[k] = new_id h[k] = new_id } @source_name_iids = Hash.new do |h, source_class| h[source_class] = @interned_event_name_iids[source_class.name] end.compare_by_identity @auth_name_iids = Hash.new do |h, graphql_type| h[graphql_type] = @interned_event_name_iids["Authorize: #{graphql_type.graphql_name}"] end.compare_by_identity @resolve_type_name_iids = Hash.new do |h, graphql_type| h[graphql_type] = @interned_event_name_iids["Resolve Type: #{graphql_type.graphql_name}"] end.compare_by_identity @new_interned_da_names = {} @interned_da_name_ids = Hash.new { |h, k| next_id = 100 + h.size @new_interned_da_names[k] = next_id h[k] = next_id } @new_interned_da_string_values = {} @interned_da_string_values = Hash.new do |h, k| new_id = 100 + h.size @new_interned_da_string_values[k] = new_id h[k] = new_id end @class_name_iids = Hash.new do |h, k| h[k] = @interned_da_string_values[k.name] end.compare_by_identity @starting_objects = GC.stat(:total_allocated_objects) @objects_counter_id = :objects_counter.object_id @fibers_counter_id = :fibers_counter.object_id @fields_counter_id = :fields_counter.object_id @counts_objects = [@objects_counter_id] @counts_objects_and_fields = [@objects_counter_id, @fields_counter_id] @counts_fibers = [@fibers_counter_id] @counts_fibers_and_objects = [@fibers_counter_id, @objects_counter_id] @begin_validate = nil @begin_time = nil @packets = [] @packets << TracePacket.new( track_descriptor: TrackDescriptor.new( uuid: tid, name: "Main Thread", child_ordering: TrackDescriptor::ChildTracksOrdering::CHRONOLOGICAL, ), first_packet_on_sequence: true, previous_packet_dropped: true, trusted_packet_sequence_id: @sequence_id, sequence_flags: 3, ) @packets << TracePacket.new( interned_data: InternedData.new( event_categories: [ EventCategory.new(name: "Dataloader", iid: DATALOADER_CATEGORY_IIDS.first), EventCategory.new(name: "Field Execution", iid: FIELD_EXECUTE_CATEGORY_IIDS.first), EventCategory.new(name: "ActiveSupport::Notifications", iid: ACTIVE_SUPPORT_NOTIFICATIONS_CATEGORY_IIDS.first), EventCategory.new(name: "Authorized", iid: AUTHORIZED_CATEGORY_IIDS.first), EventCategory.new(name: "Resolve Type", iid: RESOLVE_TYPE_CATEGORY_IIDS.first), EventCategory.new(name: "Debug Inspect", iid: DEBUG_INSPECT_CATEGORY_IIDS.first), ], debug_annotation_names: [ *REVERSE_DEBUG_NAME_LOOKUP.map { |(iid, name)| DebugAnnotationName.new(name: name, iid: iid) }, DebugAnnotationName.new(name: "inspect instance of", iid: DA_DEBUG_INSPECT_CLASS_IID), DebugAnnotationName.new(name: "inspecting for", iid: DA_DEBUG_INSPECT_FOR_IID) ], debug_annotation_string_values: [ InternedString.new(str: "(nil)", iid: DA_STR_VAL_NIL_IID), ], event_names: [ EventName.new(name: "#{(@detailed_trace.is_a?(Class) ? @detailed_trace : @detailed_trace.class).name}#inspect_object", iid: DEBUG_INSPECT_EVENT_NAME_IID) ], ), trusted_packet_sequence_id: @sequence_id, sequence_flags: 2, ) @main_fiber_id = fid @packets << track_descriptor_packet(tid, fid, "Main Fiber") @packets << track_descriptor_packet(tid, @objects_counter_id, "Allocated Objects", counter: {}) @packets << trace_packet( type: TrackEvent::Type::TYPE_COUNTER, track_uuid: @objects_counter_id, counter_value: count_allocations, ) @packets << track_descriptor_packet(tid, @fibers_counter_id, "Active Fibers", counter: {}) @fibers_count = 0 @packets << trace_packet( type: TrackEvent::Type::TYPE_COUNTER, track_uuid: @fibers_counter_id, counter_value: count_fibers(0), ) @packets << track_descriptor_packet(tid, @fields_counter_id, "Resolved Fields", counter: {}) @fields_count = -1 @packets << trace_packet( type: TrackEvent::Type::TYPE_COUNTER, track_uuid: @fields_counter_id, counter_value: count_fields, ) end def execute_multiplex(multiplex:) if defined?(ActiveSupport::Notifications) && @active_support_notifications_pattern != false subscribe_to_active_support_notifications(@active_support_notifications_pattern) end @operation_name = multiplex.queries.map { |q| q.selected_operation_name || "anonymous" }.join(",") @begin_time = Time.now @packets << trace_packet( type: TrackEvent::Type::TYPE_SLICE_BEGIN, track_uuid: fid, name: "Multiplex" ) { [ payload_to_debug("query_string", multiplex.queries.map(&:sanitized_query_string).join("\n\n")) ] } result = super @packets << trace_packet( type: TrackEvent::Type::TYPE_SLICE_END, track_uuid: fid, ) result ensure unsubscribe_from_active_support_notifications if @save_profile begin_ts = (@begin_time.to_f * 1000).round end_ts = (Time.now.to_f * 1000).round duration_ms = end_ts - begin_ts multiplex.schema.detailed_trace.save_trace(@operation_name, duration_ms, begin_ts, Trace.encode(Trace.new(packet: @packets))) end end def begin_execute_field(field, object, arguments, query) packet = trace_packet( type: TrackEvent::Type::TYPE_SLICE_BEGIN, track_uuid: fid, name: query.context.current_path&.join(".") || field.path, category_iids: FIELD_EXECUTE_CATEGORY_IIDS, extra_counter_track_uuids: @counts_objects, extra_counter_values: [count_allocations], ) @packets << packet fiber_flow_stack << packet super end def end_execute_field(field, object, arguments, query, app_result) end_ts = ts start_field = fiber_flow_stack.pop if @create_debug_annotations start_field.track_event = dup_with(start_field.track_event,{ debug_annotations: [ payload_to_debug(nil, (object.is_a?(GraphQL::Schema::Object) ? object.object : object), iid: DA_OBJECT_IID, intern_value: true), payload_to_debug(nil, arguments, iid: DA_ARGUMENTS_IID), payload_to_debug(nil, app_result, iid: DA_RESULT_IID, intern_value: true) ] }) end @packets << trace_packet( timestamp: end_ts, type: TrackEvent::Type::TYPE_SLICE_END, track_uuid: fid, extra_counter_track_uuids: @counts_objects_and_fields, extra_counter_values: [count_allocations, count_fields], ) super end def begin_analyze_multiplex(m, analyzers) @packets << trace_packet( type: TrackEvent::Type::TYPE_SLICE_BEGIN, track_uuid: fid, extra_counter_track_uuids: @counts_objects, extra_counter_values: [count_allocations], name: "Analysis") { [ payload_to_debug("analyzers_count", analyzers.size), payload_to_debug("analyzers", analyzers), ] } super end def end_analyze_multiplex(m, analyzers) end_ts = ts @packets << trace_packet( timestamp: end_ts, type: TrackEvent::Type::TYPE_SLICE_END, track_uuid: fid, extra_counter_track_uuids: @counts_objects, extra_counter_values: [count_allocations], ) super end def parse(query_string:) @packets << trace_packet( type: TrackEvent::Type::TYPE_SLICE_BEGIN, track_uuid: fid, extra_counter_track_uuids: @counts_objects, extra_counter_values: [count_allocations], name: "Parse" ) result = super end_ts = ts @packets << trace_packet( timestamp: end_ts, type: TrackEvent::Type::TYPE_SLICE_END, track_uuid: fid, extra_counter_track_uuids: @counts_objects, extra_counter_values: [count_allocations], ) result end def begin_validate(query, validate) @begin_validate = trace_packet( type: TrackEvent::Type::TYPE_SLICE_BEGIN, track_uuid: fid, extra_counter_track_uuids: @counts_objects, extra_counter_values: [count_allocations], name: "Validate") { [payload_to_debug("validate?", validate)] } @packets << @begin_validate super end def end_validate(query, validate, validation_errors) end_ts = ts @packets << trace_packet( timestamp: end_ts, type: TrackEvent::Type::TYPE_SLICE_END, track_uuid: fid, extra_counter_track_uuids: @counts_objects, extra_counter_values: [count_allocations], ) if @create_debug_annotations new_bv_track_event = dup_with( @begin_validate.track_event, { debug_annotations: [ @begin_validate.track_event.debug_annotations.first, payload_to_debug("valid?", validation_errors.empty?) ] } ) @begin_validate.track_event = new_bv_track_event end super end def dataloader_spawn_execution_fiber(jobs) @packets << trace_packet( type: TrackEvent::Type::TYPE_INSTANT, track_uuid: fid, name: "Create Execution Fiber", category_iids: DATALOADER_CATEGORY_IIDS, extra_counter_track_uuids: @counts_fibers_and_objects, extra_counter_values: [count_fibers(1), count_allocations] ) @packets << track_descriptor_packet(@did, fid, "Exec Fiber ##{fid}") super end def dataloader_spawn_source_fiber(pending_sources) @packets << trace_packet( type: TrackEvent::Type::TYPE_INSTANT, track_uuid: fid, name: "Create Source Fiber", category_iids: DATALOADER_CATEGORY_IIDS, extra_counter_track_uuids: @counts_fibers_and_objects, extra_counter_values: [count_fibers(1), count_allocations] ) @packets << track_descriptor_packet(@did, fid, "Source Fiber ##{fid}") super end def dataloader_fiber_yield(source) ls = fiber_flow_stack.last if (flow_id = ls.track_event.flow_ids.first) # got it else flow_id = ls.track_event.name.object_id ls.track_event = dup_with(ls.track_event, {flow_ids: [flow_id] }, delete_counters: true) end @flow_ids[source] << flow_id @packets << trace_packet( type: TrackEvent::Type::TYPE_SLICE_END, track_uuid: fid, ) @packets << trace_packet( type: TrackEvent::Type::TYPE_INSTANT, track_uuid: fid, name: "Fiber Yield", category_iids: DATALOADER_CATEGORY_IIDS, ) super end def dataloader_fiber_resume(source) @packets << trace_packet( type: TrackEvent::Type::TYPE_INSTANT, track_uuid: fid, name: "Fiber Resume", category_iids: DATALOADER_CATEGORY_IIDS, ) ls = fiber_flow_stack.pop @packets << packet = TracePacket.new( timestamp: ts, track_event: dup_with(ls.track_event, { type: TrackEvent::Type::TYPE_SLICE_BEGIN }), trusted_packet_sequence_id: @sequence_id, ) fiber_flow_stack << packet super end def dataloader_fiber_exit @packets << trace_packet( type: TrackEvent::Type::TYPE_INSTANT, track_uuid: fid, name: "Fiber Exit", category_iids: DATALOADER_CATEGORY_IIDS, extra_counter_track_uuids: @counts_fibers, extra_counter_values: [count_fibers(-1)], ) super end def begin_dataloader(dl) @packets << trace_packet( type: TrackEvent::Type::TYPE_COUNTER, track_uuid: @fibers_counter_id, counter_value: count_fibers(1), ) @did = fid @packets << track_descriptor_packet(@main_fiber_id, @did, "Dataloader Fiber ##{@did}") super end def end_dataloader(dl) @packets << trace_packet( type: TrackEvent::Type::TYPE_COUNTER, track_uuid: @fibers_counter_id, counter_value: count_fibers(-1), ) super end def begin_dataloader_source(source) fds = @flow_ids[source] fds_copy = fds.dup fds.clear packet = trace_packet( type: TrackEvent::Type::TYPE_SLICE_BEGIN, track_uuid: fid, name_iid: @source_name_iids[source.class], category_iids: DATALOADER_CATEGORY_IIDS, flow_ids: fds_copy, extra_counter_track_uuids: @counts_objects, extra_counter_values: [count_allocations]) { [ payload_to_debug(nil, source.pending.values, iid: DA_FETCH_KEYS_IID, intern_value: true), *(source.instance_variables - [:@pending, :@fetching, :@results, :@dataloader]).map { |iv| payload_to_debug(iv.to_s, source.instance_variable_get(iv), intern_value: true) } ] } @packets << packet fiber_flow_stack << packet super end def end_dataloader_source(source) end_ts = ts @packets << trace_packet( timestamp: end_ts, type: TrackEvent::Type::TYPE_SLICE_END, track_uuid: fid, extra_counter_track_uuids: @counts_objects, extra_counter_values: [count_allocations], ) fiber_flow_stack.pop super end def begin_authorized(type, obj, ctx) packet = trace_packet( type: TrackEvent::Type::TYPE_SLICE_BEGIN, track_uuid: fid, category_iids: AUTHORIZED_CATEGORY_IIDS, extra_counter_track_uuids: @counts_objects, extra_counter_values: [count_allocations], name_iid: @auth_name_iids[type], ) @packets << packet fiber_flow_stack << packet super end def end_authorized(type, obj, ctx, is_authorized) end_ts = ts @packets << trace_packet( timestamp: end_ts, type: TrackEvent::Type::TYPE_SLICE_END, track_uuid: fid, extra_counter_track_uuids: @counts_objects, extra_counter_values: [count_allocations], ) beg_auth = fiber_flow_stack.pop if @create_debug_annotations beg_auth.track_event = dup_with(beg_auth.track_event, { debug_annotations: [payload_to_debug("authorized?", is_authorized)] }) end super end def begin_resolve_type(type, value, context) packet = trace_packet( type: TrackEvent::Type::TYPE_SLICE_BEGIN, track_uuid: fid, category_iids: RESOLVE_TYPE_CATEGORY_IIDS, extra_counter_track_uuids: @counts_objects, extra_counter_values: [count_allocations], name_iid: @resolve_type_name_iids[type], ) @packets << packet fiber_flow_stack << packet super end def end_resolve_type(type, value, context, resolved_type) end_ts = ts @packets << trace_packet( timestamp: end_ts, type: TrackEvent::Type::TYPE_SLICE_END, track_uuid: fid, extra_counter_track_uuids: @counts_objects, extra_counter_values: [count_allocations], ) rt_begin = fiber_flow_stack.pop if @create_debug_annotations rt_begin.track_event = dup_with(rt_begin.track_event, { debug_annotations: [payload_to_debug("resolved_type", resolved_type, intern_value: true)] }) end super end # Dump protobuf output in the specified file. # @param file [String] path to a file in a directory that already exists # @param debug_json [Boolean] True to print JSON instead of binary # @return [nil, String, Hash] If `file` was given, `nil`. If `file` was `nil`, a Hash if `debug_json: true`, else binary data. def write(file:, debug_json: false) trace = Trace.new( packet: @packets, ) data = if debug_json small_json = Trace.encode_json(trace) JSON.pretty_generate(JSON.parse(small_json)) else Trace.encode(trace) end if file File.write(file, data, mode: 'wb') nil else data end end private def ts Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) end def tid Thread.current.object_id end def fid Fiber.current.object_id end class ArgumentsFilter # From Rails defaults # https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt#L6-L8 SENSITIVE_KEY = /passw|token|crypt|email|_key|salt|certificate|secret|ssn|cvv|cvc|otp/i FILTERED = "[FILTERED]" def filter_param(key, value) if (key.is_a?(String) && SENSITIVE_KEY.match?(key)) || (key.is_a?(Symbol) && SENSITIVE_KEY.match?(key.name)) FILTERED else value end end end def debug_annotation(iid, value_key, value) if iid DebugAnnotation.new(name_iid: iid, value_key => value) else DebugAnnotation.new(value_key => value) end end def payload_to_debug(k, v, iid: nil, intern_value: false) if iid.nil? iid = @interned_da_name_ids[k] end case v when String if intern_value v = @interned_da_string_values[v] debug_annotation(iid, :string_value_iid, v) else debug_annotation(iid, :string_value, v) end when Float debug_annotation(iid, :double_value, v) when Integer debug_annotation(iid, :int_value, v) when true, false debug_annotation(iid, :bool_value, v) when nil if iid DebugAnnotation.new(name_iid: iid, string_value_iid: DA_STR_VAL_NIL_IID) else DebugAnnotation.new(name: k, string_value_iid: DA_STR_VAL_NIL_IID) end when Module if intern_value val_iid = @class_name_iids[v] debug_annotation(iid, :string_value_iid, val_iid) else debug_annotation(iid, :string_value, v.name) end when Symbol debug_annotation(iid, :string_value, v.inspect) when Array debug_annotation(iid, :array_values, v.each_with_index.map { |v2, idx| payload_to_debug((k ? "#{k}.#{idx}" : String(idx)), v2, intern_value: intern_value) }.compact) when Hash debug_v = v.map { |k2, v2| debug_k = case k2 when String k2 when Symbol k2.name else String(k2) end filtered_v2 = @arguments_filter.filter_param(debug_k, v2) payload_to_debug(debug_k, filtered_v2, intern_value: intern_value) } debug_v.compact! debug_annotation(iid, :dict_entries, debug_v) when GraphQL::Schema::InputObject payload_to_debug(k, v.to_h, iid: iid, intern_value: intern_value) else class_name_iid = @interned_da_string_values[(v.class.name || "(anonymous)")] da = [ debug_annotation(DA_DEBUG_INSPECT_CLASS_IID, :string_value_iid, class_name_iid), ] if k k_str_value_iid = @interned_da_string_values[k] da << debug_annotation(DA_DEBUG_INSPECT_FOR_IID, :string_value_iid, k_str_value_iid) elsif iid k = REVERSE_DEBUG_NAME_LOOKUP[iid] || @interned_da_name_ids.key(iid) if k.nil? da << debug_annotation(DA_DEBUG_INSPECT_FOR_IID, :string_value_iid, DA_STR_VAL_NIL_IID) else k_str_value_iid = @interned_da_string_values[k] da << debug_annotation(DA_DEBUG_INSPECT_FOR_IID, :string_value_iid, k_str_value_iid) end end @packets << trace_packet( type: TrackEvent::Type::TYPE_SLICE_BEGIN, track_uuid: fid, name_iid: DEBUG_INSPECT_EVENT_NAME_IID, category_iids: DEBUG_INSPECT_CATEGORY_IIDS, extra_counter_track_uuids: @counts_objects, extra_counter_values: [count_allocations], debug_annotations: da, ) debug_str = @detailed_trace.inspect_object(v) @packets << trace_packet( type: TrackEvent::Type::TYPE_SLICE_END, track_uuid: fid, ) if intern_value str_iid = @interned_da_string_values[debug_str] debug_annotation(iid, :string_value_iid, str_iid) else debug_annotation(iid, :string_value, debug_str) end end end def count_allocations GC.stat(:total_allocated_objects) - @starting_objects end def count_fibers(diff) @fibers_count += diff end def count_fields @fields_count += 1 end def dup_with(message, attrs, delete_counters: false) new_attrs = message.to_h if delete_counters new_attrs.delete(:extra_counter_track_uuids) new_attrs.delete(:extra_counter_values) end new_attrs.merge!(attrs) message.class.new(**new_attrs) end def fiber_flow_stack Fiber[:graphql_flow_stack] ||= [] end def trace_packet(timestamp: ts, **event_attrs) if @create_debug_annotations && block_given? event_attrs[:debug_annotations] = yield end track_event = TrackEvent.new(event_attrs) TracePacket.new( timestamp: timestamp, track_event: track_event, trusted_packet_sequence_id: @sequence_id, sequence_flags: 2, interned_data: new_interned_data ) end def new_interned_data if !@new_interned_da_names.empty? da_names = @new_interned_da_names.map { |(name, iid)| DebugAnnotationName.new(iid: iid, name: name) } @new_interned_da_names.clear end if !@new_interned_event_names.empty? ev_names = @new_interned_event_names.map { |(name, iid)| EventName.new(iid: iid, name: name) } @new_interned_event_names.clear end if !@new_interned_da_string_values.empty? str_vals = @new_interned_da_string_values.map { |name, iid| InternedString.new(iid: iid, str: name.b) } @new_interned_da_string_values.clear end if ev_names || da_names || str_vals InternedData.new( event_names: ev_names, debug_annotation_names: da_names, debug_annotation_string_values: str_vals, ) else nil end end def track_descriptor_packet(parent_uuid, uuid, name, counter: nil) td = if counter TrackDescriptor.new( parent_uuid: parent_uuid, uuid: uuid, name: name, counter: counter ) else TrackDescriptor.new( parent_uuid: parent_uuid, uuid: uuid, name: name, child_ordering: TrackDescriptor::ChildTracksOrdering::CHRONOLOGICAL, ) end TracePacket.new( track_descriptor: td, trusted_packet_sequence_id: @sequence_id, sequence_flags: 2, ) end def unsubscribe_from_active_support_notifications if defined?(@as_subscriber) ActiveSupport::Notifications.unsubscribe(@as_subscriber) end end def subscribe_to_active_support_notifications(pattern) @as_subscriber = ActiveSupport::Notifications.monotonic_subscribe(pattern) do |name, start, finish, id, payload| metadata = @create_debug_annotations ? payload.map { |k, v| payload_to_debug(String(k), v, intern_value: true) } : nil metadata&.compact! te = if metadata.nil? || metadata.empty? TrackEvent.new( type: TrackEvent::Type::TYPE_SLICE_BEGIN, track_uuid: fid, category_iids: ACTIVE_SUPPORT_NOTIFICATIONS_CATEGORY_IIDS, name: name, ) else TrackEvent.new( type: TrackEvent::Type::TYPE_SLICE_BEGIN, track_uuid: fid, name: name, category_iids: ACTIVE_SUPPORT_NOTIFICATIONS_CATEGORY_IIDS, debug_annotations: metadata, ) end @packets << TracePacket.new( timestamp: (start * 1_000_000_000).to_i, track_event: te, trusted_packet_sequence_id: @sequence_id, sequence_flags: 2, interned_data: new_interned_data ) @packets << TracePacket.new( timestamp: (finish * 1_000_000_000).to_i, track_event: TrackEvent.new( type: TrackEvent::Type::TYPE_SLICE_END, track_uuid: fid, name: name, extra_counter_track_uuids: @counts_objects, extra_counter_values: [count_allocations] ), trusted_packet_sequence_id: @sequence_id, sequence_flags: 2, ) end end end end end graphql-2.6.0/lib/graphql/tracing/notifications_trace.rb0000644000004100000410000001241615173430257023427 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Tracing # This implementation forwards events to a notification handler # (i.e. ActiveSupport::Notifications or Dry::Monitor::Notifications) with a `graphql` suffix. # # @see ActiveSupportNotificationsTrace ActiveSupport::Notifications integration module NotificationsTrace # @api private class Adapter def instrument(keyword, payload, &block) raise "Implement #{self.class}#instrument to measure the block" end def start_event(keyword, payload) ev = self.class::Event.new(keyword, payload) ev.start ev end class Event def initialize(name, payload) @name = name @payload = payload end attr_reader :name, :payload def start raise "Implement #{self.class}#start to begin a new event (#{inspect})" end def finish raise "Implement #{self.class}#finish to end this event (#{inspect})" end end end # @api private class DryMonitorAdapter < Adapter def instrument(...) Dry::Monitor.instrument(...) end class Event < Adapter::Event def start Dry::Monitor.start(@name, @payload) end def finish Dry::Monitor.stop(@name, @payload) end end end # @api private class ActiveSupportNotificationsAdapter < Adapter def instrument(...) ActiveSupport::Notifications.instrument(...) end class Event < Adapter::Event def start @asn_event = ActiveSupport::Notifications.instrumenter.new_event(@name, @payload) @asn_event.start! end def finish @asn_event.finish! ActiveSupport::Notifications.publish_event(@asn_event) end end end # @param engine [Class] The notifications engine to use, eg `Dry::Monitor` or `ActiveSupport::Notifications` def initialize(engine:, **rest) adapter = if defined?(Dry::Monitor) && engine == Dry::Monitor DryMonitoringAdapter elsif defined?(ActiveSupport::Notifications) && engine == ActiveSupport::Notifications ActiveSupportNotificationsAdapter else engine end @notifications = adapter.new super end def parse(**payload) @notifications.instrument("parse.graphql", payload) do super end end def lex(**payload) @notifications.instrument("lex.graphql", payload) do super end end def validate(**payload) @notifications.instrument("validate.graphql", payload) do super end end def begin_analyze_multiplex(multiplex, analyzers) begin_notifications_event("analyze.graphql", {multiplex: multiplex, analyzers: analyzers}) super end def end_analyze_multiplex(_multiplex, _analyzers) finish_notifications_event super end def execute_multiplex(**payload) @notifications.instrument("execute.graphql", payload) do super end end def begin_execute_field(field, object, arguments, query) begin_notifications_event("execute_field.graphql", {field: field, object: object, arguments: arguments, query: query}) super end def end_execute_field(_field, _object, _arguments, _query, _result) finish_notifications_event super end def dataloader_fiber_yield(source) Fiber[PREVIOUS_EV_KEY] = finish_notifications_event super end def dataloader_fiber_resume(source) prev_ev = Fiber[PREVIOUS_EV_KEY] if prev_ev begin_notifications_event(prev_ev.name, prev_ev.payload) end super end def begin_authorized(type, object, context) begin_notifications_event("authorized.graphql", {type: type, object: object, context: context}) super end def end_authorized(type, object, context, result) finish_notifications_event super end def begin_resolve_type(type, object, context) begin_notifications_event("resolve_type.graphql", {type: type, object: object, context: context}) super end def end_resolve_type(type, object, context, resolved_type) finish_notifications_event super end def begin_dataloader_source(source) begin_notifications_event("dataloader_source.graphql", { source: source }) super end def end_dataloader_source(source) finish_notifications_event super end CURRENT_EV_KEY = :__notifications_graphql_trace_event PREVIOUS_EV_KEY = :__notifications_graphql_trace_previous_event private def begin_notifications_event(name, payload) Fiber[CURRENT_EV_KEY] = @notifications.start_event(name, payload) end def finish_notifications_event if ev = Fiber[CURRENT_EV_KEY] ev.finish # Use `false` to prevent grabbing an event from a parent fiber Fiber[CURRENT_EV_KEY] = false ev end end end end end graphql-2.6.0/lib/graphql/tracing/notifications_tracing.rb0000644000004100000410000000421215173430257023753 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/platform_tracing" module GraphQL module Tracing # This implementation forwards events to a notification handler (i.e. # ActiveSupport::Notifications or Dry::Monitor::Notifications) # with a `graphql` suffix. # # @see KEYS for event names class NotificationsTracing # A cache of frequently-used keys to avoid needless string allocations KEYS = { "lex" => "lex.graphql", "parse" => "parse.graphql", "validate" => "validate.graphql", "analyze_multiplex" => "analyze_multiplex.graphql", "analyze_query" => "analyze_query.graphql", "execute_query" => "execute_query.graphql", "execute_query_lazy" => "execute_query_lazy.graphql", "execute_field" => "execute_field.graphql", "execute_field_lazy" => "execute_field_lazy.graphql", "authorized" => "authorized.graphql", "authorized_lazy" => "authorized_lazy.graphql", "resolve_type" => "resolve_type.graphql", "resolve_type_lazy" => "resolve_type.graphql", } MAX_KEYS_SIZE = 100 # Initialize a new NotificationsTracing instance # # @param [Object] notifications_engine The notifications engine to use def initialize(notifications_engine) @notifications_engine = notifications_engine end # Sends a GraphQL tracing event to the notification handler # # @example # . notifications_engine = Dry::Monitor::Notifications.new(:graphql) # . tracer = GraphQL::Tracing::NotificationsTracing.new(notifications_engine) # . tracer.trace("lex") { ... } # # @param [string] key The key for the event # @param [Hash] metadata The metadata for the event # @yield The block to execute for the event def trace(key, metadata, &blk) prefixed_key = KEYS[key] || "#{key}.graphql" # Cache the new keys while making sure not to induce a memory leak if KEYS.size < MAX_KEYS_SIZE KEYS[key] ||= prefixed_key end @notifications_engine.instrument(prefixed_key, metadata, &blk) end end end end graphql-2.6.0/lib/graphql/tracing/active_support_notifications_trace.rb0000644000004100000410000000146415173430257026557 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/notifications_trace" module GraphQL module Tracing # This implementation forwards events to ActiveSupport::Notifications with a `graphql` suffix. # # @example Sending execution events to ActiveSupport::Notifications # class MySchema < GraphQL::Schema # trace_with(GraphQL::Tracing::ActiveSupportNotificationsTrace) # end # # @example Subscribing to GraphQL events with ActiveSupport::Notifications # ActiveSupport::Notifications.subscribe(/graphql/) do |event| # pp event.name # pp event.payload # end # module ActiveSupportNotificationsTrace include NotificationsTrace def initialize(engine: ActiveSupport::Notifications, **rest) super end end end end graphql-2.6.0/lib/graphql/tracing/statsd_tracing.rb0000644000004100000410000000224615173430257022411 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/platform_tracing" module GraphQL module Tracing class StatsdTracing < PlatformTracing self.platform_keys = { 'lex' => "graphql.lex", 'parse' => "graphql.parse", 'validate' => "graphql.validate", 'analyze_query' => "graphql.analyze_query", 'analyze_multiplex' => "graphql.analyze_multiplex", 'execute_multiplex' => "graphql.execute_multiplex", 'execute_query' => "graphql.execute_query", 'execute_query_lazy' => "graphql.execute_query_lazy", } # @param statsd [Object] A statsd client def initialize(statsd:, **rest) @statsd = statsd super(**rest) end def platform_trace(platform_key, key, data) @statsd.time(platform_key) do yield end end def platform_field_key(type, field) "graphql.#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "graphql.authorized.#{type.graphql_name}" end def platform_resolve_type_key(type) "graphql.resolve_type.#{type.graphql_name}" end end end end graphql-2.6.0/lib/graphql/tracing/data_dog_tracing.rb0000644000004100000410000000570215173430257022651 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/platform_tracing" module GraphQL module Tracing class DataDogTracing < PlatformTracing self.platform_keys = { 'lex' => 'lex.graphql', 'parse' => 'parse.graphql', 'validate' => 'validate.graphql', 'analyze_query' => 'analyze.graphql', 'analyze_multiplex' => 'analyze.graphql', 'execute_multiplex' => 'execute.graphql', 'execute_query' => 'execute.graphql', 'execute_query_lazy' => 'execute.graphql', } def platform_trace(platform_key, key, data) tracer.trace(platform_key, service: options[:service], type: 'custom') do |span| span.set_tag('component', 'graphql') span.set_tag('operation', key) if key == 'execute_multiplex' operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ') resource = if operations.empty? first_query = data[:multiplex].queries.first fallback_transaction_name(first_query && first_query.context) else operations end span.resource = resource if resource # [Deprecated] will be removed in the future span.set_metric('_dd1.sr.eausr', analytics_sample_rate) if analytics_enabled? end if key == 'execute_query' span.set_tag(:selected_operation_name, data[:query].selected_operation_name) span.set_tag(:selected_operation_type, data[:query].selected_operation.operation_type) span.set_tag(:query_string, data[:query].query_string) end prepare_span(key, data, span) yield end end # Implement this method in a subclass to apply custom tags to datadog spans # @param key [String] The event being traced # @param data [Hash] The runtime data for this event (@see GraphQL::Tracing for keys for each event) # @param span [Datadog::Tracing::SpanOperation] The datadog span for this event def prepare_span(key, data, span) end def tracer default_tracer = defined?(Datadog::Tracing) ? Datadog::Tracing : Datadog.tracer # [Deprecated] options[:tracer] will be removed in the future options.fetch(:tracer, default_tracer) end def analytics_enabled? # [Deprecated] options[:analytics_enabled] will be removed in the future options.fetch(:analytics_enabled, false) end def analytics_sample_rate # [Deprecated] options[:analytics_sample_rate] will be removed in the future options.fetch(:analytics_sample_rate, 1.0) end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "#{type.graphql_name}.authorized" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type" end end end end graphql-2.6.0/lib/graphql/tracing/sentry_trace.rb0000644000004100000410000000514615173430257022104 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/monitor_trace" module GraphQL module Tracing # A tracer for reporting GraphQL-Ruby times to Sentry. # # @example Installing the tracer # class MySchema < GraphQL::Schema # trace_with GraphQL::Tracing::SentryTrace # end # @see MonitorTrace Configuration Options in the parent module SentryTrace = MonitorTrace.create_module("sentry") module SentryTrace class SentryMonitor < MonitorTrace::Monitor def instrument(keyword, object) return yield unless Sentry.initialized? platform_key = name_for(keyword, object) Sentry.with_child_span(op: platform_key, start_timestamp: Sentry.utc_now.to_f) do |span| result = yield return result unless span span.finish if keyword == :execute queries = object.queries operation_names = queries.map{|q| operation_name(q) } span.set_description(operation_names.join(", ")) if queries.size == 1 query = queries.first set_this_txn_name = query.context[:set_sentry_transaction_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name) Sentry.configure_scope do |scope| scope.set_transaction_name(transaction_name(query)) end end span.set_data('graphql.document', query.query_string) if query.selected_operation_name span.set_data('graphql.operation.name', query.selected_operation_name) end if query.selected_operation span.set_data('graphql.operation.type', query.selected_operation.operation_type) end end end result end end include MonitorTrace::Monitor::GraphQLPrefixNames private def operation_name(query) selected_op = query.selected_operation if selected_op [selected_op.operation_type, selected_op.name].compact.join(' ') else 'GraphQL Operation' end end class Event < MonitorTrace::Monitor::Event def start if Sentry.initialized? && (@span = Sentry.get_current_scope.get_span) span_name = @monitor.name_for(@keyword, @object) @span.start_child(op: span_name) end end def finish @span&.finish end end end end end end graphql-2.6.0/lib/graphql/tracing/call_legacy_tracers.rb0000644000004100000410000000531415173430257023361 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Tracing # This trace class calls legacy-style tracer with payload hashes. # New-style `trace_with` modules significantly reduce the overhead of tracing, # but that advantage is lost when legacy-style tracers are also used (since the payload hashes are still constructed). module CallLegacyTracers def lex(query_string:) (@multiplex || @query).trace("lex", { query_string: query_string }) { super } end def parse(query_string:) (@multiplex || @query).trace("parse", { query_string: query_string }) { super } end def validate(query:, validate:) query.trace("validate", { validate: validate, query: query }) { super } end def analyze_multiplex(multiplex:) multiplex.trace("analyze_multiplex", { multiplex: multiplex }) { super } end def analyze_query(query:) query.trace("analyze_query", { query: query }) { super } end def execute_multiplex(multiplex:) multiplex.trace("execute_multiplex", { multiplex: multiplex }) { super } end def execute_query(query:) query.trace("execute_query", { query: query }) { super } end def execute_query_lazy(query:, multiplex:) multiplex.trace("execute_query_lazy", { multiplex: multiplex, query: query }) { super } end def execute_field(field:, query:, ast_node:, arguments:, object:) query.trace("execute_field", { field: field, query: query, ast_node: ast_node, arguments: arguments, object: object, owner: field.owner, path: query.context[:current_path] }) { super } end def execute_field_lazy(field:, query:, ast_node:, arguments:, object:) query.trace("execute_field_lazy", { field: field, query: query, ast_node: ast_node, arguments: arguments, object: object, owner: field.owner, path: query.context[:current_path] }) { super } end def authorized(query:, type:, object:) query.trace("authorized", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super } end def authorized_lazy(query:, type:, object:) query.trace("authorized_lazy", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super } end def resolve_type(query:, type:, object:) query.trace("resolve_type", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super } end def resolve_type_lazy(query:, type:, object:) query.trace("resolve_type_lazy", { context: query.context, type: type, object: object, path: query.context[:current_path] }) { super } end end end end graphql-2.6.0/lib/graphql/tracing/detailed_trace/0000755000004100000410000000000015173430257022000 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/tracing/detailed_trace/redis_backend.rb0000644000004100000410000000365515173430257025113 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Tracing class DetailedTrace class RedisBackend KEY_PREFIX = "gql:trace:" def initialize(redis:, limit: nil) @redis = redis @key = KEY_PREFIX + "traces" @remrangebyrank_limit = limit ? -limit - 1 : nil end def traces(last:, before:) before = case before when Numeric "(#{before}" when nil "+inf" end str_pairs = @redis.zrange(@key, before, 0, byscore: true, rev: true, limit: [0, last || 100], withscores: true) str_pairs.map do |(str_data, score)| entry_to_trace(score, str_data) end end def delete_trace(id) @redis.zremrangebyscore(@key, id, id) nil end def delete_all_traces @redis.del(@key) end def find_trace(id) str_data = @redis.zrange(@key, id, id, byscore: true).first if str_data.nil? nil else entry_to_trace(id, str_data) end end def save_trace(operation_name, duration_ms, begin_ms, trace_data) id = begin_ms data = JSON.dump({ "o" => operation_name, "d" => duration_ms, "b" => begin_ms, "t" => Base64.encode64(trace_data) }) @redis.pipelined do |pipeline| pipeline.zadd(@key, id, data) if @remrangebyrank_limit pipeline.zremrangebyrank(@key, 0, @remrangebyrank_limit) end end id end private def entry_to_trace(id, json_str) data = JSON.parse(json_str) StoredTrace.new( id: id, operation_name: data["o"], duration_ms: data["d"].to_f, begin_ms: data["b"].to_i, trace_data: Base64.decode64(data["t"]), ) end end end end end graphql-2.6.0/lib/graphql/tracing/detailed_trace/memory_backend.rb0000644000004100000410000000276215173430257025313 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Tracing class DetailedTrace # An in-memory trace storage backend. Suitable for testing and development only. # It won't work for multi-process deployments and everything is erased when the app is restarted. class MemoryBackend def initialize(limit: nil) @limit = limit @traces = {} @next_id = 0 end def traces(last:, before:) page = [] @traces.values.reverse_each do |trace| if page.size == last break elsif before.nil? || trace.begin_ms < before page << trace end end page end def find_trace(id) @traces[id] end def delete_trace(id) @traces.delete(id.to_i) nil end def delete_all_traces @traces.clear nil end def save_trace(operation_name, duration, begin_ms, trace_data) id = @next_id @next_id += 1 @traces[id] = DetailedTrace::StoredTrace.new( id: id, operation_name: operation_name, duration_ms: duration, begin_ms: begin_ms, trace_data: trace_data ) if @limit && @traces.size > @limit del_keys = @traces.keys[0...-@limit] del_keys.each { |k| @traces.delete(k) } end id end end end end end graphql-2.6.0/lib/graphql/tracing/detailed_trace/active_record_backend.rb0000644000004100000410000000350415173430257026607 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Tracing class DetailedTrace class ActiveRecordBackend class GraphqlDetailedTrace < ActiveRecord::Base end def initialize(limit: nil, model_class: nil) @limit = limit @model_class = model_class || GraphqlDetailedTrace end def traces(last:, before:) gdts = @model_class.all.order("begin_ms DESC") if before gdts = gdts.where("begin_ms < ?", before) end if last gdts = gdts.limit(last) end gdts.map { |gdt| record_to_stored_trace(gdt) } end def delete_trace(id) @model_class.where(id: id).destroy_all nil end def delete_all_traces @model_class.all.destroy_all end def find_trace(id) gdt = @model_class.find_by(id: id) if gdt record_to_stored_trace(gdt) else nil end end def save_trace(operation_name, duration_ms, begin_ms, trace_data) gdt = @model_class.create!( begin_ms: begin_ms, operation_name: operation_name, duration_ms: duration_ms, trace_data: trace_data, ) if @limit @model_class .where("id NOT IN(SELECT id FROM graphql_detailed_traces ORDER BY begin_ms DESC LIMIT ?)", @limit) .delete_all end gdt.id end private def record_to_stored_trace(gdt) StoredTrace.new( id: gdt.id, begin_ms: gdt.begin_ms, operation_name: gdt.operation_name, duration_ms: gdt.duration_ms, trace_data: gdt.trace_data ) end end end end end graphql-2.6.0/lib/graphql/tracing/scout_tracing.rb0000644000004100000410000000354515173430257022247 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/platform_tracing" module GraphQL module Tracing class ScoutTracing < PlatformTracing INSTRUMENT_OPTS = { scope: true } self.platform_keys = { "lex" => "lex.graphql", "parse" => "parse.graphql", "validate" => "validate.graphql", "analyze_query" => "analyze.graphql", "analyze_multiplex" => "analyze.graphql", "execute_multiplex" => "execute.graphql", "execute_query" => "execute.graphql", "execute_query_lazy" => "execute.graphql", } # @param set_transaction_name [Boolean] If true, the GraphQL operation name will be used as the transaction name. # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing. # It can also be specified per-query with `context[:set_scout_transaction_name]`. def initialize(options = {}) self.class.include ScoutApm::Tracer @set_transaction_name = options.fetch(:set_transaction_name, false) super(options) end def platform_trace(platform_key, key, data) if key == "execute_query" set_this_txn_name = data[:query].context[:set_scout_transaction_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name) ScoutApm::Transaction.rename(transaction_name(data[:query])) end end self.class.instrument("GraphQL", platform_key, INSTRUMENT_OPTS) do yield end end def platform_field_key(type, field) "#{type.graphql_name}.#{field.graphql_name}" end def platform_authorized_key(type) "#{type.graphql_name}.authorized" end def platform_resolve_type_key(type) "#{type.graphql_name}.resolve_type" end end end end graphql-2.6.0/lib/graphql/tracing/legacy_trace.rb0000644000004100000410000000033615173430257022020 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/trace" require "graphql/tracing/call_legacy_tracers" module GraphQL module Tracing class LegacyTrace < Trace include CallLegacyTracers end end end graphql-2.6.0/lib/graphql/tracing/detailed_trace.rb0000644000004100000410000001340515173430257022330 0ustar www-datawww-data# frozen_string_literal: true if defined?(ActiveRecord) require "graphql/tracing/detailed_trace/active_record_backend" end require "graphql/tracing/detailed_trace/memory_backend" require "graphql/tracing/detailed_trace/redis_backend" module GraphQL module Tracing # `DetailedTrace` can make detailed profiles for a subset of production traffic. Install it in Rails with `rails generate graphql:detailed_trace`. # # When `MySchema.detailed_trace?(query)` returns `true`, a profiler-specific `trace_mode: ...` will be used for the query, # overriding the one in `context[:trace_mode]`. # # By default, the detailed tracer calls `.inspect` on application objects returned from fields. You can customize # this behavior by extending {DetailedTrace} and overriding {#inspect_object}. You can opt out of debug annotations # entirely with `use ..., debug: false` or for a single query with `context: { detailed_trace_debug: false }`. # # You can store saved traces in two ways: # # - __ActiveRecord__: With `rails generate graphql:detailed_trace`, a new migration will be added to your app. # That table will be used to store trace data. # # - __Redis__: Pass `redis: ...` to save trace data to a Redis database. Depending on your needs, # you can configure this database to retain all data (persistent) or to expire data according to your rules. # # If you need to save traces indefinitely, you can download them from Perfetto after opening them there. # # @example Installing with Rails # rails generate graphql:detailed_trace # optional: --redis # # @example Adding the sampler to your schema # class MySchema < GraphQL::Schema # # Add the sampler: # use GraphQL::Tracing::DetailedTrace, redis: Redis.new(...), limit: 100 # # # And implement this hook to tell it when to take a sample: # def self.detailed_trace?(query) # # Could use `query.context`, `query.selected_operation_name`, `query.query_string` here # # Could call out to Flipper, etc # rand <= 0.000_1 # one in ten thousand # end # end # # @see Graphql::Dashboard GraphQL::Dashboard for viewing stored results # # @example Customizing debug output in traces # class CustomDetailedTrace < GraphQL::Tracing::DetailedTrace # def inspect_object(object) # if object.is_a?(SomeThing) # # handle it specially ... # else # super # end # end # end # # @example disabling debug annotations completely # use DetailedTrace, debug: false, ... # # @example disabling debug annotations for one query # MySchema.execute(query_str, context: { detailed_trace_debug: false }) # class DetailedTrace # @param redis [Redis] If provided, profiles will be stored in Redis for later review # @param limit [Integer] A maximum number of profiles to store # @param debug [Boolean] if `false`, it won't create `debug` annotations in Perfetto traces (reduces overhead) # @param model_class [Class] Overrides {ActiveRecordBackend::GraphqlDetailedTrace} if present def self.use(schema, trace_mode: :profile_sample, memory: false, debug: debug?, redis: nil, limit: nil, model_class: nil) storage = if redis RedisBackend.new(redis: redis, limit: limit) elsif memory MemoryBackend.new(limit: limit) elsif defined?(ActiveRecord) ActiveRecordBackend.new(limit: limit, model_class: model_class) else raise ArgumentError, "To store traces, install ActiveRecord or provide `redis: ...`" end detailed_trace = self.new(storage: storage, trace_mode: trace_mode, debug: debug) schema.detailed_trace = detailed_trace schema.trace_with(PerfettoTrace, mode: trace_mode, save_profile: true) end def initialize(storage:, trace_mode:, debug:) @storage = storage @trace_mode = trace_mode @debug = debug end # @return [Symbol] The trace mode to use when {Schema.detailed_trace?} returns `true` attr_reader :trace_mode # @return [String] ID of saved trace def save_trace(operation_name, duration_ms, begin_ms, trace_data) @storage.save_trace(operation_name, duration_ms, begin_ms, trace_data) end # @return [Boolean] def debug? @debug end # @param last [Integer] # @param before [Integer] Timestamp in milliseconds since epoch # @return [Enumerable] def traces(last: nil, before: nil) @storage.traces(last: last, before: before) end # @return [StoredTrace, nil] def find_trace(id) @storage.find_trace(id) end # @return [void] def delete_trace(id) @storage.delete_trace(id) end # @return [void] def delete_all_traces @storage.delete_all_traces end def inspect_object(object) self.class.inspect_object(object) end def self.inspect_object(object) if defined?(ActiveRecord::Relation) && object.is_a?(ActiveRecord::Relation) "#{object.class}, .to_sql=#{object.to_sql.inspect}" else object.inspect end end # Default debug setting # @return [true] def self.debug? true end class StoredTrace def initialize(id:, operation_name:, duration_ms:, begin_ms:, trace_data:) @id = id @operation_name = operation_name @duration_ms = duration_ms @begin_ms = begin_ms @trace_data = trace_data end attr_reader :id, :operation_name, :duration_ms, :begin_ms, :trace_data end end end end graphql-2.6.0/lib/graphql/tracing/null_trace.rb0000644000004100000410000000021315173430257021520 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/trace" module GraphQL module Tracing NullTrace = Trace.new.freeze end end graphql-2.6.0/lib/graphql/tracing/legacy_hooks_trace.rb0000644000004100000410000000503215173430257023221 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Tracing module LegacyHooksTrace def execute_multiplex(multiplex:) multiplex_instrumenters = multiplex.schema.instrumenters[:multiplex] query_instrumenters = multiplex.schema.instrumenters[:query] # First, run multiplex instrumentation, then query instrumentation for each query RunHooks.call_hooks(multiplex_instrumenters, multiplex, :before_multiplex, :after_multiplex) do RunHooks.each_query_call_hooks(query_instrumenters, multiplex.queries) do super end end end module RunHooks module_function # Call the before_ hooks of each query, # Then yield if no errors. # `call_hooks` takes care of appropriate cleanup. def each_query_call_hooks(instrumenters, queries, i = 0) if i >= queries.length yield else query = queries[i] call_hooks(instrumenters, query, :before_query, :after_query) { each_query_call_hooks(instrumenters, queries, i + 1) { yield } } end end # Call each before hook, and if they all succeed, yield. # If they don't all succeed, call after_ for each one that succeeded. def call_hooks(instrumenters, object, before_hook_name, after_hook_name) begin successful = [] instrumenters.each do |instrumenter| instrumenter.public_send(before_hook_name, object) successful << instrumenter end # if any before hooks raise an exception, quit calling before hooks, # but call the after hooks on anything that succeeded but also # raise the exception that came from the before hook. rescue GraphQL::ExecutionError => err object.context.errors << err rescue => e raise call_after_hooks(successful, object, after_hook_name, e) end begin yield # Call the user code ensure ex = call_after_hooks(successful, object, after_hook_name, nil) raise ex if ex end end def call_after_hooks(instrumenters, object, after_hook_name, ex) instrumenters.reverse_each do |instrumenter| begin instrumenter.public_send(after_hook_name, object) rescue => e ex = e end end ex end end end end end graphql-2.6.0/lib/graphql/tracing/prometheus_trace/0000755000004100000410000000000015173430257022420 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/tracing/prometheus_trace/graphql_collector.rb0000644000004100000410000000171515173430257026455 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing" module GraphQL module Tracing module PrometheusTrace class GraphQLCollector < ::PrometheusExporter::Server::TypeCollector def initialize @graphql_gauge = PrometheusExporter::Metric::Base.default_aggregation.new( 'graphql_duration_seconds', 'Time spent in GraphQL operations, in seconds' ) end def type 'graphql' end def collect(object) default_labels = { key: object['key'], platform_key: object['platform_key'] } custom = object['custom_labels'] labels = custom.nil? ? default_labels : default_labels.merge(custom) @graphql_gauge.observe object['duration'], labels end def metrics [@graphql_gauge] end end end # Backwards-compat: PrometheusTracing::GraphQLCollector = PrometheusTrace::GraphQLCollector end end graphql-2.6.0/lib/graphql/tracing/scout_trace.rb0000644000004100000410000000270615173430257021714 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing/monitor_trace" module GraphQL module Tracing # A tracer for sending GraphQL-Ruby times to Scout # # @example Adding this tracer to your schema # class MySchema < GraphQL::Schema # trace_with GraphQL::Tracing::ScoutTrace # end ScoutTrace = MonitorTrace.create_module("scout") module ScoutTrace class ScoutMonitor < MonitorTrace::Monitor def instrument(keyword, object) if keyword == :execute query = object.queries.first set_this_txn_name = query.context[:set_scout_transaction_name] if set_this_txn_name == true || (set_this_txn_name.nil? && @set_transaction_name) ScoutApm::Transaction.rename(transaction_name(query)) end end ScoutApm::Tracer.instrument("GraphQL", name_for(keyword, object), INSTRUMENT_OPTS) do yield end end INSTRUMENT_OPTS = { scope: true } include MonitorTrace::Monitor::GraphQLSuffixNames class Event < MonitorTrace::Monitor::Event def start layer = ScoutApm::Layer.new("GraphQL", @monitor.name_for(keyword, object)) layer.subscopable! @scout_req = ScoutApm::RequestManager.lookup @scout_req.start_layer(layer) end def finish @scout_req.stop_layer end end end end end end graphql-2.6.0/lib/graphql/tracing/trace.rb0000644000004100000410000001407615173430257020502 0ustar www-datawww-data# frozen_string_literal: true require "graphql/tracing" module GraphQL module Tracing # This is the base class for a `trace` instance whose methods are called during query execution. # "Trace modes" are subclasses of this with custom tracing modules mixed in. # # A trace module may implement any of the methods on `Trace`, being sure to call `super` # to continue any tracing hooks and call the actual runtime behavior. # class Trace # @param multiplex [GraphQL::Execution::Multiplex, nil] # @param query [GraphQL::Query, nil] def initialize(multiplex: nil, query: nil, **_options) @multiplex = multiplex @query = query end # The Ruby parser doesn't call this method (`graphql/c_parser` does.) def lex(query_string:) yield end # @param query_string [String] # @return [void] def parse(query_string:) yield end def validate(query:, validate:) yield end def begin_validate(query, validate) end def end_validate(query, validate, errors) end # @param multiplex [GraphQL::Execution::Multiplex] # @param analyzers [Array] # @return [void] def begin_analyze_multiplex(multiplex, analyzers); end # @param multiplex [GraphQL::Execution::Multiplex] # @param analyzers [Array] # @return [void] def end_analyze_multiplex(multiplex, analyzers); end # @param multiplex [GraphQL::Execution::Multiplex] # @return [void] def analyze_multiplex(multiplex:) yield end def analyze_query(query:) yield end # This wraps an entire `.execute` call. # @param multiplex [GraphQL::Execution::Multiplex] # @return [void] def execute_multiplex(multiplex:) yield end def execute_query(query:) yield end def execute_query_lazy(query:, multiplex:) yield end # GraphQL is about to resolve this field # @param field [GraphQL::Schema::Field] # @param object [GraphQL::Schema::Object] # @param arguments [Hash] # @param query [GraphQL::Query] def begin_execute_field(field, object, arguments, query); end # GraphQL just finished resolving this field # @param field [GraphQL::Schema::Field] # @param object [GraphQL::Schema::Object] # @param arguments [Hash] # @param query [GraphQL::Query] # @param result [Object] def end_execute_field(field, object, arguments, query, result); end def execute_field(field:, query:, ast_node:, arguments:, object:) yield end def execute_field_lazy(field:, query:, ast_node:, arguments:, object:) yield end def authorized(query:, type:, object:) yield end def objects(type, object, context) end def object_loaded(argument_definition, object, context) end # A call to `.authorized?` is starting # @param type [Class] # @param object [Object] # @param context [GraphQL::Query::Context] # @return [void] def begin_authorized(type, object, context) end # A call to `.authorized?` just finished # @param type [Class] # @param object [Object] # @param context [GraphQL::Query::Context] # @param authorized_result [Boolean] # @return [void] def end_authorized(type, object, context, authorized_result) end def authorized_lazy(query:, type:, object:) yield end def resolve_type(query:, type:, object:) yield end def resolve_type_lazy(query:, type:, object:) yield end # A call to `.resolve_type` is starting # @param type [Class, Module] # @param value [Object] # @param context [GraphQL::Query::Context] # @return [void] def begin_resolve_type(type, value, context) end # A call to `.resolve_type` just ended # @param type [Class, Module] # @param value [Object] # @param context [GraphQL::Query::Context] # @param resolved_type [Class] # @return [void] def end_resolve_type(type, value, context, resolved_type) end # A dataloader run is starting # @param dataloader [GraphQL::Dataloader] # @return [void] def begin_dataloader(dataloader); end # A dataloader run has ended # @param dataloder [GraphQL::Dataloader] # @return [void] def end_dataloader(dataloader); end # A source with pending keys is about to fetch # @param source [GraphQL::Dataloader::Source] # @return [void] def begin_dataloader_source(source); end # A fetch call has just ended # @param source [GraphQL::Dataloader::Source] # @return [void] def end_dataloader_source(source); end # Called when Dataloader spins up a new fiber for GraphQL execution # @param jobs [Array<#call>] Execution steps to run # @return [void] def dataloader_spawn_execution_fiber(jobs); end # Called when Dataloader spins up a new fiber for fetching data # @param pending_sources [GraphQL::Dataloader::Source] Instances with pending keys # @return [void] def dataloader_spawn_source_fiber(pending_sources); end # Called when an execution or source fiber terminates # @return [void] def dataloader_fiber_exit; end # Called when a Dataloader fiber is paused to wait for data # @param source [GraphQL::Dataloader::Source] The Source whose `load` call initiated this `yield` # @return [void] def dataloader_fiber_yield(source); end # Called when a Dataloader fiber is resumed because data has been loaded # @param source [GraphQL::Dataloader::Source] The Source whose `load` call previously caused this Fiber to wait # @return [void] def dataloader_fiber_resume(source); end end end end graphql-2.6.0/lib/graphql/tracing/platform_tracing.rb0000644000004100000410000001146315173430257022734 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Tracing # Each platform provides: # - `.platform_keys` # - `#platform_trace` # - `#platform_field_key(type, field)` # @api private class PlatformTracing class << self attr_accessor :platform_keys def inherited(child_class) child_class.platform_keys = self.platform_keys end end def initialize(options = {}) @options = options @platform_keys = self.class.platform_keys @trace_scalars = options.fetch(:trace_scalars, false) end def trace(key, data) case key when "lex", "parse", "validate", "analyze_query", "analyze_multiplex", "execute_query", "execute_query_lazy", "execute_multiplex" platform_key = @platform_keys.fetch(key) platform_trace(platform_key, key, data) do yield end when "execute_field", "execute_field_lazy" field = data[:field] return_type = field.type.unwrap trace_field = if return_type.kind.scalar? || return_type.kind.enum? (field.trace.nil? && @trace_scalars) || field.trace else true end platform_key = if trace_field context = data.fetch(:query).context cached_platform_key(context, field, :field) { platform_field_key(field.owner, field) } else nil end if platform_key && trace_field platform_trace(platform_key, key, data) do yield end else yield end when "authorized", "authorized_lazy" type = data.fetch(:type) context = data.fetch(:context) platform_key = cached_platform_key(context, type, :authorized) { platform_authorized_key(type) } platform_trace(platform_key, key, data) do yield end when "resolve_type", "resolve_type_lazy" type = data.fetch(:type) context = data.fetch(:context) platform_key = cached_platform_key(context, type, :resolve_type) { platform_resolve_type_key(type) } platform_trace(platform_key, key, data) do yield end else # it's a custom key yield end end def self.use(schema_defn, options = {}) if options[:legacy_tracing] tracer = self.new(**options) schema_defn.tracer(tracer) else tracing_name = self.name.split("::").last trace_name = tracing_name.sub("Tracing", "Trace") if GraphQL::Tracing.const_defined?(trace_name, false) trace_module = GraphQL::Tracing.const_get(trace_name) warn("`use(#{self.name})` is deprecated, use the equivalent `trace_with(#{trace_module.name})` instead. More info: https://graphql-ruby.org/queries/tracing.html") schema_defn.trace_with(trace_module, **options) else warn("`use(#{self.name})` and `Tracing::PlatformTracing` are deprecated. Use a `trace_with(...)` module instead. More info: https://graphql-ruby.org/queries/tracing.html. Please open an issue on the GraphQL-Ruby repo if you want to discuss further!") tracer = self.new(**options) schema_defn.tracer(tracer, silence_deprecation_warning: true) end end end private # Get the transaction name based on the operation type and name if possible, or fall back to a user provided # one. Useful for anonymous queries. def transaction_name(query) selected_op = query.selected_operation txn_name = if selected_op op_type = selected_op.operation_type op_name = selected_op.name || fallback_transaction_name(query.context) || "anonymous" "#{op_type}.#{op_name}" else "query.anonymous" end "GraphQL/#{txn_name}" end def fallback_transaction_name(context) context[:tracing_fallback_transaction_name] end attr_reader :options # Different kind of schema objects have different kinds of keys: # # - Object types: `.authorized` # - Union/Interface types: `.resolve_type` # - Fields: execution # # So, they can all share one cache. # # If the key isn't present, the given block is called and the result is cached for `key`. # # @param ctx [GraphQL::Query::Context] # @param key [Class, GraphQL::Field] A part of the schema # @param trace_phase [Symbol] The stage of execution being traced (used by OpenTelementry tracing) # @return [String] def cached_platform_key(ctx, key, trace_phase) cache = ctx.namespace(self.class)[:platform_key_cache] ||= {} cache.fetch(key) { cache[key] = yield } end end end end graphql-2.6.0/lib/graphql/schema.rb0000644000004100000410000025110415173430257017210 0ustar www-datawww-data# frozen_string_literal: true require "logger" require "graphql/schema/addition" require "graphql/schema/always_visible" require "graphql/schema/base_64_encoder" require "graphql/schema/find_inherited_value" require "graphql/schema/finder" require "graphql/schema/introspection_system" require "graphql/schema/late_bound_type" require "graphql/schema/ractor_shareable" require "graphql/schema/timeout" require "graphql/schema/type_expression" require "graphql/schema/unique_within_type" require "graphql/schema/warden" require "graphql/schema/build_from_definition" require "graphql/schema/validator" require "graphql/schema/member" require "graphql/schema/wrapper" require "graphql/schema/list" require "graphql/schema/non_null" require "graphql/schema/argument" require "graphql/schema/enum_value" require "graphql/schema/enum" require "graphql/schema/field_extension" require "graphql/schema/field" require "graphql/schema/input_object" require "graphql/schema/interface" require "graphql/schema/scalar" require "graphql/schema/object" require "graphql/schema/union" require "graphql/schema/directive" require "graphql/schema/directive/deprecated" require "graphql/schema/directive/include" require "graphql/schema/directive/one_of" require "graphql/schema/directive/skip" require "graphql/schema/directive/feature" require "graphql/schema/directive/flagged" require "graphql/schema/directive/transform" require "graphql/schema/directive/specified_by" require "graphql/schema/type_membership" require "graphql/schema/resolver" require "graphql/schema/mutation" require "graphql/schema/has_single_input_argument" require "graphql/schema/relay_classic_mutation" require "graphql/schema/subscription" require "graphql/schema/visibility" 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 # # 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}) # # @example defining a schema # class MySchema < GraphQL::Schema # query QueryType # # If types are only connected by way of interfaces, they must be added here # orphan_types ImageType, AudioType # end # class Schema extend GraphQL::Schema::Member::HasAstNode extend GraphQL::Schema::FindInheritedValue extend Autoload autoload :BUILT_IN_TYPES, "graphql/schema/built_in_types" class DuplicateNamesError < GraphQL::Error attr_reader :duplicated_name def initialize(duplicated_name:, duplicated_definition_1:, duplicated_definition_2:) @duplicated_name = duplicated_name super( "Found two visible definitions for `#{duplicated_name}`: #{duplicated_definition_1}, #{duplicated_definition_2}" ) end end class UnresolvedLateBoundTypeError < GraphQL::Error attr_reader :type def initialize(type:) @type = type super("Late bound type was never found: #{type.inspect}") end end # Error that is raised when [#Schema#from_definition] is passed an invalid schema definition string. class InvalidDocumentError < Error; end; class << self # Create schema with the result of an introspection query. # @param introspection_result [Hash] A response from {GraphQL::Introspection::INTROSPECTION_QUERY} # @return [Class] the schema described by `input` def from_introspection(introspection_result) GraphQL::Schema::Loader.load(introspection_result) end # Create schema from an IDL schema or file containing an IDL definition. # @param definition_or_path [String] A schema definition string, or a path to a file containing the definition # @param default_resolve [<#call(type, field, obj, args, ctx)>] A callable for handling field resolution # @param parser [Object] An object for handling definition string parsing (must respond to `parse`) # @param using [Hash] Plugins to attach to the created schema with `use(key, value)` # @return [Class] the schema described by `document` def from_definition(definition_or_path, default_resolve: nil, parser: GraphQL.default_parser, using: {}, base_types: {}) # If the file ends in `.graphql` or `.graphqls`, treat it like a filepath if definition_or_path.end_with?(".graphql") || definition_or_path.end_with?(".graphqls") GraphQL::Schema::BuildFromDefinition.from_definition_path( self, definition_or_path, default_resolve: default_resolve, parser: parser, using: using, base_types: base_types, ) else GraphQL::Schema::BuildFromDefinition.from_definition( self, definition_or_path, default_resolve: default_resolve, parser: parser, using: using, base_types: base_types, ) end end def deprecated_graphql_definition graphql_definition(silence_deprecation_warning: true) end # @return [GraphQL::Subscriptions] def subscriptions(inherited: true) defined?(@subscriptions) ? @subscriptions : (inherited ? find_inherited_value(:subscriptions, nil) : nil) end def subscriptions=(new_implementation) @subscriptions = new_implementation end # @param new_mode [Symbol] If configured, this will be used when `context: { trace_mode: ... }` isn't set. def default_trace_mode(new_mode = NOT_CONFIGURED) if !NOT_CONFIGURED.equal?(new_mode) @default_trace_mode = new_mode elsif defined?(@default_trace_mode) && !@default_trace_mode.nil? # This `nil?` check seems necessary because of # Ractors silently initializing @default_trace_mode somehow @default_trace_mode elsif superclass.respond_to?(:default_trace_mode) superclass.default_trace_mode else :default end end def trace_class(new_class = nil) if new_class # If any modules were already added for `:default`, # re-apply them here mods = trace_modules_for(:default) mods.each { |mod| new_class.include(mod) } new_class.include(DefaultTraceClass) trace_mode(:default, new_class) end trace_class_for(:default, build: true) end # @return [Class] Return the trace class to use for this mode, looking one up on the superclass if this Schema doesn't have one defined. def trace_class_for(mode, build: false) if (trace_class = own_trace_modes[mode]) trace_class elsif superclass.respond_to?(:trace_class_for) && (trace_class = superclass.trace_class_for(mode, build: false)) trace_class elsif build own_trace_modes[mode] = build_trace_mode(mode) else nil end end # Configure `trace_class` to be used whenever `context: { trace_mode: mode_name }` is requested. # {default_trace_mode} is used when no `trace_mode: ...` is requested. # # When a `trace_class` is added this way, it will _not_ receive other modules added with `trace_with(...)` # unless `trace_mode` is explicitly given. (This class will not receive any default trace modules.) # # Subclasses of the schema will use `trace_class` as a base class for this mode and those # subclass also will _not_ receive default tracing modules. # # @param mode_name [Symbol] # @param trace_class [Class] subclass of GraphQL::Tracing::Trace # @return void def trace_mode(mode_name, trace_class) own_trace_modes[mode_name] = trace_class nil end def own_trace_modes @own_trace_modes ||= {} end def build_trace_mode(mode) case mode when :default # Use the superclass's default mode if it has one, or else start an inheritance chain at the built-in base class. base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode, build: true)) || GraphQL::Tracing::Trace const_set(:DefaultTrace, Class.new(base_class) do include DefaultTraceClass end) else # First, see if the superclass has a custom-defined class for this. # Then, if it doesn't, use this class's default trace base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || trace_class_for(:default, build: true) # Prepare the default trace class if it hasn't been initialized yet base_class ||= (own_trace_modes[:default] = build_trace_mode(:default)) mods = trace_modules_for(mode) if base_class < DefaultTraceClass mods = trace_modules_for(:default) + mods end # Copy the existing default options into this mode's options default_options = trace_options_for(:default) add_trace_options_for(mode, default_options) Class.new(base_class) do !mods.empty? && include(*mods) end end end def own_trace_modules @own_trace_modules ||= Hash.new { |h, k| h[k] = [] } end # @return [Array] Modules added for tracing in `trace_mode`, including inherited ones def trace_modules_for(trace_mode) modules = own_trace_modules[trace_mode] if superclass.respond_to?(:trace_modules_for) modules += superclass.trace_modules_for(trace_mode) end modules end # Returns the JSON response of {Introspection::INTROSPECTION_QUERY}. # @see #as_json Return a Hash representation of the schema # @return [String] def to_json(**args) JSON.pretty_generate(as_json(**args)) end # Return the Hash response of {Introspection::INTROSPECTION_QUERY}. # @param context [Hash] # @param include_deprecated_args [Boolean] If true, deprecated arguments will be included in the JSON response # @param include_schema_description [Boolean] If true, the schema's description will be queried and included in the response # @param include_is_repeatable [Boolean] If true, `isRepeatable: true|false` will be included with the schema's directives # @param include_specified_by_url [Boolean] If true, scalar types' `specifiedByUrl:` will be included in the response # @param include_is_one_of [Boolean] If true, `isOneOf: true|false` will be included with input objects # @return [Hash] GraphQL result def as_json(context: {}, include_deprecated_args: true, include_schema_description: false, include_is_repeatable: false, include_specified_by_url: false, include_is_one_of: false) introspection_query = Introspection.query( include_deprecated_args: include_deprecated_args, include_schema_description: include_schema_description, include_is_repeatable: include_is_repeatable, include_is_one_of: include_is_one_of, include_specified_by_url: include_specified_by_url, ) execute(introspection_query, context: context).to_h end # Return the GraphQL IDL for the schema # @param context [Hash] # @return [String] def to_definition(context: {}) GraphQL::Schema::Printer.print_schema(self, context: context) end # Return the GraphQL::Language::Document IDL AST for the schema # @return [GraphQL::Language::Document] def to_document GraphQL::Language::DocumentFromSchemaDefinition.new(self).document end # @return [String, nil] def description(new_description = nil) if new_description @description = new_description elsif defined?(@description) @description else find_inherited_value(:description, nil) end end def find(path) if !@finder @find_cache = {} @finder ||= GraphQL::Schema::Finder.new(self) end @find_cache[path] ||= @finder.find(path) end def static_validator GraphQL::StaticValidation::Validator.new(schema: self) end # Add `plugin` to this schema # @param plugin [#use] A Schema plugin # @return void def use(plugin, **kwargs) if !kwargs.empty? plugin.use(self, **kwargs) else plugin.use(self) end own_plugins << [plugin, kwargs] end def plugins find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins end attr_writer :null_context def null_context @null_context || GraphQL::Query::NullContext.instance end # Build a map of `{ name => type }` and return it # @return [Hash Class>] A dictionary of type classes by their GraphQL name # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes. def types(context = null_context) if use_visibility_profile? types = Visibility::Profile.from_context(context, self) return types.all_types_h end all_types = non_introspection_types.merge(introspection_system.types) visible_types = {} all_types.each do |k, v| visible_types[k] =if v.is_a?(Array) visible_t = nil v.each do |t| if t.visible?(context) if visible_t.nil? visible_t = t else raise DuplicateNamesError.new( duplicated_name: k, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect ) end end end visible_t else v end end visible_types end # @param type_name [String] # @param context [GraphQL::Query::Context] Used for filtering definitions at query-time # @param use_visibility_profile Private, for migration to {Schema::Visibility} # @return [Module, nil] A type, or nil if there's no type called `type_name` def get_type(type_name, context = null_context, use_visibility_profile = use_visibility_profile?) if use_visibility_profile profile = Visibility::Profile.from_context(context, self) return profile.type(type_name) end local_entry = own_types[type_name] type_defn = case local_entry when nil nil when Array if context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile) local_entry else visible_t = nil warden = Warden.from_context(context) local_entry.each do |t| if warden.visible_type?(t, context) if visible_t.nil? visible_t = t else raise DuplicateNamesError.new( duplicated_name: type_name, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect ) end end end visible_t end when Module local_entry else raise "Invariant: unexpected own_types[#{type_name.inspect}]: #{local_entry.inspect}" end type_defn || introspection_system.types[type_name] || # todo context-specific introspection? (superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context, use_visibility_profile) : nil) end # @return [Boolean] Does this schema have _any_ definition for a type named `type_name`, regardless of visibility? def has_defined_type?(type_name) own_types.key?(type_name) || introspection_system.types.key?(type_name) || (superclass.respond_to?(:has_defined_type?) ? superclass.has_defined_type?(type_name) : false) end # @api private attr_writer :connections # @return [GraphQL::Pagination::Connections] if installed def connections if defined?(@connections) @connections else inherited_connections = find_inherited_value(:connections, nil) # This schema is part of an inheritance chain which is using new connections, # make a new instance, so we don't pollute the upstream one. if inherited_connections @connections = Pagination::Connections.new(schema: self) else nil end end end # Get or set the root `query { ... }` object for this schema. # # @example Using `Types::Query` as the entry-point # query { Types::Query } # # @param new_query_object [Class] The root type to use for queries # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root query type. # @return [Class, nil] The configured query root type, if there is one. def query(new_query_object = nil, &lazy_load_block) if new_query_object || block_given? if @query_object dup_defn = new_query_object || yield raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}" elsif use_visibility_profile? if block_given? if visibility.preload? @query_object = lazy_load_block.call self.visibility.query_configured(@query_object) else @query_object = lazy_load_block end else @query_object = new_query_object self.visibility.query_configured(@query_object) end else @query_object = new_query_object || lazy_load_block.call add_type_and_traverse(@query_object, root: true) end nil elsif @query_object.is_a?(Proc) @query_object = @query_object.call self.visibility&.query_configured(@query_object) @query_object else @query_object || find_inherited_value(:query) end end # Get or set the root `mutation { ... }` object for this schema. # # @example Using `Types::Mutation` as the entry-point # mutation { Types::Mutation } # # @param new_mutation_object [Class] The root type to use for mutations # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root mutation type. # @return [Class, nil] The configured mutation root type, if there is one. def mutation(new_mutation_object = nil, &lazy_load_block) if new_mutation_object || block_given? if @mutation_object dup_defn = new_mutation_object || yield raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}" elsif use_visibility_profile? if block_given? if visibility.preload? @mutation_object = lazy_load_block.call self.visibility.mutation_configured(@mutation_object) else @mutation_object = lazy_load_block end else @mutation_object = new_mutation_object self.visibility.mutation_configured(@mutation_object) end else @mutation_object = new_mutation_object || lazy_load_block.call add_type_and_traverse(@mutation_object, root: true) end nil elsif @mutation_object.is_a?(Proc) @mutation_object = @mutation_object.call self.visibility&.mutation_configured(@mutation_object) @mutation_object else @mutation_object || find_inherited_value(:mutation) end end # Get or set the root `subscription { ... }` object for this schema. # # @example Using `Types::Subscription` as the entry-point # subscription { Types::Subscription } # # @param new_subscription_object [Class] The root type to use for subscriptions # @param lazy_load_block If a block is given, then it will be called when GraphQL-Ruby needs the root subscription type. # @return [Class, nil] The configured subscription root type, if there is one. def subscription(new_subscription_object = nil, &lazy_load_block) if new_subscription_object || block_given? if @subscription_object dup_defn = new_subscription_object || yield raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}" elsif use_visibility_profile? if block_given? if visibility.preload? @subscription_object = lazy_load_block.call visibility.subscription_configured(@subscription_object) else @subscription_object = lazy_load_block end else @subscription_object = new_subscription_object self.visibility.subscription_configured(@subscription_object) end add_subscription_extension_if_necessary else @subscription_object = new_subscription_object || lazy_load_block.call add_subscription_extension_if_necessary add_type_and_traverse(@subscription_object, root: true) end nil elsif @subscription_object.is_a?(Proc) @subscription_object = @subscription_object.call add_subscription_extension_if_necessary self.visibility.subscription_configured(@subscription_object) @subscription_object else @subscription_object || find_inherited_value(:subscription) end end # @api private 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 # @return [Array] The root types (query, mutation, subscription) defined for this schema def root_types if use_visibility_profile? [query, mutation, subscription].compact else @root_types end end # @api private def warden_class if defined?(@warden_class) @warden_class elsif superclass.respond_to?(:warden_class) superclass.warden_class else GraphQL::Schema::Warden end end # @api private attr_writer :warden_class # @api private def visibility_profile_class if defined?(@visibility_profile_class) @visibility_profile_class elsif superclass.respond_to?(:visibility_profile_class) superclass.visibility_profile_class else GraphQL::Schema::Visibility::Profile end end # @api private attr_writer :visibility_profile_class, :use_visibility_profile # @api private attr_accessor :visibility # @api private def use_visibility_profile? if defined?(@use_visibility_profile) @use_visibility_profile elsif superclass.respond_to?(:use_visibility_profile?) superclass.use_visibility_profile? else false end end # @param type [Module] The type definition whose possible types you want to see # @param context [GraphQL::Query::Context] used for filtering visible possible types at runtime # @param use_visibility_profile Private, for migration to {Schema::Visibility} # @return [Hash] All possible types, if no `type` is given. # @return [Array] Possible types for `type`, if it's given. def possible_types(type = nil, context = null_context, use_visibility_profile = use_visibility_profile?) if use_visibility_profile if type return Visibility::Profile.from_context(context, self).possible_types(type) else raise "Schema.possible_types is not implemented for `use_visibility_profile?`" end end if type # TODO duck-typing `.possible_types` would probably be nicer here if type.kind.union? type.possible_types(context: context) else stored_possible_types = own_possible_types[type] visible_possible_types = if stored_possible_types && type.kind.interface? stored_possible_types.select do |possible_type| possible_type.interfaces(context).include?(type) end else stored_possible_types end visible_possible_types || introspection_system.possible_types[type] || ( superclass.respond_to?(:possible_types) ? superclass.possible_types(type, context, use_visibility_profile) : EMPTY_ARRAY ) end else find_inherited_value(:possible_types, EMPTY_HASH) .merge(own_possible_types) .merge(introspection_system.possible_types) end end def union_memberships(type = nil) if type own_um = own_union_memberships.fetch(type.graphql_name, EMPTY_ARRAY) inherited_um = find_inherited_value(:union_memberships, EMPTY_HASH).fetch(type.graphql_name, EMPTY_ARRAY) own_um + inherited_um else joined_um = own_union_memberships.dup find_inherited_value(:union_memberhips, EMPTY_HASH).each do |k, v| um = joined_um[k] ||= [] um.concat(v) end joined_um end end # @api private # @see GraphQL::Dataloader def dataloader_class @dataloader_class || GraphQL::Dataloader::NullDataloader end attr_writer :dataloader_class def references_to(to_type = nil, from: nil) if to_type if from refs = own_references_to[to_type] ||= [] refs << from else get_references_to(to_type) || EMPTY_ARRAY end else # `@own_references_to` can be quite large for big schemas, # and generally speaking, we won't inherit any values. # So optimize the most common case -- don't create a duplicate Hash. inherited_value = find_inherited_value(:references_to, EMPTY_HASH) if !inherited_value.empty? inherited_value.merge(own_references_to) else own_references_to end end end def type_from_ast(ast_node, context: self.query_class.new(self, "{ __typename }").context) GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node) end def get_field(type_or_name, field_name, context = null_context, use_visibility_profile = use_visibility_profile?) if use_visibility_profile profile = Visibility::Profile.from_context(context, self) parent_type = case type_or_name when String profile.type(type_or_name) when Module type_or_name when LateBoundType profile.type(type_or_name.name) else raise GraphQL::InvariantError, "Unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})" end return profile.field(parent_type, field_name) end parent_type = case type_or_name when LateBoundType get_type(type_or_name.name, context) when String get_type(type_or_name, context) when Module type_or_name else raise GraphQL::InvariantError, "Unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})" end if parent_type.kind.fields? && (field = parent_type.get_field(field_name, context)) field elsif parent_type == query && (entry_point_field = introspection_system.entry_point(name: field_name)) entry_point_field elsif (dynamic_field = introspection_system.dynamic_field(name: field_name)) dynamic_field else nil end end def get_fields(type, context = null_context) type.fields(context) end # Pass a custom introspection module here to use it for this schema. # @param new_introspection_namespace [Module] If given, use this module for custom introspection on the schema # @return [Module, nil] The configured namespace, if there is one def introspection(new_introspection_namespace = nil) if new_introspection_namespace @introspection = new_introspection_namespace # reset this cached value: @introspection_system = nil introspection_system @introspection else @introspection || find_inherited_value(:introspection) end end # @return [Schema::IntrospectionSystem] Based on {introspection} def introspection_system if !@introspection_system @introspection_system = Schema::IntrospectionSystem.new(self) @introspection_system.resolve_late_bindings self.visibility&.introspection_system_configured(@introspection_system) end @introspection_system end def cursor_encoder(new_encoder = nil) if new_encoder @cursor_encoder = new_encoder end @cursor_encoder || find_inherited_value(:cursor_encoder, Base64Encoder) end def default_max_page_size(new_default_max_page_size = nil) if new_default_max_page_size @default_max_page_size = new_default_max_page_size else @default_max_page_size || find_inherited_value(:default_max_page_size) end end # A limit on the number of tokens to accept on incoming query strings. # Use this to prevent parsing maliciously-large query strings. # @return [nil, Integer] def max_query_string_tokens(new_max_tokens = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_max_tokens) defined?(@max_query_string_tokens) ? @max_query_string_tokens : find_inherited_value(:max_query_string_tokens) else @max_query_string_tokens = new_max_tokens end end def default_page_size(new_default_page_size = nil) if new_default_page_size @default_page_size = new_default_page_size else @default_page_size || find_inherited_value(:default_page_size) end end def query_execution_strategy(new_query_execution_strategy = nil, deprecation_warning: true) if deprecation_warning warn "GraphQL::Schema.query_execution_strategy is deprecated without replacement. Use `GraphQL::Query.new` directly to create and execute a custom query instead." warn " #{caller(1, 1).first}" end if new_query_execution_strategy @query_execution_strategy = new_query_execution_strategy else @query_execution_strategy || (superclass.respond_to?(:query_execution_strategy) ? superclass.query_execution_strategy(deprecation_warning: false) : self.default_execution_strategy) end end def mutation_execution_strategy(new_mutation_execution_strategy = nil, deprecation_warning: true) if deprecation_warning warn "GraphQL::Schema.mutation_execution_strategy is deprecated without replacement. Use `GraphQL::Query.new` directly to create and execute a custom query instead." warn " #{caller(1, 1).first}" end if new_mutation_execution_strategy @mutation_execution_strategy = new_mutation_execution_strategy else @mutation_execution_strategy || (superclass.respond_to?(:mutation_execution_strategy) ? superclass.mutation_execution_strategy(deprecation_warning: false) : self.default_execution_strategy) end end def subscription_execution_strategy(new_subscription_execution_strategy = nil, deprecation_warning: true) if deprecation_warning warn "GraphQL::Schema.subscription_execution_strategy is deprecated without replacement. Use `GraphQL::Query.new` directly to create and execute a custom query instead." warn " #{caller(1, 1).first}" end if new_subscription_execution_strategy @subscription_execution_strategy = new_subscription_execution_strategy else @subscription_execution_strategy || (superclass.respond_to?(:subscription_execution_strategy) ? superclass.subscription_execution_strategy(deprecation_warning: false) : self.default_execution_strategy) end end attr_writer :validate_timeout def validate_timeout(new_validate_timeout = NOT_CONFIGURED) if !NOT_CONFIGURED.equal?(new_validate_timeout) @validate_timeout = new_validate_timeout elsif defined?(@validate_timeout) @validate_timeout else find_inherited_value(:validate_timeout) || 3 end end # Validate a query string according to this schema. # @param string_or_document [String, GraphQL::Language::Nodes::Document] # @return [Array] def validate(string_or_document, rules: nil, context: nil) doc = if string_or_document.is_a?(String) GraphQL.parse(string_or_document) else string_or_document end query = query_class.new(self, document: doc, context: context) validator_opts = { schema: self } rules && (validator_opts[:rules] = rules) validator = GraphQL::StaticValidation::Validator.new(**validator_opts) res = validator.validate(query, timeout: validate_timeout, max_errors: validate_max_errors) res[:errors] end # @param new_query_class [Class] A subclass to use when executing queries def query_class(new_query_class = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_query_class) @query_class || (superclass.respond_to?(:query_class) ? superclass.query_class : GraphQL::Query) else @query_class = new_query_class end end attr_writer :validate_max_errors def validate_max_errors(new_validate_max_errors = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_validate_max_errors) defined?(@validate_max_errors) ? @validate_max_errors : find_inherited_value(:validate_max_errors) else @validate_max_errors = new_validate_max_errors end end attr_writer :max_complexity def max_complexity(max_complexity = nil, count_introspection_fields: true) if max_complexity @max_complexity = max_complexity @max_complexity_count_introspection_fields = count_introspection_fields elsif defined?(@max_complexity) @max_complexity else find_inherited_value(:max_complexity) end end def max_complexity_count_introspection_fields if defined?(@max_complexity_count_introspection_fields) @max_complexity_count_introspection_fields else find_inherited_value(:max_complexity_count_introspection_fields, true) end end attr_writer :analysis_engine def analysis_engine @analysis_engine || find_inherited_value(:analysis_engine, self.default_analysis_engine) end def error_bubbling(new_error_bubbling = nil) if !new_error_bubbling.nil? warn("error_bubbling(#{new_error_bubbling.inspect}) is deprecated; the default value of `false` will be the only option in GraphQL-Ruby 3.0") @error_bubbling = new_error_bubbling else @error_bubbling.nil? ? find_inherited_value(:error_bubbling) : @error_bubbling end end attr_writer :error_bubbling attr_writer :max_depth def max_depth(new_max_depth = nil, count_introspection_fields: true) if new_max_depth @max_depth = new_max_depth @count_introspection_fields = count_introspection_fields elsif defined?(@max_depth) @max_depth else find_inherited_value(:max_depth) end end def count_introspection_fields if defined?(@count_introspection_fields) @count_introspection_fields else find_inherited_value(:count_introspection_fields, true) end end def disable_introspection_entry_points @disable_introspection_entry_points = true # TODO: this clears the cache made in `def types`. But this is not a great solution. @introspection_system = nil end def disable_schema_introspection_entry_point @disable_schema_introspection_entry_point = true # TODO: this clears the cache made in `def types`. But this is not a great solution. @introspection_system = nil end def disable_type_introspection_entry_point @disable_type_introspection_entry_point = true # TODO: this clears the cache made in `def types`. But this is not a great solution. @introspection_system = nil end def disable_introspection_entry_points? if instance_variable_defined?(:@disable_introspection_entry_points) @disable_introspection_entry_points else find_inherited_value(:disable_introspection_entry_points?, false) end end def disable_schema_introspection_entry_point? if instance_variable_defined?(:@disable_schema_introspection_entry_point) @disable_schema_introspection_entry_point else find_inherited_value(:disable_schema_introspection_entry_point?, false) end end def disable_type_introspection_entry_point? if instance_variable_defined?(:@disable_type_introspection_entry_point) @disable_type_introspection_entry_point else find_inherited_value(:disable_type_introspection_entry_point?, false) end end # @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema # @return [Array] Type definitions added to this schema def extra_types(*new_extra_types) if !new_extra_types.empty? new_extra_types = new_extra_types.flatten @own_extra_types ||= [] @own_extra_types.concat(new_extra_types) end inherited_et = find_inherited_value(:extra_types, nil) if inherited_et if @own_extra_types inherited_et + @own_extra_types else inherited_et end else @own_extra_types || EMPTY_ARRAY end end # Tell the schema about these types so that they can be registered as implementations of interfaces in the schema. # # This method must be used when an object type is connected to the schema as an interface implementor but # not as a return type of a field. In that case, if the object type isn't registered here, GraphQL-Ruby won't be able to find it. # # @param new_orphan_types [Array>] Object types to register as implementations of interfaces in the schema. # @return [Array>] All previously-registered orphan types for this schema def orphan_types(*new_orphan_types) if !new_orphan_types.empty? new_orphan_types = new_orphan_types.flatten non_object_types = new_orphan_types.reject { |ot| ot.is_a?(Class) && ot < GraphQL::Schema::Object } if !non_object_types.empty? raise ArgumentError, <<~ERR Only object type classes should be added as `orphan_types(...)`. - Remove these no-op types from `orphan_types`: #{non_object_types.map { |t| "#{t.inspect} (#{t.kind.name})"}.join(", ")} - See https://graphql-ruby.org/type_definitions/interfaces.html#orphan-types To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types ERR end add_type_and_traverse(new_orphan_types, root: false) unless use_visibility_profile? own_orphan_types.concat(new_orphan_types.flatten) self.visibility&.orphan_types_configured(new_orphan_types) end inherited_ot = find_inherited_value(:orphan_types, nil) if inherited_ot if !own_orphan_types.empty? inherited_ot + own_orphan_types else inherited_ot end else own_orphan_types end end def default_execution_strategy if superclass <= GraphQL::Schema superclass.default_execution_strategy else @default_execution_strategy ||= GraphQL::Execution::Interpreter end end def default_analysis_engine if superclass <= GraphQL::Schema superclass.default_analysis_engine else @default_analysis_engine ||= GraphQL::Analysis::AST end end # @param new_default_logger [#log] Something to use for logging messages def default_logger(new_default_logger = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_default_logger) if defined?(@default_logger) @default_logger elsif superclass.respond_to?(:default_logger) superclass.default_logger elsif defined?(Rails) && Rails.respond_to?(:logger) && (rails_logger = Rails.logger) rails_logger else def_logger = Logger.new($stdout) def_logger.info! # It doesn't output debug info by default def_logger end elsif new_default_logger == nil @default_logger = Logger.new(IO::NULL) else @default_logger = new_default_logger end end # @param context [GraphQL::Query::Context, nil] # @return [Logger] A logger to use for this context configuration, falling back to {.default_logger} def logger_for(context) if context && context[:logger] == false Logger.new(IO::NULL) elsif context && (l = context[:logger]) l else default_logger end end # @param new_context_class [Class] A subclass to use when executing queries def context_class(new_context_class = nil) if new_context_class @context_class = new_context_class else @context_class || find_inherited_value(:context_class, GraphQL::Query::Context) end end # Register a handler for errors raised during execution. The handlers can return a new value or raise a new error. # # @example Handling "not found" with a client-facing error # rescue_from(ActiveRecord::NotFound) { raise GraphQL::ExecutionError, "An object could not be found" } # # @param err_classes [Array] Classes which should be rescued by `handler_block` # @param handler_block The code to run when one of those errors is raised during execution # @yieldparam error [StandardError] An instance of one of the configured `err_classes` # @yieldparam object [Object] The current application object in the query when the error was raised # @yieldparam arguments [GraphQL::Query::Arguments] The current field arguments when the error was raised # @yieldparam context [GraphQL::Query::Context] The context for the currently-running operation # @yieldreturn [Object] Some object to use in the place where this error was raised # @raise [GraphQL::ExecutionError] In the handler, raise to add a client-facing error to the response # @raise [StandardError] In the handler, raise to crash the query with a developer-facing error def rescue_from(*err_classes, &handler_block) err_classes.each do |err_class| Execution::Errors.register_rescue_from(err_class, error_handlers[:subclass_handlers], handler_block) end end def error_handlers @error_handlers ||= begin new_handler_hash = ->(h, k) { h[k] = { class: k, handler: nil, subclass_handlers: Hash.new(&new_handler_hash), } } { class: nil, handler: nil, subclass_handlers: Hash.new(&new_handler_hash), } end end # @api private attr_accessor :using_backtrace # @api private def handle_or_reraise(context, err) handler = Execution::Errors.find_handler_for(self, err.class) if handler obj = context[:current_object] args = context[:current_arguments] args = args && args.respond_to?(:keyword_arguments) ? args.keyword_arguments : nil field = context[:current_field] if obj.is_a?(GraphQL::Schema::Object) obj = obj.object end handler[:handler].call(err, obj, args, context, field) else if (context[:backtrace] || using_backtrace) && !err.is_a?(GraphQL::ExecutionError) err = GraphQL::Backtrace::TracedError.new(err, context) end raise err end end # rubocop:disable Lint/DuplicateMethods module ResolveTypeWithType def resolve_type(type, obj, ctx) maybe_lazy_resolve_type_result = if type.is_a?(Module) && type.respond_to?(:resolve_type) type.resolve_type(obj, ctx) else super end after_lazy(maybe_lazy_resolve_type_result) do |resolve_type_result| if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2 resolved_type = resolve_type_result[0] resolved_value = resolve_type_result[1] else resolved_type = resolve_type_result resolved_value = obj end if resolved_type.nil? || (resolved_type.is_a?(Module) && resolved_type.respond_to?(:kind)) [resolved_type, resolved_value] else raise ".resolve_type should return a type definition, but got #{resolved_type.inspect} (#{resolved_type.class}) from `resolve_type(#{type}, #{obj}, #{ctx})`" end end end end # GraphQL-Ruby calls this method during execution when it needs the application to determine the type to use for an object. # # Usually, this object was returned from a field whose return type is an {GraphQL::Schema::Interface} or a {GraphQL::Schema::Union}. # But this method is called in other cases, too -- for example, when {GraphQL::Schema::Argument#loads} cases an object to be directly loaded from the database. # # @example Returning a GraphQL type based on the object's class name # class MySchema < GraphQL::Schema # def resolve_type(_abs_type, object, _context) # graphql_type_name = "Types::#{object.class.name}Type" # graphql_type_name.constantize # If this raises a NameError, then come implement special cases in this method # end # end # @param abstract_type [Class, Module, nil] The Interface or Union type which is being resolved, if there is one # @param application_object [Object] The object returned from a field whose type must be determined # @param context [GraphQL::Query::Context] The query context for the currently-executing query # @return [Class 2 end def instrument(instrument_step, instrumenter, options = {}) warn <<~WARN Schema.instrument is deprecated, use `trace_with` instead: https://graphql-ruby.org/queries/tracing.html" (From `#{self}.instrument(#{instrument_step}, #{instrumenter})` at #{caller(1, 1).first}) WARN trace_with(Tracing::LegacyHooksTrace) own_instrumenters[instrument_step] << instrumenter end # Add several directives at once # @param new_directives [Class] def directives(*new_directives) if !new_directives.empty? new_directives.flatten.each { |d| directive(d) } end inherited_dirs = find_inherited_value(:directives, default_directives) if !own_directives.empty? inherited_dirs.merge(own_directives) else inherited_dirs end end # Attach a single directive to this schema # @param new_directive [Class] # @return void def directive(new_directive) if use_visibility_profile? own_directives[new_directive.graphql_name] = new_directive else add_type_and_traverse(new_directive, root: false) end end def default_directives @default_directives ||= { "include" => GraphQL::Schema::Directive::Include, "skip" => GraphQL::Schema::Directive::Skip, "deprecated" => GraphQL::Schema::Directive::Deprecated, "oneOf" => GraphQL::Schema::Directive::OneOf, "specifiedBy" => GraphQL::Schema::Directive::SpecifiedBy, }.freeze end # @return [GraphQL::Tracing::DetailedTrace] if it has been configured for this schema attr_accessor :detailed_trace # @param query [GraphQL::Query, GraphQL::Execution::Multiplex] Called with a multiplex when multiple queries are executed at once (with {.multiplex}) # @return [Boolean] When `true`, save a detailed trace for this query. # @see Tracing::DetailedTrace DetailedTrace saves traces when this method returns true def detailed_trace?(query) raise "#{self} must implement `def.detailed_trace?(query)` to use DetailedTrace. Implement this method in your schema definition." end def tracer(new_tracer, silence_deprecation_warning: false) if !silence_deprecation_warning warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html") warn " #{caller(1, 1).first}" end default_trace = trace_class_for(:default, build: true) if default_trace.nil? || !(default_trace < GraphQL::Tracing::CallLegacyTracers) trace_with(GraphQL::Tracing::CallLegacyTracers) end own_tracers << new_tracer end def tracers inherited = find_inherited_value(:tracers, EMPTY_ARRAY) if inherited.length > 0 if own_tracers.length > 0 inherited + own_tracers else inherited end else own_tracers end end # Mix `trace_mod` into this schema's `Trace` class so that its methods will be called at runtime. # # You can attach a module to run in only _some_ circumstances by using `mode:`. When a module is added with `mode:`, # it will only run for queries with a matching `context[:trace_mode]`. # # Any custom trace modes _also_ include the default `trace_with ...` modules (that is, those added _without_ any particular `mode: ...` configuration). # # @example Adding a trace in a special mode # # only runs when `query.context[:trace_mode]` is `:special` # trace_with SpecialTrace, mode: :special # # @param trace_mod [Module] A module that implements tracing methods # @param mode [Symbol] Trace module will only be used for this trade mode # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize` # @return [void] # @see GraphQL::Tracing::Trace Tracing::Trace for available tracing methods def trace_with(trace_mod, mode: :default, **options) if mode.is_a?(Array) mode.each { |m| trace_with(trace_mod, mode: m, **options) } else tc = own_trace_modes[mode] ||= build_trace_mode(mode) tc.include(trace_mod) own_trace_modules[mode] << trace_mod add_trace_options_for(mode, options) if mode == :default # This module is being added as a default tracer. If any other mode classes # have already been created, but get their default behavior from a superclass, # Then mix this into this schema's subclass. # (But don't mix it into mode classes that aren't default-based.) own_trace_modes.each do |other_mode_name, other_mode_class| if other_mode_class < DefaultTraceClass # Don't add it back to the inheritance tree if it's already there if !(other_mode_class < trace_mod) other_mode_class.include(trace_mod) end # Add any options so they'll be available add_trace_options_for(other_mode_name, options) end end end end nil end # The options hash for this trace mode # @return [Hash] def trace_options_for(mode) @trace_options_for_mode ||= {} @trace_options_for_mode[mode] ||= begin # It may be time to create an options hash for a mode that wasn't registered yet. # Mix in the default options in that case. default_options = mode == :default ? EMPTY_HASH : trace_options_for(:default) # Make sure this returns a new object so that other hashes aren't modified later if superclass.respond_to?(:trace_options_for) superclass.trace_options_for(mode).merge(default_options) else default_options.dup end end end # Create a trace instance which will include the trace modules specified for the optional mode. # # If no `mode:` is given, then {default_trace_mode} will be used. # # If this schema is using {Tracing::DetailedTrace} and {.detailed_trace?} returns `true`, then # DetailedTrace's mode will override the passed-in `mode`. # # @param mode [Symbol] Trace modules for this trade mode will be included # @param options [Hash] Keywords that will be passed to the tracing class during `#initialize` # @return [Tracing::Trace] def new_trace(mode: nil, **options) should_sample = if detailed_trace if (query = options[:query]) detailed_trace?(query) elsif (multiplex = options[:multiplex]) if multiplex.queries.length == 1 detailed_trace?(multiplex.queries.first) else detailed_trace?(multiplex) end end else false end if should_sample mode = detailed_trace.trace_mode else target = options[:query] || options[:multiplex] mode ||= target && target.context[:trace_mode] end trace_mode = mode || default_trace_mode base_trace_options = trace_options_for(trace_mode) trace_options = base_trace_options.merge(options) trace_class_for_mode = trace_class_for(trace_mode, build: true) trace_class_for_mode.new(**trace_options) end # @param new_analyzer [Class] An analyzer to run on queries to this schema # @see GraphQL::Analysis the analysis system def query_analyzer(new_analyzer) own_query_analyzers << new_analyzer end def query_analyzers inherited_qa = find_inherited_value(:query_analyzers, EMPTY_ARRAY) inherited_qa.empty? ? own_query_analyzers : (inherited_qa + own_query_analyzers) end # @param new_analyzer [Class] An analyzer to run on multiplexes to this schema # @see GraphQL::Analysis the analysis system def multiplex_analyzer(new_analyzer) own_multiplex_analyzers << new_analyzer end def multiplex_analyzers find_inherited_value(:multiplex_analyzers, EMPTY_ARRAY) + own_multiplex_analyzers end def sanitized_printer(new_sanitized_printer = nil) if new_sanitized_printer @own_sanitized_printer = new_sanitized_printer else @own_sanitized_printer || GraphQL::Language::SanitizedPrinter end end # Execute a query on itself. # @see {Query#initialize} for arguments. # @return [GraphQL::Query::Result] query result, ready to be serialized as JSON def execute(query_str = nil, **kwargs) if query_str kwargs[:query] = query_str end # Some of the query context _should_ be passed to the multiplex, too multiplex_context = if (ctx = kwargs[:context]) { backtrace: ctx[:backtrace], tracers: ctx[:tracers], trace: ctx[:trace], dataloader: ctx[:dataloader], trace_mode: ctx[:trace_mode], } else {} end # Since we're running one query, don't run a multiplex-level complexity analyzer all_results = multiplex([kwargs], max_complexity: nil, context: multiplex_context) all_results[0] end # Execute several queries on itself, concurrently. # # @example Run several queries at once # context = { ... } # queries = [ # { query: params[:query_1], variables: params[:variables_1], context: context }, # { query: params[:query_2], variables: params[:variables_2], context: context }, # ] # results = MySchema.multiplex(queries) # render json: { # result_1: results[0], # result_2: results[1], # } # # @see {Query#initialize} for query keyword arguments # @see {Execution::Multiplex#run_all} for multiplex keyword arguments # @param queries [Array] Keyword arguments for each query # @option kwargs [Hash] :context ({}) Multiplex-level context # @option kwargs [nil, Integer] :max_complexity (nil) # @return [Array] One result for each query in the input def multiplex(queries, **kwargs) GraphQL::Execution::Interpreter.run_all(self, queries, **kwargs) end def instrumenters inherited_instrumenters = find_inherited_value(:instrumenters) || Hash.new { |h,k| h[k] = [] } inherited_instrumenters.merge(own_instrumenters) do |_step, inherited, own| inherited + own end end # @api private def add_subscription_extension_if_necessary # TODO: when there's a proper API for extending root types, migrat this to use it. if !defined?(@subscription_extension_added) && @subscription_object.is_a?(Class) && self.subscriptions @subscription_extension_added = true subscription.all_field_definitions.each do |field| if !field.extensions.any? { |ext| ext.is_a?(Subscriptions::DefaultSubscriptionResolveExtension) } field.extension(Subscriptions::DefaultSubscriptionResolveExtension) end end end end # Called when execution encounters a `SystemStackError`. By default, it adds a client-facing error to the response. # You could modify this method to report this error to your bug tracker. # @param query [GraphQL::Query] # @param err [SystemStackError] # @return [void] def query_stack_error(query, err) query.context.errors.push(GraphQL::ExecutionError.new("This query is too large to execute.")) end # Call the given block at the right time, either: # - Right away, if `value` is not registered with `lazy_resolve` # - After resolving `value`, if it's registered with `lazy_resolve` (eg, `Promise`) # @api private def after_lazy(value, &block) if lazy?(value) GraphQL::Execution::Lazy.new do result = sync_lazy(value) # The returned result might also be lazy, so check it, too after_lazy(result, &block) end else yield(value) if block_given? end end # Override this method to handle lazy objects in a custom way. # @param value [Object] an instance of a class registered with {.lazy_resolve} # @return [Object] A GraphQL-ready (non-lazy) object # @api private def sync_lazy(value) lazy_method = lazy_method_name(value) if lazy_method synced_value = value.public_send(lazy_method) sync_lazy(synced_value) else value end end # @return [Symbol, nil] The method name to lazily resolve `obj`, or nil if `obj`'s class wasn't registered with {.lazy_resolve}. def lazy_method_name(obj) lazy_methods.get(obj) end # @return [Boolean] True if this object should be lazily resolved def lazy?(obj) !!lazy_method_name(obj) end # Return a lazy if any of `maybe_lazies` are lazy, # otherwise, call the block eagerly and return the result. # @param maybe_lazies [Array] # @api private def after_any_lazies(maybe_lazies) if maybe_lazies.any? { |l| lazy?(l) } GraphQL::Execution::Lazy.all(maybe_lazies).then do |result| yield result end else yield maybe_lazies end end # Returns `DidYouMean` if it's defined. # Override this to return `nil` if you don't want to use `DidYouMean` def did_you_mean(new_dym = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_dym) if defined?(@did_you_mean) @did_you_mean else find_inherited_value(:did_you_mean, defined?(DidYouMean) ? DidYouMean : nil) end else @did_you_mean = new_dym end end # This setting controls how GraphQL-Ruby handles empty selections on Union types. # # To opt into future, spec-compliant behavior where these selections are rejected, set this to `false`. # # If you need to support previous, non-spec behavior which allowed selecting union fields # but *not* selecting any fields on that union, set this to `true` to continue allowing that behavior. # # If this is `true`, then {.legacy_invalid_empty_selections_on_union_with_type} will be called with {Query} objects # with that kind of selections. You must implement that method # @param new_value [Boolean] # @return [true, false, nil] def allow_legacy_invalid_empty_selections_on_union(new_value = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_value) if defined?(@allow_legacy_invalid_empty_selections_on_union) @allow_legacy_invalid_empty_selections_on_union else find_inherited_value(:allow_legacy_invalid_empty_selections_on_union) end else @allow_legacy_invalid_empty_selections_on_union = new_value end end # This method is called during validation when a previously-allowed, but non-spec # query is encountered where a union field has no child selections on it. # # If `legacy_invalid_empty_selections_on_union_with_type` is overridden, this method will not be called. # # You should implement this method or `legacy_invalid_empty_selections_on_union_with_type` # to log the violation so that you can contact clients and notify them about changing their queries. # Then return a suitable value to tell GraphQL-Ruby how to continue. # @param query [GraphQL::Query] # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query # @return [String] A validation error to return for this query # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute) def legacy_invalid_empty_selections_on_union(query) raise "Implement `def self.legacy_invalid_empty_selections_on_union_with_type(query, type)` or `def self.legacy_invalid_empty_selections_on_union(query)` to handle this scenario" end # This method is called during validation when a previously-allowed, but non-spec # query is encountered where a union field has no child selections on it. # # You should implement this method to log the violation so that you can contact clients # and notify them about changing their queries. Then return a suitable value to # tell GraphQL-Ruby how to continue. # @param query [GraphQL::Query] # @param type [Module] A GraphQL type definition # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query # @return [String] A validation error to return for this query # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute) def legacy_invalid_empty_selections_on_union_with_type(query, type) legacy_invalid_empty_selections_on_union(query) end # This setting controls how GraphQL-Ruby handles overlapping selections on scalar types when the types # don't match. # # When set to `false`, GraphQL-Ruby will reject those queries with a validation error (as per the GraphQL spec). # # When set to `true`, GraphQL-Ruby will call {.legacy_invalid_return_type_conflicts} when the scenario is encountered. # # @param new_value [Boolean] `true` permits the legacy behavior, `false` rejects it. # @return [true, false, nil] def allow_legacy_invalid_return_type_conflicts(new_value = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_value) if defined?(@allow_legacy_invalid_return_type_conflicts) @allow_legacy_invalid_return_type_conflicts else find_inherited_value(:allow_legacy_invalid_return_type_conflicts) end else @allow_legacy_invalid_return_type_conflicts = new_value end end # This method is called when the query contains fields which don't contain matching scalar types. # This was previously allowed by GraphQL-Ruby but it's a violation of the GraphQL spec. # # You should implement this method to log the violation so that you observe usage of these fields. # Fixing this scenario might mean adding new fields, and telling clients to use those fields. # (Changing the field return type would be a breaking change, but if it works for your client use cases, # that might work, too.) # # @param query [GraphQL::Query] # @param type1 [Module] A GraphQL type definition # @param type2 [Module] A GraphQL type definition # @param node1 [GraphQL::Language::Nodes::Field] This node is recognized as conflicting. You might call `.line` and `.col` for custom error reporting. # @param node2 [GraphQL::Language::Nodes::Field] The other node recognized as conflicting. # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query # @return [String] A validation error to return for this query # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute) def legacy_invalid_return_type_conflicts(query, type1, type2, node1, node2) raise "Implement #{self}.legacy_invalid_return_type_conflicts to handle this invalid selection" end # The legacy complexity implementation included several bugs: # # - In some cases, it used the lexically _last_ field to determine a cost, instead of calculating the maximum among selections # - In some cases, it called field complexity hooks repeatedly (when it should have only called them once) # # The future implementation may produce higher total complexity scores, so it's not active by default yet. You can opt into # the future default behavior by configuring `:future` here. Or, you can choose a mode for each query with {.complexity_cost_calculation_mode_for}. # # The legacy mode is currently maintained alongside the future one, but it will be removed in a future GraphQL-Ruby version. # # If you choose `:compare`, you must also implement {.legacy_complexity_cost_calculation_mismatch} to handle the input somehow. # # @example Opting into the future calculation mode # complexity_cost_calculation_mode(:future) # # @example Choosing the legacy mode (which will work until that mode is removed...) # complexity_cost_calculation_mode(:legacy) # # @example Run both modes for every query, call {.legacy_complexity_cost_calculation_mismatch} when they don't match: # complexity_cost_calculation_mode(:compare) def complexity_cost_calculation_mode(new_mode = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_mode) if defined?(@complexity_cost_calculation_mode) @complexity_cost_calculation_mode else find_inherited_value(:complexity_cost_calculation_mode) end else @complexity_cost_calculation_mode = new_mode end end # Implement this method to produce a per-query complexity cost calculation mode. (Technically, it's per-multiplex.) # # This is a way to check the compatibility of queries coming to your API without adding overhead of running `:compare` # for every query. You could sample traffic, turn it off/on with feature flags, or anything else. # # @example Sampling traffic # def self.complexity_cost_calculation_mode_for(_context) # if rand < 0.1 # 10% of the time # :compare # else # :legacy # end # end # # @example Using a feature flag to manage future mode # def complexity_cost_calculation_mode_for(context) # current_user = context[:current_user] # if Flipper.enabled?(:future_complexity_cost, current_user) # :future # elsif rand < 0.5 # 50% # :compare # else # :legacy # end # end # # @param multiplex_context [Hash] The context for the currently-running {Execution::Multiplex} (which contains one or more queries) # @return [:future] Use the new calculation algorithm -- may be higher than `:legacy` # @return [:legacy] Use the legacy calculation algorithm, warts and all # @return [:compare] Run both algorithms and call {.legacy_complexity_cost_calculation_mismatch} if they don't match def complexity_cost_calculation_mode_for(multiplex_context) complexity_cost_calculation_mode end # Implement this method in your schema to handle mismatches when `:compare` is used. # # @example Logging the mismatch # def self.legacy_cost_calculation_mismatch(multiplex, future_cost, legacy_cost) # client_id = multiplex.context[:api_client].id # operation_names = multiplex.queries.map { |q| q.selected_operation_name || "anonymous" }.join(", ") # Stats.increment(:complexity_mismatch, tags: { client: client_id, ops: operation_names }) # legacy_cost # end # @see Query::Context#add_error Adding an error to the response to notify the client # @see Query::Context#response_extensions Adding key-value pairs to the response `"extensions" => { ... }` # @param multiplex [GraphQL::Execution::Multiplex] # @param future_complexity_cost [Integer] # @param legacy_complexity_cost [Integer] # @return [Integer] the cost to use for this query (probably one of `future_complexity_cost` or `legacy_complexity_cost`) def legacy_complexity_cost_calculation_mismatch(multiplex, future_complexity_cost, legacy_complexity_cost) raise "Implement #{self}.legacy_complexity_cost(multiplex, future_complexity_cost, legacy_complexity_cost) to handle this mismatch (#{future_complexity_cost} vs. #{legacy_complexity_cost}) and return a value to use" end private def add_trace_options_for(mode, new_options) if mode == :default own_trace_modes.each do |mode_name, t_class| if t_class <= DefaultTraceClass t_opts = trace_options_for(mode_name) t_opts.merge!(new_options) end end else t_opts = trace_options_for(mode) t_opts.merge!(new_options) end nil end # @param t [Module, Array] # @return [void] def add_type_and_traverse(t, root:) if root @root_types ||= [] @root_types << t end new_types = Array(t) addition = Schema::Addition.new(schema: self, own_types: own_types, new_types: new_types) addition.types.each do |name, types_entry| # rubocop:disable Development/ContextIsPassedCop -- build-time, not query-time if (prev_entry = own_types[name]) prev_entries = case prev_entry when Array prev_entry when Module own_types[name] = [prev_entry] else raise "Invariant: unexpected prev_entry at #{name.inspect} when adding #{t.inspect}" end case types_entry when Array prev_entries.concat(types_entry) prev_entries.uniq! # in case any are being re-visited when Module if !prev_entries.include?(types_entry) prev_entries << types_entry end else raise "Invariant: unexpected types_entry at #{name} when adding #{t.inspect}" end else if types_entry.is_a?(Array) types_entry.uniq! end own_types[name] = types_entry end end own_possible_types.merge!(addition.possible_types) { |key, old_val, new_val| old_val + new_val } own_union_memberships.merge!(addition.union_memberships) addition.references.each { |thing, pointers| prev_refs = own_references_to[thing] || [] own_references_to[thing] = prev_refs | pointers.to_a } addition.directives.each { |dir_class| own_directives[dir_class.graphql_name] = dir_class } addition.arguments_with_default_values.each do |arg| arg.validate_default_value end end def lazy_methods if !defined?(@lazy_methods) if inherited_map = find_inherited_value(:lazy_methods) # this isn't _completely_ inherited :S (Things added after `dup` won't work) @lazy_methods = inherited_map.dup else @lazy_methods = GraphQL::Execution::Lazy::LazyMethodMap.new @lazy_methods.set(GraphQL::Execution::Lazy, :value) @lazy_methods.set(GraphQL::Dataloader::Request, :load_with_deprecation_warning) end end @lazy_methods end def own_types @own_types ||= {} end def own_references_to @own_references_to ||= {}.compare_by_identity end def non_introspection_types find_inherited_value(:non_introspection_types, EMPTY_HASH).merge(own_types) end def own_plugins @own_plugins ||= [] end def own_orphan_types @own_orphan_types ||= [] end def own_possible_types @own_possible_types ||= {}.compare_by_identity end def own_union_memberships @own_union_memberships ||= {} end def own_directives @own_directives ||= {} end def own_instrumenters @own_instrumenters ||= Hash.new { |h,k| h[k] = [] } end def own_tracers @own_tracers ||= [] end def own_query_analyzers @defined_query_analyzers ||= [] end def own_multiplex_analyzers @own_multiplex_analyzers ||= [] end # This is overridden in subclasses to check the inheritance chain def get_references_to(type_defn) own_references_to[type_defn] end end module SubclassGetReferencesTo def get_references_to(type_defn) own_refs = own_references_to[type_defn] inherited_refs = superclass.references_to(type_defn) if inherited_refs&.any? if own_refs&.any? own_refs + inherited_refs else inherited_refs end else own_refs end end end # Install these here so that subclasses will also install it. self.connections = GraphQL::Pagination::Connections.new(schema: self) # @api private module DefaultTraceClass end end end require "graphql/schema/loader" require "graphql/schema/printer" graphql-2.6.0/lib/graphql/introspection/0000755000004100000410000000000015173430257020320 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/introspection/input_value_type.rb0000644000004100000410000000544015173430257024244 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Introspection class InputValueType < Introspection::BaseObject graphql_name "__InputValue" description "Arguments provided to Fields or Directives and the input fields of an "\ "InputObject are represented as Input Values which describe their type and "\ "optionally a default value." field :name, String, null: false field :description, String field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false field :default_value, String, "A GraphQL-formatted string representing the default value for this input value.", resolve_each: :resolve_default_value field :is_deprecated, Boolean, null: false, resolve_each: :resolve_is_deprecated field :deprecation_reason, String def self.resolve_is_deprecated(object, context) !!object.deprecation_reason end def is_deprecated self.class.resolve_is_deprecated(object, context) end def self.resolve_default_value(object, context) if object.default_value? value = object.default_value if value.nil? 'null' else if (object.type.kind.list? || (object.type.kind.non_null? && object.type.of_type.kind.list?)) && !value.respond_to?(:map) # This is a bit odd -- we expect the default value to be an application-style value, so we use coerce result below. # But coerce_result doesn't wrap single-item lists, which are valid inputs to list types. # So, apply that wrapper here if needed. value = [value] end coerced_default_value = object.type.coerce_result(value, context) serialize_default_value(coerced_default_value, object.type, context) end else nil end end def default_value self.class.resolve_default_value(object, context) end private # Recursively serialize, taking care not to add quotes to enum values def self.serialize_default_value(value, type, context) if value.nil? 'null' elsif type.kind.list? inner_type = type.of_type "[" + value.map { |v| serialize_default_value(v, inner_type, context) }.join(", ") + "]" elsif type.kind.non_null? serialize_default_value(value, type.of_type, context) elsif type.kind.enum? value elsif type.kind.input_object? "{" + value.map do |k, v| arg_defn = type.get_argument(k, context) "#{k}: #{serialize_default_value(v, arg_defn.type, context)}" end.join(", ") + "}" else GraphQL::Language.serialize(value) end end end end end graphql-2.6.0/lib/graphql/introspection/entry_points.rb0000644000004100000410000000226115173430257023403 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Introspection class EntryPoints < Introspection::BaseObject field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false, dynamic_introspection: true, resolve_static: :__schema field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", dynamic_introspection: true, resolve_static: :__type do argument :name, String end def self.__schema(context) # Apply wrapping manually since this field isn't wrapped by instrumentation schema = context.schema schema_type = schema.introspection_system.types["__Schema"] schema_type.wrap(schema, context) end def __schema self.class.__schema(context) end def __type(name:) self.class.__type(context, name: name) end def self.__type(context, name:) if context.types.reachable_type?(name) && (type = context.types.type(name)) type elsif (type = context.schema.extra_types.find { |t| t.graphql_name == name }) type else nil end end end end end graphql-2.6.0/lib/graphql/introspection/enum_value_type.rb0000644000004100000410000000150215173430257024044 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Introspection class EnumValueType < Introspection::BaseObject graphql_name "__EnumValue" description "One possible value for a given Enum. Enum values are unique values, not a "\ "placeholder for a string or numeric value. However an Enum value is returned in "\ "a JSON response as a string." field :name, String, null: false, method: :graphql_name field :description, String field :is_deprecated, Boolean, null: false, resolve_each: :resolve_is_deprecated field :deprecation_reason, String def self.resolve_is_deprecated(object, context) !!object.deprecation_reason end def is_deprecated self.class.resolve_is_deprecated(object, context) end end end end graphql-2.6.0/lib/graphql/introspection/introspection_query.rb0000644000004100000410000000050315173430257024770 0ustar www-datawww-data# frozen_string_literal: true # This query is used by graphql-client so don't add the includeDeprecated # argument for inputFields since the server may not support it. Two stage # introspection queries will be required to handle this in clients. GraphQL::Introspection::INTROSPECTION_QUERY = GraphQL::Introspection.query graphql-2.6.0/lib/graphql/introspection/schema_type.rb0000644000004100000410000000346415173430257023155 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Introspection class SchemaType < Introspection::BaseObject graphql_name "__Schema" description "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all "\ "available types and directives on the server, as well as the entry points for "\ "query, mutation, and subscription operations." field :types, [GraphQL::Schema::LateBoundType.new("__Type")], "A list of all types supported by this server.", null: false, scope: false field :query_type, GraphQL::Schema::LateBoundType.new("__Type"), "The type that query operations will be rooted at.", null: false field :mutation_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server supports mutation, the type that mutation operations will be rooted at." field :subscription_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server support subscription, the type that subscription operations will be rooted at." field :directives, [GraphQL::Schema::LateBoundType.new("__Directive")], "A list of all directives supported by this server.", null: false, scope: false field :description, String, resolver_method: :schema_description def schema_description context.schema.description end def types query_types = context.types.all_types types = query_types + context.schema.extra_types types.sort_by!(&:graphql_name) types end def query_type @context.types.query_root end def mutation_type @context.types.mutation_root end def subscription_type @context.types.subscription_root end def directives @context.types.directives.sort_by(&:graphql_name) end end end end graphql-2.6.0/lib/graphql/introspection/dynamic_fields.rb0000644000004100000410000000065015173430257023620 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Introspection class DynamicFields < Introspection::BaseObject field :__typename, String, "The name of this type", null: false, dynamic_introspection: true, resolve_each: true def __typename self.class.__typename(object, context) end def self.__typename(object, context) object.class.graphql_name end end end end graphql-2.6.0/lib/graphql/introspection/field_type.rb0000644000004100000410000000255615173430257023001 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Introspection class FieldType < Introspection::BaseObject graphql_name "__Field" description "Object and Interface types are described by a list of Fields, each of which has "\ "a name, potentially a list of arguments, and a return type." field :name, String, null: false field :description, String field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false, resolve_each: :resolve_args do argument :include_deprecated, Boolean, required: false, default_value: false end field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false field :is_deprecated, Boolean, null: false, resolve_each: :resolve_is_deprecated field :deprecation_reason, String def self.resolve_is_deprecated(object, _context) !!object.deprecation_reason end def is_deprecated self.class.resolve_is_deprecated(object, context) end def self.resolve_args(object, context, include_deprecated:) args = context.types.arguments(object) args = args.reject(&:deprecation_reason) unless include_deprecated args end def args(include_deprecated:) self.class.resolve_args(object, context, include_deprecated: include_deprecated) end end end end graphql-2.6.0/lib/graphql/introspection/base_object.rb0000644000004100000410000000044515173430257023110 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Introspection class BaseObject < GraphQL::Schema::Object introspection(true) def self.field(*args, **kwargs, &block) kwargs[:introspection] = true super(*args, **kwargs, &block) end end end end graphql-2.6.0/lib/graphql/introspection/directive_location_enum.rb0000644000004100000410000000110615173430257025535 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Introspection class DirectiveLocationEnum < GraphQL::Schema::Enum graphql_name "__DirectiveLocation" description "A Directive can be adjacent to many parts of the GraphQL language, "\ "a __DirectiveLocation describes one such possible adjacencies." GraphQL::Schema::Directive::LOCATIONS.each do |location| value(location.to_s, GraphQL::Schema::Directive::LOCATION_DESCRIPTIONS[location], value: location, value_method: false) end introspection true end end end graphql-2.6.0/lib/graphql/introspection/directive_type.rb0000644000004100000410000000346415173430257023673 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Introspection class DirectiveType < Introspection::BaseObject graphql_name "__Directive" description "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document."\ "\n\n"\ "In some cases, you need to provide options to alter GraphQL's execution behavior "\ "in ways field arguments will not suffice, such as conditionally including or "\ "skipping a field. Directives provide this by describing additional information "\ "to the executor." field :name, String, null: false, method: :graphql_name field :description, String field :locations, [GraphQL::Schema::LateBoundType.new("__DirectiveLocation")], null: false, scope: false field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false, resolve_each: :resolve_args do argument :include_deprecated, Boolean, required: false, default_value: false end field :on_operation, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_operation? field :on_fragment, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_fragment? field :on_field, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_field? field :is_repeatable, Boolean, method: :repeatable? def self.resolve_args(object, context, include_deprecated:) args = context.types.arguments(object) args = args.reject(&:deprecation_reason) unless include_deprecated args end def args(include_deprecated:) self.class.resolve_args(object, context, include_deprecated: include_deprecated) end end end end graphql-2.6.0/lib/graphql/introspection/type_type.rb0000644000004100000410000001204215173430257022666 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Introspection class TypeType < Introspection::BaseObject graphql_name "__Type" description "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in "\ "GraphQL as represented by the `__TypeKind` enum.\n\n"\ "Depending on the kind of a type, certain fields describe information about that type. "\ "Scalar types provide no information beyond a name and description, while "\ "Enum types provide their values. Object and Interface types provide the fields "\ "they describe. Abstract types, Union and Interface, provide the Object types "\ "possible at runtime. List and NonNull types compose other types." field :kind, GraphQL::Schema::LateBoundType.new("__TypeKind"), null: false, resolve_each: :resolve_kind field :name, String, method: :graphql_name field :description, String field :fields, [GraphQL::Schema::LateBoundType.new("__Field")], scope: false, resolve_each: :resolve_fields do argument :include_deprecated, Boolean, required: false, default_value: false end field :interfaces, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false, resolve_each: :resolve_interfaces field :possible_types, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false, resolve_each: :resolve_possible_types field :enum_values, [GraphQL::Schema::LateBoundType.new("__EnumValue")], scope: false, resolve_each: :resolve_enum_values do argument :include_deprecated, Boolean, required: false, default_value: false end field :input_fields, [GraphQL::Schema::LateBoundType.new("__InputValue")], scope: false, resolve_each: :resolve_input_fields do argument :include_deprecated, Boolean, required: false, default_value: false end field :of_type, GraphQL::Schema::LateBoundType.new("__Type"), resolve_each: :resolve_of_type field :specifiedByURL, String, resolve_each: :resolve_specified_by_url, resolver_method: :specified_by_url field :is_one_of, Boolean, null: false, resolve_each: :resolve_is_one_of def self.resolve_is_one_of(object, _ctx) object.kind.input_object? && object.directives.any? { |d| d.graphql_name == "oneOf" } end def is_one_of self.class.resolve_is_one_of(object, context) end def self.resolve_specified_by_url(object, _ctx) if object.kind.scalar? object.specified_by_url else nil end end def specified_by_url self.class.resolve_specified_by_url(object, context) end def self.resolve_kind(object, context) object.kind.name end def kind self.class.resolve_kind(object, context) end def self.resolve_enum_values(object, context, include_deprecated:) if !object.kind.enum? nil else enum_values = context.types.enum_values(object) if !include_deprecated enum_values = enum_values.select {|f| !f.deprecation_reason } end enum_values end end def enum_values(include_deprecated:) self.class.resolve_enum_values(object, context, include_deprecated: include_deprecated) end def self.resolve_interfaces(object, context) if object.kind.object? || object.kind.interface? context.types.interfaces(object).sort_by(&:graphql_name) else nil end end def interfaces self.class.resolve_interfaces(object, context) end def self.resolve_input_fields(object, context, include_deprecated:) if object.kind.input_object? args = context.types.arguments(object) args = args.reject(&:deprecation_reason) unless include_deprecated args else nil end end def input_fields(include_deprecated:) self.class.resolve_input_fields(object, context, include_deprecated: include_deprecated) end def self.resolve_possible_types(object, context) if object.kind.abstract? context.types.possible_types(object).sort_by(&:graphql_name) else nil end end def possible_types self.class.resolve_possible_types(object, context) end def self.resolve_fields(object, context, include_deprecated:) if !object.kind.fields? nil else fields = context.types.fields(object) if !include_deprecated fields = fields.select {|f| !f.deprecation_reason } end fields.sort_by(&:name) end end def fields(include_deprecated:) self.class.resolve_fields(object, context, include_deprecated: include_deprecated) end def self.resolve_of_type(object, _ctx) object.kind.wraps? ? object.of_type : nil end def of_type self.class.resolve_of_type(object, context) end end end end graphql-2.6.0/lib/graphql/introspection/type_kind_enum.rb0000644000004100000410000000060415173430257023657 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Introspection class TypeKindEnum < GraphQL::Schema::Enum graphql_name "__TypeKind" description "An enum describing what kind of type a given `__Type` is." GraphQL::TypeKinds::TYPE_KINDS.each do |type_kind| value(type_kind.name, type_kind.description) end introspection true end end end graphql-2.6.0/lib/graphql/invalid_name_error.rb0000644000004100000410000000046715173430257021613 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class InvalidNameError < GraphQL::Error attr_reader :name, :valid_regex def initialize(name, valid_regex) @name = name @valid_regex = valid_regex super("Names must match #{@valid_regex.inspect} but '#{@name}' does not") end end end graphql-2.6.0/lib/graphql/rubocop.rb0000644000004100000410000000036715173430257017424 0ustar www-datawww-data# frozen_string_literal: true require "graphql/rubocop/graphql/default_null_true" require "graphql/rubocop/graphql/default_required_true" require "graphql/rubocop/graphql/field_type_in_block" require "graphql/rubocop/graphql/root_types_in_block" graphql-2.6.0/lib/graphql/language/0000755000004100000410000000000015173430257017203 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/language/sanitized_printer.rb0000644000004100000410000001555115173430257023274 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Language # A custom printer used to print sanitized queries. It inlines provided variables # within the query for facilitate logging and analysis of queries. # # The printer returns `nil` if the query is invalid. # # Since the GraphQL Ruby AST for a GraphQL query doesnt contain any reference # on the type of fields or arguments, we have to track the current object, field # and input type while printing the query. # # @example Printing a scrubbed string # printer = QueryPrinter.new(query) # puts printer.sanitized_query_string # # @see {Query#sanitized_query_string} class SanitizedPrinter < GraphQL::Language::Printer REDACTED = "\"\"" def initialize(query, inline_variables: true) @query = query @current_type = nil @current_field = nil @current_input_type = nil @inline_variables = inline_variables end # @return [String, nil] A scrubbed query string, if the query was valid. def sanitized_query_string if query.valid? print(query.document) else nil end end def print_node(node, indent: "") case node when FalseClass, Float, Integer, String, TrueClass if @current_argument && redact_argument_value?(@current_argument, node) print_string(redacted_argument_value(@current_argument)) else super end when Array old_input_type = @current_input_type if @current_input_type && @current_input_type.list? @current_input_type = @current_input_type.of_type @current_input_type = @current_input_type.of_type if @current_input_type.non_null? end super @current_input_type = old_input_type else super end end # Indicates whether or not to redact non-null values for the given argument. Defaults to redacting all strings # arguments but this can be customized by subclasses. def redact_argument_value?(argument, value) # Default to redacting any strings or custom scalars encoded as strings type = argument.type.unwrap value.is_a?(String) && type.kind.scalar? && (type.graphql_name == "String" || !type.default_scalar?) end # Returns the value to use for redacted versions of the given argument. Defaults to the # string "". def redacted_argument_value(argument) REDACTED end def print_argument(argument) # We won't have type information if we're recursing into a custom scalar return super if @current_input_type && @current_input_type.kind.scalar? arg_owner = @current_input_type || @current_directive || @current_field old_current_argument = @current_argument @current_argument = arg_owner.get_argument(argument.name, @query.context) old_input_type = @current_input_type @current_input_type = @current_argument.type.non_null? ? @current_argument.type.of_type : @current_argument.type argument_value = if coerce_argument_value_to_list?(@current_input_type, argument.value) [argument.value] else argument.value end print_string("#{argument.name}: ") print_node(argument_value) @current_input_type = old_input_type @current_argument = old_current_argument end def coerce_argument_value_to_list?(type, value) type.list? && !value.is_a?(Array) && !value.nil? && !value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) end def print_variable_identifier(variable_id) if @inline_variables variable_value = query.variables[variable_id.name] print_node(value_to_ast(variable_value, @current_input_type)) else super end end def print_field(field, indent: "") @current_field = query.types.field(@current_type, field.name) old_type = @current_type @current_type = @current_field.type.unwrap super @current_type = old_type end def print_inline_fragment(inline_fragment, indent: "") old_type = @current_type if inline_fragment.type @current_type = query.get_type(inline_fragment.type.name) end super @current_type = old_type end def print_fragment_definition(fragment_def, indent: "") old_type = @current_type @current_type = query.get_type(fragment_def.type.name) super @current_type = old_type end def print_directive(directive) @current_directive = query.schema.directives[directive.name] super @current_directive = nil end # Print the operation definition but do not include the variable # definitions since we will inline them within the query def print_operation_definition(operation_definition, indent: "") old_type = @current_type @current_type = query.schema.public_send(operation_definition.operation_type) if @inline_variables print_string("#{indent}#{operation_definition.operation_type}") print_string(" #{operation_definition.name}") if operation_definition.name print_directives(operation_definition.directives) print_selections(operation_definition.selections, indent: indent) else super end @current_type = old_type end private def value_to_ast(value, type) type = type.of_type if type.non_null? if value.nil? return GraphQL::Language::Nodes::NullValue.new(name: "null") end case type.kind.name when "INPUT_OBJECT" value = if value.respond_to?(:to_unsafe_h) # for ActionController::Parameters value.to_unsafe_h else value.to_h end arguments = value.map do |key, val| sub_type = type.get_argument(key.to_s, @query.context).type GraphQL::Language::Nodes::Argument.new( name: key.to_s, value: value_to_ast(val, sub_type) ) end GraphQL::Language::Nodes::InputObject.new( arguments: arguments ) when "LIST" if value.is_a?(Array) value.map { |v| value_to_ast(v, type.of_type) } else [value].map { |v| value_to_ast(v, type.of_type) } end when "ENUM" if value.is_a?(GraphQL::Language::Nodes::Enum) # if it was a default value, it's already wrapped value else GraphQL::Language::Nodes::Enum.new(name: value) end else value end end attr_reader :query end end end graphql-2.6.0/lib/graphql/language/static_visitor.rb0000644000004100000410000001405715173430257022605 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Language # Like `GraphQL::Language::Visitor` except it doesn't support # making changes to the document -- only visiting it as-is. class StaticVisitor def initialize(document) @document = document end # Visit `document` and all children # @return [void] def visit # `@document` may be any kind of node: visit_method = @document.visit_method result = public_send(visit_method, @document, nil) @result = if result.is_a?(Array) result.first else # The node wasn't modified @document end end def on_document_children(document_node) document_node.children.each do |child_node| visit_method = child_node.visit_method public_send(visit_method, child_node, document_node) end end def on_field_children(new_node) new_node.arguments.each do |arg_node| # rubocop:disable Development/ContextIsPassedCop on_argument(arg_node, new_node) end visit_directives(new_node) visit_selections(new_node) end def visit_directives(new_node) new_node.directives.each do |dir_node| on_directive(dir_node, new_node) end end def visit_selections(new_node) new_node.selections.each do |selection| case selection when GraphQL::Language::Nodes::Field on_field(selection, new_node) when GraphQL::Language::Nodes::InlineFragment on_inline_fragment(selection, new_node) when GraphQL::Language::Nodes::FragmentSpread on_fragment_spread(selection, new_node) else raise ArgumentError, "Invariant: unexpected field selection #{selection.class} (#{selection.inspect})" end end end def on_fragment_definition_children(new_node) visit_directives(new_node) visit_selections(new_node) end alias :on_inline_fragment_children :on_fragment_definition_children def on_operation_definition_children(new_node) new_node.variables.each do |arg_node| on_variable_definition(arg_node, new_node) end visit_directives(new_node) visit_selections(new_node) end def on_argument_children(new_node) new_node.children.each do |value_node| case value_node when Language::Nodes::VariableIdentifier on_variable_identifier(value_node, new_node) when Language::Nodes::InputObject on_input_object(value_node, new_node) when Language::Nodes::Enum on_enum(value_node, new_node) when Language::Nodes::NullValue on_null_value(value_node, new_node) else raise ArgumentError, "Invariant: unexpected argument value node #{value_node.class} (#{value_node.inspect})" end end end # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time # We don't use `alias` here because it breaks `super` def self.make_visit_methods(ast_node_class) node_method = ast_node_class.visit_method children_of_type = ast_node_class.children_of_type child_visit_method = :"#{node_method}_children" class_eval(<<-RUBY, __FILE__, __LINE__ + 1) # The default implementation for visiting an AST node. # It doesn't _do_ anything, but it continues to visiting the node's children. # To customize this hook, override one of its make_visit_methods (or the base method?) # in your subclasses. # # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node. # @return [void] def #{node_method}(node, parent) #{ if method_defined?(child_visit_method) "#{child_visit_method}(node)" elsif children_of_type children_of_type.map do |child_accessor, child_class| "node.#{child_accessor}.each do |child_node| #{child_class.visit_method}(child_node, node) end" end.join("\n") else "" end } end RUBY end [ Language::Nodes::Argument, Language::Nodes::Directive, Language::Nodes::DirectiveDefinition, Language::Nodes::DirectiveLocation, Language::Nodes::Document, Language::Nodes::Enum, Language::Nodes::EnumTypeDefinition, Language::Nodes::EnumTypeExtension, Language::Nodes::EnumValueDefinition, Language::Nodes::Field, Language::Nodes::FieldDefinition, Language::Nodes::FragmentDefinition, Language::Nodes::FragmentSpread, Language::Nodes::InlineFragment, Language::Nodes::InputObject, Language::Nodes::InputObjectTypeDefinition, Language::Nodes::InputObjectTypeExtension, Language::Nodes::InputValueDefinition, Language::Nodes::InterfaceTypeDefinition, Language::Nodes::InterfaceTypeExtension, Language::Nodes::ListType, Language::Nodes::NonNullType, Language::Nodes::NullValue, Language::Nodes::ObjectTypeDefinition, Language::Nodes::ObjectTypeExtension, Language::Nodes::OperationDefinition, Language::Nodes::ScalarTypeDefinition, Language::Nodes::ScalarTypeExtension, Language::Nodes::SchemaDefinition, Language::Nodes::SchemaExtension, Language::Nodes::TypeName, Language::Nodes::UnionTypeDefinition, Language::Nodes::UnionTypeExtension, Language::Nodes::VariableDefinition, Language::Nodes::VariableIdentifier, ].each do |ast_node_class| make_visit_methods(ast_node_class) end # rubocop:disable Development/NoEvalCop end end end graphql-2.6.0/lib/graphql/language/visitor.rb0000644000004100000410000002610615173430257021234 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Language # Depth-first traversal through the tree, calling hooks at each stop. # # @example Create a visitor counting certain field names # class NameCounter < GraphQL::Language::Visitor # def initialize(document, field_name) # super(document) # @field_name = field_name # @count = 0 # end # # attr_reader :count # # def on_field(node, parent) # # if this field matches our search, increment the counter # if node.name == @field_name # @count += 1 # end # # Continue visiting subfields: # super # end # end # # # Initialize a visitor # visitor = NameCounter.new(document, "name") # # Run it # visitor.visit # # Check the result # visitor.count # # => 3 # # @see GraphQL::Language::StaticVisitor for a faster visitor that doesn't support modifying the document class Visitor class DeleteNode; end # When this is returned from a visitor method, # Then the `node` passed into the method is removed from `parent`'s children. DELETE_NODE = DeleteNode.new def initialize(document) @document = document @result = nil end # @return [GraphQL::Language::Nodes::Document] The document with any modifications applied attr_reader :result # Visit `document` and all children # @return [void] def visit # `@document` may be any kind of node: visit_method = :"#{@document.visit_method}_with_modifications" result = public_send(visit_method, @document, nil) @result = if result.is_a?(Array) result.first else # The node wasn't modified @document end end def on_document_children(document_node) new_node = document_node document_node.children.each do |child_node| visit_method = :"#{child_node.visit_method}_with_modifications" new_child_and_node = public_send(visit_method, child_node, new_node) # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end new_node end def on_field_children(new_node) new_node.arguments.each do |arg_node| # rubocop:disable Development/ContextIsPassedCop new_child_and_node = on_argument_with_modifications(arg_node, new_node) # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end new_node = visit_directives(new_node) new_node = visit_selections(new_node) new_node end def visit_directives(new_node) new_node.directives.each do |dir_node| new_child_and_node = on_directive_with_modifications(dir_node, new_node) # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end new_node end def visit_selections(new_node) new_node.selections.each do |selection| new_child_and_node = case selection when GraphQL::Language::Nodes::Field on_field_with_modifications(selection, new_node) when GraphQL::Language::Nodes::InlineFragment on_inline_fragment_with_modifications(selection, new_node) when GraphQL::Language::Nodes::FragmentSpread on_fragment_spread_with_modifications(selection, new_node) else raise ArgumentError, "Invariant: unexpected field selection #{selection.class} (#{selection.inspect})" end # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end new_node end def on_fragment_definition_children(new_node) new_node = visit_directives(new_node) new_node = visit_selections(new_node) new_node end alias :on_inline_fragment_children :on_fragment_definition_children def on_operation_definition_children(new_node) new_node.variables.each do |arg_node| new_child_and_node = on_variable_definition_with_modifications(arg_node, new_node) # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end new_node = visit_directives(new_node) new_node = visit_selections(new_node) new_node end def on_argument_children(new_node) new_node.children.each do |value_node| new_child_and_node = case value_node when Language::Nodes::VariableIdentifier on_variable_identifier_with_modifications(value_node, new_node) when Language::Nodes::InputObject on_input_object_with_modifications(value_node, new_node) when Language::Nodes::Enum on_enum_with_modifications(value_node, new_node) when Language::Nodes::NullValue on_null_value_with_modifications(value_node, new_node) else raise ArgumentError, "Invariant: unexpected argument value node #{value_node.class} (#{value_node.inspect})" end # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end new_node end # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time # We don't use `alias` here because it breaks `super` def self.make_visit_methods(ast_node_class) node_method = ast_node_class.visit_method children_of_type = ast_node_class.children_of_type child_visit_method = :"#{node_method}_children" class_eval(<<-RUBY, __FILE__, __LINE__ + 1) # The default implementation for visiting an AST node. # It doesn't _do_ anything, but it continues to visiting the node's children. # To customize this hook, override one of its make_visit_methods (or the base method?) # in your subclasses. # # @param node [GraphQL::Language::Nodes::AbstractNode] the node being visited # @param parent [GraphQL::Language::Nodes::AbstractNode, nil] the previously-visited node, or `nil` if this is the root node. # @return [Array, nil] If there were modifications, it returns an array of new nodes, otherwise, it returns `nil`. def #{node_method}(node, parent) if node.equal?(DELETE_NODE) # This might be passed to `super(DELETE_NODE, ...)` # by a user hook, don't want to keep visiting in that case. [node, parent] else new_node = node #{ if method_defined?(child_visit_method) "new_node = #{child_visit_method}(new_node)" elsif children_of_type children_of_type.map do |child_accessor, child_class| "node.#{child_accessor}.each do |child_node| new_child_and_node = #{child_class.visit_method}_with_modifications(child_node, new_node) # Reassign `node` in case the child hook makes a modification if new_child_and_node.is_a?(Array) new_node = new_child_and_node[1] end end" end.join("\n") else "" end } if new_node.equal?(node) [node, parent] else [new_node, parent] end end end def #{node_method}_with_modifications(node, parent) new_node_and_new_parent = #{node_method}(node, parent) apply_modifications(node, parent, new_node_and_new_parent) end RUBY end [ Language::Nodes::Argument, Language::Nodes::Directive, Language::Nodes::DirectiveDefinition, Language::Nodes::DirectiveLocation, Language::Nodes::Document, Language::Nodes::Enum, Language::Nodes::EnumTypeDefinition, Language::Nodes::EnumTypeExtension, Language::Nodes::EnumValueDefinition, Language::Nodes::Field, Language::Nodes::FieldDefinition, Language::Nodes::FragmentDefinition, Language::Nodes::FragmentSpread, Language::Nodes::InlineFragment, Language::Nodes::InputObject, Language::Nodes::InputObjectTypeDefinition, Language::Nodes::InputObjectTypeExtension, Language::Nodes::InputValueDefinition, Language::Nodes::InterfaceTypeDefinition, Language::Nodes::InterfaceTypeExtension, Language::Nodes::ListType, Language::Nodes::NonNullType, Language::Nodes::NullValue, Language::Nodes::ObjectTypeDefinition, Language::Nodes::ObjectTypeExtension, Language::Nodes::OperationDefinition, Language::Nodes::ScalarTypeDefinition, Language::Nodes::ScalarTypeExtension, Language::Nodes::SchemaDefinition, Language::Nodes::SchemaExtension, Language::Nodes::TypeName, Language::Nodes::UnionTypeDefinition, Language::Nodes::UnionTypeExtension, Language::Nodes::VariableDefinition, Language::Nodes::VariableIdentifier, ].each do |ast_node_class| make_visit_methods(ast_node_class) end # rubocop:enable Development/NoEvalCop private def apply_modifications(node, parent, new_node_and_new_parent) if new_node_and_new_parent.is_a?(Array) new_node = new_node_and_new_parent[0] new_parent = new_node_and_new_parent[1] if new_node.is_a?(Nodes::AbstractNode) && !node.equal?(new_node) # The user-provided hook returned a new node. new_parent = new_parent && new_parent.replace_child(node, new_node) return new_node, new_parent elsif new_node.equal?(DELETE_NODE) # The user-provided hook requested to remove this node new_parent = new_parent && new_parent.delete_child(node) return nil, new_parent elsif new_node_and_new_parent.none? { |n| n == nil || n.class < Nodes::AbstractNode } # The user-provided hook returned an array of who-knows-what # return nil here to signify that no changes should be made nil else new_node_and_new_parent end else # The user-provided hook didn't make any modifications. # In fact, the hook might have returned who-knows-what, so # ignore the return value and use the original values. new_node_and_new_parent end end end end end graphql-2.6.0/lib/graphql/language/document_from_schema_definition.rb0000644000004100000410000003365015173430257026130 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Language # @api private # # {GraphQL::Language::DocumentFromSchemaDefinition} is used to convert a {GraphQL::Schema} object # To a {GraphQL::Language::Document} AST node. # # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] # @param include_introspection_types [Boolean] Whether or not to include introspection types in the AST # @param include_built_in_scalars [Boolean] Whether or not to include built in scalars in the AST # @param include_built_in_directives [Boolean] Whether or not to include built in directives in the AST class DocumentFromSchemaDefinition def initialize( schema, context: nil, include_introspection_types: false, include_built_in_directives: false, include_built_in_scalars: false, always_include_schema: false ) @schema = schema @context = context @always_include_schema = always_include_schema @include_introspection_types = include_introspection_types @include_built_in_scalars = include_built_in_scalars @include_built_in_directives = include_built_in_directives @include_one_of = false dummy_query = @schema.query_class.new(@schema, "{ __typename }", validate: false, context: context) @types = dummy_query.types # rubocop:disable Development/ContextIsPassedCop end def document GraphQL::Language::Nodes::Document.new( definitions: build_definition_nodes ) end def build_schema_node if !schema_respects_root_name_conventions?(@schema) GraphQL::Language::Nodes::SchemaDefinition.new( query: @types.query_root&.graphql_name, mutation: @types.mutation_root&.graphql_name, subscription: @types.subscription_root&.graphql_name, directives: definition_directives(@schema, :schema_directives) ) else # A plain `schema ...` _must_ include root type definitions. # If the only difference is directives, then you have to use `extend schema` GraphQL::Language::Nodes::SchemaExtension.new(directives: definition_directives(@schema, :schema_directives)) end end def build_object_type_node(object_type) ints = @types.interfaces(object_type) if !ints.empty? ints = ints.sort_by(&:graphql_name) ints.map! { |iface| build_type_name_node(iface) } end GraphQL::Language::Nodes::ObjectTypeDefinition.new( name: object_type.graphql_name, comment: object_type.comment, interfaces: ints, fields: build_field_nodes(@types.fields(object_type)), description: object_type.description, directives: directives(object_type), ) end def build_field_node(field) GraphQL::Language::Nodes::FieldDefinition.new( name: field.graphql_name, comment: field.comment, arguments: build_argument_nodes(@types.arguments(field)), type: build_type_name_node(field.type), description: field.description, directives: directives(field), ) end def build_union_type_node(union_type) GraphQL::Language::Nodes::UnionTypeDefinition.new( name: union_type.graphql_name, comment: union_type.comment, description: union_type.description, types: @types.possible_types(union_type).sort_by(&:graphql_name).map { |type| build_type_name_node(type) }, directives: directives(union_type), ) end def build_interface_type_node(interface_type) GraphQL::Language::Nodes::InterfaceTypeDefinition.new( name: interface_type.graphql_name, comment: interface_type.comment, interfaces: @types.interfaces(interface_type).sort_by(&:graphql_name).map { |type| build_type_name_node(type) }, description: interface_type.description, fields: build_field_nodes(@types.fields(interface_type)), directives: directives(interface_type), ) end def build_enum_type_node(enum_type) GraphQL::Language::Nodes::EnumTypeDefinition.new( name: enum_type.graphql_name, comment: enum_type.comment, values: @types.enum_values(enum_type).sort_by(&:graphql_name).map do |enum_value| build_enum_value_node(enum_value) end, description: enum_type.description, directives: directives(enum_type), ) end def build_enum_value_node(enum_value) GraphQL::Language::Nodes::EnumValueDefinition.new( name: enum_value.graphql_name, comment: enum_value.comment, description: enum_value.description, directives: directives(enum_value), ) end def build_scalar_type_node(scalar_type) GraphQL::Language::Nodes::ScalarTypeDefinition.new( name: scalar_type.graphql_name, comment: scalar_type.comment, description: scalar_type.description, directives: directives(scalar_type), ) end def build_argument_node(argument) if argument.default_value? default_value = build_default_value(argument.default_value, argument.type) else default_value = nil end argument_node = GraphQL::Language::Nodes::InputValueDefinition.new( name: argument.graphql_name, comment: argument.comment, description: argument.description, type: build_type_name_node(argument.type), default_value: default_value, directives: directives(argument), ) argument_node end def build_input_object_node(input_object) GraphQL::Language::Nodes::InputObjectTypeDefinition.new( name: input_object.graphql_name, comment: input_object.comment, fields: build_argument_nodes(@types.arguments(input_object)), description: input_object.description, directives: directives(input_object), ) end def build_directive_node(directive) GraphQL::Language::Nodes::DirectiveDefinition.new( name: directive.graphql_name, repeatable: directive.repeatable?, arguments: build_argument_nodes(@types.arguments(directive)), locations: build_directive_location_nodes(directive.locations), description: directive.description, ) end def build_directive_location_nodes(locations) locations.sort.map { |location| build_directive_location_node(location) } end def build_directive_location_node(location) GraphQL::Language::Nodes::DirectiveLocation.new( name: location.to_s ) end def build_type_name_node(type) case type.kind.name when "LIST" GraphQL::Language::Nodes::ListType.new( of_type: build_type_name_node(type.of_type) ) when "NON_NULL" GraphQL::Language::Nodes::NonNullType.new( of_type: build_type_name_node(type.of_type) ) else @cached_type_name_nodes ||= {} @cached_type_name_nodes[type.graphql_name] ||= GraphQL::Language::Nodes::TypeName.new(name: type.graphql_name) end end def build_default_value(default_value, type) if default_value.nil? return GraphQL::Language::Nodes::NullValue.new(name: "null") end case type.kind.name when "SCALAR" type.coerce_isolated_result(default_value) when "ENUM" GraphQL::Language::Nodes::Enum.new(name: type.coerce_isolated_result(default_value)) when "INPUT_OBJECT" GraphQL::Language::Nodes::InputObject.new( arguments: default_value.to_h.map do |arg_name, arg_value| args = @types.arguments(type) arg = args.find { |a| a.keyword.to_s == arg_name.to_s } if arg.nil? raise ArgumentError, "No argument definition on #{type.graphql_name} for argument: #{arg_name.inspect} (expected one of: #{args.map(&:keyword)})" end GraphQL::Language::Nodes::Argument.new( name: arg.graphql_name.to_s, value: build_default_value(arg_value, arg.type) ) end ) when "NON_NULL" build_default_value(default_value, type.of_type) when "LIST" default_value.to_a.map { |v| build_default_value(v, type.of_type) } else raise GraphQL::RequiredImplementationMissingError, "Unexpected default value type #{type.inspect}" end end def build_type_definition_node(type) case type.kind.name when "OBJECT" build_object_type_node(type) when "UNION" build_union_type_node(type) when "INTERFACE" build_interface_type_node(type) when "SCALAR" build_scalar_type_node(type) when "ENUM" build_enum_type_node(type) when "INPUT_OBJECT" build_input_object_node(type) else raise TypeError end end def build_argument_nodes(arguments) if !arguments.empty? nodes = arguments.map { |arg| build_argument_node(arg) } nodes.sort_by!(&:name) nodes else arguments end end def build_directive_nodes(directives) directives .map { |directive| build_directive_node(directive) } .sort_by(&:name) end def build_definition_nodes dirs_to_build = @types.directives if !include_built_in_directives dirs_to_build = dirs_to_build.reject { |directive| directive.default_directive? } end definitions = build_directive_nodes(dirs_to_build) all_types = @types.all_types type_nodes = build_type_definition_nodes(all_types) if !(ex_t = schema.extra_types).empty? dummy_query = Class.new(GraphQL::Schema::Object) do graphql_name "DummyQuery" (all_types + ex_t).each_with_index do |type, idx| if !type.kind.input_object? && !type.introspection? field "f#{idx}", type end end end extra_types_schema = Class.new(GraphQL::Schema) do query(dummy_query) end extra_types_types = GraphQL::Query.new(extra_types_schema, "{ __typename }", context: @context).types # rubocop:disable Development/ContextIsPassedCop # Temporarily replace `@types` with something from this example schema. # It'd be much nicer to pass this in, but that would be a big refactor :S prev_types = @types @types = extra_types_types type_nodes += build_type_definition_nodes(ex_t) @types = prev_types end type_nodes.sort_by!(&:name) if @include_one_of # This may have been set to true when iterating over all types definitions.concat(build_directive_nodes([GraphQL::Schema::Directive::OneOf])) end definitions.concat(type_nodes) if include_schema_node? definitions.unshift(build_schema_node) end definitions end def build_type_definition_nodes(types) if !include_introspection_types types = types.reject { |type| type.introspection? } end if !include_built_in_scalars types = types.reject { |type| type.kind.scalar? && type.default_scalar? } end types.map { |type| build_type_definition_node(type) } end def build_field_nodes(fields) f_nodes = fields.map { |field| build_field_node(field) } f_nodes.sort_by!(&:name) f_nodes end private def include_schema_node? always_include_schema || !schema_respects_root_name_conventions?(schema) || !schema.schema_directives.empty? end def schema_respects_root_name_conventions?(schema) (schema.query.nil? || schema.query.graphql_name == 'Query') && (schema.mutation.nil? || schema.mutation.graphql_name == 'Mutation') && (schema.subscription.nil? || schema.subscription.graphql_name == 'Subscription') end def directives(member) definition_directives(member, :directives) end def definition_directives(member, directives_method) if !member.respond_to?(directives_method) || member.directives.empty? EmptyObjects::EMPTY_ARRAY else visible_directives = member.public_send(directives_method).select { |dir| @types.directive_exists?(dir.graphql_name) } visible_directives.map! do |dir| args = [] dir.arguments.argument_values.each_value do |arg_value| # rubocop:disable Development/ContextIsPassedCop -- directive instance method arg_defn = arg_value.definition if arg_defn.default_value? && arg_value.value == arg_defn.default_value next else value_node = build_default_value(arg_value.value, arg_value.definition.type) args << GraphQL::Language::Nodes::Argument.new( name: arg_value.definition.name, value: value_node, ) end end # If this schema uses this built-in directive definition, # include it in the print-out since it's not part of the spec yet. @include_one_of ||= dir.class == GraphQL::Schema::Directive::OneOf GraphQL::Language::Nodes::Directive.new( name: dir.class.graphql_name, arguments: args ) end visible_directives end end attr_reader :schema, :always_include_schema, :include_introspection_types, :include_built_in_directives, :include_built_in_scalars end end end graphql-2.6.0/lib/graphql/language/block_string.rb0000644000004100000410000000612515173430257022214 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Language module BlockString # Remove leading and trailing whitespace from a block string. # See "Block Strings" in https://github.com/facebook/graphql/blob/master/spec/Section%202%20--%20Language.md def self.trim_whitespace(str) # Early return for the most common cases: if str == "" return "".dup elsif !(has_newline = str.include?("\n")) && !(str.start_with?(" ")) return str end lines = has_newline ? str.split("\n") : [str] common_indent = nil # find the common whitespace lines.each_with_index do |line, idx| if idx == 0 next end line_length = line.size line_indent = if line.match?(/\A [^ ]/) 2 elsif line.match?(/\A [^ ]/) 4 elsif line.match?(/\A[^ ]/) 0 else line[/\A */].size end if line_indent < line_length && (common_indent.nil? || line_indent < common_indent) common_indent = line_indent end end # Remove the common whitespace if common_indent && common_indent > 0 lines.each_with_index do |line, idx| if idx == 0 next else line.slice!(0, common_indent) end end end # Remove leading & trailing blank lines while lines.size > 0 && contains_only_whitespace?(lines.first) lines.shift end while lines.size > 0 && contains_only_whitespace?(lines.last) lines.pop end # Rebuild the string lines.size > 1 ? lines.join("\n") : (lines.first || "".dup) end def self.print(str, indent: '') line_length = 120 - indent.length block_str = "".dup triple_quotes = "\"\"\"\n" block_str << indent block_str << triple_quotes if str.include?("\n") str.split("\n") do |line| if line == '' block_str << "\n" else break_line(line, line_length) do |subline| block_str << indent block_str << subline block_str << "\n" end end end else break_line(str, line_length) do |subline| block_str << indent block_str << subline block_str << "\n" end end block_str << indent block_str << triple_quotes end private def self.break_line(line, length) return yield(line) if line.length < length + 5 parts = line.split(Regexp.new("((?: |^).{15,#{length - 40}}(?= |$))")) return yield(line) if parts.length < 4 yield(parts.slice!(0, 3).join) parts.each_with_index do |part, i| next if i % 2 == 1 yield "#{part[1..-1]}#{parts[i + 1]}" end nil end def self.contains_only_whitespace?(line) line.match?(/^\s*$/) end end end end graphql-2.6.0/lib/graphql/language/definition_slice.rb0000644000004100000410000000223015173430257023034 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Language module DefinitionSlice extend self def slice(document, name) definitions = {} document.definitions.each { |d| definitions[d.name] = d } names = Set.new DependencyVisitor.find_definition_dependencies(definitions, name, names) definitions = document.definitions.select { |d| names.include?(d.name) } Nodes::Document.new(definitions: definitions) end private class DependencyVisitor < GraphQL::Language::StaticVisitor def initialize(doc, definitions, names) @names = names @definitions = definitions super(doc) end def on_fragment_spread(node, parent) if fragment = @definitions[node.name] self.class.find_definition_dependencies(@definitions, fragment.name, @names) end super end def self.find_definition_dependencies(definitions, name, names) names.add(name) visitor = self.new(definitions[name], definitions, names) visitor.visit nil end end end end end graphql-2.6.0/lib/graphql/language/nodes.rb0000644000004100000410000006557215173430257020657 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Language module Nodes NONE = GraphQL::EmptyObjects::EMPTY_ARRAY # {AbstractNode} is the base class for all nodes in a GraphQL AST. # # It provides some APIs for working with ASTs: # - `children` returns all AST nodes attached to this one. Used for tree traversal. # - `scalars` returns all scalar (Ruby) values attached to this one. Used for comparing nodes. # - `to_query_string` turns an AST node into a GraphQL string class AbstractNode module DefinitionNode # This AST node's {#line} returns the first line, which may be the description. # @return [Integer] The first line of the definition (not the description) attr_reader :definition_line def initialize(definition_line: nil, **_rest) @definition_line = definition_line super(**_rest) end def marshal_dump super << @definition_line end def marshal_load(values) @definition_line = values.pop super end end attr_reader :filename def line @line ||= @source&.line_at(@pos) end def col @col ||= @source&.column_at(@pos) end def definition_line @definition_line ||= (@source && @definition_pos) ? @source.line_at(@definition_pos) : nil end # Value equality # @return [Boolean] True if `self` is equivalent to `other` def ==(other) return true if equal?(other) other.kind_of?(self.class) && other.scalars == self.scalars && other.children == self.children end NO_CHILDREN = GraphQL::EmptyObjects::EMPTY_ARRAY # @return [Array] all nodes in the tree below this one def children NO_CHILDREN end # @return [Array] Scalar values attached to this node def scalars NO_CHILDREN end # This might be unnecessary, but its easiest to add it here. def initialize_copy(other) @children = nil @scalars = nil @query_string = nil end def children_method_name self.class.children_method_name end def position [line, col] end def to_query_string(printer: GraphQL::Language::Printer.new) if printer.is_a?(GraphQL::Language::Printer) if frozen? @query_string || printer.print(self) else @query_string ||= printer.print(self) end else printer.print(self) end end # This creates a copy of `self`, with `new_options` applied. # @param new_options [Hash] # @return [AbstractNode] a shallow copy of `self` def merge(new_options) dup.merge!(new_options) end # Copy `self`, but modify the copy so that `previous_child` is replaced by `new_child` def replace_child(previous_child, new_child) # Figure out which list `previous_child` may be found in method_name = previous_child.children_method_name # Get the value from this (original) node prev_children = public_send(method_name) if prev_children.is_a?(Array) # Copy that list, and replace `previous_child` with `new_child` # in the list. new_children = prev_children.dup prev_idx = new_children.index(previous_child) new_children[prev_idx] = new_child else # Use the new value for the given attribute new_children = new_child end # Copy this node, but with the new child value copy_of_self = merge(method_name => new_children) # Return the copy: copy_of_self end # TODO DRY with `replace_child` def delete_child(previous_child) # Figure out which list `previous_child` may be found in method_name = previous_child.children_method_name # Copy that list, and delete previous_child new_children = public_send(method_name).dup new_children.delete(previous_child) # Copy this node, but with the new list of children: copy_of_self = merge(method_name => new_children) # Return the copy: copy_of_self end protected def merge!(new_options) new_options.each do |key, value| instance_variable_set(:"@#{key}", value) end self end class << self # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time # Add a default `#visit_method` and `#children_method_name` using the class name def inherited(child_class) super name_underscored = child_class.name .split("::").last .gsub(/([a-z])([A-Z])/,'\1_\2') # insert underscores .downcase # remove caps child_class.module_eval <<-RUBY, __FILE__, __LINE__ def visit_method :on_#{name_underscored} end class << self attr_accessor :children_method_name def visit_method :on_#{name_underscored} end end self.children_method_name = :#{name_underscored}s RUBY end def children_of_type @children_methods end private # Name accessors which return lists of nodes, # along with the kind of node they return, if possible. # - Add a reader for these children # - Add a persistent update method to add a child # - Generate a `#children` method def children_methods(children_of_type) if defined?(@children_methods) raise "Can't re-call .children_methods for #{self} (already have: #{@children_methods})" else @children_methods = children_of_type end if children_of_type == false @children_methods = {} # skip else children_of_type.each do |method_name, node_type| module_eval <<-RUBY, __FILE__, __LINE__ # A reader for these children attr_reader :#{method_name} RUBY if node_type # Only generate a method if we know what kind of node to make module_eval <<-RUBY, __FILE__, __LINE__ # Singular method: create a node with these options # and return a new `self` which includes that node in this list. def merge_#{method_name.to_s.sub(/s$/, "")}(**node_opts) merge(#{method_name}: #{method_name} + [#{node_type.name}.new(**node_opts)]) end RUBY end end if children_of_type.size == 1 module_eval <<-RUBY, __FILE__, __LINE__ alias :children #{children_of_type.keys.first} RUBY else module_eval <<-RUBY, __FILE__, __LINE__ def children @children ||= begin if #{children_of_type.keys.map { |k| "@#{k}.any?" }.join(" || ")} new_children = [] #{children_of_type.keys.map { |k| "new_children.concat(@#{k})" }.join("; ")} new_children.freeze new_children else NO_CHILDREN end end end RUBY end end if defined?(@scalar_methods) if !@initialize_was_generated @initialize_was_generated = true generate_initialize else # This method was defined manually end else raise "Can't generate_initialize because scalar_methods wasn't called; call it before children_methods" end end # These methods return a plain Ruby value, not another node # - Add reader methods # - Add a `#scalars` method def scalar_methods(*method_names) if defined?(@scalar_methods) raise "Can't re-call .scalar_methods for #{self} (already have: #{@scalar_methods})" else @scalar_methods = method_names end if method_names == [false] @scalar_methods = [] # skip it else module_eval <<-RUBY, __FILE__, __LINE__ # add readers for each scalar attr_reader #{method_names.map { |m| ":#{m}"}.join(", ")} def scalars @scalars ||= [#{method_names.map { |k| "@#{k}" }.join(", ")}].freeze end RUBY end end DEFAULT_INITIALIZE_OPTIONS = [ "line: nil", "col: nil", "pos: nil", "filename: nil", "source: nil" ] IGNORED_MARSHALLING_KEYWORDS = [:comment] def generate_initialize return if method_defined?(:marshal_load, false) # checking for `:initialize` doesn't work right scalar_method_names = @scalar_methods # TODO: These probably should be scalar methods, but `types` returns an array [:types, :description, :comment].each do |extra_method| if method_defined?(extra_method) scalar_method_names += [extra_method] end end children_method_names = @children_methods.keys all_method_names = scalar_method_names + children_method_names if all_method_names.include?(:alias) # Rather than complicating this special case, # let it be overridden (in field) return else arguments = scalar_method_names.map { |m| "#{m}: nil"} + children_method_names.map { |m| "#{m}: NO_CHILDREN" } + DEFAULT_INITIALIZE_OPTIONS assignments = scalar_method_names.map { |m| "@#{m} = #{m}"} + children_method_names.map { |m| "@#{m} = #{m}.freeze" } if name.end_with?("Definition") && name != "FragmentDefinition" arguments << "definition_pos: nil" assignments << "@definition_pos = definition_pos" end keywords = scalar_method_names.map { |m| "#{m}: #{m}"} + children_method_names.map { |m| "#{m}: #{m}" } ignored_keywords = IGNORED_MARSHALLING_KEYWORDS.map do |keyword| "#{keyword.to_s}: nil" end marshalling_method_names = all_method_names - IGNORED_MARSHALLING_KEYWORDS module_eval <<-RUBY, __FILE__, __LINE__ def initialize(#{arguments.join(", ")}) @line = line @col = col @pos = pos @filename = filename @source = source #{assignments.join("\n")} end def self.from_a(filename, line, col, #{marshalling_method_names.join(", ")}, #{ignored_keywords.join(", ")}) self.new(filename: filename, line: line, col: col, #{keywords.join(", ")}) end def marshal_dump [ line, col, # use methods here to force them to be calculated @filename, #{marshalling_method_names.map { |n| "@#{n}," }.join} ] end def marshal_load(values) @line, @col, @filename #{marshalling_method_names.map { |n| ", @#{n}"}.join} = values end RUBY end end # rubocop:enable Development/NoEvalCop end end # Base class for non-null type names and list type names class WrapperType < AbstractNode scalar_methods :of_type children_methods(false) end # Base class for nodes whose only value is a name (no child nodes or other scalars) class NameOnlyNode < AbstractNode scalar_methods :name children_methods(false) end # A key-value pair for a field's inputs class Argument < AbstractNode scalar_methods :name, :value children_methods(false) # @!attribute name # @return [String] the key for this argument # @!attribute value # @return [String, Float, Integer, Boolean, Array, InputObject, VariableIdentifier] The value passed for this key def children @children ||= Array(value).flatten.tap { _1.select! { |v| v.is_a?(AbstractNode) } } end end class Directive < AbstractNode scalar_methods :name children_methods(arguments: GraphQL::Language::Nodes::Argument) end class DirectiveLocation < NameOnlyNode end class DirectiveDefinition < AbstractNode attr_reader :description scalar_methods :name, :repeatable children_methods( arguments: Nodes::Argument, locations: Nodes::DirectiveLocation, ) self.children_method_name = :definitions end # An enum value. The string is available as {#name}. class Enum < NameOnlyNode end # A null value literal. class NullValue < NameOnlyNode end # A single selection in a GraphQL query. class Field < AbstractNode def initialize(name: nil, arguments: NONE, directives: NONE, selections: NONE, field_alias: nil, line: nil, col: nil, pos: nil, filename: nil, source: nil) @name = name @arguments = arguments || NONE @directives = directives || NONE @selections = selections || NONE # oops, alias is a keyword: @alias = field_alias @line = line @col = col @pos = pos @filename = filename @source = source end def self.from_a(filename, line, col, field_alias, name, arguments, directives, selections) # rubocop:disable Metrics/ParameterLists self.new(filename: filename, line: line, col: col, field_alias: field_alias, name: name, arguments: arguments, directives: directives, selections: selections) end def marshal_dump [line, col, @filename, @name, @arguments, @directives, @selections, @alias] end def marshal_load(values) @line, @col, @filename, @name, @arguments, @directives, @selections, @alias = values end scalar_methods :name, :alias children_methods({ arguments: GraphQL::Language::Nodes::Argument, selections: GraphQL::Language::Nodes::Field, directives: GraphQL::Language::Nodes::Directive, }) # Override this because default is `:fields` self.children_method_name = :selections end # A reusable fragment, defined at document-level. class FragmentDefinition < AbstractNode def initialize(name: nil, type: nil, directives: NONE, selections: NONE, filename: nil, pos: nil, source: nil, line: nil, col: nil) @name = name @type = type @directives = directives @selections = selections @filename = filename @pos = pos @source = source @line = line @col = col end def self.from_a(filename, line, col, name, type, directives, selections) self.new(filename: filename, line: line, col: col, name: name, type: type, directives: directives, selections: selections) end def marshal_dump [line, col, @filename, @name, @type, @directives, @selections] end def marshal_load(values) @line, @col, @filename, @name, @type, @directives, @selections = values end scalar_methods :name, :type children_methods({ selections: GraphQL::Language::Nodes::Field, directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end # Application of a named fragment in a selection class FragmentSpread < AbstractNode scalar_methods :name children_methods(directives: GraphQL::Language::Nodes::Directive) self.children_method_name = :selections # @!attribute name # @return [String] The identifier of the fragment to apply, corresponds with {FragmentDefinition#name} end # An unnamed fragment, defined directly in the query with `... { }` class InlineFragment < AbstractNode scalar_methods :type children_methods({ directives: GraphQL::Language::Nodes::Directive, selections: GraphQL::Language::Nodes::Field, }) self.children_method_name = :selections # @!attribute type # @return [String, nil] Name of the type this fragment applies to, or `nil` if this fragment applies to any type end # A collection of key-value inputs which may be a field argument class InputObject < AbstractNode scalar_methods(false) children_methods(arguments: GraphQL::Language::Nodes::Argument) # @!attribute arguments # @return [Array] A list of key-value pairs inside this input object # @return [Hash] Recursively turn this input object into a Ruby Hash def to_h(options={}) arguments.inject({}) do |memo, pair| v = pair.value memo[pair.name] = serialize_value_for_hash v memo end end self.children_method_name = :value private def serialize_value_for_hash(value) case value when InputObject value.to_h when Array value.map do |v| serialize_value_for_hash v end when Enum value.name when NullValue nil else value end end end # A list type definition, denoted with `[...]` (used for variable type definitions) class ListType < WrapperType end # A non-null type definition, denoted with `...!` (used for variable type definitions) class NonNullType < WrapperType end # An operation-level query variable class VariableDefinition < AbstractNode scalar_methods :name, :type, :default_value children_methods(directives: Directive) # @!attribute default_value # @return [String, Integer, Float, Boolean, Array, NullValue] A Ruby value to use if no other value is provided # @!attribute type # @return [TypeName, NonNullType, ListType] The expected type of this value # @!attribute name # @return [String] The identifier for this variable, _without_ `$` self.children_method_name = :variables end # A query, mutation or subscription. # May be anonymous or named. # May be explicitly typed (eg `mutation { ... }`) or implicitly a query (eg `{ ... }`). class OperationDefinition < AbstractNode scalar_methods :operation_type, :name children_methods({ variables: GraphQL::Language::Nodes::VariableDefinition, directives: GraphQL::Language::Nodes::Directive, selections: GraphQL::Language::Nodes::Field, }) # @!attribute variables # @return [Array] Variable $definitions for this operation # @!attribute selections # @return [Array] Root-level fields on this operation # @!attribute operation_type # @return [String, nil] The root type for this operation, or `nil` for implicit `"query"` # @!attribute name # @return [String, nil] The name for this operation, or `nil` if unnamed self.children_method_name = :definitions end # This is the AST root for normal queries # # @example Deriving a document by parsing a string # document = GraphQL.parse(query_string) # # @example Creating a string from a document # document.to_query_string # # { ... } # # @example Creating a custom string from a document # class VariableScrubber < GraphQL::Language::Printer # def print_argument(arg) # print_string("#{arg.name}: ") # end # end # # document.to_query_string(printer: VariableScrubber.new) # class Document < AbstractNode scalar_methods false children_methods(definitions: nil) # @!attribute definitions # @return [Array] top-level GraphQL units: operations or fragments def slice_definition(name) GraphQL::Language::DefinitionSlice.slice(self, name) end end # A type name, used for variable definitions class TypeName < NameOnlyNode end # Usage of a variable in a query. Name does _not_ include `$`. class VariableIdentifier < NameOnlyNode self.children_method_name = :value end class SchemaDefinition < AbstractNode scalar_methods :query, :mutation, :subscription children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class SchemaExtension < AbstractNode scalar_methods :query, :mutation, :subscription children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class ScalarTypeDefinition < AbstractNode attr_reader :description, :comment scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class ScalarTypeExtension < AbstractNode scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class InputValueDefinition < AbstractNode attr_reader :description, :comment scalar_methods :name, :type, :default_value children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :fields end class FieldDefinition < AbstractNode attr_reader :description, :comment scalar_methods :name, :type children_methods({ arguments: GraphQL::Language::Nodes::InputValueDefinition, directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :fields # this is so that `children_method_name` of `InputValueDefinition` works properly # with `#replace_child` alias :fields :arguments def merge(new_options) if (f = new_options.delete(:fields)) new_options[:arguments] = f end super end end class ObjectTypeDefinition < AbstractNode attr_reader :description, :comment scalar_methods :name, :interfaces children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) self.children_method_name = :definitions end class ObjectTypeExtension < AbstractNode scalar_methods :name, :interfaces children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) self.children_method_name = :definitions end class InterfaceTypeDefinition < AbstractNode attr_reader :description, :comment scalar_methods :name children_methods({ interfaces: GraphQL::Language::Nodes::TypeName, directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) self.children_method_name = :definitions end class InterfaceTypeExtension < AbstractNode scalar_methods :name children_methods({ interfaces: GraphQL::Language::Nodes::TypeName, directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::FieldDefinition, }) self.children_method_name = :definitions end class UnionTypeDefinition < AbstractNode attr_reader :description, :comment, :types scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class UnionTypeExtension < AbstractNode attr_reader :types scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :definitions end class EnumValueDefinition < AbstractNode attr_reader :description, :comment scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, }) self.children_method_name = :values end class EnumTypeDefinition < AbstractNode attr_reader :description, :comment scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, values: GraphQL::Language::Nodes::EnumValueDefinition, }) self.children_method_name = :definitions end class EnumTypeExtension < AbstractNode scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, values: GraphQL::Language::Nodes::EnumValueDefinition, }) self.children_method_name = :definitions end class InputObjectTypeDefinition < AbstractNode attr_reader :description, :comment scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::InputValueDefinition, }) self.children_method_name = :definitions end class InputObjectTypeExtension < AbstractNode scalar_methods :name children_methods({ directives: GraphQL::Language::Nodes::Directive, fields: GraphQL::Language::Nodes::InputValueDefinition, }) self.children_method_name = :definitions end end end end graphql-2.6.0/lib/graphql/language/generation.rb0000644000004100000410000000164615173430257021672 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Language # Exposes {.generate}, which turns AST nodes back into query strings. module Generation extend self # Turn an AST node back into a string. # # @example Turning a document into a query # document = GraphQL.parse(query_string) # GraphQL::Language::Generation.generate(document) # # => "{ ... }" # # @param node [GraphQL::Language::Nodes::AbstractNode] an AST node to recursively stringify # @param indent [String] Whitespace to add to each printed node # @param printer [GraphQL::Language::Printer] An optional custom printer for printing AST nodes. Defaults to GraphQL::Language::Printer # @return [String] Valid GraphQL for `node` def generate(node, indent: "", printer: GraphQL::Language::Printer.new) printer.print(node, indent: indent) end end end end graphql-2.6.0/lib/graphql/language/lexer.rb0000644000004100000410000002747415173430257020665 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Language class Lexer def initialize(graphql_str, filename: nil, max_tokens: nil) if !(graphql_str.encoding == Encoding::UTF_8 || graphql_str.ascii_only?) graphql_str = graphql_str.dup.force_encoding(Encoding::UTF_8) end @string = graphql_str @filename = filename @scanner = StringScanner.new(graphql_str) @pos = nil @max_tokens = max_tokens || Float::INFINITY @tokens_count = 0 @finished = false end def finished? @finished end def freeze @scanner = nil super end attr_reader :pos, :tokens_count def advance @scanner.skip(IGNORE_REGEXP) if @scanner.eos? @finished = true return false end @tokens_count += 1 if @tokens_count > @max_tokens raise_parse_error("This query is too large to execute.") end @pos = @scanner.pos next_byte = @string.getbyte(@pos) next_byte_is_for = FIRST_BYTES[next_byte] case next_byte_is_for when ByteFor::PUNCTUATION @scanner.pos += 1 PUNCTUATION_NAME_FOR_BYTE[next_byte] when ByteFor::NAME if len = @scanner.skip(KEYWORD_REGEXP) case len when 2 :ON when 12 :SUBSCRIPTION else pos = @pos # Use bytes 2 and 3 as a unique identifier for this keyword bytes = (@string.getbyte(pos + 2) << 8) | @string.getbyte(pos + 1) KEYWORD_BY_TWO_BYTES[_hash(bytes)] end else @scanner.skip(IDENTIFIER_REGEXP) :IDENTIFIER end when ByteFor::IDENTIFIER @scanner.skip(IDENTIFIER_REGEXP) :IDENTIFIER when ByteFor::NUMBER if len = @scanner.skip(NUMERIC_REGEXP) if GraphQL.reject_numbers_followed_by_names new_pos = @scanner.pos peek_byte = @string.getbyte(new_pos) next_first_byte = FIRST_BYTES[peek_byte] if next_first_byte == ByteFor::NAME || next_first_byte == ByteFor::IDENTIFIER number_part = token_value name_part = @scanner.scan(IDENTIFIER_REGEXP) raise_parse_error("Name after number is not allowed (in `#{number_part}#{name_part}`)") end end # Check for a matched decimal: @scanner[1] ? :FLOAT : :INT else # Attempt to find the part after the `-` value = @scanner.scan(/-\s?[a-z0-9]*/i) invalid_byte_for_number_error_message = "Expected type 'number', but it was malformed#{value.nil? ? "" : ": #{value.inspect}"}." raise_parse_error(invalid_byte_for_number_error_message) end when ByteFor::ELLIPSIS if @string.getbyte(@pos + 1) != 46 || @string.getbyte(@pos + 2) != 46 raise_parse_error("Expected `...`, actual: #{@string[@pos..@pos + 2].inspect}") end @scanner.pos += 3 :ELLIPSIS when ByteFor::STRING if @scanner.skip(BLOCK_STRING_REGEXP) || @scanner.skip(QUOTED_STRING_REGEXP) :STRING else raise_parse_error("Expected string or block string, but it was malformed") end else @scanner.pos += 1 :UNKNOWN_CHAR end rescue ArgumentError => err if err.message == "invalid byte sequence in UTF-8" raise_parse_error("Parse error on bad Unicode escape sequence", nil, nil) end end def token_value @string.byteslice(@scanner.pos - @scanner.matched_size, @scanner.matched_size) rescue StandardError => err raise GraphQL::Error, "(token_value failed: #{err.class}: #{err.message})" end def debug_token_value(token_name) if token_name && Lexer::Punctuation.const_defined?(token_name) Lexer::Punctuation.const_get(token_name) elsif token_name == :ELLIPSIS "..." elsif token_name == :STRING string_value elsif @scanner.matched_size.nil? @scanner.peek(1) else token_value end end ESCAPES = /\\["\\\/bfnrt]/ ESCAPES_REPLACE = { '\\"' => '"', "\\\\" => "\\", "\\/" => '/', "\\b" => "\b", "\\f" => "\f", "\\n" => "\n", "\\r" => "\r", "\\t" => "\t", } UTF_8 = /\\u(?:([\dAa-f]{4})|\{([\da-f]{4,})\})(?:\\u([\dAa-f]{4}))?/i VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o ESCAPED = /(?:#{ESCAPES}|#{UTF_8})/o def string_value str = token_value is_block = str.start_with?('"""') if is_block str.gsub!(/\A"""|"""\z/, '') return Language::BlockString.trim_whitespace(str) else str.gsub!(/\A"|"\z/, '') if !str.valid_encoding? || !str.match?(VALID_STRING) raise_parse_error("Bad unicode escape in #{str.inspect}") else Lexer.replace_escaped_characters_in_place(str) if !str.valid_encoding? raise_parse_error("Bad unicode escape in #{str.inspect}") else str end end end end def line_number @scanner.string[0..@pos].count("\n") + 1 end def column_number @scanner.string[0..@pos].split("\n").last.length end def raise_parse_error(message, line = line_number, col = column_number) raise GraphQL::ParseError.new(message, line, col, @string, filename: @filename) end IGNORE_REGEXP = %r{ (?: [, \c\r\n\t]+ | \#.*$ )* }x IDENTIFIER_REGEXP = /[_A-Za-z][_0-9A-Za-z]*/ INT_REGEXP = /-?(?:[0]|[1-9][0-9]*)/ FLOAT_DECIMAL_REGEXP = /[.][0-9]+/ FLOAT_EXP_REGEXP = /[eE][+-]?[0-9]+/ # TODO: FLOAT_EXP_REGEXP should not be allowed to follow INT_REGEXP, integers are not allowed to have exponent parts. NUMERIC_REGEXP = /#{INT_REGEXP}(#{FLOAT_DECIMAL_REGEXP}#{FLOAT_EXP_REGEXP}|#{FLOAT_DECIMAL_REGEXP}|#{FLOAT_EXP_REGEXP})?/ KEYWORDS = [ "on", "fragment", "true", "false", "null", "query", "mutation", "subscription", "schema", "scalar", "type", "extend", "implements", "interface", "union", "enum", "input", "directive", "repeatable" ].freeze KEYWORD_REGEXP = /#{Regexp.union(KEYWORDS.sort)}\b/ KEYWORD_BY_TWO_BYTES = [ :INTERFACE, :MUTATION, :EXTEND, :FALSE, :ENUM, :TRUE, :NULL, nil, nil, nil, nil, nil, nil, nil, :QUERY, nil, nil, :REPEATABLE, :IMPLEMENTS, :INPUT, :TYPE, :SCHEMA, nil, nil, nil, :DIRECTIVE, :UNION, nil, nil, :SCALAR, nil, :FRAGMENT ].freeze # This produces a unique integer for bytes 2 and 3 of each keyword string # See https://tenderlovemaking.com/2023/09/02/fast-tokenizers-with-stringscanner.html def _hash key (key * 18592990) >> 27 & 0x1f end module Punctuation LCURLY = '{' RCURLY = '}' LPAREN = '(' RPAREN = ')' LBRACKET = '[' RBRACKET = ']' COLON = ':' VAR_SIGN = '$' DIR_SIGN = '@' EQUALS = '=' BANG = '!' PIPE = '|' AMP = '&' end # A sparse array mapping the bytes for each punctuation # to a symbol name for that punctuation PUNCTUATION_NAME_FOR_BYTE = Punctuation.constants.each_with_object([]) { |name, arr| punct = Punctuation.const_get(name) arr[punct.ord] = name }.freeze QUOTE = '"' UNICODE_DIGIT = /[0-9A-Za-z]/ FOUR_DIGIT_UNICODE = /#{UNICODE_DIGIT}{4}/ N_DIGIT_UNICODE = %r{#{Punctuation::LCURLY}#{UNICODE_DIGIT}{4,}#{Punctuation::RCURLY}}x UNICODE_ESCAPE = %r{\\u(?:#{FOUR_DIGIT_UNICODE}|#{N_DIGIT_UNICODE})} STRING_ESCAPE = %r{[\\][\\/bfnrt]} BLOCK_QUOTE = '"""' ESCAPED_QUOTE = /\\"/; STRING_CHAR = /#{ESCAPED_QUOTE}|[^"\\\n\r]|#{UNICODE_ESCAPE}|#{STRING_ESCAPE}/ QUOTED_STRING_REGEXP = %r{#{QUOTE} (?:#{STRING_CHAR})* #{QUOTE}}x BLOCK_STRING_REGEXP = %r{ #{BLOCK_QUOTE} (?: [^"\\] | # Any characters that aren't a quote or slash (?= 0xD800 && codepoint_1 <= 0xDBFF) && # leading surrogate (codepoint_2 >= 0xDC00 && codepoint_2 <= 0xDFFF) # trailing surrogate # A surrogate pair combined = ((codepoint_1 - 0xD800) * 0x400) + (codepoint_2 - 0xDC00) + 0x10000 [combined].pack('U'.freeze) else # Two separate code points [codepoint_1].pack('U'.freeze) + [codepoint_2].pack('U'.freeze) end else [codepoint_1].pack('U'.freeze) end else ESCAPES_REPLACE[matched_str] end end nil end # This is not used during parsing because the parser # doesn't actually need tokens. def self.tokenize(string) lexer = GraphQL::Language::Lexer.new(string) tokens = [] while (token_name = lexer.advance) new_token = [ token_name, lexer.line_number, lexer.column_number, lexer.debug_token_value(token_name), ] tokens << new_token end tokens end end end end graphql-2.6.0/lib/graphql/language/printer.rb0000644000004100000410000004421115173430257021215 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Language class Printer OMISSION = "... (truncated)" class TruncatableBuffer class TruncateSizeReached < StandardError; end DEFAULT_INIT_CAPACITY = 500 def initialize(truncate_size: nil) @out = String.new(capacity: truncate_size || DEFAULT_INIT_CAPACITY) @truncate_size = truncate_size end def append(other) if @truncate_size && (@out.size + other.size) > @truncate_size @out << other.slice(0, @truncate_size - @out.size) raise(TruncateSizeReached, "Truncate size reached") else @out << other end end def to_string @out end end # Turn an arbitrary AST node back into a string. # # @example Turning a document into a query string # document = GraphQL.parse(query_string) # GraphQL::Language::Printer.new.print(document) # # => "{ ... }" # # # @example Building a custom printer # # class MyPrinter < GraphQL::Language::Printer # def print_argument(arg) # print_string("#{arg.name}: ") # end # end # # MyPrinter.new.print(document) # # => "mutation { pay(creditCard: ) { success } }" # # @param node [Nodes::AbstractNode] # @param indent [String] Whitespace to add to the printed node # @param truncate_size [Integer, nil] The size to truncate to. # @return [String] Valid GraphQL for `node` def print(node, indent: "", truncate_size: nil) truncate_size = truncate_size ? [truncate_size - OMISSION.size, 0].max : nil @out = TruncatableBuffer.new(truncate_size: truncate_size) print_node(node, indent: indent) @out.to_string rescue TruncatableBuffer::TruncateSizeReached @out.to_string << OMISSION end protected def print_string(str) @out.append(str) end def print_document(document) document.definitions.each_with_index do |d, i| print_node(d) print_string("\n\n") if i < document.definitions.size - 1 end end def print_argument(argument) print_string(argument.name) print_string(": ") print_node(argument.value) end def print_input_object(input_object) print_string("{") input_object.arguments.each_with_index do |a, i| print_argument(a) print_string(", ") if i < input_object.arguments.size - 1 end print_string("}") end def print_directive(directive) print_string("@") print_string(directive.name) if !directive.arguments.empty? print_string("(") directive.arguments.each_with_index do |a, i| print_argument(a) print_string(", ") if i < directive.arguments.size - 1 end print_string(")") end end def print_enum(enum) print_string(enum.name) end def print_null_value print_string("null") end def print_field(field, indent: "") print_string(indent) if field.alias print_string(field.alias) print_string(": ") end print_string(field.name) if !field.arguments.empty? print_string("(") field.arguments.each_with_index do |a, i| print_argument(a) print_string(", ") if i < field.arguments.size - 1 end print_string(")") end print_directives(field.directives) print_selections(field.selections, indent: indent) end def print_fragment_definition(fragment_def, indent: "") print_string(indent) print_string("fragment") if fragment_def.name print_string(" ") print_string(fragment_def.name) end if fragment_def.type print_string(" on ") print_node(fragment_def.type) end print_directives(fragment_def.directives) print_selections(fragment_def.selections, indent: indent) end def print_fragment_spread(fragment_spread, indent: "") print_string(indent) print_string("...") print_string(fragment_spread.name) print_directives(fragment_spread.directives) end def print_inline_fragment(inline_fragment, indent: "") print_string(indent) print_string("...") if inline_fragment.type print_string(" on ") print_node(inline_fragment.type) end print_directives(inline_fragment.directives) print_selections(inline_fragment.selections, indent: indent) end def print_list_type(list_type) print_string("[") print_node(list_type.of_type) print_string("]") end def print_non_null_type(non_null_type) print_node(non_null_type.of_type) print_string("!") end def print_operation_definition(operation_definition, indent: "") print_string(indent) print_string(operation_definition.operation_type) if operation_definition.name print_string(" ") print_string(operation_definition.name) end if !operation_definition.variables.empty? print_string("(") operation_definition.variables.each_with_index do |v, i| print_variable_definition(v) print_string(", ") if i < operation_definition.variables.size - 1 end print_string(")") end print_directives(operation_definition.directives) print_selections(operation_definition.selections, indent: indent) end def print_type_name(type_name) print_string(type_name.name) end def print_variable_definition(variable_definition) print_string("$") print_string(variable_definition.name) print_string(": ") print_node(variable_definition.type) unless variable_definition.default_value.nil? print_string(" = ") print_node(variable_definition.default_value) end variable_definition.directives.each do |dir| print_string(" ") print_directive(dir) end end def print_variable_identifier(variable_identifier) print_string("$") print_string(variable_identifier.name) end def print_schema_definition(schema, extension: false) has_conventional_names = (schema.query.nil? || schema.query == 'Query') && (schema.mutation.nil? || schema.mutation == 'Mutation') && (schema.subscription.nil? || schema.subscription == 'Subscription') if has_conventional_names && schema.directives.empty? return end extension ? print_string("extend schema") : print_string("schema") if !schema.directives.empty? schema.directives.each do |dir| print_string("\n ") print_node(dir) end if !has_conventional_names print_string("\n") end end if !has_conventional_names if schema.directives.empty? print_string(" ") end print_string("{\n") print_string(" query: #{schema.query}\n") if schema.query print_string(" mutation: #{schema.mutation}\n") if schema.mutation print_string(" subscription: #{schema.subscription}\n") if schema.subscription print_string("}") end end def print_scalar_type_definition(scalar_type, extension: false) extension ? print_string("extend ") : print_description_and_comment(scalar_type) print_string("scalar ") print_string(scalar_type.name) print_directives(scalar_type.directives) end def print_object_type_definition(object_type, extension: false) extension ? print_string("extend ") : print_description_and_comment(object_type) print_string("type ") print_string(object_type.name) print_implements(object_type) unless object_type.interfaces.empty? print_directives(object_type.directives) print_field_definitions(object_type.fields) end def print_implements(type) print_string(" implements ") i = 0 type.interfaces.each do |int| if i > 0 print_string(" & ") end print_string(int.name) i += 1 end end def print_input_value_definition(input_value) print_string(input_value.name) print_string(": ") print_node(input_value.type) unless input_value.default_value.nil? print_string(" = ") print_node(input_value.default_value) end print_directives(input_value.directives) end def print_arguments(arguments, indent: "") if arguments.all? { |arg| !arg.description && !arg.comment } print_string("(") arguments.each_with_index do |arg, i| print_input_value_definition(arg) print_string(", ") if i < arguments.size - 1 end print_string(")") return end print_string("(\n") arguments.each_with_index do |arg, i| print_comment(arg, indent: " " + indent, first_in_block: i == 0) print_description(arg, indent: " " + indent, first_in_block: i == 0) print_string(" ") print_string(indent) print_input_value_definition(arg) print_string("\n") if i < arguments.size - 1 end print_string("\n") print_string(indent) print_string(")") end def print_field_definition(field) print_string(field.name) unless field.arguments.empty? print_arguments(field.arguments, indent: " ") end print_string(": ") print_node(field.type) print_directives(field.directives) end def print_interface_type_definition(interface_type, extension: false) extension ? print_string("extend ") : print_description_and_comment(interface_type) print_string("interface ") print_string(interface_type.name) print_implements(interface_type) if !interface_type.interfaces.empty? print_directives(interface_type.directives) print_field_definitions(interface_type.fields) end def print_union_type_definition(union_type, extension: false) extension ? print_string("extend ") : print_description_and_comment(union_type) print_string("union ") print_string(union_type.name) print_directives(union_type.directives) if !union_type.types.empty? print_string(" = ") i = 0 union_type.types.each do |t| if i > 0 print_string(" | ") end print_string(t.name) i += 1 end end end def print_enum_type_definition(enum_type, extension: false) extension ? print_string("extend ") : print_description_and_comment(enum_type) print_string("enum ") print_string(enum_type.name) print_directives(enum_type.directives) if !enum_type.values.empty? print_string(" {\n") enum_type.values.each.with_index do |value, i| print_description(value, indent: " ", first_in_block: i == 0) print_comment(value, indent: " ", first_in_block: i == 0) print_enum_value_definition(value) end print_string("}") end end def print_enum_value_definition(enum_value) print_string(" ") print_string(enum_value.name) print_directives(enum_value.directives) print_string("\n") end def print_input_object_type_definition(input_object_type, extension: false) extension ? print_string("extend ") : print_description_and_comment(input_object_type) print_string("input ") print_string(input_object_type.name) print_directives(input_object_type.directives) if !input_object_type.fields.empty? print_string(" {\n") input_object_type.fields.each.with_index do |field, i| print_description(field, indent: " ", first_in_block: i == 0) print_comment(field, indent: " ", first_in_block: i == 0) print_string(" ") print_input_value_definition(field) print_string("\n") end print_string("}") end end def print_directive_definition(directive) print_description(directive) print_string("directive @") print_string(directive.name) if !directive.arguments.empty? print_arguments(directive.arguments) end if directive.repeatable print_string(" repeatable") end print_string(" on ") i = 0 directive.locations.each do |loc| if i > 0 print_string(" | ") end print_string(loc.name) i += 1 end end def print_description(node, indent: "", first_in_block: true) return unless node.description print_string("\n") if indent != "" && !first_in_block print_string(GraphQL::Language::BlockString.print(node.description, indent: indent)) end def print_comment(node, indent: "", first_in_block: true) return unless node.comment print_string("\n") if indent != "" && !first_in_block print_string(GraphQL::Language::Comment.print(node.comment, indent: indent)) end def print_description_and_comment(node) print_description(node) print_comment(node) end def print_field_definitions(fields) return if fields.empty? print_string(" {\n") i = 0 fields.each do |field| print_description(field, indent: " ", first_in_block: i == 0) print_comment(field, indent: " ", first_in_block: i == 0) print_string(" ") print_field_definition(field) print_string("\n") i += 1 end print_string("}") end def print_directives(directives) return if directives.empty? directives.each do |d| print_string(" ") print_directive(d) end end def print_selections(selections, indent: "") return if selections.empty? print_string(" {\n") selections.each do |selection| print_node(selection, indent: indent + " ") print_string("\n") end print_string(indent) print_string("}") end def print_node(node, indent: "") case node when Nodes::Document print_document(node) when Nodes::Argument print_argument(node) when Nodes::Directive print_directive(node) when Nodes::Enum print_enum(node) when Nodes::NullValue print_null_value when Nodes::Field print_field(node, indent: indent) when Nodes::FragmentDefinition print_fragment_definition(node, indent: indent) when Nodes::FragmentSpread print_fragment_spread(node, indent: indent) when Nodes::InlineFragment print_inline_fragment(node, indent: indent) when Nodes::InputObject print_input_object(node) when Nodes::ListType print_list_type(node) when Nodes::NonNullType print_non_null_type(node) when Nodes::OperationDefinition print_operation_definition(node, indent: indent) when Nodes::TypeName print_type_name(node) when Nodes::VariableDefinition print_variable_definition(node) when Nodes::VariableIdentifier print_variable_identifier(node) when Nodes::SchemaDefinition print_schema_definition(node) when Nodes::SchemaExtension print_schema_definition(node, extension: true) when Nodes::ScalarTypeDefinition print_scalar_type_definition(node) when Nodes::ScalarTypeExtension print_scalar_type_definition(node, extension: true) when Nodes::ObjectTypeDefinition print_object_type_definition(node) when Nodes::ObjectTypeExtension print_object_type_definition(node, extension: true) when Nodes::InputValueDefinition print_input_value_definition(node) when Nodes::FieldDefinition print_field_definition(node) when Nodes::InterfaceTypeDefinition print_interface_type_definition(node) when Nodes::InterfaceTypeExtension print_interface_type_definition(node, extension: true) when Nodes::UnionTypeDefinition print_union_type_definition(node) when Nodes::UnionTypeExtension print_union_type_definition(node, extension: true) when Nodes::EnumTypeDefinition print_enum_type_definition(node) when Nodes::EnumTypeExtension print_enum_type_definition(node, extension: true) when Nodes::EnumValueDefinition print_enum_value_definition(node) when Nodes::InputObjectTypeDefinition print_input_object_type_definition(node) when Nodes::InputObjectTypeExtension print_input_object_type_definition(node, extension: true) when Nodes::DirectiveDefinition print_directive_definition(node) when FalseClass, Float, Integer, NilClass, String, TrueClass, Symbol print_string(GraphQL::Language.serialize(node)) when Array print_string("[") node.each_with_index do |v, i| print_node(v) print_string(", ") if i < node.length - 1 end print_string("]") when Hash print_string("{") node.each_with_index do |(k, v), i| print_string(k) print_string(": ") print_node(v) print_string(", ") if i < node.length - 1 end print_string("}") else print_string(GraphQL::Language.serialize(node.to_s)) end end end end end graphql-2.6.0/lib/graphql/language/comment.rb0000644000004100000410000000060715173430257021175 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Language module Comment def self.print(str, indent: '') lines = str.split("\n").map do |line| comment_str = "".dup comment_str << indent comment_str << "# " comment_str << line comment_str.rstrip end lines.join("\n") + "\n" end end end end graphql-2.6.0/lib/graphql/language/parser.rb0000644000004100000410000006520015173430257021027 0ustar www-datawww-data# frozen_string_literal: true require "strscan" require "graphql/language/nodes" require "graphql/tracing/null_trace" module GraphQL module Language class Parser include GraphQL::Language::Nodes include EmptyObjects class << self attr_accessor :cache def parse(graphql_str, filename: nil, trace: Tracing::NullTrace, max_tokens: nil) self.new(graphql_str, filename: filename, trace: trace, max_tokens: max_tokens).parse end def parse_file(filename, trace: Tracing::NullTrace) if cache cache.fetch(filename) do parse(File.read(filename), filename: filename, trace: trace) end else parse(File.read(filename), filename: filename, trace: trace) end end end def initialize(graphql_str, filename: nil, trace: Tracing::NullTrace, max_tokens: nil) if graphql_str.nil? raise GraphQL::ParseError.new("No query string was present", nil, nil, nil) end @lexer = Lexer.new(graphql_str, filename: filename, max_tokens: max_tokens) @graphql_str = graphql_str @filename = filename @trace = trace @dedup_identifiers = false @lines_at = nil end def parse @document ||= begin @trace.parse(query_string: @graphql_str) do document end rescue SystemStackError raise GraphQL::ParseError.new("This query is too large to execute.", nil, nil, @query_str, filename: @filename) end end def tokens_count parse @lexer.tokens_count end def line_at(pos) line = lines_at.bsearch_index { |l| l >= pos } if line.nil? @lines_at.size + 1 else line + 1 end end def column_at(pos) next_line_idx = lines_at.bsearch_index { |l| l >= pos } || 0 if next_line_idx > 0 line_pos = @lines_at[next_line_idx - 1] pos - line_pos else pos + 1 end end private # @return [Array] Positions of each line break in the original string def lines_at @lines_at ||= begin la = [] idx = 0 while idx idx = @graphql_str.index("\n", idx) if idx la << idx idx += 1 end end la end end attr_reader :token_name def advance_token @token_name = @lexer.advance end def pos @lexer.pos end def document any_tokens = advance_token defns = [] if any_tokens defns << definition else # Only ignored characters is not a valid document raise GraphQL::ParseError.new("Unexpected end of document", nil, nil, @graphql_str) end while !@lexer.finished? defns << definition end Document.new(pos: 0, definitions: defns, filename: @filename, source: self) end def definition case token_name when :FRAGMENT loc = pos expect_token :FRAGMENT f_name = if !at?(:ON) parse_name end expect_token :ON f_type = parse_type_name directives = parse_directives selections = selection_set Nodes::FragmentDefinition.new( pos: loc, name: f_name, type: f_type, directives: directives, selections: selections, filename: @filename, source: self ) when :QUERY, :MUTATION, :SUBSCRIPTION, :LCURLY op_loc = pos op_type = case token_name when :LCURLY "query" else parse_operation_type end op_name = case token_name when :LPAREN, :LCURLY, :DIR_SIGN nil else parse_name end variable_definitions = if at?(:LPAREN) expect_token(:LPAREN) defs = [] while !at?(:RPAREN) loc = pos expect_token(:VAR_SIGN) var_name = parse_name expect_token(:COLON) var_type = self.type || raise_parse_error("Missing type definition for variable: $#{var_name}") default_value = if at?(:EQUALS) advance_token value end directives = parse_directives defs << Nodes::VariableDefinition.new( pos: loc, name: var_name, type: var_type, default_value: default_value, directives: directives, filename: @filename, source: self ) end expect_token(:RPAREN) defs else EmptyObjects::EMPTY_ARRAY end directives = parse_directives OperationDefinition.new( pos: op_loc, operation_type: op_type, name: op_name, variables: variable_definitions, directives: directives, selections: selection_set, filename: @filename, source: self ) when :EXTEND loc = pos advance_token case token_name when :SCALAR advance_token name = parse_name directives = parse_directives ScalarTypeExtension.new(pos: loc, name: name, directives: directives, filename: @filename, source: self) when :TYPE advance_token name = parse_name implements_interfaces = parse_implements directives = parse_directives field_defns = at?(:LCURLY) ? parse_field_definitions : EMPTY_ARRAY ObjectTypeExtension.new(pos: loc, name: name, interfaces: implements_interfaces, directives: directives, fields: field_defns, filename: @filename, source: self) when :INTERFACE advance_token name = parse_name directives = parse_directives interfaces = parse_implements fields_definition = at?(:LCURLY) ? parse_field_definitions : EMPTY_ARRAY InterfaceTypeExtension.new(pos: loc, name: name, directives: directives, fields: fields_definition, interfaces: interfaces, filename: @filename, source: self) when :UNION advance_token name = parse_name directives = parse_directives union_member_types = parse_union_members UnionTypeExtension.new(pos: loc, name: name, directives: directives, types: union_member_types, filename: @filename, source: self) when :ENUM advance_token name = parse_name directives = parse_directives enum_values_definition = parse_enum_value_definitions Nodes::EnumTypeExtension.new(pos: loc, name: name, directives: directives, values: enum_values_definition, filename: @filename, source: self) when :INPUT advance_token name = parse_name directives = parse_directives input_fields_definition = parse_input_object_field_definitions InputObjectTypeExtension.new(pos: loc, name: name, directives: directives, fields: input_fields_definition, filename: @filename, source: self) when :SCHEMA advance_token directives = parse_directives query = mutation = subscription = nil if at?(:LCURLY) advance_token while !at?(:RCURLY) if at?(:QUERY) advance_token expect_token(:COLON) query = parse_name elsif at?(:MUTATION) advance_token expect_token(:COLON) mutation = parse_name elsif at?(:SUBSCRIPTION) advance_token expect_token(:COLON) subscription = parse_name else expect_one_of([:QUERY, :MUTATION, :SUBSCRIPTION]) end end expect_token :RCURLY end SchemaExtension.new( subscription: subscription, mutation: mutation, query: query, directives: directives, pos: loc, filename: @filename, source: self, ) else expect_one_of([:SCHEMA, :SCALAR, :TYPE, :ENUM, :INPUT, :UNION, :INTERFACE]) end else loc = pos desc = at?(:STRING) ? string_value : nil defn_loc = pos case token_name when :SCHEMA advance_token directives = parse_directives query = mutation = subscription = nil expect_token :LCURLY while !at?(:RCURLY) if at?(:QUERY) advance_token expect_token(:COLON) query = parse_name elsif at?(:MUTATION) advance_token expect_token(:COLON) mutation = parse_name elsif at?(:SUBSCRIPTION) advance_token expect_token(:COLON) subscription = parse_name else expect_one_of([:QUERY, :MUTATION, :SUBSCRIPTION]) end end expect_token :RCURLY SchemaDefinition.new(pos: loc, definition_pos: defn_loc, query: query, mutation: mutation, subscription: subscription, directives: directives, filename: @filename, source: self) when :DIRECTIVE advance_token expect_token :DIR_SIGN name = parse_name arguments_definition = parse_argument_definitions repeatable = if at?(:REPEATABLE) advance_token true else false end expect_token :ON directive_locations = [DirectiveLocation.new(pos: pos, name: parse_name, filename: @filename, source: self)] while at?(:PIPE) advance_token directive_locations << DirectiveLocation.new(pos: pos, name: parse_name, filename: @filename, source: self) end DirectiveDefinition.new(pos: loc, definition_pos: defn_loc, description: desc, name: name, arguments: arguments_definition, locations: directive_locations, repeatable: repeatable, filename: @filename, source: self) when :TYPE advance_token name = parse_name implements_interfaces = parse_implements directives = parse_directives field_defns = at?(:LCURLY) ? parse_field_definitions : EMPTY_ARRAY ObjectTypeDefinition.new(pos: loc, definition_pos: defn_loc, description: desc, name: name, interfaces: implements_interfaces, directives: directives, fields: field_defns, filename: @filename, source: self) when :INTERFACE advance_token name = parse_name interfaces = parse_implements directives = parse_directives fields_definition = parse_field_definitions InterfaceTypeDefinition.new(pos: loc, definition_pos: defn_loc, description: desc, name: name, directives: directives, fields: fields_definition, interfaces: interfaces, filename: @filename, source: self) when :UNION advance_token name = parse_name directives = parse_directives union_member_types = parse_union_members UnionTypeDefinition.new(pos: loc, definition_pos: defn_loc, description: desc, name: name, directives: directives, types: union_member_types, filename: @filename, source: self) when :SCALAR advance_token name = parse_name directives = parse_directives ScalarTypeDefinition.new(pos: loc, definition_pos: defn_loc, description: desc, name: name, directives: directives, filename: @filename, source: self) when :ENUM advance_token name = parse_name directives = parse_directives enum_values_definition = parse_enum_value_definitions Nodes::EnumTypeDefinition.new(pos: loc, definition_pos: defn_loc, description: desc, name: name, directives: directives, values: enum_values_definition, filename: @filename, source: self) when :INPUT advance_token name = parse_name directives = parse_directives input_fields_definition = parse_input_object_field_definitions InputObjectTypeDefinition.new(pos: loc, definition_pos: defn_loc, description: desc, name: name, directives: directives, fields: input_fields_definition, filename: @filename, source: self) else expect_one_of([:SCHEMA, :SCALAR, :TYPE, :ENUM, :INPUT, :UNION, :INTERFACE]) end end end def parse_input_object_field_definitions if at?(:LCURLY) expect_token :LCURLY list = [] while !at?(:RCURLY) list << parse_input_value_definition end expect_token :RCURLY list else EMPTY_ARRAY end end def parse_enum_value_definitions if at?(:LCURLY) expect_token :LCURLY list = [] while !at?(:RCURLY) v_loc = pos description = if at?(:STRING); string_value; end defn_loc = pos # Any identifier, but not true, false, or null enum_value = if at?(:TRUE) || at?(:FALSE) || at?(:NULL) expect_token(:IDENTIFIER) else parse_name end v_directives = parse_directives list << EnumValueDefinition.new(pos: v_loc, definition_pos: defn_loc, description: description, name: enum_value, directives: v_directives, filename: @filename, source: self) end expect_token :RCURLY list else EMPTY_ARRAY end end def parse_union_members if at?(:EQUALS) expect_token :EQUALS if at?(:PIPE) advance_token end list = [parse_type_name] while at?(:PIPE) advance_token list << parse_type_name end list else EMPTY_ARRAY end end def parse_implements if at?(:IMPLEMENTS) advance_token list = [] while true advance_token if at?(:AMP) break unless at?(:IDENTIFIER) list << parse_type_name end list else EMPTY_ARRAY end end def parse_field_definitions expect_token :LCURLY list = [] while !at?(:RCURLY) loc = pos description = if at?(:STRING); string_value; end defn_loc = pos name = parse_name arguments_definition = parse_argument_definitions expect_token :COLON type = self.type directives = parse_directives list << FieldDefinition.new(pos: loc, definition_pos: defn_loc, description: description, name: name, arguments: arguments_definition, type: type, directives: directives, filename: @filename, source: self) end expect_token :RCURLY list end def parse_argument_definitions if at?(:LPAREN) advance_token list = [] while !at?(:RPAREN) list << parse_input_value_definition end expect_token :RPAREN list else EMPTY_ARRAY end end def parse_input_value_definition loc = pos description = if at?(:STRING); string_value; end defn_loc = pos name = parse_name expect_token :COLON type = self.type default_value = if at?(:EQUALS) advance_token value else nil end directives = parse_directives InputValueDefinition.new(pos: loc, definition_pos: defn_loc, description: description, name: name, type: type, default_value: default_value, directives: directives, filename: @filename, source: self) end def type parsed_type = case token_name when :IDENTIFIER parse_type_name when :LBRACKET list_type else nil end if at?(:BANG) && parsed_type parsed_type = Nodes::NonNullType.new(pos: pos, of_type: parsed_type, source: self) expect_token(:BANG) end parsed_type end def list_type loc = pos expect_token(:LBRACKET) inner_type = self.type parsed_list_type = if inner_type Nodes::ListType.new(pos: loc, of_type: inner_type, source: self) else nil end expect_token(:RBRACKET) parsed_list_type end def parse_operation_type val = if at?(:QUERY) "query" elsif at?(:MUTATION) "mutation" elsif at?(:SUBSCRIPTION) "subscription" else expect_one_of([:QUERY, :MUTATION, :SUBSCRIPTION]) end advance_token val end def selection_set expect_token(:LCURLY) selections = [] while @token_name != :RCURLY selections << if at?(:ELLIPSIS) loc = pos advance_token case token_name when :ON, :DIR_SIGN, :LCURLY if_type = if at?(:ON) advance_token parse_type_name else nil end directives = parse_directives Nodes::InlineFragment.new(pos: loc, type: if_type, directives: directives, selections: selection_set, filename: @filename, source: self) else name = parse_name_without_on directives = parse_directives # Can this ever happen? # expect_token(:IDENTIFIER) if at?(:ON) FragmentSpread.new(pos: loc, name: name, directives: directives, filename: @filename, source: self) end else loc = pos name = parse_name field_alias = nil if at?(:COLON) advance_token field_alias = name name = parse_name end arguments = at?(:LPAREN) ? parse_arguments : nil directives = at?(:DIR_SIGN) ? parse_directives : nil selection_set = at?(:LCURLY) ? self.selection_set : nil Nodes::Field.new(pos: loc, field_alias: field_alias, name: name, arguments: arguments, directives: directives, selections: selection_set, filename: @filename, source: self) end end expect_token(:RCURLY) selections end def parse_name case token_name when :IDENTIFIER expect_token_value(:IDENTIFIER) when :SCHEMA advance_token "schema" when :SCALAR advance_token "scalar" when :IMPLEMENTS advance_token "implements" when :INTERFACE advance_token "interface" when :UNION advance_token "union" when :ENUM advance_token "enum" when :INPUT advance_token "input" when :DIRECTIVE advance_token "directive" when :TYPE advance_token "type" when :QUERY advance_token "query" when :MUTATION advance_token "mutation" when :SUBSCRIPTION advance_token "subscription" when :TRUE advance_token "true" when :FALSE advance_token "false" when :FRAGMENT advance_token "fragment" when :REPEATABLE advance_token "repeatable" when :NULL advance_token "null" when :ON advance_token "on" when :EXTEND advance_token "extend" else expect_token(:NAME) end end def parse_name_without_on if at?(:ON) expect_token(:IDENTIFIER) else parse_name end end def parse_type_name TypeName.new(pos: pos, name: parse_name, filename: @filename, source: self) end def parse_directives if at?(:DIR_SIGN) dirs = [] while at?(:DIR_SIGN) loc = pos advance_token name = parse_name arguments = parse_arguments dirs << Nodes::Directive.new(pos: loc, name: name, arguments: arguments, filename: @filename, source: self) end dirs else EMPTY_ARRAY end end def parse_arguments if at?(:LPAREN) advance_token args = [] while !at?(:RPAREN) loc = pos name = parse_name expect_token(:COLON) args << Nodes::Argument.new(pos: loc, name: name, value: value, filename: @filename, source: self) end if args.empty? expect_token(:ARGUMENT_NAME) # At least one argument is required end expect_token(:RPAREN) args else EMPTY_ARRAY end end def string_value token_value = @lexer.string_value expect_token :STRING token_value end def value case token_name when :INT expect_token_value(:INT).to_i when :FLOAT expect_token_value(:FLOAT).to_f when :STRING string_value when :TRUE advance_token true when :FALSE advance_token false when :NULL advance_token NullValue.new(pos: pos, name: "null", filename: @filename, source: self) when :IDENTIFIER Nodes::Enum.new(pos: pos, name: expect_token_value(:IDENTIFIER), filename: @filename, source: self) when :LBRACKET advance_token list = [] while !at?(:RBRACKET) list << value end expect_token(:RBRACKET) list when :LCURLY start = pos advance_token args = [] while !at?(:RCURLY) loc = pos n = parse_name expect_token(:COLON) args << Argument.new(pos: loc, name: n, value: value, filename: @filename, source: self) end expect_token(:RCURLY) InputObject.new(pos: start, arguments: args, filename: @filename, source: self) when :VAR_SIGN loc = pos advance_token VariableIdentifier.new(pos: loc, name: parse_name, filename: @filename, source: self) when :SCHEMA advance_token Nodes::Enum.new(pos: pos, name: "schema", filename: @filename, source: self) when :SCALAR advance_token Nodes::Enum.new(pos: pos, name: "scalar", filename: @filename, source: self) when :IMPLEMENTS advance_token Nodes::Enum.new(pos: pos, name: "implements", filename: @filename, source: self) when :INTERFACE advance_token Nodes::Enum.new(pos: pos, name: "interface", filename: @filename, source: self) when :UNION advance_token Nodes::Enum.new(pos: pos, name: "union", filename: @filename, source: self) when :ENUM advance_token Nodes::Enum.new(pos: pos, name: "enum", filename: @filename, source: self) when :INPUT advance_token Nodes::Enum.new(pos: pos, name: "input", filename: @filename, source: self) when :DIRECTIVE advance_token Nodes::Enum.new(pos: pos, name: "directive", filename: @filename, source: self) when :TYPE advance_token Nodes::Enum.new(pos: pos, name: "type", filename: @filename, source: self) when :QUERY advance_token Nodes::Enum.new(pos: pos, name: "query", filename: @filename, source: self) when :MUTATION advance_token Nodes::Enum.new(pos: pos, name: "mutation", filename: @filename, source: self) when :SUBSCRIPTION advance_token Nodes::Enum.new(pos: pos, name: "subscription", filename: @filename, source: self) when :FRAGMENT advance_token Nodes::Enum.new(pos: pos, name: "fragment", filename: @filename, source: self) when :REPEATABLE advance_token Nodes::Enum.new(pos: pos, name: "repeatable", filename: @filename, source: self) when :ON advance_token Nodes::Enum.new(pos: pos, name: "on", filename: @filename, source: self) when :EXTEND advance_token Nodes::Enum.new(pos: pos, name: "extend", filename: @filename, source: self) else expect_token(:VALUE) end end def at?(expected_token_name) @token_name == expected_token_name end def expect_token(expected_token_name) unless @token_name == expected_token_name raise_parse_error("Expected #{expected_token_name}, actual: #{token_name || "(none)"} (#{debug_token_value.inspect})") end advance_token end def expect_one_of(token_names) raise_parse_error("Expected one of #{token_names.join(", ")}, actual: #{token_name || "NOTHING"} (#{debug_token_value.inspect})") end def raise_parse_error(message) message += " at [#{@lexer.line_number}, #{@lexer.column_number}]" raise GraphQL::ParseError.new( message, @lexer.line_number, @lexer.column_number, @graphql_str, filename: @filename, ) end # Only use when we care about the expected token's value def expect_token_value(tok) token_value = @lexer.token_value if @dedup_identifiers token_value = -token_value end expect_token(tok) token_value end # token_value works for when the scanner matched something # which is usually fine and it's good for it to be fast at that. def debug_token_value @lexer.debug_token_value(token_name) end class SchemaParser < Parser def initialize(*args, **kwargs) super @dedup_identifiers = true end end end end end graphql-2.6.0/lib/graphql/language/cache.rb0000644000004100000410000000320215173430257020570 0ustar www-datawww-data# frozen_string_literal: true require 'graphql/version' require 'digest/sha2' module GraphQL module Language # This cache is used by {GraphQL::Language::Parser.parse_file} when it's enabled. # # With Rails, parser caching may enabled by setting `config.graphql.parser_cache = true` in your Rails application. # # The cache may be manually built by assigning `GraphQL::Language::Parser.cache = GraphQL::Language::Cache.new("some_dir")`. # This will create a directory (`tmp/cache/graphql` by default) that stores a cache of parsed files. # # Much like [bootsnap](https://github.com/Shopify/bootsnap), the parser cache needs to be cleaned up manually. # You will need to clear the cache directory for each new deployment of your application. # Also note that the parser cache will grow as your schema is loaded, so the cache directory must be writable. # # @see GraphQL::Railtie for simple Rails integration class Cache def initialize(path) @path = path end DIGEST = Digest::SHA256.new << GraphQL::VERSION def fetch(filename) hash = DIGEST.dup << filename begin hash << File.mtime(filename).to_i.to_s rescue SystemCallError return yield end cache_path = @path.join(hash.to_s) if cache_path.exist? Marshal.load(cache_path.read) else payload = yield tmp_path = "#{cache_path}.#{rand}" @path.mkpath File.binwrite(tmp_path, Marshal.dump(payload)) File.rename(tmp_path, cache_path.to_s) payload end end end end end graphql-2.6.0/lib/graphql/unauthorized_enum_value_error.rb0000644000004100000410000000072615173430257024124 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class UnauthorizedEnumValueError < GraphQL::UnauthorizedError # @return [GraphQL::Schema::EnumValue] The value whose `#authorized?` check returned false attr_accessor :enum_value def initialize(type:, context:, enum_value:) @enum_value = enum_value message ||= "#{enum_value.path} failed authorization" super(message, object: enum_value.value, type: type, context: context) end end end graphql-2.6.0/lib/graphql/integer_encoding_error.rb0000644000004100000410000000221515173430257022461 0ustar www-datawww-data# frozen_string_literal: true module GraphQL # This error is raised when `Types::Int` is asked to return a value outside of 32-bit integer range. # # For values outside that range, consider: # # - `ID` for database primary keys or other identifiers # - `GraphQL::Types::BigInt` for really big integer values # # @see GraphQL::Types::Int which raises this error class IntegerEncodingError < GraphQL::RuntimeTypeError # The value which couldn't be encoded attr_reader :integer_value # @return [GraphQL::Schema::Field] The field that returned a too-big integer attr_reader :field # @return [Array] Where the field appeared in the GraphQL response attr_reader :path def initialize(value, context:) @integer_value = value @field = context[:current_field] @path = context[:current_path] message = "Integer out of bounds: #{value}".dup if @path message << " @ #{@path.join(".")}" end if @field message << " (#{@field.path})" end message << ". Consider using ID or GraphQL::Types::BigInt instead." super(message) end end end graphql-2.6.0/lib/graphql/dig.rb0000644000004100000410000000122015173430257016503 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Dig # implemented using the old activesupport #dig instead of the ruby built-in # so we can use some of the magic in Schema::InputObject and Interpreter::Arguments # to handle stringified/symbolized keys. # # @param own_key [String, Symbol] A key to retrieve # @param rest_keys [Array<[String, Symbol>] Retrieves the value object corresponding to the each key objects repeatedly # @return [Object] def dig(own_key, *rest_keys) val = self[own_key] if val.nil? || rest_keys.empty? val else val.dig(*rest_keys) end end end end graphql-2.6.0/lib/graphql/static_validation/0000755000004100000410000000000015173430257021121 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/static_validation/rules/0000755000004100000410000000000015173430257022253 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb0000644000004100000410000000124415173430257032215 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class OneOfInputObjectsAreValidError < StaticValidation::Error attr_reader :input_object_type def initialize(message, path:, nodes:, input_object_type:) super(message, path: path, nodes: nodes) @input_object_type = input_object_type end # A hash representation of this Message def to_h extensions = { "code" => code, "inputObjectType" => input_object_type } super.merge({ "extensions" => extensions }) end def code "invalidOneOfInputObject" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/required_arguments_are_present.rb0000644000004100000410000000363415173430257031102 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module RequiredArgumentsArePresent def initialize(*) super @required_args_cache = {}.compare_by_identity end def on_field(node, _parent) assert_required_args(node, @current_field_definition) super end def on_directive(node, _parent) directive_defn = context.schema_directives[node.name] assert_required_args(node, directive_defn) super end private def assert_required_args(ast_node, defn) return unless defn # Cache required argument names per definition to avoid re-iterating # arguments for the same definition across field instances if @required_args_cache.key?(defn) required_argument_names = @required_args_cache[defn] else args = @types.arguments(defn) required_argument_names = nil if !args.empty? args.each do |a| if a.type.kind.non_null? && !a.default_value? && @types.argument(defn, a.name) (required_argument_names ||= []) << a.graphql_name end end end @required_args_cache[defn] = required_argument_names end return if required_argument_names.nil? present_argument_names = ast_node.arguments.map(&:name) missing_names = required_argument_names - present_argument_names if !missing_names.empty? add_error(GraphQL::StaticValidation::RequiredArgumentsArePresentError.new( "#{ast_node.class.name.split("::").last} '#{ast_node.name}' is missing required arguments: #{missing_names.join(", ")}", nodes: ast_node, class_name: ast_node.class.name.split("::").last, name: ast_node.name, arguments: "#{missing_names.join(", ")}" )) end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/arguments_are_defined_error.rb0000644000004100000410000000156515173430257030332 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class ArgumentsAreDefinedError < StaticValidation::Error attr_reader :name attr_reader :type_name attr_reader :argument_name attr_reader :parent def initialize(message, path: nil, nodes: [], name:, type:, argument_name:, parent:) super(message, path: path, nodes: nodes) @name = name @type_name = type @argument_name = argument_name @parent = parent end # A hash representation of this Message def to_h extensions = { "code" => code, "name" => name, "typeName" => type_name, "argumentName" => argument_name } super.merge({ "extensions" => extensions }) end def code "argumentNotAccepted" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fragments_are_on_composite_types_error.rb0000644000004100000410000000123115173430257032625 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreOnCompositeTypesError < StaticValidation::Error attr_reader :type_name attr_reader :argument_name def initialize(message, path: nil, nodes: [], type:) super(message, path: path, nodes: nodes) @type_name = type end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name } super.merge({ "extensions" => extensions }) end def code "fragmentOnNonCompositeType" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fragment_types_exist.rb0000644000004100000410000000256615173430257027054 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module FragmentTypesExist def on_fragment_definition(node, _parent) if validate_type_exists(node) super end end def on_inline_fragment(node, _parent) if validate_type_exists(node) super end end private def validate_type_exists(fragment_node) if !fragment_node.type true else type_name = fragment_node.type.name type = @types.type(type_name) if type.nil? suggestion = if @schema.did_you_mean @all_possible_fragment_type_names ||= begin names = [] context.types.all_types.each do |type| if type.kind.fields? names << type.graphql_name end end names end context.did_you_mean_suggestion(type_name, @all_possible_fragment_type_names) end add_error(GraphQL::StaticValidation::FragmentTypesExistError.new( "No such type #{type_name}, so it can't be a fragment condition#{suggestion}", nodes: fragment_node, type: type_name )) false else true end end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fields_will_merge_error.rb0000644000004100000410000000263215173430257027470 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class FieldsWillMergeError < StaticValidation::Error attr_reader :field_name attr_reader :kind def initialize(kind:, field_name:) super(nil) @field_name = field_name @kind = kind @conflicts = [] end def message @message || "Field '#{field_name}' has #{kind == :argument ? 'an' : 'a'} #{kind} conflict: #{conflicts}?" end attr_writer :message def path [] end def conflicts @conflicts.join(' or ') end def add_conflict(node, conflict_str) # Check if we already have an error for this exact node. # Use object identity first (fast path), then fall back to # value + location comparison for duplicate AST nodes. if nodes.any? { |n| n.equal?(node) || (n.line == node.line && n.col == node.col && n == node) } # already have an error for this node return end @nodes << node @conflicts << conflict_str end # A hash representation of this Message def to_h extensions = { "code" => code, "fieldName" => field_name, "conflicts" => conflicts } super.merge({ "extensions" => extensions }) end def code "fieldConflict" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/no_definitions_are_present.rb0000644000004100000410000000271415173430257030202 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module NoDefinitionsArePresent include GraphQL::StaticValidation::Error::ErrorHelper def initialize(*) super @schema_definition_nodes = [] end def on_invalid_node(node, parent) @schema_definition_nodes << node nil end alias :on_directive_definition :on_invalid_node alias :on_schema_definition :on_invalid_node alias :on_scalar_type_definition :on_invalid_node alias :on_object_type_definition :on_invalid_node alias :on_input_object_type_definition :on_invalid_node alias :on_interface_type_definition :on_invalid_node alias :on_union_type_definition :on_invalid_node alias :on_enum_type_definition :on_invalid_node alias :on_schema_extension :on_invalid_node alias :on_scalar_type_extension :on_invalid_node alias :on_object_type_extension :on_invalid_node alias :on_input_object_type_extension :on_invalid_node alias :on_interface_type_extension :on_invalid_node alias :on_union_type_extension :on_invalid_node alias :on_enum_type_extension :on_invalid_node def on_document(node, parent) super if !@schema_definition_nodes.empty? add_error(GraphQL::StaticValidation::NoDefinitionsArePresentError.new(%|Query cannot contain schema definitions|, nodes: @schema_definition_nodes)) end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fragments_are_finite_error.rb0000644000004100000410000000116015173430257030162 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreFiniteError < StaticValidation::Error attr_reader :fragment_name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @fragment_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "fragmentName" => fragment_name } super.merge({ "extensions" => extensions }) end def code "infiniteLoop" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fragments_are_finite.rb0000644000004100000410000000116615173430257026757 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreFinite def on_document(_n, _p) super dependency_map = context.dependencies dependency_map.cyclical_definitions.each do |defn| if defn.node.is_a?(GraphQL::Language::Nodes::FragmentDefinition) add_error(GraphQL::StaticValidation::FragmentsAreFiniteError.new( "Fragment #{defn.name} contains an infinite loop", nodes: defn.node, path: defn.path, name: defn.name )) end end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fields_will_merge.rb0000644000004100000410000004511515173430257026262 0ustar www-datawww-data # frozen_string_literal: true # frozen_string_literal: true module GraphQL module StaticValidation module FieldsWillMerge # Validates that a selection set is valid if all fields (including spreading any # fragments) either correspond to distinct response names or can be merged # without ambiguity. # # Optimized algorithm based on: # https://tech.new-work.se/graphql-overlapping-fields-can-be-merged-fast-ea6e92e0a01 # # Instead of comparing fields, fields-vs-fragments, and fragments-vs-fragments # separately (which leads to exponential recursion through nested fragments), # we flatten all fragment spreads into a single field map and compare within it. NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH class Field attr_reader :node, :definition, :owner_type, :parents def initialize(node, definition, owner_type, parents) @node = node @definition = definition @owner_type = owner_type @parents = parents end def return_type @return_type ||= @definition&.type end def unwrapped_return_type @unwrapped_return_type ||= return_type&.unwrap end end def initialize(*) super @conflict_count = 0 @max_errors = context.max_errors @fragments = context.fragments # Track which sub-selection node pairs have been compared to prevent # infinite recursion with cyclic fragments @compared_sub_selections = {}.compare_by_identity # Cache mutually_exclusive? results for type pairs @mutually_exclusive_cache = {}.compare_by_identity # Cache collect_fields results for sub-selection comparison @sub_fields_cache = {}.compare_by_identity end def on_operation_definition(node, _parent) @conflicts = nil conflicts_within_selection_set(node, type_definition) @conflicts&.each_value { |error_type| error_type.each_value { |error| add_error(error) } } super end def on_field(node, _parent) if !node.selections.empty? && selections_may_conflict?(node.selections) @conflicts = nil conflicts_within_selection_set(node, type_definition) @conflicts&.each_value { |error_type| error_type.each_value { |error| add_error(error) } } end super end private # Quick check: can the direct children of this selection set possibly conflict? # If all direct selections are Fields with unique names and no aliases, # and there are no fragments, then no response key can have >1 field, # so there are no merge conflicts to check at this level. def selections_may_conflict?(selections) i = 0 len = selections.size while i < len sel = selections[i] # Fragment spread or inline fragment — needs full check return true unless sel.is_a?(GraphQL::Language::Nodes::Field) # Aliased field — could create duplicate response key return true if sel.alias i += 1 end # All are unaliased fields — check for duplicate names # For small sets, O(n²) is cheaper than hash allocation if len <= 8 i = 0 while i < len j = i + 1 name_i = selections[i].name while j < len return true if selections[j].name == name_i j += 1 end i += 1 end false else true # Assume potential conflicts for larger sets end end def conflicts @conflicts ||= Hash.new do |h, error_type| h[error_type] = Hash.new do |h2, field_name| h2[field_name] = GraphQL::StaticValidation::FieldsWillMergeError.new(kind: error_type, field_name: field_name) end end end # Core algorithm: collect ALL fields (expanding fragments inline) into a flat # map keyed by response key, then compare within each group. def conflicts_within_selection_set(node, parent_type) return if parent_type.nil? return if node.selections.empty? # Collect all fields from this selection set, expanding fragments transitively response_keys = collect_fields(node.selections, owner_type: parent_type, parents: []) # Find conflicts within each response key group find_conflicts_within(response_keys) end # Collect all fields from selections, expanding fragment spreads inline. # Returns a Hash of { response_key => Field | [Field, ...] } def collect_fields(selections, owner_type:, parents:) response_keys = {} collect_fields_inner(selections, owner_type: owner_type, parents: parents, response_keys: response_keys, visited_fragments: nil) response_keys end def collect_fields_inner(selections, owner_type:, parents:, response_keys:, visited_fragments:) deferred_spreads = nil sel_idx = 0 sel_len = selections.size while sel_idx < sel_len sel = selections[sel_idx] case sel when GraphQL::Language::Nodes::Field definition = @types.field(owner_type, sel.name) key = sel.alias || sel.name field = Field.new(sel, definition, owner_type, parents) existing = response_keys[key] if existing.nil? response_keys[key] = field elsif existing.is_a?(Field) response_keys[key] = [existing, field] else existing << field end when GraphQL::Language::Nodes::InlineFragment frag_type = sel.type ? @types.type(sel.type.name) : owner_type if frag_type new_parents = parents.dup new_parents << frag_type collect_fields_inner(sel.selections, owner_type: frag_type, parents: new_parents, response_keys: response_keys, visited_fragments: visited_fragments) end when GraphQL::Language::Nodes::FragmentSpread (deferred_spreads ||= []) << sel end sel_idx += 1 end if deferred_spreads visited_fragments ||= {} sel_idx = 0 sel_len = deferred_spreads.size while sel_idx < sel_len sel = deferred_spreads[sel_idx] sel_idx += 1 next if visited_fragments.key?(sel.name) visited_fragments[sel.name] = true frag = @fragments[sel.name] next unless frag frag_type = @types.type(frag.type.name) next unless frag_type new_parents = parents.dup new_parents << frag_type collect_fields_inner(frag.selections, owner_type: frag_type, parents: new_parents, response_keys: response_keys, visited_fragments: visited_fragments) end end end def find_conflicts_within(response_keys) response_keys.each do |key, fields| next unless fields.is_a?(Array) # Optimization: group fields by signature (name + definition + arguments). # Fields with the same signature can only conflict on sub-selections, # so we only need to compare one pair within each group. if fields.size > 4 f0 = fields[0] all_same = true i = 1 while i < fields.size unless fields_same_signature?(f0, fields[i]) all_same = false break end i += 1 end if all_same # All fields share a signature, so they can only conflict on # sub-selections. Deduplicate by AST node identity — fields from # the same node always have identical sub-selections. unique_nodes = fields.uniq { |f| f.node.object_id } i = 0 while i < unique_nodes.size j = i + 1 while j < unique_nodes.size if unique_nodes[i].node.selections.size > 0 || unique_nodes[j].node.selections.size > 0 find_conflict(key, unique_nodes[i], unique_nodes[j]) end j += 1 end i += 1 end else groups = fields.group_by { |f| field_signature(f) } unique_groups = groups.values # Compare representatives across different groups gi = 0 while gi < unique_groups.size gj = gi + 1 while gj < unique_groups.size find_conflict(key, unique_groups[gi][0], unique_groups[gj][0]) gj += 1 end # Within same group, deduplicate by AST node and compare all # pairs for sub-selection conflicts group = unique_groups[gi] if group.size >= 2 unique_in_group = group.uniq { |f| f.node.object_id } ui = 0 while ui < unique_in_group.size uj = ui + 1 while uj < unique_in_group.size if unique_in_group[ui].node.selections.size > 0 || unique_in_group[uj].node.selections.size > 0 find_conflict(key, unique_in_group[ui], unique_in_group[uj]) end uj += 1 end ui += 1 end end gi += 1 end end else # Small number of fields — original O(n²) is fine i = 0 while i < fields.size j = i + 1 while j < fields.size find_conflict(key, fields[i], fields[j]) j += 1 end i += 1 end end end end def fields_same_signature?(f1, f2) n1 = f1.node n2 = f2.node f1.definition.equal?(f2.definition) && n1.name == n2.name && same_arguments?(n1, n2) end def field_signature(field) node = field.node defn = field.definition args = node.arguments if args.empty? [node.name, defn.object_id] else [node.name, defn.object_id, args.map { |a| [a.name, serialize_arg(a.value)] }] end end def find_conflict(response_key, field1, field2, mutually_exclusive: false) return if @conflict_count >= @max_errors return if field1.definition.nil? || field2.definition.nil? node1 = field1.node node2 = field2.node are_mutually_exclusive = mutually_exclusive || mutually_exclusive?(field1.parents, field2.parents) if !are_mutually_exclusive if node1.name != node2.name conflict = conflicts[:field][response_key] conflict.add_conflict(node1, node1.name) conflict.add_conflict(node2, node2.name) @conflict_count += 1 end if !same_arguments?(node1, node2) conflict = conflicts[:argument][response_key] conflict.add_conflict(node1, GraphQL::Language.serialize(serialize_field_args(node1))) conflict.add_conflict(node2, GraphQL::Language.serialize(serialize_field_args(node2))) @conflict_count += 1 end end if !conflicts[:field].key?(response_key) && !field1.definition.equal?(field2.definition) && (t1 = field1.return_type) && (t2 = field2.return_type) && return_types_conflict?(t1, t2) return_error = nil message_override = nil case @schema.allow_legacy_invalid_return_type_conflicts when false return_error = true when true legacy_handling = @schema.legacy_invalid_return_type_conflicts(@context.query, t1, t2, node1, node2) case legacy_handling when nil return_error = false when :return_validation_error return_error = true when String return_error = true message_override = legacy_handling else raise GraphQL::Error, "#{@schema}.legacy_invalid_scalar_conflicts returned unexpected value: #{legacy_handling.inspect}. Expected `nil`, String, or `:return_validation_error`." end else return_error = false @context.query.logger.warn <<~WARN GraphQL-Ruby encountered mismatched types in this query: `#{t1.to_type_signature}` (at #{node1.line}:#{node1.col}) vs. `#{t2.to_type_signature}` (at #{node2.line}:#{node2.col}). This will return an error in future GraphQL-Ruby versions, as per the GraphQL specification Learn about migrating here: https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#allow_legacy_invalid_return_type_conflicts-class_method WARN end if return_error conflict = conflicts[:return_type][response_key] if message_override conflict.message = message_override end conflict.add_conflict(node1, "`#{t1.to_type_signature}`") conflict.add_conflict(node2, "`#{t2.to_type_signature}`") @conflict_count += 1 end end find_conflicts_between_sub_selection_sets( field1, field2, mutually_exclusive: are_mutually_exclusive, ) end def return_types_conflict?(type1, type2) if type1.list? if type2.list? return_types_conflict?(type1.of_type, type2.of_type) else true end elsif type2.list? true elsif type1.non_null? if type2.non_null? return_types_conflict?(type1.of_type, type2.of_type) else true end elsif type2.non_null? true elsif type1.kind.leaf? && type2.kind.leaf? type1 != type2 else false end end # When two fields with the same response key both have sub-selections, # we need to check those sub-selections against each other. def find_conflicts_between_sub_selection_sets(field1, field2, mutually_exclusive:) return if field1.definition.nil? || field2.definition.nil? || (field1.node.selections.empty? && field2.node.selections.empty?) node1 = field1.node node2 = field2.node # Prevent infinite recursion from cyclic fragments return if node1.equal?(node2) inner = @compared_sub_selections[node1] if inner return if inner.key?(node2) inner[node2] = true else inner = {}.compare_by_identity inner[node2] = true @compared_sub_selections[node1] = inner end return_type1 = field1.unwrapped_return_type return_type2 = field2.unwrapped_return_type response_keys1 = cached_sub_fields(node1, return_type1) response_keys2 = cached_sub_fields(node2, return_type2) find_conflicts_between(response_keys1, response_keys2, mutually_exclusive: mutually_exclusive) end def cached_sub_fields(node, return_type) inner = @sub_fields_cache[node] if inner && inner.key?(return_type) inner[return_type] else result = collect_fields(node.selections, owner_type: return_type, parents: [return_type]) inner ||= {}.compare_by_identity inner[return_type] = result @sub_fields_cache[node] = inner result end end def find_conflicts_between(response_keys, response_keys2, mutually_exclusive:) response_keys.each do |key, fields| fields2 = response_keys2[key] next unless fields2 fields_arr = fields.is_a?(Field) ? [fields] : fields fields2_arr = fields2.is_a?(Field) ? [fields2] : fields2 fields_arr.each do |field| fields2_arr.each do |field2| find_conflict( key, field, field2, mutually_exclusive: mutually_exclusive, ) end end end end def same_arguments?(field1, field2) arguments1 = field1.arguments arguments2 = field2.arguments return false if arguments1.length != arguments2.length arguments1.all? do |argument1| argument2 = arguments2.find { |argument| argument.name == argument1.name } return false if argument2.nil? serialize_arg(argument1.value) == serialize_arg(argument2.value) end end def serialize_arg(arg_value) case arg_value when GraphQL::Language::Nodes::AbstractNode arg_value.to_query_string when Array "[#{arg_value.map { |a| serialize_arg(a) }.join(", ")}]" else GraphQL::Language.serialize(arg_value) end end def serialize_field_args(field) serialized_args = {} field.arguments.each do |argument| serialized_args[argument.name] = serialize_arg(argument.value) end serialized_args end # Given two list of parents, find out if they are mutually exclusive def mutually_exclusive?(parents1, parents2) if parents1.empty? || parents2.empty? false elsif parents1.length == parents2.length i = 0 len = parents1.length while i < len type1 = parents1[i - 1] type2 = parents2[i - 1] unless type1.equal?(type2) inner = @mutually_exclusive_cache[type1] if inner cached = inner[type2] if cached.nil? cached = types_mutually_exclusive?(type1, type2) inner[type2] = cached end else cached = types_mutually_exclusive?(type1, type2) inner = {}.compare_by_identity inner[type2] = cached @mutually_exclusive_cache[type1] = inner end return true if cached end i += 1 end false else true end end def types_mutually_exclusive?(type1, type2) possible_right_types = @types.possible_types(type1) possible_left_types = @types.possible_types(type2) (possible_right_types & possible_left_types).empty? end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb0000644000004100000410000000256415173430257030117 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module FieldsAreDefinedOnType def on_field(node, parent) parent_type = @parent_object_type field = context.query.types.field(parent_type, node.name) if field.nil? if parent_type.kind.union? add_error(GraphQL::StaticValidation::FieldsHaveAppropriateSelectionsError.new( "Selections can't be made directly on unions (see selections on #{parent_type.graphql_name})", nodes: parent, node_name: parent_type.graphql_name )) else suggestion = if @schema.did_you_mean context.did_you_mean_suggestion(node.name, possible_fields(context, parent_type)) end message = "Field '#{node.name}' doesn't exist on type '#{parent_type.graphql_name}'#{suggestion}" add_error(GraphQL::StaticValidation::FieldsAreDefinedOnTypeError.new( message, nodes: node, field: node.name, type: parent_type.graphql_name )) end else super end end private def possible_fields(context, parent_type) return EmptyObjects::EMPTY_ARRAY if parent_type.kind.leaf? context.types.fields(parent_type).map(&:graphql_name) end end end end graphql-2.6.0/lib/graphql/static_validation/rules/mutation_root_exists.rb0000644000004100000410000000073115173430257027103 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module MutationRootExists def on_operation_definition(node, _parent) if node.operation_type == 'mutation' && context.query.types.mutation_root.nil? add_error(GraphQL::StaticValidation::MutationRootExistsError.new( 'Schema is not configured for mutations', nodes: node )) else super end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb0000644000004100000410000000113415173430257032075 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class InputObjectNamesAreUniqueError < StaticValidation::Error attr_reader :name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "name" => name } super.merge({ "extensions" => extensions }) end def code "inputFieldNotUnique" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/variable_names_are_unique_error.rb0000644000004100000410000000117115173430257031176 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class VariableNamesAreUniqueError < StaticValidation::Error attr_reader :variable_name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @variable_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name } super.merge({ "extensions" => extensions }) end def code "variableNotUnique" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fragments_are_named.rb0000644000004100000410000000060315173430257026560 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreNamed def on_fragment_definition(node, _parent) if node.name.nil? add_error(GraphQL::StaticValidation::FragmentsAreNamedError.new( "Fragment definition has no name", nodes: node )) end super end end end end graphql-2.6.0/lib/graphql/static_validation/rules/query_root_exists_error.rb0000644000004100000410000000101215173430257027612 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class QueryRootExistsError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "missingQueryConfiguration" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fields_are_defined_on_type_error.rb0000644000004100000410000000131515173430257031321 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class FieldsAreDefinedOnTypeError < StaticValidation::Error attr_reader :type_name attr_reader :field_name def initialize(message, path: nil, nodes: [], type:, field:) super(message, path: path, nodes: nodes) @type_name = type @field_name = field end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name, "fieldName" => field_name } super.merge({ "extensions" => extensions }) end def code "undefinedField" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/directives_are_defined_error.rb0000644000004100000410000000120615173430257030456 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class DirectivesAreDefinedError < StaticValidation::Error attr_reader :directive_name def initialize(message, path: nil, nodes: [], directive:) super(message, path: path, nodes: nodes) @directive_name = directive end # A hash representation of this Message def to_h extensions = { "code" => code, "directiveName" => directive_name } super.merge({ "extensions" => extensions }) end def code "undefinedDirective" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb0000644000004100000410000001146215173430257031713 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation # Scalars _can't_ have selections # Objects _must_ have selections module FieldsHaveAppropriateSelections include GraphQL::StaticValidation::Error::ErrorHelper def on_field(node, parent) if validate_field_selections(node, @current_object_type) super end end def on_operation_definition(node, _parent) if validate_field_selections(node, type_definition) super end end private def validate_field_selections(ast_node, resolved_type) # Fast paths for the two most common cases: # 1. Leaf type with no selections (scalars, enums) — most fields # 2. Non-leaf type with selections (objects, interfaces) if resolved_type if ast_node.selections.empty? return true if resolved_type.kind.leaf? else return true unless resolved_type.kind.leaf? end end msg = if resolved_type.nil? nil elsif resolved_type.kind.leaf? if !ast_node.selections.empty? selection_strs = ast_node.selections.map do |n| case n when GraphQL::Language::Nodes::InlineFragment "\"... on #{n.type.name} { ... }\"" when GraphQL::Language::Nodes::Field "\"#{n.name}\"" when GraphQL::Language::Nodes::FragmentSpread "\"#{n.name}\"" else raise "Invariant: unexpected selection node: #{n}" end end "Selections can't be made on #{resolved_type.kind.name.sub("_", " ").downcase}s (%{node_name} returns #{resolved_type.graphql_name} but has selections [#{selection_strs.join(", ")}])" else nil end elsif ast_node.selections.empty? return_validation_error = true legacy_invalid_empty_selection_result = nil if !resolved_type.kind.fields? case @schema.allow_legacy_invalid_empty_selections_on_union when true legacy_invalid_empty_selection_result = @schema.legacy_invalid_empty_selections_on_union_with_type(@context.query, resolved_type) case legacy_invalid_empty_selection_result when :return_validation_error # keep `return_validation_error = true` when String return_validation_error = false # the string is returned below when nil # No error: return_validation_error = false legacy_invalid_empty_selection_result = nil else raise GraphQL::InvariantError, "Unexpected return value from legacy_invalid_empty_selections_on_union_with_type, must be `:return_validation_error`, String, or nil (got: #{legacy_invalid_empty_selection_result.inspect})" end when false # pass -- error below else return_validation_error = false @context.query.logger.warn("Unions require selections but #{ast_node.alias || ast_node.name} (#{resolved_type.graphql_name}) doesn't have any. This will fail with a validation error on a future GraphQL-Ruby version. More info: https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#allow_legacy_invalid_empty_selections_on_union-class_method") end end if return_validation_error "Field must have selections (%{node_name} returns #{resolved_type.graphql_name} but has no selections. Did you mean '#{ast_node.name} { ... }'?)" else legacy_invalid_empty_selection_result end else nil end if msg node_name = case ast_node when GraphQL::Language::Nodes::Field "field '#{ast_node.name}'" when GraphQL::Language::Nodes::OperationDefinition if ast_node.name.nil? "anonymous query" else "#{ast_node.operation_type} '#{ast_node.name}'" end else raise("Unexpected node #{ast_node}") end extensions = { "rule": "StaticValidation::FieldsHaveAppropriateSelections", "name": node_name.to_s } unless resolved_type.nil? extensions["type"] = resolved_type.to_type_signature end add_error(GraphQL::StaticValidation::FieldsHaveAppropriateSelectionsError.new( msg % { node_name: node_name }, nodes: ast_node, node_name: node_name.to_s, type: resolved_type.nil? ? nil : resolved_type.graphql_name, )) false else true end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fragment_types_exist_error.rb0000644000004100000410000000114115173430257030251 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class FragmentTypesExistError < StaticValidation::Error attr_reader :type_name def initialize(message, path: nil, nodes: [], type:) super(message, path: path, nodes: nodes) @type_name = type end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name } super.merge({ "extensions" => extensions }) end def code "undefinedType" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb0000644000004100000410000000421515173430257031005 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module OneOfInputObjectsAreValid def on_input_object(node, parent) return super unless parent.is_a?(GraphQL::Language::Nodes::Argument) parent_type = get_parent_type(context, parent) return super unless parent_type && parent_type.kind.input_object? && parent_type.one_of? validate_one_of_input_object(node, context, parent_type) super end private def validate_one_of_input_object(ast_node, context, parent_type) present_fields = ast_node.arguments.map(&:name) input_object_type = parent_type.to_type_signature if present_fields.count != 1 add_error( OneOfInputObjectsAreValidError.new( "OneOf Input Object '#{input_object_type}' must specify exactly one key.", path: context.path, nodes: ast_node, input_object_type: input_object_type ) ) return end field = present_fields.first value = ast_node.arguments.first.value if value.is_a?(GraphQL::Language::Nodes::NullValue) add_error( OneOfInputObjectsAreValidError.new( "Argument '#{input_object_type}.#{field}' must be non-null.", path: [*context.path, field], nodes: ast_node.arguments.first, input_object_type: input_object_type ) ) return end if value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) variable_name = value.name variable_type = @declared_variables[variable_name].type unless variable_type.is_a?(GraphQL::Language::Nodes::NonNullType) add_error( OneOfInputObjectsAreValidError.new( "Variable '#{variable_name}' must be non-nullable to be used for OneOf Input Object '#{input_object_type}'.", path: [*context.path, field], nodes: ast_node, input_object_type: input_object_type ) ) end end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fragments_are_used.rb0000644000004100000410000000204615173430257026437 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreUsed def on_document(node, parent) super dependency_map = context.dependencies dependency_map.unmet_dependencies.each do |op_defn, spreads| spreads.each do |fragment_spread| add_error(GraphQL::StaticValidation::FragmentsAreUsedError.new( "Fragment #{fragment_spread.name} was used, but not defined", nodes: fragment_spread.node, path: fragment_spread.path, fragment: fragment_spread.name )) end end dependency_map.unused_dependencies.each do |fragment| if fragment && !fragment.name.nil? add_error(GraphQL::StaticValidation::FragmentsAreUsedError.new( "Fragment #{fragment.name} was defined, but not used", nodes: fragment.node, path: fragment.path, fragment: fragment.name )) end end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/variables_are_input_types_error.rb0000644000004100000410000000134215173430257031253 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class VariablesAreInputTypesError < StaticValidation::Error attr_reader :type_name attr_reader :variable_name def initialize(message, path: nil, nodes: [], type:, name:) super(message, path: path, nodes: nodes) @type_name = type @variable_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name, "variableName" => variable_name } super.merge({ "extensions" => extensions }) end def code "variableRequiresValidType" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/variable_usages_are_allowed_error.rb0000644000004100000410000000170715173430257031510 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class VariableUsagesAreAllowedError < StaticValidation::Error attr_reader :type_name attr_reader :variable_name attr_reader :argument_name attr_reader :error_message def initialize(message, path: nil, nodes: [], type:, name:, argument:, error:) super(message, path: path, nodes: nodes) @type_name = type @variable_name = name @argument_name = argument @error_message = error end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name, "typeName" => type_name, "argumentName" => argument_name, "errorMessage" => error_message } super.merge({ "extensions" => extensions }) end def code "variableMismatch" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/arguments_are_defined.rb0000644000004100000410000000454215173430257027117 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module ArgumentsAreDefined def on_argument(node, parent) parent_defn = parent_definition(parent) if parent_defn && @types.argument(parent_defn, node.name) super elsif parent_defn kind_of_node = node_type(parent) error_arg_name = parent_name(parent, parent_defn) suggestion = if @schema.did_you_mean arg_names = context.types.arguments(parent_defn).map(&:graphql_name) context.did_you_mean_suggestion(node.name, arg_names) end add_error(GraphQL::StaticValidation::ArgumentsAreDefinedError.new( "#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'#{suggestion}", nodes: node, name: error_arg_name, type: kind_of_node, argument_name: node.name, parent: parent_defn )) else # Some other weird error super end end private # TODO smell: these methods are added to all visitors, since they're included in a module. def parent_name(parent, type_defn) case parent when GraphQL::Language::Nodes::Field parent.alias || parent.name when GraphQL::Language::Nodes::InputObject type_defn.graphql_name when GraphQL::Language::Nodes::Argument, GraphQL::Language::Nodes::Directive parent.name else raise "Invariant: Unexpected parent #{parent.inspect} (#{parent.class})" end end def node_type(parent) parent.class.name.split("::").last end def parent_definition(parent) case parent when GraphQL::Language::Nodes::InputObject arg_defn = context.argument_definition if arg_defn.nil? nil else arg_ret_type = arg_defn.type.unwrap if arg_ret_type.kind.input_object? arg_ret_type else nil end end when GraphQL::Language::Nodes::Directive context.schema_directives[parent.name] when GraphQL::Language::Nodes::Field context.field_definition else raise "Unexpected argument parent: #{parent.class} (##{parent})" end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/mutation_root_exists_error.rb0000644000004100000410000000102015173430257030304 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class MutationRootExistsError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "missingMutationConfiguration" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb0000644000004100000410000000241115173430257033570 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module VariableDefaultValuesAreCorrectlyTyped def on_variable_definition(node, parent) if !node.default_value.nil? value = node.default_value type = context.schema.type_from_ast(node.type, context: context) if type.nil? # This is handled by another validator else validation_result = context.validate_literal(value, type) if !validation_result.valid? problems = validation_result.problems first_problem = problems && problems.first if first_problem error_message = first_problem["explanation"] end error_message ||= "Default value for $#{node.name} doesn't match type #{type.to_type_signature}" add_error(GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTypedError.new( error_message, nodes: node, name: node.name, type: type.to_type_signature, error_type: VariableDefaultValuesAreCorrectlyTypedError::VIOLATIONS[:INVALID_TYPE], )) end end end super end end end end graphql-2.6.0/lib/graphql/static_validation/rules/unique_directives_per_location.rb0000644000004100000410000000416515173430257031073 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module UniqueDirectivesPerLocation DIRECTIVE_NODE_HOOKS = [ :on_fragment_definition, :on_fragment_spread, :on_inline_fragment, :on_operation_definition, :on_scalar_type_definition, :on_object_type_definition, :on_input_value_definition, :on_field_definition, :on_interface_type_definition, :on_union_type_definition, :on_enum_type_definition, :on_enum_value_definition, :on_input_object_type_definition, :on_field, ] VALIDATE_DIRECTIVE_LOCATION_ON_NODE = <<~RUBY def %{method_name}(node, parent) if !node.directives.empty? validate_directive_location(node) end super(node, parent) end RUBY DIRECTIVE_NODE_HOOKS.each do |method_name| # Can't use `define_method {...}` here because the proc can't be isolated for use in non-main Ractors module_eval(VALIDATE_DIRECTIVE_LOCATION_ON_NODE % { method_name: method_name }) # rubocop:disable Development/NoEvalCop end private def validate_directive_location(node) used_directives = {} node.directives.each do |ast_directive| directive_name = ast_directive.name if (first_node = used_directives[directive_name]) @directives_are_unique_errors_by_first_node ||= {} err = @directives_are_unique_errors_by_first_node[first_node] ||= begin error = GraphQL::StaticValidation::UniqueDirectivesPerLocationError.new( "The directive \"#{directive_name}\" can only be used once at this location.", nodes: [used_directives[directive_name]], directive: directive_name, ) add_error(error) error end err.nodes << ast_directive elsif !((dir_defn = context.schema_directives[directive_name]) && dir_defn.repeatable?) used_directives[directive_name] = ast_directive end end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb0000644000004100000410000000176615173430257031431 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module FragmentsAreOnCompositeTypes def on_fragment_definition(node, parent) validate_type_is_composite(node) && super end def on_inline_fragment(node, parent) validate_type_is_composite(node) && super end private def validate_type_is_composite(node) node_type = node.type if node_type.nil? # Inline fragment on the same type true else type_name = node_type.to_query_string type_def = @types.type(type_name) if type_def.nil? || !type_def.kind.composite? add_error(GraphQL::StaticValidation::FragmentsAreOnCompositeTypesError.new( "Invalid fragment on type #{type_name} (must be Union, Interface or Object)", nodes: node, type: type_name )) false else true end end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/query_root_exists.rb0000644000004100000410000000075115173430257026412 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module QueryRootExists def on_operation_definition(node, _parent) if (node.operation_type == 'query' || node.operation_type.nil?) && context.query.types.query_root.nil? add_error(GraphQL::StaticValidation::QueryRootExistsError.new( 'Schema is not configured for queries', nodes: node )) else super end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/variable_names_are_unique.rb0000644000004100000410000000131415173430257027764 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module VariableNamesAreUnique def on_operation_definition(node, parent) var_defns = node.variables if !var_defns.empty? vars_by_name = Hash.new { |h, k| h[k] = [] } var_defns.each { |v| vars_by_name[v.name] << v } vars_by_name.each do |name, defns| if defns.size > 1 add_error(GraphQL::StaticValidation::VariableNamesAreUniqueError.new( "There can only be one variable named \"#{name}\"", nodes: defns, name: name )) end end end super end end end end ././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootgraphql-2.6.0/lib/graphql/static_validation/rules/required_input_object_attributes_are_present_error.rbgraphql-2.6.0/lib/graphql/static_validation/rules/required_input_object_attributes_are_present_error0000644000004100000410000000170515173430257034634 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class RequiredInputObjectAttributesArePresentError < StaticValidation::Error attr_reader :argument_type attr_reader :argument_name attr_reader :input_object_type def initialize(message, path:, nodes:, argument_type:, argument_name:, input_object_type:) super(message, path: path, nodes: nodes) @argument_type = argument_type @argument_name = argument_name @input_object_type = input_object_type end # A hash representation of this Message def to_h extensions = { "code" => code, "argumentName" => argument_name, "argumentType" => argument_type, "inputObjectType" => input_object_type, } super.merge({ "extensions" => extensions }) end def code "missingRequiredInputObjectAttribute" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb0000644000004100000410000000257115173430257032565 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class ArgumentLiteralsAreCompatibleError < StaticValidation::Error attr_reader :type_name attr_reader :argument_name attr_reader :argument attr_reader :value def initialize(message, path: nil, nodes: [], type:, argument_name: nil, extensions: nil, coerce_extensions: nil, argument: nil, value: nil) super(message, path: path, nodes: nodes) @type_name = type @argument_name = argument_name @extensions = extensions @coerce_extensions = coerce_extensions @argument = argument @value = value end # A hash representation of this Message def to_h if @coerce_extensions extensions = @coerce_extensions # This is for legacy compat -- but this key is supposed to be a GraphQL type name :confounded: extensions["typeName"] = "CoercionError" else extensions = { "code" => code, "typeName" => type_name } if argument_name extensions["argumentName"] = argument_name end end extensions.merge!(@extensions) unless @extensions.nil? super.merge({ "extensions" => extensions }) end def code "argumentLiteralsIncompatible" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb0000644000004100000410000000427415173430257034031 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module RequiredInputObjectAttributesArePresent def on_input_object(node, parent) if parent.is_a? GraphQL::Language::Nodes::Argument validate_input_object(node, context, parent) end super end private def get_parent_type(context, parent) # If argument_definition is defined we're at nested object # and need to refer to the containing input object type rather # than the field_definition. # h/t @rmosolgo arg_defn = context.argument_definition # Double checking that arg_defn is an input object as nested # scalars, namely JSON, can make it to this branch defn = if arg_defn && arg_defn.type.unwrap.kind.input_object? arg_defn.type.unwrap else context.directive_definition || context.field_definition end parent_type = context.types.argument(defn, parent_name(parent, defn)) parent_type ? parent_type.type.unwrap : nil end def validate_input_object(ast_node, context, parent) parent_type = get_parent_type(context, parent) return unless parent_type && parent_type.kind.input_object? required_fields = context.types.arguments(parent_type) .select{ |arg| arg.type.kind.non_null? && !arg.default_value? } .map!(&:graphql_name) present_fields = ast_node.arguments.map(&:name) missing_fields = required_fields - present_fields missing_fields.each do |missing_field| path = [*context.path, missing_field] missing_field_type = context.types.argument(parent_type, missing_field).type add_error(RequiredInputObjectAttributesArePresentError.new( "Argument '#{missing_field}' on InputObject '#{parent_type.to_type_signature}' is required. Expected type #{missing_field_type.to_type_signature}", argument_name: missing_field, argument_type: missing_field_type.to_type_signature, input_object_type: parent_type.to_type_signature, path: path, nodes: ast_node, )) end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/operation_names_are_valid_error.rb0000644000004100000410000000124515173430257031204 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class OperationNamesAreValidError < StaticValidation::Error attr_reader :operation_name def initialize(message, path: nil, nodes: [], name: nil) super(message, path: path, nodes: nodes) @operation_name = name end # A hash representation of this Message def to_h extensions = { "code" => code }.tap { |h| h["operationName"] = operation_name unless operation_name.nil? } super.merge({ "extensions" => extensions }) end def code "uniquelyNamedOperations" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/directives_are_in_valid_locations_error.rb0000644000004100000410000000135115173430257032721 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class DirectivesAreInValidLocationsError < StaticValidation::Error attr_reader :target_name attr_reader :name def initialize(message, path: nil, nodes: [], target:, name: nil) super(message, path: path, nodes: nodes) @target_name = target @name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "targetName" => target_name }.tap { |h| h["name"] = name unless name.nil? } super.merge({ "extensions" => extensions }) end def code "directiveCannotBeApplied" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/operation_names_are_valid.rb0000644000004100000410000000202115173430257027764 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module OperationNamesAreValid def initialize(*) super @operation_names = Hash.new { |h, k| h[k] = [] } end def on_operation_definition(node, parent) @operation_names[node.name] << node super end def on_document(node, parent) super op_count = @operation_names.values.inject(0) { |m, v| m + v.size } @operation_names.each do |name, nodes| if name.nil? && op_count > 1 add_error(GraphQL::StaticValidation::OperationNamesAreValidError.new( %|Operation name is required when multiple operations are present|, nodes: nodes )) elsif nodes.length > 1 add_error(GraphQL::StaticValidation::OperationNamesAreValidError.new( %|Operation name "#{name}" must be unique|, nodes: nodes, name: name )) end end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/input_object_names_are_unique.rb0000644000004100000410000000146515173430257030673 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module InputObjectNamesAreUnique def on_input_object(node, parent) validate_input_fields(node) super end private def validate_input_fields(node) input_field_defns = node.arguments input_fields_by_name = Hash.new { |h, k| h[k] = [] } input_field_defns.each { |a| input_fields_by_name[a.name] << a } input_fields_by_name.each do |name, defns| if defns.size > 1 error = GraphQL::StaticValidation::InputObjectNamesAreUniqueError.new( "There can be only one input field named \"#{name}\"", nodes: defns, name: name ) add_error(error) end end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/no_definitions_are_present_error.rb0000644000004100000410000000102615173430257031406 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class NoDefinitionsArePresentError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "queryContainsSchemaDefinitions" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/variables_are_used_and_defined_error.rb0000644000004100000410000000165215173430257032134 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class VariablesAreUsedAndDefinedError < StaticValidation::Error attr_reader :variable_name attr_reader :violation VIOLATIONS = { :VARIABLE_NOT_USED => "variableNotUsed", :VARIABLE_NOT_DEFINED => "variableNotDefined", } def initialize(message, path: nil, nodes: [], name:, error_type:) super(message, path: path, nodes: nodes) @variable_name = name raise("Unexpected error type: #{error_type}") if !VIOLATIONS.values.include?(error_type) @violation = error_type end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name } super.merge({ "extensions" => extensions }) end def code @violation end end end end graphql-2.6.0/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb0000644000004100000410000000566415173430257031523 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module DirectivesAreInValidLocations include GraphQL::Language def on_directive(node, parent) validate_location(node, parent, context.schema_directives) super end private LOCATION_MESSAGE_NAMES = { GraphQL::Schema::Directive::QUERY => "queries", GraphQL::Schema::Directive::MUTATION => "mutations", GraphQL::Schema::Directive::SUBSCRIPTION => "subscriptions", GraphQL::Schema::Directive::FIELD => "fields", GraphQL::Schema::Directive::FRAGMENT_DEFINITION => "fragment definitions", GraphQL::Schema::Directive::FRAGMENT_SPREAD => "fragment spreads", GraphQL::Schema::Directive::INLINE_FRAGMENT => "inline fragments", GraphQL::Schema::Directive::VARIABLE_DEFINITION => "variable definitions", } SIMPLE_LOCATIONS = { Nodes::Field => GraphQL::Schema::Directive::FIELD, Nodes::InlineFragment => GraphQL::Schema::Directive::INLINE_FRAGMENT, Nodes::FragmentSpread => GraphQL::Schema::Directive::FRAGMENT_SPREAD, Nodes::FragmentDefinition => GraphQL::Schema::Directive::FRAGMENT_DEFINITION, Nodes::VariableDefinition => GraphQL::Schema::Directive::VARIABLE_DEFINITION, } SIMPLE_LOCATION_NODES = SIMPLE_LOCATIONS.keys def validate_location(ast_directive, ast_parent, directives) directive_defn = directives[ast_directive.name] case ast_parent when Nodes::OperationDefinition required_location = GraphQL::Schema::Directive.const_get(ast_parent.operation_type.upcase) assert_includes_location(directive_defn, ast_directive, required_location) when *SIMPLE_LOCATION_NODES required_location = SIMPLE_LOCATIONS[ast_parent.class] assert_includes_location(directive_defn, ast_directive, required_location) else add_error(GraphQL::StaticValidation::DirectivesAreInValidLocationsError.new( "Directives can't be applied to #{ast_parent.class.name}s", nodes: ast_directive, target: ast_parent.class.name )) end end def assert_includes_location(directive_defn, directive_ast, required_location) if !directive_defn.locations.include?(required_location) location_name = LOCATION_MESSAGE_NAMES[required_location] allowed_location_names = directive_defn.locations.map { |loc| LOCATION_MESSAGE_NAMES[loc] } add_error(GraphQL::StaticValidation::DirectivesAreInValidLocationsError.new( "'@#{directive_defn.graphql_name}' can't be applied to #{location_name} (allowed: #{allowed_location_names.join(", ")})", nodes: directive_ast, target: location_name, name: directive_defn.graphql_name )) end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/argument_names_are_unique_error.rb0000644000004100000410000000112715173430257031234 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class ArgumentNamesAreUniqueError < StaticValidation::Error attr_reader :name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "name" => name } super.merge({ "extensions" => extensions }) end def code "argumentNotUnique" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb0000644000004100000410000001370315173430257030723 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation # The problem is # - Variable $usage must be determined at the OperationDefinition level # - You can't tell how fragments use variables until you visit FragmentDefinitions (which may be at the end of the document) # # So, this validator includes some crazy logic to follow fragment spreads recursively, while avoiding infinite loops. # # `graphql-js` solves this problem by: # - re-visiting the AST for each validator # - allowing validators to say `followSpreads: true` # module VariablesAreUsedAndDefined class VariableUsage attr_accessor :ast_node, :used_by, :declared_by, :path def used? !!@used_by end def declared? !!@declared_by end end def initialize(*) super @variable_usages_for_context = Hash.new {|hash, key| hash[key] = Hash.new {|h, k| h[k] = VariableUsage.new } } @spreads_for_context = Hash.new {|hash, key| hash[key] = [] } @variable_context_stack = [] end def on_operation_definition(node, parent) # initialize the hash of vars for this context: @variable_usages_for_context[node] @variable_context_stack.push(node) # mark variables as defined: var_hash = @variable_usages_for_context[node] node.variables.each { |var| var_usage = var_hash[var.name] var_usage.declared_by = node var_usage.path = context.path } super @variable_context_stack.pop end def on_fragment_definition(node, parent) # initialize the hash of vars for this context: @variable_usages_for_context[node] @variable_context_stack.push(node) super @variable_context_stack.pop end # For FragmentSpreads: # - find the context on the stack # - mark the context as containing this spread def on_fragment_spread(node, parent) variable_context = @variable_context_stack.last @spreads_for_context[variable_context] << node.name super end # For VariableIdentifiers: # - mark the variable as used # - assign its AST node def on_variable_identifier(node, parent) usage_context = @variable_context_stack.last declared_variables = @variable_usages_for_context[usage_context] usage = declared_variables[node.name] usage.used_by = usage_context usage.ast_node = node usage.path = context.path super end def on_document(node, parent) super fragment_definitions = @variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::FragmentDefinition) } operation_definitions = @variable_usages_for_context.select { |key, value| key.is_a?(GraphQL::Language::Nodes::OperationDefinition) } operation_definitions.each do |node, node_variables| follow_spreads(node, node_variables, @spreads_for_context, fragment_definitions, []) create_errors(node_variables) end end private # Follow spreads in `node`, looking them up from `spreads_for_context` and finding their match in `fragment_definitions`. # Use those fragments to update {VariableUsage}s in `parent_variables`. # Avoid infinite loops by skipping anything in `visited_fragments`. def follow_spreads(node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments) spreads = spreads_for_context[node] - visited_fragments spreads.each do |spread_name| def_node = nil variables = nil # Implement `.find` by hand to avoid Ruby's internal allocations fragment_definitions.each do |frag_def_node, vars| if frag_def_node.name == spread_name def_node = frag_def_node variables = vars break end end next if !def_node visited_fragments << spread_name variables.each do |name, child_usage| parent_usage = parent_variables[name] if child_usage.used? parent_usage.ast_node = child_usage.ast_node parent_usage.used_by = child_usage.used_by parent_usage.path = child_usage.path end end follow_spreads(def_node, parent_variables, spreads_for_context, fragment_definitions, visited_fragments) end end # Determine all the error messages, # Then push messages into the validation context def create_errors(node_variables) # Declared but not used: node_variables .select { |name, usage| usage.declared? && !usage.used? } .each { |var_name, usage| declared_by_error_name = usage.declared_by.name || "anonymous #{usage.declared_by.operation_type}" add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new( "Variable $#{var_name} is declared by #{declared_by_error_name} but not used", nodes: usage.declared_by, path: usage.path, name: var_name, error_type: VariablesAreUsedAndDefinedError::VIOLATIONS[:VARIABLE_NOT_USED] )) } # Used but not declared: node_variables .select { |name, usage| usage.used? && !usage.declared? } .each { |var_name, usage| used_by_error_name = usage.used_by.name || "anonymous #{usage.used_by.operation_type}" add_error(GraphQL::StaticValidation::VariablesAreUsedAndDefinedError.new( "Variable $#{var_name} is used by #{used_by_error_name} but not declared", nodes: usage.ast_node, path: usage.path, name: var_name, error_type: VariablesAreUsedAndDefinedError::VIOLATIONS[:VARIABLE_NOT_DEFINED] )) } end end end end graphql-2.6.0/lib/graphql/static_validation/rules/subscription_root_exists_error.rb0000644000004100000410000000103015173430257031171 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class SubscriptionRootExistsError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "missingSubscriptionConfiguration" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fragment_names_are_unique.rb0000644000004100000410000000133015173430257030000 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module FragmentNamesAreUnique def initialize(*) super @fragments_by_name = Hash.new { |h, k| h[k] = [] } end def on_fragment_definition(node, parent) @fragments_by_name[node.name] << node super end def on_document(_n, _p) super @fragments_by_name.each do |name, fragments| if fragments.length > 1 add_error(GraphQL::StaticValidation::FragmentNamesAreUniqueError.new( %|Fragment name "#{name}" must be unique|, nodes: fragments, name: name )) end end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb0000644000004100000410000000462415173430257030661 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module FragmentSpreadsArePossible def initialize(*) super @spreads_to_validate = [] end def on_inline_fragment(node, parent) fragment_parent = @parent_object_type fragment_child = @current_object_type if fragment_child validate_fragment_in_scope(fragment_parent, fragment_child, node, context, context.path) end super end def on_fragment_spread(node, parent) fragment_parent = @current_object_type @spreads_to_validate << FragmentSpread.new(node: node, parent_type: fragment_parent, path: context.path) super end def on_document(node, parent) super @spreads_to_validate.each do |frag_spread| frag_node = context.fragments[frag_spread.node.name] if frag_node fragment_child_name = frag_node.type.name fragment_child = @types.type(fragment_child_name) # Might be non-existent type name if fragment_child validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path) end end end end private def validate_fragment_in_scope(parent_type, child_type, node, context, path) if !child_type.kind.fields? # It's not a valid fragment type, this error was handled someplace else return end parent_types = @types.possible_types(parent_type.unwrap) child_types = @types.possible_types(child_type.unwrap) if child_types.none? { |c| parent_types.include?(c) } name = node.respond_to?(:name) ? " #{node.name}" : "" add_error(GraphQL::StaticValidation::FragmentSpreadsArePossibleError.new( "Fragment#{name} on #{child_type.graphql_name} can't be spread inside #{parent_type.graphql_name}", nodes: node, path: path, fragment_name: name.empty? ? "unknown" : name, type: child_type.graphql_name, parent: parent_type.graphql_name )) end end class FragmentSpread attr_reader :node, :parent_type, :path def initialize(node:, parent_type:, path:) @node = node @parent_type = parent_type @path = path end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fragments_are_used_error.rb0000644000004100000410000000117615173430257027653 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreUsedError < StaticValidation::Error attr_reader :fragment_name def initialize(message, path: nil, nodes: [], fragment:) super(message, path: path, nodes: nodes) @fragment_name = fragment end # A hash representation of this Message def to_h extensions = { "code" => code, "fragmentName" => fragment_name } super.merge({ "extensions" => extensions }) end def code "useAndDefineFragment" end end end end ././@LongLink0000644000000000000000000000016000000000000011600 Lustar rootrootgraphql-2.6.0/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rbgraphql-2.6.0/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_s0000644000004100000410000000146015173430257034676 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module SubscriptionRootExistsAndSingleSubscriptionSelection def on_operation_definition(node, parent) if node.operation_type == "subscription" if context.types.subscription_root.nil? add_error(GraphQL::StaticValidation::SubscriptionRootExistsError.new( 'Schema is not configured for subscriptions', nodes: node )) elsif node.selections.size != 1 add_error(GraphQL::StaticValidation::NotSingleSubscriptionError.new( 'A subscription operation may only have one selection', nodes: node, )) else super end else super end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/directives_are_defined.rb0000644000004100000410000000167715173430257027261 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module DirectivesAreDefined def initialize(*) super end def on_directive(node, parent) if !@types.directive_exists?(node.name) @directives_are_defined_errors_by_name ||= {} error = @directives_are_defined_errors_by_name[node.name] ||= begin suggestion = if @schema.did_you_mean @directive_names ||= @types.directives.map(&:graphql_name) context.did_you_mean_suggestion(node.name, @directive_names) end err = GraphQL::StaticValidation::DirectivesAreDefinedError.new( "Directive @#{node.name} is not defined#{suggestion}", nodes: [], directive: node.name ) add_error(err) err end error.nodes << node else super end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fragment_names_are_unique_error.rb0000644000004100000410000000117115173430257031214 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class FragmentNamesAreUniqueError < StaticValidation::Error attr_reader :fragment_name def initialize(message, path: nil, nodes: [], name:) super(message, path: path, nodes: nodes) @fragment_name = name end # A hash representation of this Message def to_h extensions = { "code" => code, "fragmentName" => fragment_name } super.merge({ "extensions" => extensions }) end def code "fragmentNotUnique" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fragments_are_named_error.rb0000644000004100000410000000100415173430257027765 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class FragmentsAreNamedError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "anonymousFragment" end end end end ././@LongLink0000644000000000000000000000014700000000000011605 Lustar rootrootgraphql-2.6.0/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed_error.rbgraphql-2.6.0/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed_error.0000644000004100000410000000211415173430257034455 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class VariableDefaultValuesAreCorrectlyTypedError < StaticValidation::Error attr_reader :variable_name attr_reader :type_name attr_reader :violation VIOLATIONS = { :INVALID_TYPE => "defaultValueInvalidType", :INVALID_ON_NON_NULL => "defaultValueInvalidOnNonNullVariable", } def initialize(message, path: nil, nodes: [], name:, type: nil, error_type:) super(message, path: path, nodes: nodes) @variable_name = name @type_name = type raise("Unexpected error type: #{error_type}") if !VIOLATIONS.values.include?(error_type) @violation = error_type end # A hash representation of this Message def to_h extensions = { "code" => code, "variableName" => variable_name }.tap { |h| h["typeName"] = type_name unless type_name.nil? } super.merge({ "extensions" => extensions }) end def code @violation end end end end graphql-2.6.0/lib/graphql/static_validation/rules/unique_directives_per_location_error.rb0000644000004100000410000000123015173430257032272 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class UniqueDirectivesPerLocationError < StaticValidation::Error attr_reader :directive_name def initialize(message, path: nil, nodes: [], directive:) super(message, path: path, nodes: nodes) @directive_name = directive end # A hash representation of this Message def to_h extensions = { "code" => code, "directiveName" => directive_name } super.merge({ "extensions" => extensions }) end def code "directiveNotUniqueForLocation" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb0000644000004100000410000001272415173430257030300 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module VariableUsagesAreAllowed def initialize(*) super # holds { name => ast_node } pairs @declared_variables = {} end def on_operation_definition(node, parent) @declared_variables = node.variables.each_with_object({}) { |var, memo| memo[var.name] = var } super end def on_argument(node, parent) node_values = if node.value.is_a?(Array) node.value else [node.value] end node_values = node_values.select { |value| value.is_a? GraphQL::Language::Nodes::VariableIdentifier } if !node_values.empty? argument_owner = case parent when GraphQL::Language::Nodes::Field context.field_definition when GraphQL::Language::Nodes::Directive context.directive_definition when GraphQL::Language::Nodes::InputObject arg_type = context.argument_definition.type.unwrap if arg_type.kind.input_object? arg_type else # This is some kind of error nil end else raise("Unexpected argument parent: #{parent}") end node_values.each do |node_value| var_defn_ast = @declared_variables[node_value.name] # Might be undefined :( # VariablesAreUsedAndDefined can't finalize its search until the end of the document. var_defn_ast && argument_owner && validate_usage(argument_owner, node, var_defn_ast) end end super end private def validate_usage(argument_owner, arg_node, ast_var) var_type = context.schema.type_from_ast(ast_var.type, context: context) if var_type.nil? return end if !ast_var.default_value.nil? unless var_type.kind.non_null? # If the value is required, but the argument is not, # and yet there's a non-nil default, then we impliclty # make the argument also a required type. var_type = var_type.to_non_null_type end end arg_defn = @types.argument(argument_owner, arg_node.name) arg_defn_type = arg_defn.type # If the argument is non-null, but it was given a default value, # then treat it as nullable in practice, see https://github.com/rmosolgo/graphql-ruby/issues/3793 if arg_defn_type.non_null? && arg_defn.default_value? arg_defn_type = arg_defn_type.of_type end var_inner_type = var_type.unwrap arg_inner_type = arg_defn_type.unwrap var_type = wrap_var_type_with_depth_of_arg(var_type, arg_node) if var_inner_type != arg_inner_type create_error("Type mismatch", var_type, ast_var, arg_defn, arg_node) elsif list_dimension(var_type) != list_dimension(arg_defn_type) create_error("List dimension mismatch", var_type, ast_var, arg_defn, arg_node) elsif !non_null_levels_match(arg_defn_type, var_type) create_error("Nullability mismatch", var_type, ast_var, arg_defn, arg_node) end end def create_error(error_message, var_type, ast_var, arg_defn, arg_node) add_error(GraphQL::StaticValidation::VariableUsagesAreAllowedError.new( "#{error_message} on variable $#{ast_var.name} and argument #{arg_node.name} (#{var_type.to_type_signature} / #{arg_defn.type.to_type_signature})", nodes: arg_node, name: ast_var.name, type: var_type.to_type_signature, argument: arg_node.name, error: error_message )) end def wrap_var_type_with_depth_of_arg(var_type, arg_node) arg_node_value = arg_node.value return var_type unless arg_node_value.is_a?(Array) new_var_type = var_type depth_of_array(arg_node_value).times do # Since the array _is_ present, treat it like a non-null type # (It satisfies a non-null requirement AND a nullable requirement) new_var_type = new_var_type.to_list_type.to_non_null_type end new_var_type end # @return [Integer] Returns the max depth of `array`, or `0` if it isn't an array at all def depth_of_array(array) case array when Array max_child_depth = 0 array.each do |item| item_depth = depth_of_array(item) if item_depth > max_child_depth max_child_depth = item_depth end end 1 + max_child_depth else 0 end end def list_dimension(type) if type.kind.list? 1 + list_dimension(type.of_type) elsif type.kind.non_null? list_dimension(type.of_type) else 0 end end def non_null_levels_match(arg_type, var_type) if arg_type.kind.non_null? && !var_type.kind.non_null? false elsif arg_type.kind.wraps? && var_type.kind.wraps? # If var_type is a non-null wrapper for a type, and arg_type is nullable, peel off the wrapper # That way, a var_type of `[DairyAnimal]!` works with an arg_type of `[DairyAnimal]` if var_type.kind.non_null? && !arg_type.kind.non_null? var_type = var_type.of_type end non_null_levels_match(arg_type.of_type, var_type.of_type) else true end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fragment_spreads_are_possible_error.rb0000644000004100000410000000154015173430257032064 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class FragmentSpreadsArePossibleError < StaticValidation::Error attr_reader :type_name attr_reader :fragment_name attr_reader :parent_name def initialize(message, path: nil, nodes: [], type:, fragment_name:, parent:) super(message, path: path, nodes: nodes) @type_name = type @fragment_name = fragment_name @parent_name = parent end # A hash representation of this Message def to_h extensions = { "code" => code, "typeName" => type_name, "fragmentName" => fragment_name, "parentName" => parent_name } super.merge({ "extensions" => extensions }) end def code "cannotSpreadFragment" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/variables_are_input_types.rb0000644000004100000410000000272315173430257030046 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module VariablesAreInputTypes def on_variable_definition(node, parent) type_name = get_type_name(node.type) type = context.query.types.type(type_name) if type.nil? suggestion = if @schema.did_you_mean @all_possible_input_type_names ||= begin names = [] context.types.all_types.each { |(t)| if t.kind.input? names << t.graphql_name end } names end context.did_you_mean_suggestion(type_name, @all_possible_input_type_names) end add_error(GraphQL::StaticValidation::VariablesAreInputTypesError.new( "#{type_name} isn't a defined input type (on $#{node.name})#{suggestion}", nodes: node, name: node.name, type: type_name )) elsif !type.kind.input? add_error(GraphQL::StaticValidation::VariablesAreInputTypesError.new( "#{type.graphql_name} isn't a valid input type (on $#{node.name})", nodes: node, name: node.name, type: type_name )) end super end private def get_type_name(ast_type) if ast_type.respond_to?(:of_type) get_type_name(ast_type.of_type) else ast_type.name end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/argument_names_are_unique.rb0000644000004100000410000000213615173430257030024 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module ArgumentNamesAreUnique include GraphQL::StaticValidation::Error::ErrorHelper def on_field(node, parent) validate_arguments(node) super end def on_directive(node, parent) validate_arguments(node) super end def validate_arguments(node) argument_defns = node.arguments if argument_defns.size > 1 seen = {} argument_defns.each do |a| name = a.name if seen.key?(name) prev = seen[name] if prev.is_a?(Array) prev << a else seen[name] = [prev, a] end else seen[name] = a end end seen.each do |name, val| if val.is_a?(Array) add_error(GraphQL::StaticValidation::ArgumentNamesAreUniqueError.new("There can be only one argument named \"#{name}\"", nodes: val, name: name)) end end end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb0000644000004100000410000000454315173430257031355 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation module ArgumentLiteralsAreCompatible def on_argument(node, parent) # Check the child arguments first; # don't add a new error if one of them reports an error super # Don't validate variables here if node.value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) return end if @context.schema.error_bubbling || context.errors.none? { |err| err.path.take(@path_depth) == @path[0, @path_depth] } parent_defn = parent_definition(parent) if parent_defn && (arg_defn = @types.argument(parent_defn, node.name)) validation_result = context.validate_literal(node.value, arg_defn.type) if !validation_result.valid? kind_of_node = node_type(parent) error_arg_name = parent_name(parent, parent_defn) string_value = if node.value == Float::INFINITY "" else " (#{GraphQL::Language::Printer.new.print(node.value)})" end problems = validation_result.problems first_problem = problems && problems.first if first_problem message = first_problem["message"] # This is some legacy stuff from when `CoercionError` was raised thru the stack if message coerce_extensions = first_problem["extensions"] || { "code" => "argumentLiteralsIncompatible" } end end error_options = { nodes: parent, type: kind_of_node, argument_name: node.name, argument: arg_defn, value: node.value } if coerce_extensions error_options[:coerce_extensions] = coerce_extensions end message ||= "Argument '#{node.name}' on #{kind_of_node} '#{error_arg_name}' has an invalid value#{string_value}. Expected type '#{arg_defn.type.to_type_signature}'." error = GraphQL::StaticValidation::ArgumentLiteralsAreCompatibleError.new( message, **error_options ) add_error(error) end end end end end end end graphql-2.6.0/lib/graphql/static_validation/rules/required_arguments_are_present_error.rb0000644000004100000410000000147715173430257032316 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class RequiredArgumentsArePresentError < StaticValidation::Error attr_reader :class_name attr_reader :name attr_reader :arguments def initialize(message, path: nil, nodes: [], class_name:, name:, arguments:) super(message, path: path, nodes: nodes) @class_name = class_name @name = name @arguments = arguments end # A hash representation of this Message def to_h extensions = { "code" => code, "className" => class_name, "name" => name, "arguments" => arguments } super.merge({ "extensions" => extensions }) end def code "missingRequiredArguments" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/fields_have_appropriate_selections_error.rb0000644000004100000410000000137215173430257033123 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class FieldsHaveAppropriateSelectionsError < StaticValidation::Error attr_reader :type_name attr_reader :node_name def initialize(message, path: nil, nodes: [], node_name:, type: nil) super(message, path: path, nodes: nodes) @node_name = node_name @type_name = type end # A hash representation of this Message def to_h extensions = { "code" => code, "nodeName" => node_name }.tap { |h| h["typeName"] = type_name unless type_name.nil? } super.merge({ "extensions" => extensions }) end def code "selectionMismatch" end end end end graphql-2.6.0/lib/graphql/static_validation/rules/not_single_subscription_error.rb0000644000004100000410000000101315173430257030751 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class NotSingleSubscriptionError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code, } super.merge({ "extensions" => extensions }) end def code "notSingleSubscription" end end end end graphql-2.6.0/lib/graphql/static_validation/validator.rb0000644000004100000410000000632715173430257023443 0ustar www-datawww-data# frozen_string_literal: true require "timeout" module GraphQL module StaticValidation # Initialized with a {GraphQL::Schema}, then it can validate {GraphQL::Language::Nodes::Documents}s based on that schema. # # By default, it's used by {GraphQL::Query} # # @example Validate a query # validator = GraphQL::StaticValidation::Validator.new(schema: MySchema) # query = GraphQL::Query.new(MySchema, query_string) # errors = validator.validate(query)[:errors] # class Validator # @param schema [GraphQL::Schema] # @param rules [Array<#validate(context)>] a list of rules to use when validating def initialize(schema:, rules: GraphQL::StaticValidation::ALL_RULES) @schema = schema @rules = rules end # Validate `query` against the schema. Returns an array of message hashes. # @param query [GraphQL::Query] # @param validate [Boolean] # @param timeout [Float] Number of seconds to wait before aborting validation. Any positive number may be used, including Floats to specify fractional seconds. # @param max_errors [Integer] Maximum number of errors before aborting validation. Any positive number will limit the number of errors. Defaults to nil for no limit. # @return [Array] def validate(query, validate: true, timeout: nil, max_errors: nil) errors = nil query.current_trace.begin_validate(query, validate) query.current_trace.validate(validate: validate, query: query) do begin_t = Time.now errors = if validate == false [] else rules_to_use = validate ? @rules : [] visitor_class = BaseVisitor.including_rules(rules_to_use) context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class, max_errors) begin # CAUTION: Usage of the timeout module makes the assumption that validation rules are stateless Ruby code that requires no cleanup if process was interrupted. This means no blocking IO calls, native gems, locks, or `rescue` clauses that must be reached. # A timeout value of 0 or nil will execute the block without any timeout. Timeout::timeout(timeout) do catch(:too_many_validation_errors) do context.visitor.visit end end rescue Timeout::Error handle_timeout(query, context) end context.errors end { remaining_timeout: timeout ? (timeout - (Time.now - begin_t)) : nil, errors: errors, } end rescue GraphQL::ExecutionError => e errors = [e] { remaining_timeout: nil, errors: errors, } ensure query.current_trace.end_validate(query, validate, errors) end # Invoked when static validation times out. # @param query [GraphQL::Query] # @param context [GraphQL::StaticValidation::ValidationContext] def handle_timeout(query, context) context.errors << GraphQL::StaticValidation::ValidationTimeoutError.new( "Timeout on validation of query" ) end end end end graphql-2.6.0/lib/graphql/static_validation/error.rb0000644000004100000410000000221715173430257022601 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation # Generates GraphQL-compliant validation message. class Error # Convenience for validators module ErrorHelper # Error `error_message` is located at `node` def error(error_message, nodes, context: nil, path: nil, extensions: {}) path ||= context.path nodes = Array(nodes) GraphQL::StaticValidation::Error.new(error_message, nodes: nodes, path: path) end end attr_reader :message attr_accessor :path def initialize(message, path: nil, nodes: []) @message = message @nodes = Array(nodes) @path = path end # A hash representation of this Message def to_h { "message" => message, "locations" => locations }.tap { |h| h["path"] = path unless path.nil? } end attr_reader :nodes private def locations nodes.map do |node| h = {"line" => node.line, "column" => node.col} h["filename"] = node.filename if node.filename h end end end end end graphql-2.6.0/lib/graphql/static_validation/validation_timeout_error.rb0000644000004100000410000000100215173430257026550 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class ValidationTimeoutError < StaticValidation::Error def initialize(message, path: nil, nodes: []) super(message, path: path, nodes: nodes) end # A hash representation of this Message def to_h extensions = { "code" => code } super.merge({ "extensions" => extensions }) end def code "validationTimeout" end end end end graphql-2.6.0/lib/graphql/static_validation/definition_dependencies.rb0000644000004100000410000001771715173430257026321 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation # Track fragment dependencies for operations # and expose the fragment definitions which # are used by a given operation module DefinitionDependencies attr_reader :dependencies def initialize(*) super @defdep_node_paths = {} # { name => [node, ...] } pairs for fragments (although duplicate-named fragments are _invalid_, they are _possible_) @defdep_fragment_definitions = Hash.new{ |h, k| h[k] = [] } # This tracks dependencies from fragment to Node where it was used # { fragment_definition_name => [dependent_node, dependent_node]} @defdep_dependent_definitions = Hash.new { |h, k| h[k] = Set.new } # First-level usages of spreads within definitions # (When a key has an empty list as its value, # we can resolve that key's dependents) # { definition_node => [node, node ...] } @defdep_immediate_dependencies = Hash.new { |h, k| h[k] = Set.new } # When we encounter a spread, # this node is the one who depends on it @defdep_current_parent = nil end def on_document(node, parent) node.definitions.each do |definition| if definition.is_a? GraphQL::Language::Nodes::FragmentDefinition @defdep_fragment_definitions[definition.name] << definition end end super @dependencies = dependency_map { |defn, spreads, frag| context.on_dependency_resolve_handlers.each { |h| h.call(defn, spreads, frag) } } end def on_operation_definition(node, prev_node) @defdep_node_paths[node.name] = NodeWithPath.new(node, context.path) @defdep_current_parent = node super @defdep_current_parent = nil end def on_fragment_definition(node, parent) @defdep_node_paths[node] = NodeWithPath.new(node, context.path) @defdep_current_parent = node super @defdep_current_parent = nil end def on_fragment_spread(node, parent) @defdep_node_paths[node] = NodeWithPath.new(node, context.path) # Track both sides of the dependency @defdep_dependent_definitions[node.name] << @defdep_current_parent @defdep_immediate_dependencies[@defdep_current_parent] << node super end # A map of operation definitions to an array of that operation's dependencies # @return [DependencyMap] def dependency_map(&block) @dependency_map ||= resolve_dependencies(&block) end # Map definition AST nodes to the definition AST nodes they depend on. # Expose circular dependencies. class DependencyMap # @return [Array] attr_reader :cyclical_definitions # @return [Hash>] attr_reader :unmet_dependencies # @return [Array] attr_reader :unused_dependencies def initialize @dependencies = Hash.new { |h, k| h[k] = [] } @cyclical_definitions = [] @unmet_dependencies = Hash.new { |h, k| h[k] = [] } @unused_dependencies = [] end # @return [Array] dependencies for `definition_node` def [](definition_node) @dependencies[definition_node] end end class NodeWithPath extend Forwardable attr_reader :node, :path def initialize(node, path) @node = node @path = path end def_delegators :@node, :name, :eql?, :hash end private # Return a hash of { node => [node, node ... ]} pairs # Keys are top-level definitions # Values are arrays of flattened dependencies def resolve_dependencies dependency_map = DependencyMap.new # Don't allow the loop to run more times # than the number of fragments in the document max_loops = 0 @defdep_fragment_definitions.each_value do |v| max_loops += v.size end loops = 0 # Instead of tracking independent fragments _as you visit_, # determine them at the end. This way, we can treat fragments with the # same name as if they were the same name. If _any_ of the fragments # with that name has a dependency, we record it. independent_fragment_nodes = @defdep_fragment_definitions.values.flatten - @defdep_immediate_dependencies.keys visited_fragment_names = Set.new while fragment_node = independent_fragment_nodes.pop if visited_fragment_names.add?(fragment_node.name) # this is a new fragment name else # this is a duplicate fragment name next end loops += 1 if loops > max_loops raise("Resolution loops exceeded the number of definitions; infinite loop detected. (Max: #{max_loops}, Current: #{loops})") end # Since it's independent, let's remove it from here. # That way, we can use the remainder to identify cycles @defdep_immediate_dependencies.delete(fragment_node) fragment_usages = @defdep_dependent_definitions[fragment_node.name] if fragment_usages.empty? # If we didn't record any usages during the visit, # then this fragment is unused. dependency_map.unused_dependencies << @defdep_node_paths[fragment_node] else fragment_usages.each do |definition_node| # Register the dependency AND second-order dependencies dependency_map[definition_node] << fragment_node dependency_map[definition_node].concat(dependency_map[fragment_node]) # Since we've registered it, remove it from our to-do list deps = @defdep_immediate_dependencies[definition_node] # Can't find a way to _just_ delete from `deps` and return the deleted entries removed, remaining = deps.partition { |spread| spread.name == fragment_node.name } @defdep_immediate_dependencies[definition_node] = remaining if block_given? yield(definition_node, removed, fragment_node) end if remaining.empty? && definition_node.is_a?(GraphQL::Language::Nodes::FragmentDefinition) && definition_node.name != fragment_node.name # If all of this definition's dependencies have # been resolved, we can now resolve its # own dependents. # # But, it's possible to have a duplicate-named fragment here. # Skip it in that case independent_fragment_nodes << definition_node end end end end # If any dependencies were _unmet_ # (eg, spreads with no corresponding definition) # then they're still in there @defdep_immediate_dependencies.each do |defn_node, deps| deps.each do |spread| if !@defdep_fragment_definitions.key?(spread.name) dependency_map.unmet_dependencies[@defdep_node_paths[defn_node]] << @defdep_node_paths[spread] deps.delete(spread) end end if deps.empty? @defdep_immediate_dependencies.delete(defn_node) end end # Anything left in @immediate_dependencies is cyclical cyclical_nodes = @defdep_immediate_dependencies.keys.map { |n| @defdep_node_paths[n] } # @immediate_dependencies also includes operation names, but we don't care about # those. They became nil when we looked them up on `@fragment_definitions`, so remove them. cyclical_nodes.compact! dependency_map.cyclical_definitions.concat(cyclical_nodes) dependency_map end end end end graphql-2.6.0/lib/graphql/static_validation/literal_validator.rb0000644000004100000410000001342515173430257025154 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation # Test whether `ast_value` is a valid input for `type` class LiteralValidator def initialize(context:) @context = context @types = context.types @invalid_response = GraphQL::Query::InputValidationResult.new(valid: false, problems: []) @valid_response = GraphQL::Query::InputValidationResult.new(valid: true, problems: []) end def validate(ast_value, type) catch(:invalid) do recursively_validate(ast_value, type) end end private def replace_nulls_in(ast_value) case ast_value when Array ast_value.map { |v| replace_nulls_in(v) } when GraphQL::Language::Nodes::InputObject ast_value.to_h when GraphQL::Language::Nodes::NullValue nil else ast_value end end def recursively_validate(ast_value, type) if type.nil? # this means we're an undefined argument, see #present_input_field_values_are_valid maybe_raise_if_invalid(ast_value) do @invalid_response end elsif ast_value.is_a?(GraphQL::Language::Nodes::NullValue) maybe_raise_if_invalid(ast_value) do type.kind.non_null? ? @invalid_response : @valid_response end elsif type.kind.non_null? maybe_raise_if_invalid(ast_value) do ast_value.nil? ? @invalid_response : recursively_validate(ast_value, type.of_type) end elsif type.kind.list? item_type = type.of_type results = ensure_array(ast_value).map { |val| recursively_validate(val, item_type) } merge_results(results) elsif ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) @valid_response elsif type.kind.scalar? && constant_scalar?(ast_value) maybe_raise_if_invalid(ast_value) do ruby_value = replace_nulls_in(ast_value) type.validate_input(ruby_value, @context) end elsif type.kind.enum? maybe_raise_if_invalid(ast_value) do if ast_value.is_a?(GraphQL::Language::Nodes::Enum) type.validate_input(ast_value.name, @context) else # if our ast_value isn't an Enum it's going to be invalid so return false @invalid_response end end elsif type.kind.input_object? && ast_value.is_a?(GraphQL::Language::Nodes::InputObject) maybe_raise_if_invalid(ast_value) do merge_results([ required_input_fields_are_present(type, ast_value), present_input_field_values_are_valid(type, ast_value) ]) end else maybe_raise_if_invalid(ast_value) do @invalid_response end end end # When `error_bubbling` is false, we want to bail on the first failure that we find. # Use `throw` to escape the current call stack, returning the invalid response. def maybe_raise_if_invalid(ast_value) ret = yield if !@context.schema.error_bubbling && !ret.valid? throw(:invalid, ret) else ret end end # The GraphQL grammar supports variables embedded within scalars but graphql.js # doesn't support it so we won't either for simplicity def constant_scalar?(ast_value) if ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier) false elsif ast_value.is_a?(Array) ast_value.all? { |element| constant_scalar?(element) } elsif ast_value.is_a?(GraphQL::Language::Nodes::InputObject) ast_value.arguments.all? { |arg| constant_scalar?(arg.value) } else true end end def required_input_fields_are_present(type, ast_node) # TODO - would be nice to use these to create an error message so the caller knows # that required fields are missing required_field_names = @types.arguments(type) .select { |argument| argument.type.kind.non_null? && !argument.default_value? } .map!(&:name) present_field_names = ast_node.arguments.map(&:name) missing_required_field_names = required_field_names - present_field_names if @context.schema.error_bubbling missing_required_field_names.empty? ? @valid_response : @invalid_response else results = missing_required_field_names.map do |name| arg_type = @types.argument(type, name).type recursively_validate(GraphQL::Language::Nodes::NullValue.new(name: name), arg_type) end if type.one_of? && ast_node.arguments.size != 1 results << Query::InputValidationResult.from_problem("`#{type.graphql_name}` is a OneOf type, so only one argument may be given (instead of #{ast_node.arguments.size})") end merge_results(results) end end def present_input_field_values_are_valid(type, ast_node) results = ast_node.arguments.map do |value| field = @types.argument(type, value.name) # we want to call validate on an argument even if it's an invalid one # so that our raise exception is on it instead of the entire InputObject field_type = field && field.type recursively_validate(value.value, field_type) end merge_results(results) end def ensure_array(value) value.is_a?(Array) ? value : [value] end def merge_results(results_list) merged_result = Query::InputValidationResult.new results_list.each do |inner_result| merged_result.merge_result!([], inner_result) end merged_result end end end end graphql-2.6.0/lib/graphql/static_validation/base_visitor.rb0000644000004100000410000001656115173430257024150 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class BaseVisitor < GraphQL::Language::StaticVisitor def initialize(document, context) @path = [] @path_depth = 0 @current_object_type = nil @parent_object_type = nil @current_field_definition = nil @current_argument_definition = nil @parent_argument_definition = nil @current_directive_definition = nil @context = context @types = context.query.types @schema = context.schema @inline_fragment_paths = {} @field_unwrapped_types = {}.compare_by_identity super(document) end attr_reader :context # @return [Array] The nesting of the current position in the AST def path @path[0, @path_depth] end # Build a class to visit the AST and perform validation, # or use a pre-built class if rules is `ALL_RULES` or empty. # @param rules [Array] # @return [Class] A class for validating `rules` during visitation def self.including_rules(rules) if rules.empty? # It's not doing _anything?!?_ BaseVisitor elsif rules == ALL_RULES InterpreterVisitor else visitor_class = Class.new(self) do include(GraphQL::StaticValidation::DefinitionDependencies) end rules.reverse_each do |r| # If it's a class, it gets attached later. if !r.is_a?(Class) visitor_class.include(r) end end visitor_class.include(ContextMethods) visitor_class end end module ContextMethods def on_operation_definition(node, parent) object_type = @schema.root_type_for_operation(node.operation_type) prev_parent_ot = @parent_object_type @parent_object_type = @current_object_type @current_object_type = object_type @path[@path_depth] = "#{node.operation_type}#{node.name ? " #{node.name}" : ""}" @path_depth += 1 super @current_object_type = @parent_object_type @parent_object_type = prev_parent_ot @path_depth -= 1 end def on_fragment_definition(node, parent) object_type = if node.type @types.type(node.type.name) else @current_object_type end prev_parent_ot = @parent_object_type @parent_object_type = @current_object_type @current_object_type = object_type @path[@path_depth] = "fragment #{node.name}" @path_depth += 1 super @current_object_type = @parent_object_type @parent_object_type = prev_parent_ot @path_depth -= 1 end INLINE_FRAGMENT_NO_TYPE = "..." def on_inline_fragment(node, parent) if node.type object_type = @types.type(node.type.name) @path[@path_depth] = @inline_fragment_paths[node.type.name] ||= -"... on #{node.type.to_query_string}" @path_depth += 1 else object_type = @current_object_type @path[@path_depth] = INLINE_FRAGMENT_NO_TYPE @path_depth += 1 end prev_parent_ot = @parent_object_type @parent_object_type = @current_object_type @current_object_type = object_type super @current_object_type = @parent_object_type @parent_object_type = prev_parent_ot @path_depth -= 1 end def on_field(node, parent) parent_type = @current_object_type field_definition = @types.field(parent_type, node.name) prev_field_definition = @current_field_definition @current_field_definition = field_definition prev_parent_ot = @parent_object_type @parent_object_type = @current_object_type if field_definition @current_object_type = @field_unwrapped_types[field_definition] ||= field_definition.type.unwrap else @current_object_type = nil end @path[@path_depth] = node.alias || node.name @path_depth += 1 super @current_field_definition = prev_field_definition @current_object_type = @parent_object_type @parent_object_type = prev_parent_ot @path_depth -= 1 end def on_directive(node, parent) directive_defn = @context.schema_directives[node.name] prev_directive_definition = @current_directive_definition @current_directive_definition = directive_defn super @current_directive_definition = prev_directive_definition end def on_argument(node, parent) argument_defn = if (arg = @current_argument_definition) arg_type = arg.type.unwrap if arg_type.kind.input_object? @types.argument(arg_type, node.name) else nil end elsif (directive_defn = @current_directive_definition) @types.argument(directive_defn, node.name) elsif (field_defn = @current_field_definition) @types.argument(field_defn, node.name) else nil end prev_parent = @parent_argument_definition @parent_argument_definition = @current_argument_definition @current_argument_definition = argument_defn @path[@path_depth] = node.name @path_depth += 1 super @current_argument_definition = @parent_argument_definition @parent_argument_definition = prev_parent @path_depth -= 1 end def on_fragment_spread(node, parent) @path[@path_depth] = "... #{node.name}" @path_depth += 1 super @path_depth -= 1 end def on_input_object(node, parent) arg_defn = @current_argument_definition if arg_defn && arg_defn.type.list? @path[@path_depth] = parent.children.index(node) @path_depth += 1 super @path_depth -= 1 else super end end # @return [GraphQL::BaseType] The current object type def type_definition @current_object_type end # @return [GraphQL::BaseType] The type which the current type came from def parent_type_definition @parent_object_type end # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one def field_definition @current_field_definition end # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one def directive_definition @current_directive_definition end # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one def argument_definition # Return the parent argument definition (not the current one). @parent_argument_definition end private end private def add_error(error, path: nil) if @context.too_many_errors? throw :too_many_validation_errors end error.path ||= (path || @path[0, @path_depth]) context.errors << error end end end end graphql-2.6.0/lib/graphql/static_validation/all_rules.rb0000644000004100000410000000415015173430257023430 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation # Default rules for {GraphQL::StaticValidation::Validator} # # Order is important here. Some validators skip later hooks. # which stops the visit on that node. That way it doesn't try to find fields on types that # don't exist, etc. ALL_RULES = [ GraphQL::StaticValidation::NoDefinitionsArePresent, GraphQL::StaticValidation::DirectivesAreDefined, GraphQL::StaticValidation::DirectivesAreInValidLocations, GraphQL::StaticValidation::UniqueDirectivesPerLocation, GraphQL::StaticValidation::OperationNamesAreValid, GraphQL::StaticValidation::FragmentNamesAreUnique, GraphQL::StaticValidation::FragmentsAreFinite, GraphQL::StaticValidation::FragmentsAreNamed, GraphQL::StaticValidation::FragmentsAreUsed, GraphQL::StaticValidation::FragmentTypesExist, GraphQL::StaticValidation::FragmentsAreOnCompositeTypes, GraphQL::StaticValidation::FragmentSpreadsArePossible, GraphQL::StaticValidation::FieldsAreDefinedOnType, GraphQL::StaticValidation::FieldsWillMerge, GraphQL::StaticValidation::FieldsHaveAppropriateSelections, GraphQL::StaticValidation::ArgumentsAreDefined, GraphQL::StaticValidation::ArgumentLiteralsAreCompatible, GraphQL::StaticValidation::RequiredArgumentsArePresent, GraphQL::StaticValidation::RequiredInputObjectAttributesArePresent, GraphQL::StaticValidation::ArgumentNamesAreUnique, GraphQL::StaticValidation::VariableNamesAreUnique, GraphQL::StaticValidation::VariablesAreInputTypes, GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTyped, GraphQL::StaticValidation::VariablesAreUsedAndDefined, GraphQL::StaticValidation::VariableUsagesAreAllowed, GraphQL::StaticValidation::MutationRootExists, GraphQL::StaticValidation::QueryRootExists, GraphQL::StaticValidation::SubscriptionRootExistsAndSingleSubscriptionSelection, GraphQL::StaticValidation::InputObjectNamesAreUnique, GraphQL::StaticValidation::OneOfInputObjectsAreValid, ].freeze end end graphql-2.6.0/lib/graphql/static_validation/validation_context.rb0000644000004100000410000000434215173430257025347 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation # The validation context gets passed to each validator. # # It exposes a {GraphQL::Language::Visitor} where validators may add hooks. ({Language::Visitor#visit} is called in {Validator#validate}) # # It provides access to the schema & fragments which validators may read from. # # It holds a list of errors which each validator may add to. class ValidationContext extend Forwardable attr_reader :query, :errors, :visitor, :on_dependency_resolve_handlers, :max_errors, :types, :schema def_delegators :@query, :document, :fragments, :operations def initialize(query, visitor_class, max_errors) @query = query @types = query.types # TODO update migrated callers to use this accessor @schema = query.schema @literal_validator = LiteralValidator.new(context: query.context) @errors = [] @max_errors = max_errors || Float::INFINITY @on_dependency_resolve_handlers = [] @visitor = visitor_class.new(document, self) end # TODO stop using def_delegators because of Array allocations def_delegators :@visitor, :path, :type_definition, :field_definition, :argument_definition, :parent_type_definition, :directive_definition, :dependencies def on_dependency_resolve(&handler) @on_dependency_resolve_handlers << handler end def validate_literal(ast_value, type) @literal_validator.validate(ast_value, type) end def too_many_errors? @errors.length >= @max_errors end def schema_directives @schema_directives ||= schema.directives end def did_you_mean_suggestion(name, options) if did_you_mean = schema.did_you_mean suggestions = did_you_mean::SpellChecker.new(dictionary: options).correct(name) case suggestions.size when 0 "" when 1 " (Did you mean `#{suggestions.first}`?)" else last_sugg = suggestions.pop " (Did you mean #{suggestions.map {|s| "`#{s}`"}.join(", ")} or `#{last_sugg}`?)" end end end end end end graphql-2.6.0/lib/graphql/static_validation/interpreter_visitor.rb0000644000004100000410000000047015173430257025571 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module StaticValidation class InterpreterVisitor < BaseVisitor include(GraphQL::StaticValidation::DefinitionDependencies) StaticValidation::ALL_RULES.reverse_each do |r| include(r) end include(ContextMethods) end end end graphql-2.6.0/lib/graphql/dataloader.rb0000644000004100000410000003076315173430257020056 0ustar www-datawww-data# frozen_string_literal: true require "graphql/dataloader/null_dataloader" require "graphql/dataloader/request" require "graphql/dataloader/request_all" require "graphql/dataloader/source" require "graphql/dataloader/active_record_association_source" require "graphql/dataloader/active_record_source" module GraphQL # This plugin supports Fiber-based concurrency, along with {GraphQL::Dataloader::Source}. # # @example Installing Dataloader # # class MySchema < GraphQL::Schema # use GraphQL::Dataloader # end # # @example Waiting for batch-loaded data in a GraphQL field # # field :team, Types::Team, null: true # # def team # dataloader.with(Sources::Record, Team).load(object.team_id) # end # class Dataloader class << self attr_accessor :default_nonblocking, :default_fiber_limit end def self.use(schema, nonblocking: nil, fiber_limit: nil) dataloader_class = if nonblocking warn("`nonblocking: true` is deprecated from `GraphQL::Dataloader`, please use `GraphQL::Dataloader::AsyncDataloader` instead. Docs: https://graphql-ruby.org/dataloader/async_dataloader.") Class.new(self) { self.default_nonblocking = true } else self end if fiber_limit dataloader_class = Class.new(dataloader_class) dataloader_class.default_fiber_limit = fiber_limit end schema.dataloader_class = dataloader_class end # Call the block with a Dataloader instance, # then run all enqueued jobs and return the result of the block. def self.with_dataloading(&block) dataloader = self.new result = nil dataloader.append_job { result = block.call(dataloader) } dataloader.run result end def initialize(nonblocking: self.class.default_nonblocking, fiber_limit: self.class.default_fiber_limit) @source_cache = Hash.new { |h, k| h[k] = {} } @pending_jobs = [] if !nonblocking.nil? @nonblocking = nonblocking end @fiber_limit = fiber_limit @lazies_at_depth = Hash.new { |h, k| h[k] = [] } end # @return [Integer, nil] attr_reader :fiber_limit def nonblocking? @nonblocking end # This is called before the fiber is spawned, from the parent context (i.e. from # the thread or fiber that it is scheduled from). # # @return [Hash] Current fiber-local variables def get_fiber_variables fiber_vars = {} Thread.current.keys.each do |fiber_var_key| fiber_vars[fiber_var_key] = Thread.current[fiber_var_key] end fiber_vars end # Set up the fiber variables in a new fiber. # # This is called within the fiber, right after it is spawned. # # @param vars [Hash] Fiber-local variables from {get_fiber_variables} # @return [void] def set_fiber_variables(vars) vars.each { |k, v| Thread.current[k] = v } nil end # This method is called when Dataloader is finished using a fiber. # Use it to perform any cleanup, such as releasing database connections (if required manually) def cleanup_fiber end # Get a Source instance from this dataloader, for calling `.load(...)` or `.request(...)` on. # # @param source_class [Class] # @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`, # and cached for the lifetime of this {Multiplex}. if RUBY_VERSION < "3" || RUBY_ENGINE != "ruby" # truffle-ruby wasn't doing well with the implementation below def with(source_class, *batch_args) batch_key = source_class.batch_key_for(*batch_args) @source_cache[source_class][batch_key] ||= begin source = source_class.new(*batch_args) source.setup(self) source end end else def with(source_class, *batch_args, **batch_kwargs) batch_key = source_class.batch_key_for(*batch_args, **batch_kwargs) @source_cache[source_class][batch_key] ||= begin source = source_class.new(*batch_args, **batch_kwargs) source.setup(self) source end end end # Tell the dataloader that this fiber is waiting for data. # # Dataloader will resume the fiber after the requested data has been loaded (by another Fiber). # # @return [void] def yield(source = Fiber[:__graphql_current_dataloader_source]) trace = Fiber[:__graphql_current_multiplex]&.current_trace trace&.dataloader_fiber_yield(source) Fiber.yield trace&.dataloader_fiber_resume(source) nil end # @api private Nothing to see here def append_job(callable = nil, &job) # Given a block, queue it up to be worked through when `#run` is called. # (If the dataloader is already running, then a Fiber will pick this up later.) @pending_jobs.push(callable || job) nil end # Clear any already-loaded objects from {Source} caches # @return [void] def clear_cache @source_cache.each do |_source_class, batched_sources| batched_sources.each_value(&:clear_cache) end nil end # Use a self-contained queue for the work in the block. def run_isolated prev_queue = @pending_jobs prev_pending_keys = {} prev_lazies_at_depth = @lazies_at_depth @lazies_at_depth = @lazies_at_depth.dup.clear # Clear pending loads but keep already-cached records # in case they are useful to the given block. @source_cache.each do |source_class, batched_sources| batched_sources.each do |batch_args, batched_source_instance| if batched_source_instance.pending? prev_pending_keys[batched_source_instance] = batched_source_instance.pending.dup batched_source_instance.pending.clear end end end @pending_jobs = [] res = nil # Make sure the block is inside a Fiber, so it can `Fiber.yield` append_job { res = yield } run res ensure @pending_jobs = prev_queue @lazies_at_depth = prev_lazies_at_depth prev_pending_keys.each do |source_instance, pending| pending.each do |key, value| if !source_instance.results.key?(key) source_instance.pending[key] = value end end end end # @param trace_query_lazy [nil, Execution::Multiplex] def run(trace_query_lazy: nil) trace = Fiber[:__graphql_current_multiplex]&.current_trace jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit job_fibers = [] next_job_fibers = [] source_fibers = [] next_source_fibers = [] first_pass = true manager = spawn_fiber do trace&.begin_dataloader(self) while first_pass || !job_fibers.empty? first_pass = false run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit) if !@lazies_at_depth.empty? with_trace_query_lazy(trace_query_lazy) do run_next_pending_lazies(job_fibers, trace) run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit) end end end trace&.end_dataloader(self) end run_fiber(manager) if manager.alive? raise "Invariant: Manager fiber didn't terminate properly." end if !job_fibers.empty? raise "Invariant: job fibers should have exited but #{job_fibers.size} remained" end if !source_fibers.empty? raise "Invariant: source fibers should have exited but #{source_fibers.size} remained" end rescue UncaughtThrowError => e throw e.tag, e.value end def run_fiber(f) f.resume end # @api private def lazy_at_depth(depth, lazy) @lazies_at_depth[depth] << lazy end def spawn_fiber fiber_vars = get_fiber_variables Fiber.new(blocking: !@nonblocking) { set_fiber_variables(fiber_vars) yield cleanup_fiber } end # Pre-warm the Dataloader cache with ActiveRecord objects which were loaded elsewhere. # These will be used by {Dataloader::ActiveRecordSource}, {Dataloader::ActiveRecordAssociationSource} and their helper # methods, `dataload_record` and `dataload_association`. # @param records [Array] Already-loaded records to warm the cache with # @param index_by [Symbol] The attribute to use as the cache key. (Should match `find_by:` when using {ActiveRecordSource}) # @return [void] def merge_records(records, index_by: :id) records_by_class = Hash.new { |h, k| h[k] = {} } records.each do |r| records_by_class[r.class][r.public_send(index_by)] = r end records_by_class.each do |r_class, records| with(ActiveRecordSource, r_class).merge(records) end end private def run_next_pending_lazies(job_fibers, trace) smallest_depth = nil @lazies_at_depth.each_key do |depth_key| smallest_depth ||= depth_key if depth_key < smallest_depth smallest_depth = depth_key end end if smallest_depth lazies = @lazies_at_depth.delete(smallest_depth) if !lazies.empty? lazies.each_with_index do |l, idx| append_job { l.value } end job_fibers.unshift(spawn_job_fiber(trace)) end end end def run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit) while (f = (job_fibers.shift || (((next_job_fibers.size + job_fibers.size) < jobs_fiber_limit) && spawn_job_fiber(trace)))) if f.alive? finished = run_fiber(f) if !finished next_job_fibers << f end end end join_queues(job_fibers, next_job_fibers) while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }) while (f = source_fibers.shift || (((job_fibers.size + source_fibers.size + next_source_fibers.size + next_job_fibers.size) < total_fiber_limit) && spawn_source_fiber(trace))) if f.alive? finished = run_fiber(f) if !finished next_source_fibers << f end end end join_queues(source_fibers, next_source_fibers) end end def with_trace_query_lazy(multiplex_or_nil, &block) if (multiplex = multiplex_or_nil) query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil multiplex.current_trace.execute_query_lazy(query: query, multiplex: multiplex, &block) else yield end end def calculate_fiber_limit total_fiber_limit = @fiber_limit || Float::INFINITY if total_fiber_limit < 4 raise ArgumentError, "Dataloader fiber limit is too low (#{total_fiber_limit}), it must be at least 4" end total_fiber_limit -= 1 # deduct one fiber for `manager` # Deduct at least one fiber for sources jobs_fiber_limit = total_fiber_limit - 2 return jobs_fiber_limit, total_fiber_limit end def join_queues(prev_queue, new_queue) @nonblocking && Fiber.scheduler.run prev_queue.concat(new_queue) new_queue.clear end def spawn_job_fiber(trace) if !@pending_jobs.empty? spawn_fiber do trace&.dataloader_spawn_execution_fiber(@pending_jobs) while job = @pending_jobs.shift job.call end trace&.dataloader_fiber_exit end end end def spawn_source_fiber(trace) pending_sources = nil @source_cache.each_value do |source_by_batch_params| source_by_batch_params.each_value do |source| if source.pending? pending_sources ||= [] pending_sources << source end end end if pending_sources spawn_fiber do trace&.dataloader_spawn_source_fiber(pending_sources) pending_sources.each do |source| Fiber[:__graphql_current_dataloader_source] = source trace&.begin_dataloader_source(source) source.run_pending_keys trace&.end_dataloader_source(source) end trace&.dataloader_fiber_exit end end end end end require "graphql/dataloader/async_dataloader" graphql-2.6.0/lib/graphql/analysis/0000755000004100000410000000000015173430257017243 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/analysis/visitor.rb0000644000004100000410000002125315173430257021272 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Analysis # Depth first traversal through a query AST, calling AST analyzers # along the way. # # The visitor is a special case of GraphQL::Language::StaticVisitor, visiting # only the selected operation, providing helpers for common use cases such # as skipped fields and visiting fragment spreads. # # @see {GraphQL::Analysis::Analyzer} AST Analyzers for queries class Visitor < GraphQL::Language::StaticVisitor def initialize(query:, analyzers:, timeout:) @analyzers = analyzers @path = [] @object_types = [] @directives = [] @field_definitions = [] @argument_definitions = [] @directive_definitions = [] @rescued_errors = [] @query = query @schema = query.schema @types = query.types @response_path = [] @skip_stack = [false] @timeout_time = if timeout Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) + timeout else Float::INFINITY end super(query.selected_operation) end # @return [GraphQL::Query] the query being visited attr_reader :query # @return [Array] Types whose scope we've entered attr_reader :object_types # @return [Array] The path to the response key for the current field def response_path @response_path.dup end # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time # Visitor Hooks [ :operation_definition, :fragment_definition, :inline_fragment, :field, :directive, :argument, :fragment_spread ].each do |node_type| module_eval <<-RUBY, __FILE__, __LINE__ def call_on_enter_#{node_type}(node, parent) @analyzers.each do |a| a.on_enter_#{node_type}(node, parent, self) rescue AnalysisError => err @rescued_errors << err end end def call_on_leave_#{node_type}(node, parent) @analyzers.each do |a| a.on_leave_#{node_type}(node, parent, self) rescue AnalysisError => err @rescued_errors << err end end RUBY end # rubocop:enable Development/NoEvalCop def on_operation_definition(node, parent) check_timeout object_type = @schema.root_type_for_operation(node.operation_type) @object_types.push(object_type) @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}") call_on_enter_operation_definition(node, parent) super call_on_leave_operation_definition(node, parent) @object_types.pop @path.pop end def on_inline_fragment(node, parent) check_timeout object_type = if node.type @types.type(node.type.name) else @object_types.last end @object_types.push(object_type) @path.push("...#{node.type ? " on #{node.type.name}" : ""}") @skipping = @skip_stack.last || skip?(node) @skip_stack << @skipping call_on_enter_inline_fragment(node, parent) super @skipping = @skip_stack.pop call_on_leave_inline_fragment(node, parent) @object_types.pop @path.pop end def on_field(node, parent) check_timeout @response_path.push(node.alias || node.name) parent_type = @object_types.last # This could be nil if the previous field wasn't found: field_definition = parent_type && @types.field(parent_type, node.name) @field_definitions.push(field_definition) if !field_definition.nil? next_object_type = field_definition.type.unwrap @object_types.push(next_object_type) else @object_types.push(nil) end @path.push(node.alias || node.name) @skipping = @skip_stack.last || skip?(node) @skip_stack << @skipping call_on_enter_field(node, parent) super @skipping = @skip_stack.pop call_on_leave_field(node, parent) @response_path.pop @field_definitions.pop @object_types.pop @path.pop end def on_directive(node, parent) check_timeout directive_defn = @schema.directives[node.name] @directive_definitions.push(directive_defn) call_on_enter_directive(node, parent) super call_on_leave_directive(node, parent) @directive_definitions.pop end def on_argument(node, parent) check_timeout argument_defn = if (arg = @argument_definitions.last) arg_type = arg.type.unwrap if arg_type.kind.input_object? @types.argument(arg_type, node.name) else nil end elsif (directive_defn = @directive_definitions.last) @types.argument(directive_defn, node.name) elsif (field_defn = @field_definitions.last) @types.argument(field_defn, node.name) else nil end @argument_definitions.push(argument_defn) @path.push(node.name) call_on_enter_argument(node, parent) super call_on_leave_argument(node, parent) @argument_definitions.pop @path.pop end def on_fragment_spread(node, parent) check_timeout @path.push("... #{node.name}") @skipping = @skip_stack.last || skip?(node) @skip_stack << @skipping call_on_enter_fragment_spread(node, parent) enter_fragment_spread_inline(node) super @skipping = @skip_stack.pop leave_fragment_spread_inline(node) call_on_leave_fragment_spread(node, parent) @path.pop end # @return [GraphQL::BaseType] The current object type def type_definition @object_types.last end # @return [GraphQL::BaseType] The type which the current type came from def parent_type_definition @object_types[-2] end # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one def field_definition @field_definitions.last end # @return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to def previous_field_definition @field_definitions[-2] end # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one def directive_definition @directive_definitions.last end # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one def argument_definition @argument_definitions.last end # @return [GraphQL::Argument, nil] The previous GraphQL argument def previous_argument_definition @argument_definitions[-2] end private # Visit a fragment spread inline instead of visiting the definition # by itself. def enter_fragment_spread_inline(fragment_spread) fragment_def = query.fragments[fragment_spread.name] object_type = if fragment_def.type @types.type(fragment_def.type.name) else object_types.last end object_types << object_type on_fragment_definition_children(fragment_def) end # Visit a fragment spread inline instead of visiting the definition # by itself. def leave_fragment_spread_inline(_fragment_spread) object_types.pop end def skip?(ast_node) dir = ast_node.directives !dir.empty? && !GraphQL::Execution::DirectiveChecks.include?(dir, query) end def check_timeout if Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second) > @timeout_time raise GraphQL::Analysis::TimeoutError end end end end end graphql-2.6.0/lib/graphql/analysis/field_usage.rb0000644000004100000410000000564215173430257022046 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Analysis class FieldUsage < Analyzer def initialize(query) super @used_fields = Set.new @used_deprecated_fields = Set.new @used_deprecated_arguments = Set.new @used_deprecated_enum_values = Set.new end def on_leave_field(node, parent, visitor) field_defn = visitor.field_definition field = "#{visitor.parent_type_definition.graphql_name}.#{field_defn.graphql_name}" @used_fields << field @used_deprecated_fields << field if field_defn.deprecation_reason arguments = visitor.query.arguments_for(node, field_defn) # If there was an error when preparing this argument object, # then this might be an error or something: if arguments.respond_to?(:argument_values) extract_deprecated_arguments(arguments.argument_values) end end def result { used_fields: @used_fields.to_a, used_deprecated_fields: @used_deprecated_fields.to_a, used_deprecated_arguments: @used_deprecated_arguments.to_a, used_deprecated_enum_values: @used_deprecated_enum_values.to_a, } end private def extract_deprecated_arguments(argument_values) argument_values.each_pair do |_argument_name, argument| if argument.definition.deprecation_reason @used_deprecated_arguments << argument.definition.path end arg_val = argument.value next if arg_val.nil? argument_type = argument.definition.type if argument_type.non_null? argument_type = argument_type.of_type end if argument_type.kind.input_object? extract_deprecated_arguments(argument.original_value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance elsif argument_type.kind.enum? extract_deprecated_enum_value(argument_type, arg_val) elsif argument_type.list? inner_type = argument_type.unwrap case inner_type.kind when TypeKinds::INPUT_OBJECT argument.original_value.each do |value| extract_deprecated_arguments(value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance end when TypeKinds::ENUM arg_val.each do |value| extract_deprecated_enum_value(inner_type, value) end else # Not a kind of input that we track end end end end def extract_deprecated_enum_value(enum_type, value) enum_value = @query.types.enum_values(enum_type).find { |ev| ev.value == value } if enum_value&.deprecation_reason @used_deprecated_enum_values << enum_value.path end end end end end graphql-2.6.0/lib/graphql/analysis/query_complexity.rb0000644000004100000410000002707215173430257023222 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Analysis # Calculate the complexity of a query, using {Field#complexity} values. class QueryComplexity < Analyzer # State for the query complexity calculation: # - `complexities_on_type` holds complexity scores for each type def initialize(query) super @skip_introspection_fields = !query.schema.max_complexity_count_introspection_fields @complexities_on_type_by_query = {} end # Override this method to use the complexity result def result case subject.schema.complexity_cost_calculation_mode_for(subject.context) when :future max_possible_complexity when :legacy max_possible_complexity(mode: :legacy) when :compare future_complexity = max_possible_complexity legacy_complexity = max_possible_complexity(mode: :legacy) if future_complexity != legacy_complexity subject.schema.legacy_complexity_cost_calculation_mismatch(subject, future_complexity, legacy_complexity) else future_complexity end when nil subject.logger.warn <<~GRAPHQL GraphQL-Ruby's complexity cost system is getting some "breaking fixes" in a future version. See the migration notes at https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#complexity_cost_calculation_mode_for-class_method To opt into the future behavior, configure your schema (#{subject.schema.name ? subject.schema.name : subject.schema.ancestors}) with: complexity_cost_calculation_mode(:future) # or `:legacy`, `:compare` GRAPHQL max_possible_complexity(mode: :legacy) else raise ArgumentError, "Expected `:future`, `:legacy`, `:compare`, or `nil` from `#{query.schema}.complexity_cost_calculation_mode_for` but got: #{query.schema.complexity_cost_calculation_mode.inspect}" end end # ScopedTypeComplexity models a tree of GraphQL types mapped to inner selections, ie: # Hash> class ScopedTypeComplexity < Hash # A proc for defaulting empty namespace requests as a new scope hash. DEFAULT_PROC = ->(h, k) { h[k] = {} } attr_reader :field_definition, :response_path, :query # @param parent_type [Class] The owner of `field_definition` # @param field_definition [GraphQL::Field, GraphQL::Schema::Field] Used for getting the `.complexity` configuration # @param query [GraphQL::Query] Used for `query.possible_types` # @param response_path [Array] The path to the response key for the field # @return [Hash>] def initialize(parent_type, field_definition, query, response_path) super(&DEFAULT_PROC) @parent_type = parent_type @field_definition = field_definition @query = query @response_path = response_path @nodes = [] end # @return [Array] attr_reader :nodes def own_complexity(child_complexity) @field_definition.calculate_complexity(query: @query, nodes: @nodes, child_complexity: child_complexity) end def composite? !empty? end end def on_enter_field(node, parent, visitor) # We don't want to visit fragment definitions, # we'll visit them when we hit the spreads instead return if visitor.visiting_fragment_definition? return if visitor.skipping? return if @skip_introspection_fields && visitor.field_definition.introspection? parent_type = visitor.parent_type_definition field_key = node.alias || node.name # Find or create a complexity scope stack for this query. scopes_stack = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query, visitor.response_path)] # Find or create the complexity costing node for this field. scope = scopes_stack.last[parent_type][field_key] ||= ScopedTypeComplexity.new(parent_type, visitor.field_definition, visitor.query, visitor.response_path) scope.nodes.push(node) scopes_stack.push(scope) end def on_leave_field(node, parent, visitor) # We don't want to visit fragment definitions, # we'll visit them when we hit the spreads instead return if visitor.visiting_fragment_definition? return if visitor.skipping? return if @skip_introspection_fields && visitor.field_definition.introspection? scopes_stack = @complexities_on_type_by_query[visitor.query] scopes_stack.pop end private # @return [Integer] def max_possible_complexity(mode: :future) @complexities_on_type_by_query.reduce(0) do |total, (query, scopes_stack)| total + merged_max_complexity_for_scopes(query, [scopes_stack.first], mode) end end # @param query [GraphQL::Query] Used for `query.possible_types` # @param scopes [Array] Array of scoped type complexities # @param mode [:future, :legacy] # @return [Integer] def merged_max_complexity_for_scopes(query, scopes, mode) # Aggregate a set of all possible scope types encountered (scope keys). # Use a hash, but ignore the values; it's just a fast way to work with the keys. possible_scope_types = scopes.each_with_object({}) do |scope, memo| memo.merge!(scope) end # Expand abstract scope types into their concrete implementations; # overlapping abstracts coalesce through their intersecting types. possible_scope_types.keys.each do |possible_scope_type| next unless possible_scope_type.kind.abstract? query.types.possible_types(possible_scope_type).each do |impl_type| possible_scope_types[impl_type] ||= true end possible_scope_types.delete(possible_scope_type) end # Aggregate the lexical selections that may apply to each possible type, # and then return the maximum cost among possible typed selections. possible_scope_types.each_key.reduce(0) do |max, possible_scope_type| # Collect inner selections from all scopes that intersect with this possible type. all_inner_selections = scopes.each_with_object([]) do |scope, memo| scope.each do |scope_type, inner_selections| memo << inner_selections if types_intersect?(query, scope_type, possible_scope_type) end end # Find the maximum complexity for the scope type among possible lexical branches. complexity = case mode when :legacy legacy_merged_max_complexity(query, all_inner_selections) when :future merged_max_complexity(query, all_inner_selections) else raise ArgumentError, "Expected :legacy or :future, not: #{mode.inspect}" end complexity > max ? complexity : max end end def types_intersect?(query, a, b) return true if a == b a_types = query.types.possible_types(a) query.types.possible_types(b).any? { |t| a_types.include?(t) } end # A hook which is called whenever a field's max complexity is calculated. # Override this method to capture individual field complexity details. # # @param scoped_type_complexity [ScopedTypeComplexity] # @param max_complexity [Numeric] Field's maximum complexity including child complexity # @param child_complexity [Numeric, nil] Field's child complexity def field_complexity(scoped_type_complexity, max_complexity:, child_complexity: nil) end # @param inner_selections [Array>] Field selections for a scope # @return [Integer] Total complexity value for all these selections in the parent scope def merged_max_complexity(query, inner_selections) # Aggregate a set of all unique field selection keys across all scopes. # Use a hash, but ignore the values; it's just a fast way to work with the keys. unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo| memo.merge!(inner_selection) end # Add up the total cost for each unique field name's coalesced selections unique_field_keys.each_key.reduce(0) do |total, field_key| # Collect all child scopes for this field key; # all keys come with at least one scope. child_scopes = inner_selections.filter_map { _1[field_key] } # Compute maximum possible cost of child selections; # composites merge their maximums, while leaf scopes are always zero. # FieldsWillMerge validation assures all scopes are uniformly composite or leaf. maximum_children_cost = if child_scopes.any?(&:composite?) merged_max_complexity_for_scopes(query, child_scopes, :future) else 0 end # Identify the maximum cost and scope among possibilities maximum_cost = 0 maximum_scope = child_scopes.reduce(child_scopes.last) do |max_scope, possible_scope| scope_cost = possible_scope.own_complexity(maximum_children_cost) if scope_cost > maximum_cost maximum_cost = scope_cost possible_scope else max_scope end end field_complexity( maximum_scope, max_complexity: maximum_cost, child_complexity: maximum_children_cost, ) total + maximum_cost end end def legacy_merged_max_complexity(query, inner_selections) # Aggregate a set of all unique field selection keys across all scopes. # Use a hash, but ignore the values; it's just a fast way to work with the keys. unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo| memo.merge!(inner_selection) end # Add up the total cost for each unique field name's coalesced selections unique_field_keys.each_key.reduce(0) do |total, field_key| composite_scopes = nil field_cost = 0 # Collect composite selection scopes for further aggregation, # leaf selections report their costs directly. inner_selections.each do |inner_selection| child_scope = inner_selection[field_key] next unless child_scope # Empty child scopes are leaf nodes with zero child complexity. if child_scope.empty? field_cost = child_scope.own_complexity(0) field_complexity(child_scope, max_complexity: field_cost, child_complexity: nil) else composite_scopes ||= [] composite_scopes << child_scope end end if composite_scopes child_complexity = merged_max_complexity_for_scopes(query, composite_scopes, :legacy) # This is the last composite scope visited; assume it's representative (for backwards compatibility). # Note: it would be more correct to score each composite scope and use the maximum possibility. field_cost = composite_scopes.last.own_complexity(child_complexity) field_complexity(composite_scopes.last, max_complexity: field_cost, child_complexity: child_complexity) end total + field_cost end end end end end graphql-2.6.0/lib/graphql/analysis/max_query_depth.rb0000644000004100000410000000077315173430257022775 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Analysis class MaxQueryDepth < QueryDepth def result configured_max_depth = if query query.max_depth else multiplex.schema.max_depth end if configured_max_depth && @max_depth > configured_max_depth GraphQL::AnalysisError.new("Query has depth of #{@max_depth}, which exceeds max depth of #{configured_max_depth}") else nil end end end end end graphql-2.6.0/lib/graphql/analysis/analyzer.rb0000644000004100000410000000566115173430257021425 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Analysis # Query analyzer for query ASTs. Query analyzers respond to visitor style methods # but are prefixed by `enter` and `leave`. # # When an analyzer is initialized with a Multiplex, you can always get the current query from # `visitor.query` in the visit methods. # # @param [GraphQL::Query, GraphQL::Execution::Multiplex] The query or multiplex to analyze class Analyzer def initialize(subject) @subject = subject if subject.is_a?(GraphQL::Query) @query = subject @multiplex = nil else @multiplex = subject @query = nil end end # Analyzer hook to decide at analysis time whether a query should # be analyzed or not. # @return [Boolean] If the query should be analyzed or not def analyze? true end # Analyzer hook to decide at analysis time whether analysis # requires a visitor pass; can be disabled for precomputed results. # @return [Boolean] If analysis requires visitation or not def visit? true end # The result for this analyzer. Returning {GraphQL::AnalysisError} results # in a query error. # @return [Any] The analyzer result def result raise GraphQL::RequiredImplementationMissingError end # rubocop:disable Development/NoEvalCop This eval takes static inputs at load-time class << self private def build_visitor_hooks(member_name) class_eval(<<-EOS, __FILE__, __LINE__ + 1) def on_enter_#{member_name}(node, parent, visitor) end def on_leave_#{member_name}(node, parent, visitor) end EOS end end build_visitor_hooks :argument build_visitor_hooks :directive build_visitor_hooks :document build_visitor_hooks :enum build_visitor_hooks :field build_visitor_hooks :fragment_spread build_visitor_hooks :inline_fragment build_visitor_hooks :input_object build_visitor_hooks :list_type build_visitor_hooks :non_null_type build_visitor_hooks :null_value build_visitor_hooks :operation_definition build_visitor_hooks :type_name build_visitor_hooks :variable_definition build_visitor_hooks :variable_identifier build_visitor_hooks :abstract_node # rubocop:enable Development/NoEvalCop protected # @return [GraphQL::Query, GraphQL::Execution::Multiplex] Whatever this analyzer is analyzing attr_reader :subject # @return [GraphQL::Query, nil] `nil` if this analyzer is visiting a multiplex # (When this is `nil`, use `visitor.query` inside visit methods to get the current query) attr_reader :query # @return [GraphQL::Execution::Multiplex, nil] `nil` if this analyzer is visiting a query attr_reader :multiplex end end end graphql-2.6.0/lib/graphql/analysis/max_query_complexity.rb0000644000004100000410000000114415173430257024057 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Analysis # Used under the hood to implement complexity validation, # see {Schema#max_complexity} and {Query#max_complexity} class MaxQueryComplexity < QueryComplexity def result return if subject.max_complexity.nil? total_complexity = max_possible_complexity if total_complexity > subject.max_complexity GraphQL::AnalysisError.new("Query has complexity of #{total_complexity}, which exceeds max complexity of #{subject.max_complexity}") else nil end end end end end graphql-2.6.0/lib/graphql/analysis/query_depth.rb0000644000004100000410000000311515173430257022121 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Analysis # A query reducer for measuring the depth of a given query. # # See https://graphql-ruby.org/queries/ast_analysis.html for more examples. # # @example Logging the depth of a query # class LogQueryDepth < GraphQL::Analysis::QueryDepth # def result # log("GraphQL query depth: #{@max_depth}") # end # end # # # In your Schema file: # # class MySchema < GraphQL::Schema # query_analyzer LogQueryDepth # end # # # When you run the query, the depth will get logged: # # Schema.execute(query_str) # # GraphQL query depth: 8 # class QueryDepth < Analyzer def initialize(query) @max_depth = 0 @current_depth = 0 @count_introspection_fields = query.schema.count_introspection_fields super end def on_enter_field(node, parent, visitor) return if visitor.skipping? || visitor.visiting_fragment_definition? || (@count_introspection_fields == false && visitor.field_definition.introspection?) @current_depth += 1 end def on_leave_field(node, parent, visitor) return if visitor.skipping? || visitor.visiting_fragment_definition? || (@count_introspection_fields == false && visitor.field_definition.introspection?) if @max_depth < @current_depth @max_depth = @current_depth end @current_depth -= 1 end def result @max_depth end end end end graphql-2.6.0/lib/graphql/tracing.rb0000644000004100000410000000615215173430257017400 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Tracing autoload :Trace, "graphql/tracing/trace" autoload :CallLegacyTracers, "graphql/tracing/call_legacy_tracers" autoload :LegacyTrace, "graphql/tracing/legacy_trace" autoload :LegacyHooksTrace, "graphql/tracing/legacy_hooks_trace" autoload :NullTrace, "graphql/tracing/null_trace" autoload :ActiveSupportNotificationsTracing, "graphql/tracing/active_support_notifications_tracing" autoload :PlatformTracing, "graphql/tracing/platform_tracing" autoload :AppOpticsTracing, "graphql/tracing/appoptics_tracing" autoload :AppsignalTracing, "graphql/tracing/appsignal_tracing" autoload :DataDogTracing, "graphql/tracing/data_dog_tracing" autoload :NewRelicTracing, "graphql/tracing/new_relic_tracing" autoload :NotificationsTracing, "graphql/tracing/notifications_tracing" autoload :ScoutTracing, "graphql/tracing/scout_tracing" autoload :StatsdTracing, "graphql/tracing/statsd_tracing" autoload :PrometheusTracing, "graphql/tracing/prometheus_tracing" autoload :ActiveSupportNotificationsTrace, "graphql/tracing/active_support_notifications_trace" autoload :PlatformTrace, "graphql/tracing/platform_trace" autoload :AppOpticsTrace, "graphql/tracing/appoptics_trace" autoload :AppsignalTrace, "graphql/tracing/appsignal_trace" autoload :DataDogTrace, "graphql/tracing/data_dog_trace" autoload :MonitorTrace, "graphql/tracing/monitor_trace" autoload :NewRelicTrace, "graphql/tracing/new_relic_trace" autoload :NotificationsTrace, "graphql/tracing/notifications_trace" autoload :SentryTrace, "graphql/tracing/sentry_trace" autoload :ScoutTrace, "graphql/tracing/scout_trace" autoload :StatsdTrace, "graphql/tracing/statsd_trace" autoload :PrometheusTrace, "graphql/tracing/prometheus_trace" autoload :PerfettoTrace, "graphql/tracing/perfetto_trace" autoload :DetailedTrace, "graphql/tracing/detailed_trace" # Objects may include traceable to gain a `.trace(...)` method. # The object must have a `@tracers` ivar of type `Array<<#trace(k, d, &b)>>`. # @api private module Traceable # @param key [String] The name of the event in GraphQL internals # @param metadata [Hash] Event-related metadata (can be anything) # @return [Object] Must return the value of the block def trace(key, metadata, &block) return yield if @tracers.empty? call_tracers(0, key, metadata, &block) end private # If there's a tracer at `idx`, call it and then increment `idx`. # Otherwise, yield. # # @param idx [Integer] Which tracer to call # @param key [String] The current event name # @param metadata [Object] The current event object # @return Whatever the block returns def call_tracers(idx, key, metadata, &block) if idx == @tracers.length yield else @tracers[idx].trace(key, metadata) { call_tracers(idx + 1, key, metadata, &block) } end end end module NullTracer module_function def trace(k, v) yield end end end end graphql-2.6.0/lib/graphql/schema/0000755000004100000410000000000015173430257016660 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/schema/always_visible.rb0000644000004100000410000000046715173430257022231 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema module AlwaysVisible def self.use(schema, **opts) schema.use(GraphQL::Schema::Visibility, profiles: { nil => {} }) schema.extend(self) end def visible?(_member, _context) true end end end end graphql-2.6.0/lib/graphql/schema/field/0000755000004100000410000000000015173430257017743 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/schema/field/scope_extension.rb0000644000004100000410000000211315173430257023472 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Field class ScopeExtension < GraphQL::Schema::FieldExtension def after_resolve(object:, arguments:, context:, value:, memo:) if object.is_a?(GraphQL::Schema::Object) if value.nil? value else return_type = field.type.unwrap if return_type.respond_to?(:scope_items) scoped_items = return_type.scope_items(value, context) if !scoped_items.equal?(value) && !return_type.reauthorize_scoped_objects if (current_runtime_state = Fiber[:__graphql_runtime_info]) && (query_runtime_state = current_runtime_state[context.query]) query_runtime_state.was_authorized_by_scope_items = true end end scoped_items else value end end else # TODO skip this entirely? value end end end end end end graphql-2.6.0/lib/graphql/schema/field/connection_extension.rb0000644000004100000410000000254715173430257024533 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Field class ConnectionExtension < GraphQL::Schema::FieldExtension def apply field.argument :after, "String", "Returns the elements in the list that come after the specified cursor.", required: false field.argument :before, "String", "Returns the elements in the list that come before the specified cursor.", required: false field.argument :first, "Int", "Returns the first _n_ elements from the list.", required: false field.argument :last, "Int", "Returns the last _n_ elements from the list.", required: false end # Remove pagination args before passing it to a user method def resolve(object: nil, objects: nil, arguments:, context:) next_args = arguments.dup next_args.delete(:first) next_args.delete(:last) next_args.delete(:before) next_args.delete(:after) yield(object || objects, next_args, arguments) end def after_resolve(value:, object:, arguments:, context:, memo:) original_arguments = memo context.query.after_lazy(value) do |resolved_value| context.schema.connections.populate_connection(field, object.object, resolved_value, original_arguments, context) end end end end end end graphql-2.6.0/lib/graphql/schema/validator/0000755000004100000410000000000015173430257020645 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/schema/validator/numericality_validator.rb0000644000004100000410000000675115173430257025755 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Validator # Use this to assert numerical comparisons hold true for inputs. # # @example Require a number between 0 and 1 # # argument :batting_average, Float, required: true, validates: { numericality: { within: 0..1 } } # # @example Require the number 42 # # argument :the_answer, Integer, required: true, validates: { numericality: { equal_to: 42 } } # # @example Require a real number # # argument :items_count, Integer, required: true, validates: { numericality: { greater_than_or_equal_to: 0 } } # class NumericalityValidator < Validator # @param greater_than [Integer] # @param greater_than_or_equal_to [Integer] # @param less_than [Integer] # @param less_than_or_equal_to [Integer] # @param equal_to [Integer] # @param other_than [Integer] # @param odd [Boolean] # @param even [Boolean] # @param within [Range] # @param message [String] used for all validation failures def initialize( greater_than: nil, greater_than_or_equal_to: nil, less_than: nil, less_than_or_equal_to: nil, equal_to: nil, other_than: nil, odd: nil, even: nil, within: nil, message: "%{validated} must be %{comparison} %{target}", null_message: Validator::AllowNullValidator::MESSAGE, **default_options ) @greater_than = greater_than @greater_than_or_equal_to = greater_than_or_equal_to @less_than = less_than @less_than_or_equal_to = less_than_or_equal_to @equal_to = equal_to @other_than = other_than @odd = odd @even = even @within = within @message = message @null_message = null_message super(**default_options) end def validate(object, context, value) if permitted_empty_value?(value) # pass in this case elsif value.nil? # @allow_null is handled in the parent class @null_message elsif @greater_than && value <= @greater_than partial_format(@message, { comparison: "greater than", target: @greater_than }) elsif @greater_than_or_equal_to && value < @greater_than_or_equal_to partial_format(@message, { comparison: "greater than or equal to", target: @greater_than_or_equal_to }) elsif @less_than && value >= @less_than partial_format(@message, { comparison: "less than", target: @less_than }) elsif @less_than_or_equal_to && value > @less_than_or_equal_to partial_format(@message, { comparison: "less than or equal to", target: @less_than_or_equal_to }) elsif @equal_to && value != @equal_to partial_format(@message, { comparison: "equal to", target: @equal_to }) elsif @other_than && value == @other_than partial_format(@message, { comparison: "something other than", target: @other_than }) elsif @even && !value.even? (partial_format(@message, { comparison: "even", target: "" })).strip elsif @odd && !value.odd? (partial_format(@message, { comparison: "odd", target: "" })).strip elsif @within && !@within.include?(value) partial_format(@message, { comparison: "within", target: @within }) end end end end end end graphql-2.6.0/lib/graphql/schema/validator/allow_null_validator.rb0000644000004100000410000000152215173430257025407 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Validator # Use this to specifically reject or permit `nil` values (given as `null` from GraphQL). # # @example require a non-null value for an argument if it is provided # argument :name, String, required: false, validates: { allow_null: false } class AllowNullValidator < Validator MESSAGE = "%{validated} can't be null" def initialize(allow_null_positional, allow_null: nil, message: MESSAGE, **default_options) @message = message super(**default_options) @allow_null = allow_null.nil? ? allow_null_positional : allow_null end def validate(_object, _context, value) if value.nil? && !@allow_null @message end end end end end end graphql-2.6.0/lib/graphql/schema/validator/all_validator.rb0000644000004100000410000000352515173430257024014 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Validator # Use this to validate each member of an array value. # # @example validate format of all strings in an array # # argument :handles, [String], # validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ } } } # # @example multiple validators can be combined # # argument :handles, [String], # validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ }, length: { maximum: 32 } } } # # @example any type can be used # # argument :choices, [Integer], # validates: { all: { inclusion: { in: 1..12 } } } # class AllValidator < Validator def initialize(validated:, allow_blank: false, allow_null: false, **validators) super(validated: validated, allow_blank: allow_blank, allow_null: allow_null) @validators = Validator.from_config(validated, validators) end def validate(object, context, value) return EMPTY_ARRAY if permitted_empty_value?(value) all_errors = EMPTY_ARRAY value.each do |subvalue| @validators.each do |validator| errors = validator.validate(object, context, subvalue) if errors && (errors.is_a?(Array) && errors != EMPTY_ARRAY) || (errors.is_a?(String)) if all_errors.frozen? # It's empty all_errors = [] end if errors.is_a?(String) all_errors << errors else all_errors.concat(errors) end end end end unless all_errors.frozen? all_errors.uniq! end all_errors end end end end end graphql-2.6.0/lib/graphql/schema/validator/format_validator.rb0000644000004100000410000000266015173430257024533 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Validator # Use this to assert that string values match (or don't match) the given RegExp. # # @example requiring input to match a pattern # # argument :handle, String, required: true, # validates: { format: { with: /\A[a-z0-9_]+\Z/ } } # # @example reject inputs that match a pattern # # argument :word_that_doesnt_begin_with_a_vowel, String, required: true, # validates: { format: { without: /\A[aeiou]/ } } # # # It's pretty hard to come up with a legitimate use case for `without:` # class FormatValidator < Validator # @param with [RegExp, nil] # @param without [Regexp, nil] # @param message [String] def initialize( with: nil, without: nil, message: "%{validated} is invalid", **default_options ) @with_pattern = with @without_pattern = without @message = message super(**default_options) end def validate(_object, _context, value) if permitted_empty_value?(value) # Do nothing elsif value.nil? || (@with_pattern && !value.match?(@with_pattern)) || (@without_pattern && value.match?(@without_pattern)) @message end end end end end end graphql-2.6.0/lib/graphql/schema/validator/length_validator.rb0000644000004100000410000000474515173430257024532 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Validator # Use this to enforce a `.length` restriction on incoming values. It works for both Strings and Lists. # # @example Allow no more than 10 IDs # # argument :ids, [ID], required: true, validates: { length: { maximum: 10 } } # # @example Require three selections # # argument :ice_cream_preferences, [ICE_CREAM_FLAVOR], required: true, validates: { length: { is: 3 } } # class LengthValidator < Validator # @param maximum [Integer] # @param too_long [String] Used when `maximum` is exceeded or value is greater than `within` # @param minimum [Integer] # @param too_short [String] Used with value is less than `minimum` or less than `within` # @param is [Integer] Exact length requirement # @param wrong_length [String] Used when value doesn't match `is` # @param within [Range] An allowed range (becomes `minimum:` and `maximum:` under the hood) # @param message [String] def initialize( maximum: nil, too_long: "%{validated} is too long (maximum is %{count})", minimum: nil, too_short: "%{validated} is too short (minimum is %{count})", is: nil, within: nil, wrong_length: "%{validated} is the wrong length (should be %{count})", message: nil, **default_options ) if within && (minimum || maximum) raise ArgumentError, "`length: { ... }` may include `within:` _or_ `minimum:`/`maximum:`, but not both" end # Under the hood, `within` is decomposed into `minimum` and `maximum` @maximum = maximum || (within && within.max) @too_long = message || too_long @minimum = minimum || (within && within.min) @too_short = message || too_short @is = is @wrong_length = message || wrong_length super(**default_options) end def validate(_object, _context, value) return if permitted_empty_value?(value) # pass in this case length = value.nil? ? 0 : value.length if @maximum && length > @maximum partial_format(@too_long, { count: @maximum }) elsif @minimum && length < @minimum partial_format(@too_short, { count: @minimum }) elsif @is && length != @is partial_format(@wrong_length, { count: @is }) end end end end end end graphql-2.6.0/lib/graphql/schema/validator/inclusion_validator.rb0000644000004100000410000000211415173430257025240 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Validator # You can use this to allow certain values for an argument. # # Usually, a {GraphQL::Schema::Enum} is better for this, because it's self-documenting. # # @example only allow certain values for an argument # # argument :favorite_prime, Integer, required: true, # validates: { inclusion: { in: [2, 3, 5, 7, 11, ... ] } } # class InclusionValidator < Validator # @param message [String] # @param in [Array] The values to allow def initialize(in:, message: "%{validated} is not included in the list", **default_options) # `in` is a reserved word, so work around that @in_list = binding.local_variable_get(:in) @message = message super(**default_options) end def validate(_object, _context, value) if permitted_empty_value?(value) # pass elsif !@in_list.include?(value) @message end end end end end end graphql-2.6.0/lib/graphql/schema/validator/allow_blank_validator.rb0000644000004100000410000000170015173430257025522 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Validator # Use this to specifically reject values that respond to `.blank?` and respond truthy for that method. # # @example Require a non-empty string for an argument # argument :name, String, required: true, validate: { allow_blank: false } class AllowBlankValidator < Validator def initialize(allow_blank_positional, allow_blank: nil, message: "%{validated} can't be blank", **default_options) @message = message super(**default_options) @allow_blank = allow_blank.nil? ? allow_blank_positional : allow_blank end def validate(_object, _context, value) if value.respond_to?(:blank?) && value.blank? if (value.nil? && @allow_null) || @allow_blank # pass else @message end end end end end end end graphql-2.6.0/lib/graphql/schema/validator/required_validator.rb0000644000004100000410000001455215173430257025066 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Validator # Use this validator to require _one_ of the named arguments to be present. # Or, use Arrays of symbols to name a valid _set_ of arguments. # # (This is for specifying mutually exclusive sets of arguments.) # # If you use {GraphQL::Schema::Visibility} to hide all the arguments in a `one_of: [..]` set, # then a developer-facing {GraphQL::Error} will be raised during execution. Pass `allow_all_hidden: true` to # skip validation in this case instead. # # This validator also implements `argument ... required: :nullable`. If an argument has `required: :nullable` # but it's hidden with {GraphQL::Schema::Visibility}, then this validator doesn't run. # # @example Require exactly one of these arguments # # field :update_amount, IngredientAmount, null: false do # argument :ingredient_id, ID, required: true # argument :cups, Integer, required: false # argument :tablespoons, Integer, required: false # argument :teaspoons, Integer, required: false # validates required: { one_of: [:cups, :tablespoons, :teaspoons] } # end # # @example Require one of these _sets_ of arguments # # field :find_object, Node, null: true do # argument :node_id, ID, required: false # argument :object_type, String, required: false # argument :object_id, Integer, required: false # # either a global `node_id` or an `object_type`/`object_id` pair is required: # validates required: { one_of: [:node_id, [:object_type, :object_id]] } # end # # @example require _some_ value for an argument, even if it's null # field :update_settings, AccountSettings do # # `required: :nullable` means this argument must be given, but may be `null` # argument :age, Integer, required: :nullable # end # class RequiredValidator < Validator # @param one_of [Array] A list of arguments, exactly one of which is required for this field # @param argument [Symbol] An argument that is required for this field # @param allow_all_hidden [Boolean] If `true`, then this validator won't run if all the `one_of: ...` arguments have been hidden # @param message [String] def initialize(one_of: nil, argument: nil, allow_all_hidden: nil, message: nil, **default_options) @one_of = if one_of one_of elsif argument [ argument ] else raise ArgumentError, "`one_of:` or `argument:` must be given in `validates required: {...}`" end @allow_all_hidden = allow_all_hidden.nil? ? !!argument : allow_all_hidden @message = message super(**default_options) end def validate(_object, context, value) fully_matched_conditions = 0 partially_matched_conditions = 0 visible_keywords = context.types.arguments(@validated).map(&:keyword) no_visible_conditions = true if !value.nil? @one_of.each do |one_of_condition| case one_of_condition when Symbol if no_visible_conditions && visible_keywords.include?(one_of_condition) no_visible_conditions = false end if value.key?(one_of_condition) fully_matched_conditions += 1 end when Array any_match = false full_match = true one_of_condition.each do |k| if no_visible_conditions && visible_keywords.include?(k) no_visible_conditions = false end if value.key?(k) any_match = true else full_match = false end end partial_match = !full_match && any_match if full_match fully_matched_conditions += 1 end if partial_match partially_matched_conditions += 1 end else raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}" end end end if no_visible_conditions if @allow_all_hidden return nil else raise GraphQL::Error, <<~ERR #{@validated.path} validates `required: ...` but all required arguments were hidden. Update your schema definition to allow the client to see some fields or skip validation by adding `required: { ..., allow_all_hidden: true }` ERR end end if fully_matched_conditions == 1 && partially_matched_conditions == 0 nil # OK else @message || build_message(context) end end def build_message(context) argument_definitions = context.types.arguments(@validated) required_names = @one_of.map do |arg_keyword| if arg_keyword.is_a?(Array) names = arg_keyword.map { |arg| arg_keyword_to_graphql_name(argument_definitions, arg) } names.compact! # hidden arguments are `nil` "(" + names.join(" and ") + ")" else arg_keyword_to_graphql_name(argument_definitions, arg_keyword) end end required_names.compact! # remove entries for hidden arguments case required_names.size when 0 # The required definitions were hidden from the client. # Another option here would be to raise an error in the application.... "%{validated} is missing a required argument." when 1 "%{validated} must include the following argument: #{required_names.first}." else "%{validated} must include exactly one of the following arguments: #{required_names.join(", ")}." end end def arg_keyword_to_graphql_name(argument_definitions, arg_keyword) argument_definition = argument_definitions.find { |defn| defn.keyword == arg_keyword } argument_definition&.graphql_name end end end end end graphql-2.6.0/lib/graphql/schema/validator/exclusion_validator.rb0000644000004100000410000000170215173430257025250 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Validator # Use this to specifically reject values from an argument. # # @example disallow certain values # # argument :favorite_non_prime, Integer, required: true, # validates: { exclusion: { in: [2, 3, 5, 7, ... ]} } # class ExclusionValidator < Validator # @param message [String] # @param in [Array] The values to reject def initialize(message: "%{validated} is reserved", in:, **default_options) # `in` is a reserved word, so work around that @in_list = binding.local_variable_get(:in) @message = message super(**default_options) end def validate(_object, _context, value) if permitted_empty_value?(value) # pass elsif @in_list.include?(value) @message end end end end end end graphql-2.6.0/lib/graphql/schema/build_from_definition.rb0000644000004100000410000006144615173430257023552 0ustar www-datawww-data# frozen_string_literal: true require "graphql/schema/build_from_definition/resolve_map" module GraphQL class Schema module BuildFromDefinition class << self # @see {Schema.from_definition} def from_definition(schema_superclass, definition_string, parser: GraphQL.default_parser, **kwargs) if defined?(parser::SchemaParser) parser = parser::SchemaParser end from_document(schema_superclass, parser.parse(definition_string), **kwargs) end def from_definition_path(schema_superclass, definition_path, parser: GraphQL.default_parser, **kwargs) if defined?(parser::SchemaParser) parser = parser::SchemaParser end from_document(schema_superclass, parser.parse_file(definition_path), **kwargs) end def from_document(schema_superclass, document, default_resolve:, using: {}, base_types: {}, relay: false) Builder.build(schema_superclass, document, default_resolve: default_resolve || {}, relay: relay, using: using, base_types: base_types) end end # @api private module Builder include GraphQL::EmptyObjects extend self def build(schema_superclass, document, default_resolve:, using: {}, base_types: {}, relay:) raise InvalidDocumentError.new('Must provide a document ast.') if !document || !document.is_a?(GraphQL::Language::Nodes::Document) base_types = { object: GraphQL::Schema::Object, interface: GraphQL::Schema::Interface, union: GraphQL::Schema::Union, scalar: GraphQL::Schema::Scalar, enum: GraphQL::Schema::Enum, input_object: GraphQL::Schema::InputObject, }.merge!(base_types) 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 = schema_superclass.directives.dup 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, base_types) 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 }) directives.merge!(GraphQL::Schema.default_directives) document.definitions.each do |definition| if definition.is_a?(GraphQL::Language::Nodes::DirectiveDefinition) directives[definition.name] = build_directive(definition, directive_type_resolver) end end # In case any directives referenced built-in types for their arguments: replace_late_bound_types_with_built_in(types) schema_extensions = nil document.definitions.each do |definition| case definition when GraphQL::Language::Nodes::SchemaDefinition, GraphQL::Language::Nodes::DirectiveDefinition nil # already handled when GraphQL::Language::Nodes::SchemaExtension, GraphQL::Language::Nodes::ScalarTypeExtension, GraphQL::Language::Nodes::ObjectTypeExtension, GraphQL::Language::Nodes::InterfaceTypeExtension, GraphQL::Language::Nodes::UnionTypeExtension, GraphQL::Language::Nodes::EnumTypeExtension, GraphQL::Language::Nodes::InputObjectTypeExtension schema_extensions ||= [] schema_extensions << definition 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) if definition.is_a?(GraphQL::Language::Nodes::ObjectTypeDefinition) || definition.is_a?(Language::Nodes::InterfaceTypeDefinition) interface_names = definition.interfaces.map(&:name) transitive_names = interface_names.map { |n| document.definitions.find { |d| d.respond_to?(:name) && d.name == n }&.interfaces&.map(&:name) } transitive_names.flatten! transitive_names.compact! if !(missing_transitive_interfaces = transitive_names - interface_names).empty? raise GraphQL::Schema::InvalidDocumentError, "type #{definition.name} is missing one or more transitive interface names: #{missing_transitive_interfaces.join(", ")}. Add them to the type's `implements` list and try again." end end types[definition.name] = build_definition_from_node(definition, type_resolver, default_resolve, base_types) 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 if schema_definition.query.nil? && schema_definition.mutation.nil? && schema_definition.subscription.nil? # This schema may have been given with directives only, # check for defaults: query_root_type = types['Query'] mutation_root_type = types['Mutation'] subscription_root_type = types['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 schema_extensions&.each do |ext| next if ext.is_a?(GraphQL::Language::Nodes::SchemaExtension) built_type = types[ext.name] case ext when GraphQL::Language::Nodes::ScalarTypeExtension build_directives(built_type, ext, type_resolver) when GraphQL::Language::Nodes::ObjectTypeExtension build_directives(built_type, ext, type_resolver) build_fields(built_type, ext.fields, type_resolver, default_resolve: true) build_interfaces(built_type, ext.interfaces, type_resolver) when GraphQL::Language::Nodes::InterfaceTypeExtension build_directives(built_type, ext, type_resolver) build_fields(built_type, ext.fields, type_resolver, default_resolve: nil) build_interfaces(built_type, ext.interfaces, type_resolver) when GraphQL::Language::Nodes::UnionTypeExtension build_directives(built_type, ext, type_resolver) built_type.possible_types(*ext.types.map { |type_name| type_resolver.call(type_name) }) when GraphQL::Language::Nodes::EnumTypeExtension build_directives(built_type, ext, type_resolver) build_values(built_type, ext.values, type_resolver) when GraphQL::Language::Nodes::InputObjectTypeExtension build_directives(built_type, ext, type_resolver) build_arguments(built_type, ext.fields, type_resolver) end end builder = self found_types = types.values object_types = found_types.select { |t| t.respond_to?(:kind) && t.kind.object? } schema_class = Class.new(schema_superclass) do begin # Add these first so that there's some chance of resolving late-bound types add_type_and_traverse(found_types, root: false) orphan_types(object_types) 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 object_types.each do |t| t.interfaces.each do |int_t| if int_t.is_a?(LateBoundType) int_t = types[int_t.graphql_name] t.implements(int_t) end int_t.orphan_types(t) end 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) builder.build_directives(self, schema_definition, type_resolver) 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 super end end schema_extensions&.each do |ext| if ext.is_a?(GraphQL::Language::Nodes::SchemaExtension) build_directives(schema_class, ext, type_resolver) end end schema_class end NullResolveType = ->(type, obj, ctx) { raise(GraphQL::RequiredImplementationMissingError, "Generated Schema cannot use Interface or Union types for execution. Implement resolve_type on your resolver.") } def build_definition_from_node(definition, type_resolver, default_resolve, base_types) case definition when GraphQL::Language::Nodes::EnumTypeDefinition build_enum_type(definition, type_resolver, base_types[:enum]) when GraphQL::Language::Nodes::ObjectTypeDefinition build_object_type(definition, type_resolver, base_types[:object]) when GraphQL::Language::Nodes::InterfaceTypeDefinition build_interface_type(definition, type_resolver, base_types[:interface]) when GraphQL::Language::Nodes::UnionTypeDefinition build_union_type(definition, type_resolver, base_types[:union]) when GraphQL::Language::Nodes::ScalarTypeDefinition build_scalar_type(definition, type_resolver, base_types[:scalar], default_resolve: default_resolve) when GraphQL::Language::Nodes::InputObjectTypeDefinition build_input_object_type(definition, type_resolver, base_types[:input_object]) when GraphQL::Language::Nodes::DirectiveDefinition build_directive(definition, type_resolver) end end # 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 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 def build_directives(definition, ast_node, type_resolver) dirs = prepare_directives(ast_node, type_resolver) dirs.each do |(dir_class, options)| if definition.respond_to?(:schema_directive) # it's a schema definition.schema_directive(dir_class, **options) else definition.directive(dir_class, **options) end end end 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} #{ast_node.respond_to?(: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 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 def build_enum_type(enum_type_definition, type_resolver, base_type) builder = self Class.new(base_type) 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) builder.build_values(self, enum_type_definition.values, type_resolver) end end def build_values(type_class, enum_value_definitions, type_resolver) enum_value_definitions.each do |enum_value_definition| type_class.value(enum_value_definition.name, value: enum_value_definition.name, deprecation_reason: build_deprecation_reason(enum_value_definition.directives), description: enum_value_definition.description, directives: prepare_directives(enum_value_definition, type_resolver), ast_node: enum_value_definition, ) end end 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 def build_scalar_type(scalar_type_definition, type_resolver, base_type, default_resolve:) builder = self Class.new(base_type) 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 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 def build_union_type(union_type_definition, type_resolver, base_type) builder = self Class.new(base_type) 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 def build_object_type(object_type_definition, type_resolver, base_type) builder = self Class.new(base_type) 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) builder.build_interfaces(self, object_type_definition.interfaces, type_resolver) builder.build_fields(self, object_type_definition.fields, type_resolver, default_resolve: true) end end def build_interfaces(type_class, interface_names, type_resolver) interface_names.each do |interface_name| type_class.implements(type_resolver.call(interface_name)) end end def build_input_object_type(input_object_type_definition, type_resolver, base_type) builder = self Class.new(base_type) 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 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 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 EMPTY_HASH 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, directives: prepare_directives(argument_defn, type_resolver), **default_value_kwargs ) end end 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 def build_interface_type(interface_type_definition, type_resolver, base_type) builder = self Module.new do include base_type graphql_name(interface_type_definition.name) description(interface_type_definition.description) builder.build_interfaces(self, interface_type_definition.interfaces, type_resolver) 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 def build_fields(owner, field_definitions, type_resolver, default_resolve:) builder = self field_definitions.each do |field_definition| 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_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, resolve_batch: resolve_method_name, ) builder.build_arguments(schema_field_defn, field_definition.arguments, type_resolver) # Don't do this for interfaces if default_resolve define_field_resolve_method(owner, resolve_method_name, field_definition.name) end end end def define_field_resolve_method(owner, method_name, field_name) owner.define_method(method_name) { |**args| field_instance = context.types.field(owner, field_name) context.schema.definition_default_resolve.call(self.class, field_instance, object, args, context) } owner.define_singleton_method(method_name) { |objects, context, **args| field_instance = context.types.field(owner, field_name) objects.map do |object| context.schema.definition_default_resolve.call(self, field_instance, object, args, context) end } end 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] ||= missing_type_handler.call(ast_node) else raise "Unexpected ast_node: #{ast_node.inspect}" end } resolve_type_proc end end private_constant :Builder end end end graphql-2.6.0/lib/graphql/schema/build_from_definition/0000755000004100000410000000000015173430257023212 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/schema/build_from_definition/resolve_map.rb0000644000004100000410000000541515173430257026060 0ustar www-datawww-data# frozen_string_literal: true require "graphql/schema/build_from_definition/resolve_map/default_resolve" module GraphQL class Schema module BuildFromDefinition # Wrap a user-provided hash of resolution behavior for easy access at runtime. # # Coerce scalar values by: # - Checking for a function in the map like `{ Date: { coerce_input: ->(val, ctx) { ... }, coerce_result: ->(val, ctx) { ... } } }` # - Falling back to a passthrough # # Interface/union resolution can be provided as a `resolve_type:` key. # # @api private class ResolveMap module NullScalarCoerce def self.call(val, _ctx) val end end def initialize(user_resolve_hash) @resolve_hash = Hash.new do |h, k| # For each type name, provide a new hash if one wasn't given: h[k] = Hash.new do |h2, k2| if k2 == "coerce_input" || k2 == "coerce_result" # This isn't an object field, it's a scalar coerce function. # Use a passthrough NullScalarCoerce else # For each field, provide a resolver that will # make runtime checks & replace itself h2[k2] = DefaultResolve.new(h2, k2) end end end @user_resolve_hash = user_resolve_hash # User-provided resolve functions take priority over the default: @user_resolve_hash.each do |type_name, fields| type_name_s = type_name.to_s case fields when Hash fields.each do |field_name, resolve_fn| @resolve_hash[type_name_s][field_name.to_s] = resolve_fn end when Proc # for example, "resolve_type" @resolve_hash[type_name_s] = fields else raise ArgumentError, "Unexpected resolve hash value for #{type_name.inspect}: #{fields.inspect} (#{fields.class})" end end # Check the normalized hash, not the user input: if @resolve_hash.key?("resolve_type") define_singleton_method :resolve_type do |type, obj, ctx| @resolve_hash.fetch("resolve_type").call(type, obj, ctx) end end end def call(type, field, obj, args, ctx) resolver = @resolve_hash[type.graphql_name][field.graphql_name] resolver.call(obj, args, ctx) end def coerce_input(type, value, ctx) @resolve_hash[type.graphql_name]["coerce_input"].call(value, ctx) end def coerce_result(type, value, ctx) @resolve_hash[type.graphql_name]["coerce_result"].call(value, ctx) end end end end end graphql-2.6.0/lib/graphql/schema/build_from_definition/resolve_map/0000755000004100000410000000000015173430257025526 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb0000644000004100000410000000323615173430257031242 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema module BuildFromDefinition class ResolveMap class DefaultResolve def initialize(field_map, field_name) @field_map = field_map @field_name = field_name end # Make some runtime checks about # how `obj` implements the `field_name`. # # Create a new resolve function according to that implementation, then: # - update `field_map` with this implementation # - call the implementation now (to satisfy this field execution) # # If `obj` doesn't implement `field_name`, raise an error. def call(obj, args, ctx) method_name = @field_name if !obj.respond_to?(method_name) raise KeyError, "Can't resolve field #{method_name} on #{obj.inspect}" else method_arity = obj.method(method_name).arity resolver = case method_arity when 0, -1 # -1 Handles method_missing, eg openstruct ->(o, a, c) { o.public_send(method_name) } when 1 ->(o, a, c) { o.public_send(method_name, a) } when 2 ->(o, a, c) { o.public_send(method_name, a, c) } else raise "Unexpected resolve arity: #{method_arity}. Must be 0, 1, 2" end # Call the resolver directly next time @field_map[method_name] = resolver # Call through this time resolver.call(obj, args, ctx) end end end end end end end graphql-2.6.0/lib/graphql/schema/base_64_encoder.rb0000644000004100000410000000102715173430257022127 0ustar www-datawww-data# frozen_string_literal: true require "base64" module GraphQL class Schema # @api private module Base64Encoder def self.encode(unencoded_text, nonce: false) Base64.urlsafe_encode64(unencoded_text, padding: false) end def self.decode(encoded_text, nonce: false) # urlsafe_decode64 is for forward compatibility Base64.urlsafe_decode64(encoded_text) rescue ArgumentError raise GraphQL::ExecutionError, "Invalid input: #{encoded_text.inspect}" end end end end graphql-2.6.0/lib/graphql/schema/mutation.rb0000644000004100000410000000555715173430257021061 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema # This base class accepts configuration for a mutation root field, # then it can be hooked up to your mutation root object type. # # If you want to customize how this class generates types, in your base class, # override the various `generate_*` methods. # # @see {GraphQL::Schema::RelayClassicMutation} for an extension of this class with some conventions built-in. # # @example Creating a comment # # Define the mutation: # class Mutations::CreateComment < GraphQL::Schema::Mutation # argument :body, String, required: true # argument :post_id, ID, required: true # # field :comment, Types::Comment, null: true # field :errors, [String], null: false # # def resolve(body:, post_id:) # post = Post.find(post_id) # comment = post.comments.build(body: body, author: context[:current_user]) # if comment.save # # Successful creation, return the created object with no errors # { # comment: comment, # errors: [], # } # else # # Failed save, return the errors to the client # { # comment: nil, # errors: comment.errors.full_messages # } # end # end # end # # # Hook it up to your mutation: # class Types::Mutation < GraphQL::Schema::Object # field :create_comment, mutation: Mutations::CreateComment # end # # # Call it from GraphQL: # result = MySchema.execute <<-GRAPHQL # mutation { # createComment(postId: "1", body: "Nice Post!") { # errors # comment { # body # author { # login # } # } # } # } # GRAPHQL # class Mutation < GraphQL::Schema::Resolver extend GraphQL::Schema::Member::HasFields extend GraphQL::Schema::Resolver::HasPayloadType # @api private def call_resolve(_args_hash) # Clear any cached values from `loads` or authorization: dataloader.clear_cache super end class << self def visible?(context) true end private def conflict_field_name_warning(field_defn) "#{self.graphql_name}'s `field :#{field_defn.name}` conflicts with a built-in method, use `hash_key:` or `method:` to pick a different resolve behavior for this field (for example, `hash_key: :#{field_defn.resolver_method}_value`, and modify the return hash). Or use `method_conflict_warning: false` to suppress this warning." end # Override this to attach self as `mutation` def generate_payload_type payload_class = super payload_class.mutation(self) payload_class end end end end end graphql-2.6.0/lib/graphql/schema/visibility.rb0000644000004100000410000002535015173430257021401 0ustar www-datawww-data# frozen_string_literal: true require "graphql/schema/visibility/profile" require "graphql/schema/visibility/migration" require "graphql/schema/visibility/visit" module GraphQL class Schema # Use this plugin to make some parts of your schema hidden from some viewers. # class Visibility # @param schema [Class] # @param profiles [Hash Hash>] A hash of `name => context` pairs for preloading visibility profiles # @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?` and `Rails.env.staging?`) # @param migration_errors [Boolean] if `true`, raise an error when `Visibility` and `Warden` return different results def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails.env) ? (Rails.env.production? || Rails.env.staging?) : nil), migration_errors: false) profiles&.each { |name, ctx| ctx[:visibility_profile] = name ctx.freeze } schema.visibility = self.new(schema, dynamic: dynamic, preload: preload, profiles: profiles, migration_errors: migration_errors) end def initialize(schema, dynamic:, preload:, profiles:, migration_errors:) @schema = schema schema.use_visibility_profile = true schema.visibility_profile_class = if migration_errors Visibility::Migration else Visibility::Profile end @preload = preload @profiles = profiles @cached_profiles = {} @dynamic = dynamic @migration_errors = migration_errors # Top-level type caches: @visit = nil @interface_type_memberships = nil @directives = nil @types = nil @all_references = nil @loaded_all = false if preload self.preload end end def freeze load_all @visit = true @interface_type_memberships.default_proc = nil @all_references.default_proc = nil super end def all_directives load_all @directives end def all_interface_type_memberships load_all @interface_type_memberships end def all_references load_all @all_references end def get_type(type_name) load_all @types[type_name] end attr_accessor :types def preload? @preload end def preload # Traverse the schema now (and in the *_configured hooks below) # To make sure things are loaded during boot @preloaded_types = Set.new types_to_visit = [ @schema.query, @schema.mutation, @schema.subscription, *@schema.introspection_system.types.values, *@schema.introspection_system.entry_points.map { |ep| ep.type.unwrap }, *@schema.orphan_types, ] # Root types may have been nil: types_to_visit.compact! ensure_all_loaded(types_to_visit) @profiles.each do |profile_name, example_ctx| prof = profile_for(example_ctx) prof.preload end end # @api private def query_configured(query_type) if @preload ensure_all_loaded([query_type]) end end # @api private def mutation_configured(mutation_type) if @preload ensure_all_loaded([mutation_type]) end end # @api private def subscription_configured(subscription_type) if @preload ensure_all_loaded([subscription_type]) end end # @api private def orphan_types_configured(orphan_types) if @preload ensure_all_loaded(orphan_types) end end # @api private def introspection_system_configured(introspection_system) if @preload introspection_types = [ *@schema.introspection_system.types.values, *@schema.introspection_system.entry_points.map { |ep| ep.type.unwrap }, ] ensure_all_loaded(introspection_types) end end # Make another Visibility for `schema` based on this one # @return [Visibility] # @api private def dup_for(other_schema) self.class.new( other_schema, dynamic: @dynamic, preload: @preload, profiles: @profiles, migration_errors: @migration_errors ) end def migration_errors? @migration_errors end attr_reader :cached_profiles def profile_for(context) if !@profiles.empty? visibility_profile = context[:visibility_profile] if @profiles.include?(visibility_profile) profile_ctx = @profiles[visibility_profile] @cached_profiles[visibility_profile] ||= @schema.visibility_profile_class.new(name: visibility_profile, context: profile_ctx, schema: @schema, visibility: self) elsif @dynamic if context.is_a?(Query::NullContext) top_level_profile else @schema.visibility_profile_class.new(context: context, schema: @schema, visibility: self) end elsif !context.key?(:visibility_profile) raise ArgumentError, "#{@schema} expects a visibility profile, but `visibility_profile:` wasn't passed. Provide a `visibility_profile:` value or add `dynamic: true` to your visibility configuration." else raise ArgumentError, "`#{visibility_profile.inspect}` isn't allowed for `visibility_profile:` (must be one of #{@profiles.keys.map(&:inspect).join(", ")}). Or, add `#{visibility_profile.inspect}` to the list of profiles in the schema definition." end elsif context.is_a?(Query::NullContext) top_level_profile else @schema.visibility_profile_class.new(context: context, schema: @schema, visibility: self) end end attr_reader :top_level # @api private attr_reader :unfiltered_interface_type_memberships def top_level_profile(refresh: false) if refresh @top_level_profile = nil end @top_level_profile ||= @schema.visibility_profile_class.new(context: @schema.null_context, schema: @schema, visibility: self) end private def ensure_all_loaded(types_to_visit) while (type = types_to_visit.shift) if type.kind.fields? && @preloaded_types.add?(type) type.all_field_definitions.each do |field_defn| field_defn.ensure_loaded types_to_visit << field_defn.type.unwrap end end end top_level_profile(refresh: true) nil end def load_all(types: nil) if @visit.nil? # Set up the visit system @interface_type_memberships = Hash.new { |h, interface_type| h[interface_type] = Hash.new { |h2, obj_type| h2[obj_type] = [] }.compare_by_identity }.compare_by_identity @directives = [] @types = {} # String => Module @all_references = Hash.new { |h, member| h[member] = Set.new.compare_by_identity }.compare_by_identity @unions_for_references = Set.new @visit = Visibility::Visit.new(@schema) do |member| if member.is_a?(Module) type_name = member.graphql_name if (prev_t = @types[type_name]) if prev_t.is_a?(Array) prev_t << member else @types[type_name] = [member, prev_t] end else @types[member.graphql_name] = member end member.directives.each { |dir| @all_references[dir.class] << member } if member < GraphQL::Schema::Directive @directives << member elsif member.respond_to?(:interface_type_memberships) member.interface_type_memberships.each do |itm| @all_references[itm.abstract_type] << member # `itm.object_type` may not actually be `member` if this implementation # is inherited from a superclass @interface_type_memberships[itm.abstract_type][member] << itm end elsif member < GraphQL::Schema::Union @unions_for_references << member end elsif member.is_a?(GraphQL::Schema::Argument) member.validate_default_value @all_references[member.type.unwrap] << member if !(dirs = member.directives).empty? dir_owner = member.owner if dir_owner.respond_to?(:owner) dir_owner = dir_owner.owner end dirs.each { |dir| @all_references[dir.class] << dir_owner } end elsif member.is_a?(GraphQL::Schema::Field) @all_references[member.type.unwrap] << member if !(dirs = member.directives).empty? dir_owner = member.owner dirs.each { |dir| @all_references[dir.class] << dir_owner } end elsif member.is_a?(GraphQL::Schema::EnumValue) if !(dirs = member.directives).empty? dir_owner = member.owner dirs.each { |dir| @all_references[dir.class] << dir_owner } end end true end @schema.root_types.each { |t| @all_references[t] << true } @schema.introspection_system.types.each_value { |t| @all_references[t] << true } @schema.directives.each_value { |dir_class| @all_references[dir_class] << true } @visit.visit_each(types: []) # visit default directives end if types @visit.visit_each(types: types, directives: []) elsif @loaded_all == false @loaded_all = true @visit.visit_each else # already loaded all return end # TODO: somehow don't iterate over all these, # only the ones that may have been modified @interface_type_memberships.each do |int_type, obj_type_memberships| referrers = @all_references[int_type].select { |r| r.is_a?(GraphQL::Schema::Field) } if !referrers.empty? obj_type_memberships.each_key do |impl_type| @all_references[impl_type] |= referrers end end end @unions_for_references.each do |union_type| refs = @all_references[union_type] union_type.all_possible_types.each do |object_type| @all_references[object_type] |= refs # Add new items end end end end end end graphql-2.6.0/lib/graphql/schema/member/0000755000004100000410000000000015173430257020127 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/schema/member/scoped.rb0000644000004100000410000000220115173430257021724 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member module Scoped # This is called when a field has `scope: true`. # The field's return type class receives this call. # # By default, it's a no-op. Override it to scope your objects. # # @param items [Object] Some list-like object (eg, Array, ActiveRecord::Relation) # @param context [GraphQL::Query::Context] # @return [Object] Another list-like object, scoped to the current context def scope_items(items, context) items end def reauthorize_scoped_objects(new_value = nil) if new_value.nil? if @reauthorize_scoped_objects != nil @reauthorize_scoped_objects else find_inherited_value(:reauthorize_scoped_objects, true) end else @reauthorize_scoped_objects = new_value end end def inherited(subclass) super subclass.class_exec do @reauthorize_scoped_objects = nil end end end end end end graphql-2.6.0/lib/graphql/schema/member/type_system_helpers.rb0000644000004100000410000000320415173430257024562 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member module TypeSystemHelpers def initialize(...) super @to_non_null_type ||= nil @to_list_type ||= nil end # @return [Schema::NonNull] Make a non-null-type representation of this type def to_non_null_type @to_non_null_type || begin t = GraphQL::Schema::NonNull.new(self) if frozen? t else @to_non_null_type = t end end end # @return [Schema::List] Make a list-type representation of this type def to_list_type @to_list_type || begin t = GraphQL::Schema::List.new(self) if frozen? t else @to_list_type = t end end end # @return [Boolean] true if this is a non-nullable type. A nullable list of non-nullables is considered nullable. def non_null? false end # @return [Boolean] true if this is a list type. A non-nullable list is considered a list. def list? false end def to_type_signature graphql_name end # @return [GraphQL::TypeKinds::TypeKind] def kind raise GraphQL::RequiredImplementationMissingError, "No `.kind` defined for #{self}" end private def inherited(subclass) subclass.class_exec do @to_non_null_type ||= nil @to_list_type ||= nil end super end end end end end graphql-2.6.0/lib/graphql/schema/member/has_arguments.rb0000644000004100000410000004550215173430257023322 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member module HasArguments def self.included(cls) cls.extend(ArgumentClassAccessor) cls.include(ArgumentObjectLoader) end def self.extended(cls) cls.extend(ArgumentClassAccessor) cls.include(ArgumentObjectLoader) cls.extend(ClassConfigured) end # @param arg_name [Symbol] The underscore-cased name of this argument, `name:` keyword also accepted # @param type_expr The GraphQL type of this argument; `type:` keyword also accepted # @param desc [String] Argument description, `description:` keyword also accepted # @option kwargs [Boolean, :nullable] :required if true, this argument is non-null; if false, this argument is nullable. If `:nullable`, then the argument must be provided, though it may be `null`. # @option kwargs [String] :description Positional argument also accepted # @option kwargs [Class, Array] :type Input type; positional argument also accepted # @option kwargs [Symbol] :name positional argument also accepted # @option kwargs [Object] :default_value # @option kwargs [Class, Array] :loads A GraphQL type to load for the given ID when one is present # @option kwargs [Symbol] :as Override the keyword name when passed to a method # @option kwargs [Symbol] :prepare A method to call to transform this argument's valuebefore sending it to field resolution # @option kwargs [Boolean] :camelize if true, the name will be camelized when building the schema # @option kwargs [Boolean] :from_resolver if true, a Resolver class defined this argument # @option kwargs [Hash{Class => Hash}] :directives # @option kwargs [String] :deprecation_reason # @option kwargs [String] :comment Private, used by GraphQL-Ruby when parsing GraphQL schema files # @option kwargs [GraphQL::Language::Nodes::InputValueDefinition] :ast_node Private, used by GraphQL-Ruby when parsing schema files # @option kwargs [Hash, nil] :validates Options for building validators, if any should be applied # @option kwargs [Boolean] :replace_null_with_default if `true`, incoming values of `null` will be replaced with the configured `default_value` # @param definition_block [Proc] Called with the newly-created {Argument} # @param kwargs [Hash] Keywords for defining an argument. Any keywords not documented here must be handled by your base Argument class. # @return [GraphQL::Schema::Argument] An instance of {argument_class} created from these arguments def argument(arg_name = nil, type_expr = nil, desc = nil, **kwargs, &definition_block) if kwargs[:loads] loads_name = arg_name || kwargs[:name] loads_name_as_string = loads_name.to_s inferred_arg_name = case loads_name_as_string when /_id$/ loads_name_as_string.sub(/_id$/, "").to_sym when /_ids$/ loads_name_as_string.sub(/_ids$/, "") .sub(/([^s])$/, "\\1s") .to_sym else loads_name end kwargs[:as] ||= inferred_arg_name end kwargs[:owner] = self arg_defn = self.argument_class.new( arg_name, type_expr, desc, **kwargs, &definition_block ) add_argument(arg_defn) arg_defn end # Register this argument with the class. # @param arg_defn [GraphQL::Schema::Argument] # @return [GraphQL::Schema::Argument] def add_argument(arg_defn) @own_arguments ||= {} prev_defn = @own_arguments[arg_defn.name] case prev_defn when nil @own_arguments[arg_defn.name] = arg_defn when Array prev_defn << arg_defn when GraphQL::Schema::Argument @own_arguments[arg_defn.name] = [prev_defn, arg_defn] else raise "Invariant: unexpected `@own_arguments[#{arg_defn.name.inspect}]`: #{prev_defn.inspect}" end arg_defn end def remove_argument(arg_defn) prev_defn = @own_arguments[arg_defn.name] case prev_defn when nil # done when Array prev_defn.delete(arg_defn) when GraphQL::Schema::Argument @own_arguments.delete(arg_defn.name) else raise "Invariant: unexpected `@own_arguments[#{arg_defn.name.inspect}]`: #{prev_defn.inspect}" end nil end # @return [Hash GraphQL::Schema::Argument] Arguments defined on this thing, keyed by name. Includes inherited definitions def arguments(context = GraphQL::Query::NullContext.instance, _require_defined_arguments = nil) if !own_arguments.empty? own_arguments_that_apply = {} own_arguments.each do |name, args_entry| if (visible_defn = Warden.visible_entry?(:visible_argument?, args_entry, context)) own_arguments_that_apply[visible_defn.graphql_name] = visible_defn end end end # might be nil if there are actually no arguments own_arguments_that_apply || own_arguments end def any_arguments? !own_arguments.empty? end module ClassConfigured def inherited(child_class) super child_class.extend(InheritedArguments) end module InheritedArguments def arguments(context = GraphQL::Query::NullContext.instance, require_defined_arguments = true) own_arguments = super(context, require_defined_arguments) inherited_arguments = superclass.arguments(context, false) if !own_arguments.empty? if !inherited_arguments.empty? # Local definitions override inherited ones inherited_arguments.merge(own_arguments) else own_arguments end else inherited_arguments end end def any_arguments? super || superclass.any_arguments? end def all_argument_definitions all_defns = {} ancestors.reverse_each do |ancestor| if ancestor.respond_to?(:own_arguments) all_defns.merge!(ancestor.own_arguments) end end all_defns = all_defns.values all_defns.flatten! all_defns end def get_argument(argument_name, context = GraphQL::Query::NullContext.instance) warden = Warden.from_context(context) skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile) for ancestor in ancestors if ancestor.respond_to?(:own_arguments) && (a = ancestor.own_arguments[argument_name]) && (skip_visible || (a = Warden.visible_entry?(:visible_argument?, a, context, warden))) return a end end nil end end end module FieldConfigured def arguments(context = GraphQL::Query::NullContext.instance, _require_defined_arguments = nil) own_arguments = super if @resolver_class inherited_arguments = @resolver_class.field_arguments(context) if !own_arguments.empty? if !inherited_arguments.empty? inherited_arguments.merge(own_arguments) else own_arguments end else inherited_arguments end else own_arguments end end def any_arguments? super || (@resolver_class && @resolver_class.any_field_arguments?) end def all_argument_definitions if @resolver_class all_defns = {} @resolver_class.all_field_argument_definitions.each do |arg_defn| key = arg_defn.graphql_name case (current_value = all_defns[key]) when nil all_defns[key] = arg_defn when Array current_value << arg_defn when GraphQL::Schema::Argument all_defns[key] = [current_value, arg_defn] else raise "Invariant: Unexpected argument definition, #{current_value.class}: #{current_value.inspect}" end end all_defns.merge!(own_arguments) all_defns = all_defns.values all_defns.flatten! all_defns else super end end end def all_argument_definitions if !own_arguments.empty? all_defns = own_arguments.values all_defns.flatten! all_defns else EmptyObjects::EMPTY_ARRAY end end # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name. def get_argument(argument_name, context = GraphQL::Query::NullContext.instance) warden = Warden.from_context(context) if (arg_config = own_arguments[argument_name]) && ((context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile)) || (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden))) visible_arg || arg_config elsif defined?(@resolver_class) && @resolver_class @resolver_class.get_field_argument(argument_name, context) else nil end end # @param new_arg_class [Class] A class to use for building argument definitions def argument_class(new_arg_class = nil) self.class.argument_class(new_arg_class) end # @api private # If given a block, it will eventually yield the loaded args to the block. # # If no block is given, it will immediately dataload (but might return a Lazy). # # @param values [Hash] # @param context [GraphQL::Query::Context] # @yield [Interpreter::Arguments, Execution::Lazy] # @return [Interpreter::Arguments, Execution::Lazy] def coerce_arguments(parent_object, values, context, &block) # Cache this hash to avoid re-merging it arg_defns = context.types.arguments(self) total_args_count = arg_defns.size finished_args = nil prepare_finished_args = -> { if total_args_count == 0 finished_args = GraphQL::Execution::Interpreter::Arguments::EMPTY if block_given? block.call(finished_args) end else argument_values = {} resolved_args_count = 0 raised_error = false arg_defns.each do |arg_defn| context.dataloader.append_job do begin arg_defn.coerce_into_values(parent_object, values, context, argument_values) rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err raised_error = true finished_args = err if block_given? block.call(finished_args) end end resolved_args_count += 1 if resolved_args_count == total_args_count && !raised_error finished_args = context.schema.after_any_lazies(argument_values.values) { GraphQL::Execution::Interpreter::Arguments.new( argument_values: argument_values, ) } if block_given? block.call(finished_args) end end end end end } if block_given? prepare_finished_args.call nil else # This API returns eagerly, gotta run it now context.dataloader.run_isolated(&prepare_finished_args) finished_args end end # Usually, this is validated statically by RequiredArgumentsArePresent, # but not for directives. # TODO apply static validations on schema definitions? def validate_directive_argument(arg_defn, value) # this is only implemented on directives. nil end module HasDirectiveArguments def validate_directive_argument(arg_defn, value) if value.nil? && arg_defn.type.non_null? raise ArgumentError, "#{arg_defn.path} is required, but no value was given" end end end def arguments_statically_coercible? if defined?(@arguments_statically_coercible) && !@arguments_statically_coercible.nil? @arguments_statically_coercible else @arguments_statically_coercible = all_argument_definitions.all?(&:statically_coercible?) end end module ArgumentClassAccessor def argument_class(new_arg_class = nil) if new_arg_class @argument_class = new_arg_class elsif defined?(@argument_class) && @argument_class @argument_class else superclass.respond_to?(:argument_class) ? superclass.argument_class : GraphQL::Schema::Argument end end end module ArgumentObjectLoader # Look up the corresponding object for a provided ID. # By default, it uses Relay-style {Schema.object_from_id}, # override this to find objects another way. # # @param type [Class, Module] A GraphQL type definition # @param id [String] A client-provided to look up # @param context [GraphQL::Query::Context] the current context def object_from_id(type, id, context) context.schema.object_from_id(id, context) end def load_application_object(argument, id, context) # See if any object can be found for this ID if id.nil? return nil end object_from_id(argument.loads, id, context) end def load_and_authorize_application_object(argument, id, context) loaded_application_object = load_application_object(argument, id, context) authorize_application_object(argument, id, context, loaded_application_object) end def authorize_application_object(argument, id, context, loaded_application_object) context.query.after_lazy(loaded_application_object) do |application_object| if application_object.nil? err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object) application_object = load_application_object_failed(err) end # Double-check that the located object is actually of this type # (Don't want to allow arbitrary access to objects this way) if application_object.nil? nil else arg_loads_type = argument.loads maybe_lazy_resolve_type = context.schema.resolve_type(arg_loads_type, application_object, context) context.query.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result| if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2 application_object_type, application_object = resolve_type_result else application_object_type = resolve_type_result # application_object is already assigned end passes_possible_types_check = if context.types.loadable?(arg_loads_type, context) if arg_loads_type.kind.abstract? # This union/interface is used in `loads:` but not otherwise visible to this query context.types.loadable_possible_types(arg_loads_type, context).include?(application_object_type) else true end else context.types.possible_types(arg_loads_type).include?(application_object_type) end if !passes_possible_types_check err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object) application_object = load_application_object_failed(err) end if application_object.nil? nil else # This object was loaded successfully # and resolved to the right type, # now apply the `.authorized?` class method if there is one context.query.after_lazy(application_object_type.authorized?(application_object, context)) do |authed| if authed application_object else err = GraphQL::UnauthorizedError.new( object: application_object, type: application_object_type, context: context, ) if self.respond_to?(:unauthorized_object) err.set_backtrace(caller) unauthorized_object(err) else raise err end end end end end end end end # Called when an argument's `loads:` configuration fails to fetch an application object. # By default, this method raises the given error, but you can override it to handle failures differently. # # @param err [GraphQL::LoadApplicationObjectFailedError] The error that occurred # @return [Object, nil] If a value is returned, it will be used instead of the failed load # @api public def load_application_object_failed(err) raise err end end NO_ARGUMENTS = GraphQL::EmptyObjects::EMPTY_HASH def own_arguments @own_arguments || NO_ARGUMENTS end end end end end graphql-2.6.0/lib/graphql/schema/member/has_ast_node.rb0000644000004100000410000000132115173430257023100 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member module HasAstNode def self.extended(child_cls) super child_cls.ast_node = nil end def inherited(child_cls) super child_cls.ast_node = nil end # If this schema was parsed from a `.graphql` file (or other SDL), # this is the AST node that defined this part of the schema. def ast_node(new_ast_node = nil) if new_ast_node @ast_node = new_ast_node elsif defined?(@ast_node) @ast_node else nil end end attr_writer :ast_node end end end end graphql-2.6.0/lib/graphql/schema/member/build_type.rb0000644000004100000410000001457215173430257022625 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member # @api private module BuildType LIST_TYPE_ERROR = "Use an array of [T] or [T, null: true] for list types; other arrays are not supported" module_function # @param type_expr [String, Class, GraphQL::BaseType] # @return [GraphQL::BaseType] def parse_type(type_expr, null:) list_type = false return_type = case type_expr when String case type_expr when "String" GraphQL::Types::String when "Int", "Integer" GraphQL::Types::Int when "Float" GraphQL::Types::Float when "Boolean" GraphQL::Types::Boolean when "ID" GraphQL::Types::ID when /\A\[.*\]\Z/ list_type = true # List members are required by default parse_type(type_expr[1..-2], null: false) when /.*!\Z/ null = false parse_type(type_expr[0..-2], null: true) else maybe_type = constantize(type_expr) case maybe_type when Module # This is a way to check that it's the right kind of module: if maybe_type.respond_to?(:kind) maybe_type else raise ArgumentError, "Unexpected class/module found for GraphQL type: #{type_expr} (must be type definition class/module)" end end end when GraphQL::Schema::LateBoundType type_expr when Array case type_expr.length when 1 list_type = true # List members are required by default parse_type(type_expr.first, null: false) when 2 inner_type, nullable_option = type_expr if nullable_option.keys != [:null] || (nullable_option[:null] != true && nullable_option[:null] != false) raise ArgumentError, LIST_TYPE_ERROR end list_type = true parse_type(inner_type, null: nullable_option[:null]) else raise ArgumentError, LIST_TYPE_ERROR end when GraphQL::Schema::NonNull, GraphQL::Schema::List type_expr when Module # This is a way to check that it's the right kind of module: if type_expr.respond_to?(:kind) type_expr else # Eg `String` => GraphQL::Types::String parse_type(type_expr.name, null: true) end when Proc parse_type(type_expr.call, null: true) when false raise ArgumentError, "Received `false` instead of a type, maybe a `!` should be replaced with `null: true` (for fields) or `required: true` (for arguments)" end if return_type.nil? raise "Unexpected type input: #{type_expr.inspect} (#{type_expr.class})" end # Apply list_type first, that way the # .to_non_null_type applies to the list type, not the inner type if list_type return_type = return_type.to_list_type end if !null return_type = return_type.to_non_null_type end return_type end def to_type_name(something) case something when GraphQL::Schema::LateBoundType something.unwrap.name when Array to_type_name(something.first) when Module if something.respond_to?(:graphql_name) something.graphql_name else to_type_name(something.name) end when String if something.include?("]") || something.include?("[") || something.include?("!") || something.include?("::") something.gsub(/\]\[\!/, "").split("::").last else something end when GraphQL::Schema::NonNull, GraphQL::Schema::List to_type_name(something.unwrap) else raise "Unhandled to_type_name input: #{something} (#{something.class})" end end def camelize(string) return string if string == '_' return string unless string.include?("_") camelized = string.split('_').each(&:capitalize!).join camelized[0] = camelized[0].downcase if string.start_with?("_") match_data = string.match(/\A(_+)/) camelized = "#{match_data[0]}#{camelized}" end camelized end # Resolves constant from string (based on Rails `ActiveSupport::Inflector.constantize`) def constantize(string) names = string.split('::') # Trigger a built-in NameError exception including the ill-formed constant in the message. Object.const_get(string) if names.empty? # Remove the first blank element in case of '::ClassName' notation. names.shift if names.size > 1 && names.first.empty? names.inject(Object) do |constant, name| if constant == Object constant.const_get(name) else candidate = constant.const_get(name) next candidate if constant.const_defined?(name, false) next candidate unless Object.const_defined?(name) # Go down the ancestors to check if it is owned directly. The check # stops when we reach Object or the end of ancestors tree. constant = constant.ancestors.inject do |const, ancestor| break const if ancestor == Object break ancestor if ancestor.const_defined?(name, false) const end # Owner is in Object, so raise. constant.const_get(name, false) end end end def underscore(string) if string.match?(/\A[a-z_]+\Z/) return string end string2 = string.dup string2.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') # URLDecoder -> URL_Decoder string2.gsub!(/([a-z\d])([A-Z])/,'\1_\2') # someThing -> some_Thing string2.downcase! string2 end end end end end graphql-2.6.0/lib/graphql/schema/member/has_dataloader.rb0000644000004100000410000001142115173430257023406 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member # @api public # Shared methods for working with {Dataloader} inside GraphQL runtime objects. module HasDataloader # @return [GraphQL::Dataloader] The dataloader for the currently-running query def dataloader context.dataloader end # A shortcut method for loading a key from a source. # Identical to `dataloader.with(source_class, *source_args).load(load_key)` # @param source_class [Class] # @param source_args [Array] Any extra parameters defined in `source_class`'s `initialize` method # @param load_key [Object] The key to look up using `def fetch` def dataload(source_class, *source_args, load_key) dataloader.with(source_class, *source_args).load(load_key) end # A shortcut method for loading many keys from a source. # Identical to `dataloader.with(source_class, *source_args).load_all(load_keys)` # # @example # field :score, Integer, resolve_batch: true # # def self.score(posts) # dataload_all(PostScoreSource, posts.map(&:id)) # end # # @param source_class [Class] # @param source_args [Array] Any extra parameters defined in `source_class`'s `initialize` method # @param load_keys [Array] The keys to look up using `def fetch` def dataload_all(source_class, *source_args, load_keys) dataloader.with(source_class, *source_args).load_all(load_keys) end # Find an object with ActiveRecord via {Dataloader::ActiveRecordSource}. # @param model [Class] # @param find_by_value [Object] Usually an `id`, might be another value if `find_by:` is also provided # @param find_by [Symbol, String] A column name to look the record up by. (Defaults to the model's primary key.) # @return [ActiveRecord::Base, nil] # @example Finding a record by ID # dataload_record(Post, 5) # Like `Post.find(5)`, but dataloaded # @example Finding a record by another attribute # dataload_record(User, "matz", find_by: :handle) # Like `User.find_by(handle: "matz")`, but dataloaded def dataload_record(model, find_by_value, find_by: nil) source = if find_by dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by) else dataloader.with(Dataloader::ActiveRecordSource, model) end source.load(find_by_value) end # @see dataload_record Like `dataload_record`, but accepts an Array of `find_by_values` def dataload_all_records(model, find_by_values, find_by: nil) source = if find_by dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by) else dataloader.with(Dataloader::ActiveRecordSource, model) end source.load_all(find_by_values) end # Look up an associated record using a Rails association (via {Dataloader::ActiveRecordAssociationSource}) # @param association_name [Symbol] A `belongs_to` or `has_one` association. (If a `has_many` association is named here, it will be selected without pagination.) # @param record [ActiveRecord::Base] The object that the association belongs to. # @param scope [ActiveRecord::Relation] A scope to look up the associated record in # @return [ActiveRecord::Base, nil] The associated record, if there is one # @example Looking up a belongs_to on the current object # dataload_association(:parent) # Equivalent to `object.parent`, but dataloaded # @example Looking up an associated record on some other object # dataload_association(comment, :post) # Equivalent to `comment.post`, but dataloaded def dataload_association(record = object, association_name, scope: nil) source = if scope dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name, scope) else dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name) end source.load(record) end # @see dataload_association Like `dataload_assocation` but accepts an Array of records (required param) def dataload_all_associations(records, association_name, scope: nil) source = if scope dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name, scope) else dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name) end source.load_all(records) end end end end end graphql-2.6.0/lib/graphql/schema/member/has_directives.rb0000644000004100000410000000777515173430257023470 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member module HasDirectives def self.extended(child_cls) super child_cls.module_exec { self.own_directives = nil } end def inherited(child_cls) super child_cls.own_directives = nil end # Create an instance of `dir_class` for `self`, using `options`. # # It removes a previously-attached instance of `dir_class`, if there is one. # # @return [void] def directive(dir_class, **options) @own_directives ||= [] HasDirectives.add_directive(self, @own_directives, dir_class, options) nil end # Remove an attached instance of `dir_class`, if there is one # @param dir_class [Class] # @return [viod] def remove_directive(dir_class) HasDirectives.remove_directive(@own_directives, dir_class) nil end def directives HasDirectives.get_directives(self, @own_directives, :directives) end class << self def add_directive(schema_member, directives, directive_class, directive_options) remove_directive(directives, directive_class) unless directive_class.repeatable? directives << directive_class.new(schema_member, **directive_options) end def remove_directive(directives, directive_class) directives && directives.reject! { |d| d.is_a?(directive_class) } end def get_directives(schema_member, directives, directives_method) case schema_member when Class inherited_directives = if schema_member.superclass.respond_to?(directives_method) get_directives(schema_member.superclass, schema_member.superclass.public_send(directives_method), directives_method) else GraphQL::EmptyObjects::EMPTY_ARRAY end if !inherited_directives.empty? && directives dirs = [] merge_directives(dirs, inherited_directives) merge_directives(dirs, directives) dirs elsif directives directives elsif !inherited_directives.empty? inherited_directives else GraphQL::EmptyObjects::EMPTY_ARRAY end when Module dirs = nil schema_member.ancestors.reverse_each do |ancestor| if ancestor.respond_to?(:own_directives) && !(anc_dirs = ancestor.own_directives).empty? dirs ||= [] merge_directives(dirs, anc_dirs) end end if directives dirs ||= [] merge_directives(dirs, directives) end dirs || GraphQL::EmptyObjects::EMPTY_ARRAY when HasDirectives directives || GraphQL::EmptyObjects::EMPTY_ARRAY else raise "Invariant: how could #{schema_member} not be a Class, Module, or instance of HasDirectives?" end end private # Modify `target` by adding items from `dirs` such that: # - Any name conflict is overridden by the incoming member of `dirs` # - Any other member of `dirs` is appended # @param target [Array] # @param dirs [Array] # @return [void] def merge_directives(target, dirs) dirs.each do |dir| if (idx = target.find_index { |d| d.graphql_name == dir.graphql_name }) target.slice!(idx) target.insert(idx, dir) else target << dir end end nil end end protected attr_accessor :own_directives end end end end graphql-2.6.0/lib/graphql/schema/member/relay_shortcuts.rb0000644000004100000410000000556215173430257023716 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member module RelayShortcuts def edge_type_class(new_edge_type_class = nil) if new_edge_type_class initialize_relay_metadata @edge_type_class = new_edge_type_class else # Don't call `ancestor.edge_type_class` # because we don't want a fallback from any ancestors -- # only apply the fallback if _no_ ancestor has a configured value! for ancestor in self.ancestors if ancestor.respond_to?(:configured_edge_type_class, true) && (etc = ancestor.configured_edge_type_class) return etc end end Types::Relay::BaseEdge end end def connection_type_class(new_connection_type_class = nil) if new_connection_type_class initialize_relay_metadata @connection_type_class = new_connection_type_class else # Don't call `ancestor.connection_type_class` # because we don't want a fallback from any ancestors -- # only apply the fallback if _no_ ancestor has a configured value! for ancestor in self.ancestors if ancestor.respond_to?(:configured_connection_type_class, true) && (ctc = ancestor.configured_connection_type_class) return ctc end end Types::Relay::BaseConnection end end def edge_type initialize_relay_metadata @edge_type ||= begin edge_name = self.graphql_name + "Edge" node_type_class = self Class.new(edge_type_class) do graphql_name(edge_name) node_type(node_type_class) end end end def connection_type initialize_relay_metadata @connection_type ||= begin conn_name = self.graphql_name + "Connection" edge_type_class = self.edge_type Class.new(connection_type_class) do graphql_name(conn_name) edge_type(edge_type_class) end end end protected def configured_connection_type_class @connection_type_class end def configured_edge_type_class @edge_type_class end attr_writer :edge_type, :connection_type, :connection_type_class, :edge_type_class private # If one of these values is accessed, initialize all the instance variables to retain # a consistent object shape. def initialize_relay_metadata if !defined?(@connection_type) @connection_type = nil @edge_type = nil @connection_type_class = nil @edge_type_class = nil end end end end end end graphql-2.6.0/lib/graphql/schema/member/has_validators.rb0000644000004100000410000000304315173430257023457 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member module HasValidators include GraphQL::EmptyObjects # Build {GraphQL::Schema::Validator}s based on the given configuration # and use them for this schema member # @param validation_config [Hash{Symbol => Hash}] # @return [void] def validates(validation_config) new_validators = GraphQL::Schema::Validator.from_config(self, validation_config) @own_validators ||= [] @own_validators.concat(new_validators) nil end # @return [Array] def validators @own_validators || EMPTY_ARRAY end module ClassConfigured def inherited(child_cls) super child_cls.extend(ClassValidators) end module ClassValidators include GraphQL::EmptyObjects def validators inherited_validators = superclass.validators if !inherited_validators.empty? if @own_validators.nil? inherited_validators else inherited_validators + @own_validators end elsif @own_validators.nil? EMPTY_ARRAY else @own_validators end end end end def self.extended(child_cls) super child_cls.extend(ClassConfigured) end end end end end graphql-2.6.0/lib/graphql/schema/member/validates_input.rb0000644000004100000410000000151215173430257023646 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member module ValidatesInput def valid_input?(val, ctx) validate_input(val, ctx).valid? end def validate_input(val, ctx, max_errors: nil) if val.nil? Query::InputValidationResult::VALID else validate_non_null_input(val, ctx, max_errors: max_errors) || Query::InputValidationResult::VALID end end def valid_isolated_input?(v) valid_input?(v, GraphQL::Query::NullContext.instance) end def coerce_isolated_input(v) coerce_input(v, GraphQL::Query::NullContext.instance) end def coerce_isolated_result(v) coerce_result(v, GraphQL::Query::NullContext.instance) end end end end end graphql-2.6.0/lib/graphql/schema/member/has_authorization.rb0000644000004100000410000000154115173430257024210 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member module HasAuthorization def self.included(child_class) child_class.include(InstanceConfigured) end def self.extended(child_class) child_class.extend(ClassConfigured) child_class.class_exec do @authorizes = false end end def authorized?(object, context) true end module InstanceConfigured def authorizes?(context) raise RequiredImplementationMissingError, "#{self.class} must implement #authorizes?(context)" end end module ClassConfigured def authorizes?(context) method(:authorized?).owner != GraphQL::Schema::Member::HasAuthorization end end end end end end graphql-2.6.0/lib/graphql/schema/member/has_fields.rb0000644000004100000410000004343315173430257022564 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member # Shared code for Objects, Interfaces, Mutations, Subscriptions module HasFields include EmptyObjects # Add a field to this object or interface with the given definition # @param name_positional [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API); `name:` keyword is also accepted # @param type_positional [Class, GraphQL::BaseType, Array] The return type of this field; `type:` keyword is also accepted # @param desc_positional [String] Field description; `description:` keyword is also accepted # @option kwargs [Symbol] :name The underscore-cased version of this field name (will be camelized for the GraphQL API); positional argument also accepted # @option kwargs [Class, GraphQL::BaseType, Array] :type The return type of this field; positional argument is also accepted # @option kwargs [Boolean] :null (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null` # @option kwargs [String] :description Field description; positional argument also accepted # @option kwargs [String] :comment Field comment # @option kwargs [String] :deprecation_reason If present, the field is marked "deprecated" with this message # @option kwargs [Symbol] :method The method to call on the underlying object to resolve this field (defaults to `name`) # @option kwargs [String, Symbol] :hash_key The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`) # @option kwargs [Array] :dig The nested hash keys to lookup on the underlying hash to resolve this field using dig # @option kwargs [Symbol, true] :resolver_method The method on the type to call to resolve this field (defaults to `name`) # @option kwargs [Symbol, true] :resolve_static Used by {Schema.execute_next} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments` # @option kwargs [Symbol, true] :resolve_batch Used by {Schema.execute_next} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`. # @option kwargs [Symbol, true] :resolve_each Used by {Schema.execute_next} to get a value value for each item. Called on the owner type class with `object, context, **arguments`. # @option kwargs [Symbol, true] :resolve_legacy_instance_method Used by {Schema.execute_next} to get a value value for each item. Calls an instance method on the object type class. # @option kwargs [Boolean] :connection `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name # @option kwargs [Class] :connection_extension The extension to add, to implement connections. If `nil`, no extension is added. # @option kwargs [Integer, nil] :max_page_size For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results. # @option kwargs [Integer, nil] :default_page_size For connections, the default number of items to return from this field, or `nil` to return unlimited results. # @option kwargs [Boolean] :introspection If true, this field will be marked as `#introspection?` and the name may begin with `__` # @option kwargs [{String=>GraphQL::Schema::Argument, Hash}] :arguments Arguments for this field (may be added in the block, also) # @option kwargs [Boolean] :camelize If true, the field name will be camelized when building the schema # @option kwargs [Numeric] :complexity When provided, set the complexity for this field # @option kwargs [Boolean] :scope If true, the return type's `.scope_items` method will be called on the return value # @option kwargs [Symbol, String] :subscription_scope A key in `context` which will be used to scope subscription payloads # @option kwargs [Array Object>>] :extensions Named extensions to apply to this field (see also {#extension}) # @option kwargs [Hash{Class => Hash}] :directives Directives to apply to this field # @option kwargs [Boolean] :trace If true, a {GraphQL::Tracing} tracer will measure this scalar field # @option kwargs [Boolean] :broadcastable Whether or not this field can be distributed in subscription broadcasts # @option kwargs [Language::Nodes::FieldDefinition, nil] :ast_node If this schema was parsed from definition, this AST node defined the field # @option kwargs [Boolean] :method_conflict_warning If false, skip the warning if this field's method conflicts with a built-in method # @option kwargs [Array] :validates Configurations for validating this field # @option kwargs [Object] :fallback_value A fallback value if the method is not defined # @option kwargs [Class] :mutation # @option kwargs [Class] :resolver # @option kwargs [Class] :subscription # @option kwargs [Boolean] :dynamic_introspection (Private, used by GraphQL-Ruby) # @option kwargs [Boolean] :relay_node_field (Private, used by GraphQL-Ruby) # @option kwargs [Boolean] :relay_nodes_field (Private, used by GraphQL-Ruby) # @option kwargs [Class, Hash] :dataload Shorthand for dataloader lookups # @option kwargs [Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>] :extras Extra arguments to be injected into the resolver for this field # @param kwargs [Hash] Keywords for defining the field. Any not documented here will be passed to your base field class where they must be handled. # @param definition_block [Proc] an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new {Field}. # @yieldparam field [GraphQL::Schema::Field] The newly-created field instance # @yieldreturn [void] # @return [GraphQL::Schema::Field] def field(name_positional = nil, type_positional = nil, desc_positional = nil, **kwargs, &definition_block) resolver = kwargs.delete(:resolver) mutation = kwargs.delete(:mutation) subscription = kwargs.delete(:subscription) if (resolver_class = resolver || mutation || subscription) # Add a reference to that parent class kwargs[:resolver_class] = resolver_class end kwargs[:name] ||= name_positional if !type_positional.nil? if desc_positional if kwargs[:description] raise ArgumentError, "Provide description as a positional argument or `description:` keyword, but not both (#{desc_positional.inspect}, #{kwargs[:description].inspect})" end kwargs[:description] = desc_positional kwargs[:type] = type_positional elsif (resolver || mutation) && type_positional.is_a?(String) # The return type should be copied from the resolver, and the second positional argument is the description kwargs[:description] = type_positional else kwargs[:type] = type_positional end if type_positional.is_a?(Class) && type_positional < GraphQL::Schema::Mutation raise ArgumentError, "Use `field #{name_positional.inspect}, mutation: Mutation, ...` to provide a mutation to this field instead" end end kwargs[:owner] = self field_defn = field_class.new(**kwargs, &definition_block) add_field(field_defn) field_defn end # A list of Ruby keywords. # # @api private RUBY_KEYWORDS = [:class, :module, :def, :undef, :begin, :rescue, :ensure, :end, :if, :unless, :then, :elsif, :else, :case, :when, :while, :until, :for, :break, :next, :redo, :retry, :in, :do, :return, :yield, :super, :self, :nil, :true, :false, :and, :or, :not, :alias, :defined?, :BEGIN, :END, :__LINE__, :__FILE__] # A list of GraphQL-Ruby keywords. # # @api private GRAPHQL_RUBY_KEYWORDS = [:context, :object, :raw_value] # A list of field names that we should advise users to pick a different # resolve method name. # # @api private CONFLICT_FIELD_NAMES = Set.new(GRAPHQL_RUBY_KEYWORDS + RUBY_KEYWORDS + Object.instance_methods) # Register this field with the class, overriding a previous one if needed. # @param field_defn [GraphQL::Schema::Field] # @return [void] def add_field(field_defn, method_conflict_warning: field_defn.method_conflict_warning?) # Check that `field_defn.original_name` equals `resolver_method` and `method_sym` -- # that shows that no override value was given manually. if method_conflict_warning && CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) && field_defn.original_name == field_defn.resolver_method && field_defn.original_name == field_defn.method_sym && field_defn.hash_key == NOT_CONFIGURED && field_defn.dig_keys.nil? warn(conflict_field_name_warning(field_defn)) end prev_defn = own_fields[field_defn.name] case prev_defn when nil own_fields[field_defn.name] = field_defn when Array prev_defn << field_defn when GraphQL::Schema::Field own_fields[field_defn.name] = [prev_defn, field_defn] else raise "Invariant: unexpected previous field definition for #{field_defn.name.inspect}: #{prev_defn.inspect}" end nil end # @return [Class] The class to initialize when adding fields to this kind of schema member def field_class(new_field_class = nil) if new_field_class @field_class = new_field_class elsif defined?(@field_class) && @field_class @field_class else find_inherited_value(:field_class, GraphQL::Schema::Field) end end def global_id_field(field_name, **kwargs) type = self field field_name, "ID", **kwargs, null: false, resolve_each: true define_method(field_name) do context.schema.id_from_object(object, type, context) end define_singleton_method(field_name) do |object, context| context.schema.id_from_object(object, type, context) end end # @param new_has_no_fields [Boolean] Call with `true` to make this Object type ignore the requirement to have any defined fields. # @return [void] def has_no_fields(new_has_no_fields) @has_no_fields = new_has_no_fields nil end # @return [Boolean] `true` if `has_no_fields(true)` was configued def has_no_fields? @has_no_fields end # @return [Hash GraphQL::Schema::Field, Array>] Fields defined on this class _specifically_, not parent classes def own_fields @own_fields ||= {} end def all_field_definitions all_fields = {} ancestors.reverse_each do |ancestor| if ancestor.respond_to?(:own_fields) all_fields.merge!(ancestor.own_fields) end end all_fields = all_fields.values all_fields.flatten! all_fields end module InterfaceMethods def get_field(field_name, context = GraphQL::Query::NullContext.instance) warden = Warden.from_context(context) skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile) for ancestor in ancestors if ancestor.respond_to?(:own_fields) && (f_entry = ancestor.own_fields[field_name]) && (skip_visible || (f_entry = Warden.visible_entry?(:visible_field?, f_entry, context, warden))) return f_entry end end nil end # @return [Hash GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields def fields(context = GraphQL::Query::NullContext.instance) warden = Warden.from_context(context) # Local overrides take precedence over inherited fields visible_fields = {} for ancestor in ancestors if ancestor.respond_to?(:own_fields) ancestor.own_fields.each do |field_name, fields_entry| # Choose the most local definition that passes `.visible?` -- # stop checking for fields by name once one has been found. if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden)) visible_fields[field_name] = f.ensure_loaded end end end end visible_fields end end module ObjectMethods def get_field(field_name, context = GraphQL::Query::NullContext.instance) # Objects need to check that the interface implementation is visible, too warden = Warden.from_context(context) ancs = ancestors skip_visible = context.respond_to?(:types) && context.types.is_a?(GraphQL::Schema::Visibility::Profile) i = 0 while (ancestor = ancs[i]) if ancestor.respond_to?(:own_fields) && visible_interface_implementation?(ancestor, context, warden) && (f_entry = ancestor.own_fields[field_name]) && (skip_visible || (f_entry = Warden.visible_entry?(:visible_field?, f_entry, context, warden))) return (skip_visible ? f_entry : f_entry.ensure_loaded) end i += 1 end nil end # @return [Hash GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields def fields(context = GraphQL::Query::NullContext.instance) # Objects need to check that the interface implementation is visible, too warden = Warden.from_context(context) # Local overrides take precedence over inherited fields visible_fields = {} had_any_fields_at_all = false for ancestor in ancestors if ancestor.respond_to?(:own_fields) && visible_interface_implementation?(ancestor, context, warden) ancestor.own_fields.each do |field_name, fields_entry| had_any_fields_at_all = true # Choose the most local definition that passes `.visible?` -- # stop checking for fields by name once one has been found. if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden)) visible_fields[field_name] = f.ensure_loaded end end end end if !had_any_fields_at_all && !has_no_fields? warn(GraphQL::Schema::Object::FieldsAreRequiredError.new(self).message + "\n\nThis will raise an error in a future GraphQL-Ruby version.") end visible_fields end end def self.included(child_class) # Included in an interface definition methods module child_class.include(InterfaceMethods) super end def self.extended(child_class) child_class.extend(ObjectMethods) super end private def inherited(subclass) super subclass.class_exec do @own_fields ||= nil @field_class ||= nil @has_no_fields ||= false end end # If `type` is an interface, and `self` has a type membership for `type`, then make sure it's visible. def visible_interface_implementation?(type, context, warden) if type.respond_to?(:kind) && type.kind.interface? implements_this_interface = false implementation_is_visible = false warden.interface_type_memberships(self, context).each do |tm| if tm.abstract_type == type implements_this_interface ||= true if warden.visible_type_membership?(tm, context) implementation_is_visible = true break end end end # It's possible this interface came by way of `include` in another interface which this # object type _does_ implement, and that's ok implements_this_interface ? implementation_is_visible : true else # If there's no implementation, then we're looking at Ruby-style inheritance instead true end end # @param field_defn [GraphQL::Schema::Field] # @return [String] A warning to give when this field definition might conflict with a built-in method def conflict_field_name_warning(field_defn) "#{self.graphql_name}'s `field :#{field_defn.original_name}` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_#{field_defn.resolver_method}` and `def resolve_#{field_defn.resolver_method}`). Or use `method_conflict_warning: false` to suppress this warning." end end end end end graphql-2.6.0/lib/graphql/schema/member/base_dsl_methods.rb0000644000004100000410000000775615173430257023772 0ustar www-datawww-data# frozen_string_literal: true require "graphql/schema/find_inherited_value" module GraphQL class Schema class Member # DSL methods shared by lots of things in the GraphQL Schema. # @api private # @see Classes that extend this, eg {GraphQL::Schema::Object} module BaseDSLMethods include GraphQL::Schema::FindInheritedValue # Call this with a new name to override the default name for this schema member; OR # call it without an argument to get the name of this schema member # # The default name is implemented in default_graphql_name # @param new_name [String] # @return [String] def graphql_name(new_name = nil) if new_name GraphQL::NameValidator.validate!(new_name) @graphql_name = new_name else @graphql_name ||= default_graphql_name end end # Call this method to provide a new description; OR # call it without an argument to get the description # @param new_description [String] # @return [String] def description(new_description = nil) if new_description @description = new_description elsif defined?(@description) @description else @description = nil end end # Call this method to provide a new comment; OR # call it without an argument to get the comment # @param new_comment [String] # @return [String, nil] def comment(new_comment = NOT_CONFIGURED) if !NOT_CONFIGURED.equal?(new_comment) @comment = new_comment elsif defined?(@comment) @comment else nil end end # This pushes some configurations _down_ the inheritance tree, # in order to prevent repetitive lookups at runtime. module ConfigurationExtension def inherited(child_class) child_class.introspection(introspection) child_class.description(description) child_class.comment(nil) child_class.default_graphql_name = nil if defined?(@graphql_name) && @graphql_name && (self.name.nil? || graphql_name != default_graphql_name) child_class.graphql_name(graphql_name) else child_class.graphql_name = nil end super end end # @return [Boolean] If true, this object is part of the introspection system def introspection(new_introspection = nil) if !new_introspection.nil? @introspection = new_introspection elsif defined?(@introspection) @introspection else false end end def introspection? !!@introspection end # The mutation this type was derived from, if it was derived from a mutation # @return [Class] def mutation(mutation_class = nil) if mutation_class @mutation = mutation_class elsif defined?(@mutation) @mutation else nil end end alias :unwrap :itself # Creates the default name for a schema member. # The default name is the Ruby constant name, # without any namespaces and with any `-Type` suffix removed def default_graphql_name @default_graphql_name ||= begin raise GraphQL::RequiredImplementationMissingError, 'Anonymous class should declare a `graphql_name`' if name.nil? g_name = -name.split("::").last g_name.end_with?("Type") ? g_name.sub(/Type\Z/, "") : g_name end end def visible?(context) true end def authorized?(object, context) true end def default_relay? false end protected attr_writer :default_graphql_name, :graphql_name end end end end graphql-2.6.0/lib/graphql/schema/member/has_interfaces.rb0000644000004100000410000001244315173430257023436 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member module HasInterfaces def implements(*new_interfaces, **options) new_memberships = [] new_interfaces.each do |int| if int.is_a?(Module) unless int.include?(GraphQL::Schema::Interface) && !int.is_a?(Class) raise "#{int.respond_to?(:graphql_name) ? "#{int.graphql_name} (#{int})" : int.inspect} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules." end new_memberships << int.type_membership_class.new(int, self, **options) # Include the methods here, # `.fields` will use the inheritance chain # to find inherited fields include(int) # If this interface has interfaces of its own, add those, too int.interfaces.each do |next_interface| implements(next_interface) end elsif int.is_a?(String) || int.is_a?(GraphQL::Schema::LateBoundType) if !options.empty? raise ArgumentError, "`implements(...)` doesn't support options with late-loaded types yet. Remove #{options} and open an issue to request this feature." end new_memberships << int else raise ArgumentError, "Unexpected interface definition (expected module): #{int} (#{int.class})" end end # Remove any String or late-bound interfaces which are being replaced own_interface_type_memberships.reject! { |old_i_m| if !(old_i_m.respond_to?(:abstract_type) && old_i_m.abstract_type.is_a?(Module)) old_int_type = old_i_m.respond_to?(:abstract_type) ? old_i_m.abstract_type : old_i_m old_name = Schema::Member::BuildType.to_type_name(old_int_type) new_memberships.any? { |new_i_m| new_int_type = new_i_m.respond_to?(:abstract_type) ? new_i_m.abstract_type : new_i_m new_name = Schema::Member::BuildType.to_type_name(new_int_type) new_name == old_name } end } own_interface_type_memberships.concat(new_memberships) end def own_interface_type_memberships @own_interface_type_memberships ||= [] end def interface_type_memberships own_interface_type_memberships end module ClassConfigured # This combination of extended -> inherited -> extended # means that the base class (`Schema::Object`) *won't* # have the superclass-related code in `InheritedInterfaces`, # but child classes of `Schema::Object` will have it. # That way, we don't need a `superclass.respond_to?(...)` check. def inherited(child_class) super child_class.extend(InheritedInterfaces) end module InheritedInterfaces def interfaces(context = GraphQL::Query::NullContext.instance) visible_interfaces = super inherited_interfaces = superclass.interfaces(context) if !visible_interfaces.empty? if !inherited_interfaces.empty? visible_interfaces.concat(inherited_interfaces) visible_interfaces.uniq! end visible_interfaces elsif !inherited_interfaces.empty? inherited_interfaces else EmptyObjects::EMPTY_ARRAY end end def interface_type_memberships own_tms = super inherited_tms = superclass.interface_type_memberships if inherited_tms.size > 0 own_tms + inherited_tms else own_tms end end end end # param context [Query::Context] If omitted, skip filtering. def interfaces(context = GraphQL::Query::NullContext.instance) warden = Warden.from_context(context) visible_interfaces = nil own_interface_type_memberships.each do |type_membership| case type_membership when Schema::TypeMembership if warden.visible_type_membership?(type_membership, context) visible_interfaces ||= [] visible_interfaces << type_membership.abstract_type end when String, Schema::LateBoundType # During initialization, `type_memberships` can hold late-bound types visible_interfaces ||= [] visible_interfaces << type_membership else raise "Invariant: Unexpected type_membership #{type_membership.class}: #{type_membership.inspect}" end end if visible_interfaces visible_interfaces.uniq! visible_interfaces else EmptyObjects::EMPTY_ARRAY end end private def self.extended(child_class) child_class.extend(ClassConfigured) end def inherited(subclass) super subclass.class_exec do @own_interface_type_memberships ||= nil end end end end end end graphql-2.6.0/lib/graphql/schema/member/has_path.rb0000644000004100000410000000115515173430257022245 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member module HasPath # @return [String] A description of this member's place in the GraphQL schema def path path_str = if self.respond_to?(:graphql_name) self.graphql_name elsif self.class.respond_to?(:graphql_name) # Instances of resolvers self.class.graphql_name end if self.respond_to?(:owner) && owner.respond_to?(:path) path_str = "#{owner.path}.#{path_str}" end path_str end end end end end graphql-2.6.0/lib/graphql/schema/member/has_unresolved_type_error.rb0000644000004100000410000000110515173430257025744 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member # Set up a type-specific error to make debugging & bug tracker integration better module HasUnresolvedTypeError private def add_unresolved_type_error(child_class) if child_class.name # Don't set this for anonymous classes child_class.const_set(:UnresolvedTypeError, Class.new(GraphQL::UnresolvedTypeError)) else child_class.const_set(:UnresolvedTypeError, UnresolvedTypeError) end end end end end end graphql-2.6.0/lib/graphql/schema/member/graphql_type_names.rb0000644000004100000410000000073115173430257024337 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member # These constants are interpreted as GraphQL types when defining fields or arguments # # @example # field :is_draft, Boolean, null: false # field :id, ID, null: false # field :score, Int, null: false # # @api private module GraphQLTypeNames Boolean = "Boolean" ID = "ID" Int = "Int" end end end end graphql-2.6.0/lib/graphql/schema/member/has_deprecation_reason.rb0000644000004100000410000000223515173430257025155 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Member module HasDeprecationReason # @return [String, nil] Explains why this member was deprecated (if present, this will be marked deprecated in introspection) attr_reader :deprecation_reason # Set the deprecation reason for this member, or remove it by assigning `nil` # @param text [String, nil] def deprecation_reason=(text) @deprecation_reason = text if text.nil? remove_directive(GraphQL::Schema::Directive::Deprecated) else # This removes a previously-attached directive, if there is one: directive(GraphQL::Schema::Directive::Deprecated, reason: text) end end def self.extended(child_class) super child_class.extend(ClassMethods) end module ClassMethods def deprecation_reason(new_reason = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_reason) super() else self.deprecation_reason = new_reason end end end end end end end graphql-2.6.0/lib/graphql/schema/ractor_shareable.rb0000644000004100000410000000540215173430257022506 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema module RactorShareable def self.extended(schema_class) schema_class.extend(SchemaExtension) schema_class.freeze_schema end module SchemaExtension def freeze_error_handlers(handlers) handlers[:subclass_handlers].default_proc = nil handlers[:subclass_handlers].each do |_class, subclass_handlers| freeze_error_handlers(subclass_handlers) end Ractor.make_shareable(handlers) end def freeze_schema # warm some ivars: default_analysis_engine default_execution_strategy GraphQL.default_parser default_logger freeze_error_handlers(error_handlers) # TODO: this freezes errors of parent classes which could cause trouble parent_class = superclass while parent_class.respond_to?(:error_handlers) freeze_error_handlers(parent_class.error_handlers) parent_class = parent_class.superclass end own_tracers.freeze @frozen_tracers = tracers.freeze own_trace_modes.each do |m| trace_options_for(m) build_trace_mode(m) end build_trace_mode(:default) Ractor.make_shareable(@trace_options_for_mode) Ractor.make_shareable(own_trace_modes) Ractor.make_shareable(own_multiplex_analyzers) @frozen_multiplex_analyzers = Ractor.make_shareable(multiplex_analyzers) Ractor.make_shareable(own_query_analyzers) @frozen_query_analyzers = Ractor.make_shareable(query_analyzers) Ractor.make_shareable(own_plugins) own_plugins.each do |(plugin, options)| Ractor.make_shareable(plugin) Ractor.make_shareable(options) end @frozen_plugins = Ractor.make_shareable(plugins) Ractor.make_shareable(own_references_to) @frozen_directives = Ractor.make_shareable(directives) Ractor.make_shareable(visibility) Ractor.make_shareable(introspection_system) extend(FrozenMethods) Ractor.make_shareable(self) superclass.respond_to?(:freeze_schema) && superclass.freeze_schema end module FrozenMethods def tracers; @frozen_tracers; end def multiplex_analyzers; @frozen_multiplex_analyzers; end def query_analyzers; @frozen_query_analyzers; end def plugins; @frozen_plugins; end def directives; @frozen_directives; end # This actually accumulates info during execution... # How to support it? def lazy?(_obj); false; end def sync_lazy(obj); obj; end end end end end end graphql-2.6.0/lib/graphql/schema/validator.rb0000644000004100000410000001611115173430257021172 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Validator # The thing being validated # @return [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class] attr_reader :validated # @param validated [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class] The argument or argument owner this validator is attached to # @param allow_blank [Boolean] if `true`, then objects that respond to `.blank?` and return true for `.blank?` will skip this validation # @param allow_null [Boolean] if `true`, then incoming `null`s will skip this validation def initialize(validated:, allow_blank: false, allow_null: false) @validated = validated @allow_blank = allow_blank @allow_null = allow_null end # @param object [Object] The application object that this argument's field is being resolved for # @param context [GraphQL::Query::Context] # @param value [Object] The client-provided value for this argument (after parsing and coercing by the input type) # @return [nil, Array, String] Error message or messages to add def validate(object, context, value) raise GraphQL::RequiredImplementationMissingError, "Validator classes should implement #validate" end # This is like `String#%`, but it supports the case that only some of `string`'s # values are present in `substitutions` def partial_format(string, substitutions) substitutions.each do |key, value| sub_v = value.is_a?(String) ? value : value.to_s string = string.gsub("%{#{key}}", sub_v) end string end # @return [Boolean] `true` if `value` is `nil` and this validator has `allow_null: true` or if value is `.blank?` and this validator has `allow_blank: true` def permitted_empty_value?(value) (value.nil? && @allow_null) || (@allow_blank && value.respond_to?(:blank?) && value.blank?) end # @param schema_member [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class] # @param validates_hash [Hash{Symbol => Hash}, Hash{Class => Hash} nil] A configuration passed as `validates:` # @return [Array] def self.from_config(schema_member, validates_hash) if validates_hash.nil? || validates_hash.empty? EMPTY_ARRAY else validates_hash = validates_hash.dup default_options = {} if validates_hash[:allow_null] default_options[:allow_null] = validates_hash.delete(:allow_null) end if validates_hash[:allow_blank] default_options[:allow_blank] = validates_hash.delete(:allow_blank) end # allow_nil or allow_blank are the _only_ validations: if validates_hash.empty? validates_hash = default_options end validates_hash.map do |validator_name, options| validator_class = case validator_name when Class validator_name else all_validators[validator_name] || raise(ArgumentError, "unknown validation: #{validator_name.inspect}") end if options.is_a?(Hash) validator_class.new(validated: schema_member, **(default_options.merge(options))) else validator_class.new(options, validated: schema_member, **default_options) end end end end # Add `validator_class` to be initialized when `validates:` is given `name`. # (It's initialized with whatever options are given by the key `name`). # @param name [Symbol] # @param validator_class [Class] # @return [void] def self.install(name, validator_class) all_validators[name] = validator_class nil end # Remove whatever validator class is {.install}ed at `name`, if there is one # @param name [Symbol] # @return [void] def self.uninstall(name) all_validators.delete(name) nil end class << self attr_accessor :all_validators end self.all_validators = {} include GraphQL::EmptyObjects class ValidationFailedError < GraphQL::ExecutionError attr_reader :errors def initialize(errors:) @errors = errors super(errors.join(", ")) end end # @param validators [Array] # @param object [Object] # @param context [Query::Context] # @param value [Object] # @return [void] # @raises [ValidationFailedError] def self.validate!(validators, object, context, value, as: nil) # Assuming the default case is no errors, reduce allocations in that case. # This will be replaced with a mutable array if we actually get any errors. all_errors = EMPTY_ARRAY validators.each do |validator| validated = as || validator.validated errors = validator.validate(object, context, value) if errors && (errors.is_a?(Array) && errors != EMPTY_ARRAY) || (errors.is_a?(String)) if all_errors.frozen? # It's empty all_errors = [] end interpolation_vars = { validated: validated.graphql_name, value: value.inspect } if errors.is_a?(String) all_errors << (errors % interpolation_vars) else errors = errors.map { |e| e % interpolation_vars } all_errors.concat(errors) end end end if !all_errors.empty? raise ValidationFailedError.new(errors: all_errors) end nil end end end end require "graphql/schema/validator/length_validator" GraphQL::Schema::Validator.install(:length, GraphQL::Schema::Validator::LengthValidator) require "graphql/schema/validator/numericality_validator" GraphQL::Schema::Validator.install(:numericality, GraphQL::Schema::Validator::NumericalityValidator) require "graphql/schema/validator/format_validator" GraphQL::Schema::Validator.install(:format, GraphQL::Schema::Validator::FormatValidator) require "graphql/schema/validator/inclusion_validator" GraphQL::Schema::Validator.install(:inclusion, GraphQL::Schema::Validator::InclusionValidator) require "graphql/schema/validator/exclusion_validator" GraphQL::Schema::Validator.install(:exclusion, GraphQL::Schema::Validator::ExclusionValidator) require "graphql/schema/validator/required_validator" GraphQL::Schema::Validator.install(:required, GraphQL::Schema::Validator::RequiredValidator) require "graphql/schema/validator/allow_null_validator" GraphQL::Schema::Validator.install(:allow_null, GraphQL::Schema::Validator::AllowNullValidator) require "graphql/schema/validator/allow_blank_validator" GraphQL::Schema::Validator.install(:allow_blank, GraphQL::Schema::Validator::AllowBlankValidator) require "graphql/schema/validator/all_validator" GraphQL::Schema::Validator.install(:all, GraphQL::Schema::Validator::AllValidator) graphql-2.6.0/lib/graphql/schema/interface.rb0000644000004100000410000001340315173430257021146 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema module Interface include GraphQL::Schema::Member::GraphQLTypeNames module DefinitionMethods include GraphQL::Schema::Member::BaseDSLMethods # ConfigurationExtension's responsibilities are in `def included` below include GraphQL::Schema::Member::TypeSystemHelpers include GraphQL::Schema::Member::HasFields include GraphQL::Schema::Member::HasPath include GraphQL::Schema::Member::RelayShortcuts include GraphQL::Schema::Member::Scoped include GraphQL::Schema::Member::HasAstNode include GraphQL::Schema::Member::HasUnresolvedTypeError include GraphQL::Schema::Member::HasDataloader include GraphQL::Schema::Member::HasDirectives include GraphQL::Schema::Member::HasInterfaces # Methods defined in this block will be: # - Added as class methods to this interface # - Added as class methods to all child interfaces def definition_methods(&block) # Use an instance variable to tell whether it's been included previously or not; # You can't use constant detection because constants are brought into scope # by `include`, which has already happened at this point. if !defined?(@_definition_methods) defn_methods_module = Module.new @_definition_methods = defn_methods_module const_set(:DefinitionMethods, defn_methods_module) extend(self::DefinitionMethods) end self::DefinitionMethods.module_exec(&block) end # Instance methods defined in this block will become class methods on objects that implement this interface. # Use it to implement `resolve_each:`, `resolve_batch:`, and `resolve_static:` fields. # @example # field :thing, String, resolve_static: true # # resolver_methods do # def thing # Somehow.get.thing # end # end def resolver_methods(&block) if !defined?(@_resolver_methods) resolver_methods_module = Module.new @_resolver_methods = resolver_methods_module const_set(:ResolverMethods, resolver_methods_module) extend(self::ResolverMethods) end self::ResolverMethods.module_exec(&block) end # @see {Schema::Warden} hides interfaces without visible implementations def visible?(context) true end def type_membership_class(membership_class = nil) if membership_class @type_membership_class = membership_class else @type_membership_class || find_inherited_value(:type_membership_class, GraphQL::Schema::TypeMembership) end end # Here's the tricky part. Make sure behavior keeps making its way down the inheritance chain. def included(child_class) if !child_class.is_a?(Class) # In this case, it's been included into another interface. # This is how interface inheritance is implemented # We need this before we can call `own_interfaces` child_class.extend(Schema::Interface::DefinitionMethods) child_class.type_membership_class(self.type_membership_class) child_class.ancestors.reverse_each do |ancestor| if ancestor.const_defined?(:DefinitionMethods) && ancestor != child_class child_class.extend(ancestor::DefinitionMethods) end end child_class.introspection(introspection) child_class.description(description) child_class.comment(nil) # If interfaces are mixed into each other, only define this class once if !child_class.const_defined?(:UnresolvedTypeError, false) add_unresolved_type_error(child_class) end elsif child_class < GraphQL::Schema::Object # This is being included into an object type, make sure it's using `implements(...)` backtrace_line = caller_locations(0, 10).find do |location| location.base_label == "implements" && location.path.end_with?("schema/member/has_interfaces.rb") end if !backtrace_line raise "Attach interfaces using `implements(#{self})`, not `include(#{self})`" end child_class.ancestors.reverse_each do |ancestor| if ancestor.const_defined?(:ResolverMethods) child_class.extend(ancestor::ResolverMethods) end end end super end # Register other Interface or Object types as implementers of this Interface. # # When those Interfaces or Objects aren't used as the return values of fields, # they may have to be registered using this method so that GraphQL-Ruby can find them. # @param types [Class, Module] # @return [Array] Implementers of this interface, if they're registered def orphan_types(*types) if !types.empty? @orphan_types ||= [] @orphan_types.concat(types) else if defined?(@orphan_types) all_orphan_types = @orphan_types.dup if defined?(super) all_orphan_types += super all_orphan_types.uniq! end all_orphan_types elsif defined?(super) super else EmptyObjects::EMPTY_ARRAY end end end def kind GraphQL::TypeKinds::INTERFACE end end extend DefinitionMethods def unwrap self end end end end graphql-2.6.0/lib/graphql/schema/type_expression.rb0000644000004100000410000000311115173430257022441 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema # @api private module TypeExpression # Fetch a type from a type map by its AST specification. # Return `nil` if not found. # @param type_owner [#type] A thing for looking up types by name # @param ast_node [GraphQL::Language::Nodes::AbstractNode] # @return [Class, GraphQL::Schema::NonNull, GraphQL::Schema:List] def self.build_type(type_owner, ast_node) case ast_node when GraphQL::Language::Nodes::TypeName type_owner.type(ast_node.name) # rubocop:disable Development/ContextIsPassedCop -- this is a `context` or `warden`, it's already query-aware when GraphQL::Language::Nodes::NonNullType ast_inner_type = ast_node.of_type inner_type = build_type(type_owner, ast_inner_type) wrap_type(inner_type, :to_non_null_type) when GraphQL::Language::Nodes::ListType ast_inner_type = ast_node.of_type inner_type = build_type(type_owner, ast_inner_type) wrap_type(inner_type, :to_list_type) else raise "Invariant: unexpected type from ast: #{ast_node.inspect}" end end class << self private def wrap_type(type, wrapper_method) if type.nil? nil elsif wrapper_method == :to_list_type || wrapper_method == :to_non_null_type type.public_send(wrapper_method) else raise ArgumentError, "Unexpected wrapper method: #{wrapper_method.inspect}" end end end end end end graphql-2.6.0/lib/graphql/schema/relay_classic_mutation.rb0000644000004100000410000000446215173430257023750 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema # Mutations that extend this base class get some conventions added for free: # # - An argument called `clientMutationId` is _always_ added, but it's not passed # to the resolve method. The value is re-inserted to the response. (It's for # client libraries to manage optimistic updates.) # - The returned object type always has a field called `clientMutationId` to support that. # - The mutation accepts one argument called `input`, `argument`s defined in the mutation # class are added to that input object, which is generated by the mutation. # # These conventions were first specified by Relay Classic, but they come in handy: # # - `clientMutationId` supports optimistic updates and cache rollbacks on the client # - using a single `input:` argument makes it easy to post whole JSON objects to the mutation # using one GraphQL variable (`$input`) instead of making a separate variable for each argument. # # @see {GraphQL::Schema::Mutation} for an example, it's basically the same. # class RelayClassicMutation < GraphQL::Schema::Mutation include GraphQL::Schema::HasSingleInputArgument argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false # The payload should always include this field field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.") # Relay classic default: null(true) # Override {GraphQL::Schema::Resolver#resolve_with_support} to # delete `client_mutation_id` from the kwargs. def resolve_with_support(**inputs) input = inputs[:input].to_kwargs if input # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are. input_kwargs = input.to_h client_mutation_id = input_kwargs.delete(:client_mutation_id) inputs[:input] = input_kwargs end return_value = super(**inputs) context.query.after_lazy(return_value) do |return_hash| # It might be an error if return_hash.is_a?(Hash) return_hash[:client_mutation_id] = client_mutation_id end return_hash end end end end end graphql-2.6.0/lib/graphql/schema/find_inherited_value.rb0000644000004100000410000000152615173430257023360 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema module FindInheritedValue def self.extended(child_cls) child_cls.singleton_class.include(GraphQL::EmptyObjects) end def self.included(child_cls) child_cls.include(GraphQL::EmptyObjects) end private def find_inherited_value(method_name, default_value = nil) if self.is_a?(Class) superclass.respond_to?(method_name, true) ? superclass.send(method_name) : default_value else ancestors_except_self = ancestors ancestors_except_self.delete(self) ancestors_except_self.each do |ancestor| if ancestor.respond_to?(method_name, true) return ancestor.send(method_name) end end default_value end end end end end graphql-2.6.0/lib/graphql/schema/type_membership.rb0000644000004100000410000000337315173430257022407 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema # This class joins an object type to an abstract type (interface or union) of which # it is a member. class TypeMembership # @return [Class] attr_accessor :object_type # @return [Class, Module] attr_reader :abstract_type # @return [Hash] attr_reader :options # Called when an object is hooked up to an abstract type, such as {Schema::Union.possible_types} # or {Schema::Object.implements} (for interfaces). # # @param abstract_type [Class, Module] # @param object_type [Class] # @param options [Hash] Any options passed to `.possible_types` or `.implements` def initialize(abstract_type, object_type, **options) @abstract_type = abstract_type @object_type = object_type @options = options end # @return [Boolean] if false, {#object_type} will be treated as _not_ a member of {#abstract_type} def visible?(ctx) warden = Warden.from_context(ctx) (@object_type.respond_to?(:visible?) ? warden.visible_type?(@object_type, ctx) : true) && (@abstract_type.respond_to?(:visible?) ? warden.visible_type?(@abstract_type, ctx) : true) end def graphql_name "#{@object_type.graphql_name}.#{@abstract_type.kind.interface? ? "implements" : "belongsTo" }.#{@abstract_type.graphql_name}" end def path graphql_name end def inspect "#<#{self.class} #{@object_type.inspect} => #{@abstract_type.inspect}>" end alias :type_class :itself end end end graphql-2.6.0/lib/graphql/schema/addition.rb0000644000004100000410000002473415173430257021012 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Addition attr_reader :directives, :possible_types, :types, :union_memberships, :references, :arguments_with_default_values def initialize(schema:, own_types:, new_types:) @schema = schema @own_types = own_types @directives = Set.new @possible_types = {} @types = {} @union_memberships = {} @references = Hash.new { |h, k| h[k] = Set.new } @arguments_with_default_values = [] add_type_and_traverse(new_types) end private def references_to(thing, from:) @references[thing].add(from) end def get_type(name) local_type = @types[name] # This isn't really sophisticated, but # I think it's good enough to support the current usage of LateBoundTypes if local_type.is_a?(Array) local_type = local_type.first end local_type || @schema.get_type(name) end # Lookup using `own_types` here because it's ok to override # inherited types by name def get_local_type(name) @types[name] || @own_types[name] end def add_directives_from(owner) if !(dir_instances = owner.directives).empty? dirs = dir_instances.map(&:class) @directives.merge(dirs) add_type_and_traverse(dirs) end end def add_type_and_traverse(new_types) late_types = [] path = [] new_types.each do |t| path.push(t.graphql_name) add_type(t, owner: nil, late_types: late_types, path: path) path.pop end missed_late_types = 0 while (late_type_vals = late_types.shift) type_owner, lt = late_type_vals if lt.is_a?(String) type = Member::BuildType.constantize(lt) # Reset the counter, since we might succeed next go-round missed_late_types = 0 update_type_owner(type_owner, type) add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name]) elsif lt.is_a?(LateBoundType) if (type = get_type(lt.name)) # Reset the counter, since we might succeed next go-round missed_late_types = 0 update_type_owner(type_owner, type) add_type(type, owner: type_owner, late_types: late_types, path: [type.graphql_name]) else missed_late_types += 1 # Add it back to the list, maybe we'll be able to resolve it later. late_types << [type_owner, lt] if missed_late_types == late_types.size # We've looked at all of them and haven't resolved one. raise UnresolvedLateBoundTypeError.new(type: lt) else # Try the next one end end else raise ArgumentError, "Unexpected late type: #{lt.inspect}" end end nil end def update_type_owner(owner, type) case owner when Module if owner.kind.union? # It's a union with possible_types # Replace the item by class name owner.assign_type_membership_object_type(type) @possible_types[owner] = owner.possible_types elsif type.kind.interface? && (owner.kind.object? || owner.kind.interface?) new_interfaces = [] owner.interfaces.each do |int_t| if int_t.is_a?(String) && int_t == type.graphql_name new_interfaces << type elsif int_t.is_a?(LateBoundType) && int_t.graphql_name == type.graphql_name new_interfaces << type else # Don't re-add proper interface definitions, # they were probably already added, maybe with options. end end owner.implements(*new_interfaces) new_interfaces.each do |int| pt = @possible_types[int] ||= [] if !pt.include?(owner) && owner.is_a?(Class) pt << owner end int.interfaces.each do |indirect_int| if indirect_int.is_a?(LateBoundType) && (indirect_int_type = get_type(indirect_int.graphql_name)) update_type_owner(owner, indirect_int_type) end end end end when nil # It's a root type @types[type.graphql_name] = type when GraphQL::Schema::Field, GraphQL::Schema::Argument orig_type = owner.type unwrapped_t = type # Apply list/non-null wrapper as needed if orig_type.respond_to?(:of_type) transforms = [] while (orig_type.respond_to?(:of_type)) if orig_type.kind.non_null? transforms << :to_non_null_type elsif orig_type.kind.list? transforms << :to_list_type else raise "Invariant: :of_type isn't non-null or list" end orig_type = orig_type.of_type end transforms.reverse_each { |t| type = type.public_send(t) } end owner.type = type references_to(unwrapped_t, from: owner) else raise "Unexpected update: #{owner.inspect} #{type.inspect}" end end def add_type(type, owner:, late_types:, path:) if type.is_a?(String) || type.is_a?(GraphQL::Schema::LateBoundType) late_types << [owner, type] return end if owner.is_a?(Class) && owner < GraphQL::Schema::Union um = @union_memberships[type.graphql_name] ||= [] um << owner end if (prev_type = get_local_type(type.graphql_name)) && (prev_type == type || (prev_type.is_a?(Array) && prev_type.include?(type))) # No need to re-visit elsif type.is_a?(Class) && type < GraphQL::Schema::Directive @directives << type type.all_argument_definitions.each do |arg| arg_type = arg.type.unwrap if !arg_type.is_a?(GraphQL::Schema::LateBoundType) references_to(arg_type, from: arg) end path.push(arg.graphql_name) add_type(arg_type, owner: arg, late_types: late_types, path: path) path.pop if arg.default_value? @arguments_with_default_values << arg end end else prev_type = @types[type.graphql_name] if prev_type.nil? @types[type.graphql_name] = type elsif prev_type.is_a?(Array) prev_type << type else @types[type.graphql_name] = [prev_type, type] end add_directives_from(type) if type.kind.fields? type.all_field_definitions.each do |field| field.ensure_loaded name = field.graphql_name field_type = field.type.unwrap if !field_type.is_a?(GraphQL::Schema::LateBoundType) references_to(field_type, from: field) end path.push(name) add_type(field_type, owner: field, late_types: late_types, path: path) add_directives_from(field) field.all_argument_definitions.each do |arg| add_directives_from(arg) arg_type = arg.type.unwrap if !arg_type.is_a?(GraphQL::Schema::LateBoundType) references_to(arg_type, from: arg) end path.push(arg.graphql_name) add_type(arg_type, owner: arg, late_types: late_types, path: path) path.pop if arg.default_value? @arguments_with_default_values << arg end end path.pop end end if type.kind.input_object? type.all_argument_definitions.each do |arg| add_directives_from(arg) arg_type = arg.type.unwrap if !arg_type.is_a?(GraphQL::Schema::LateBoundType) references_to(arg_type, from: arg) end path.push(arg.graphql_name) add_type(arg_type, owner: arg, late_types: late_types, path: path) path.pop if arg.default_value? @arguments_with_default_values << arg end end end if type.kind.union? @possible_types[type] = type.all_possible_types path.push("possible_types") type.all_possible_types.each do |t| add_type(t, owner: type, late_types: late_types, path: path) end path.pop end if type.kind.interface? path.push("orphan_types") type.orphan_types.each do |t| add_type(t, owner: type, late_types: late_types, path: path) end path.pop end if type.kind.object? possible_types_for_this_name = @possible_types[type] ||= [] possible_types_for_this_name << type end if type.kind.object? || type.kind.interface? path.push("implements") type.interface_type_memberships.each do |interface_type_membership| case interface_type_membership when Schema::TypeMembership interface_type = interface_type_membership.abstract_type # We can get these now; we'll have to get late-bound types later if interface_type.is_a?(Module) && type.is_a?(Class) implementers = @possible_types[interface_type] ||= [] if !implementers.include?(type) implementers << type end end when String, Schema::LateBoundType interface_type = interface_type_membership else raise ArgumentError, "Invariant: unexpected type membership for #{type.graphql_name}: #{interface_type_membership.class} (#{interface_type_membership.inspect})" end add_type(interface_type, owner: type, late_types: late_types, path: path) end path.pop end if type.kind.enum? type.all_enum_value_definitions.each do |value_definition| add_directives_from(value_definition) end end end end end end end graphql-2.6.0/lib/graphql/schema/enum.rb0000644000004100000410000002613515173430257020160 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema # Extend this class to define GraphQL enums in your schema. # # By default, GraphQL enum values are translated into Ruby strings. # You can provide a custom value with the `value:` keyword. # # @example # # equivalent to # # enum PizzaTopping { # # MUSHROOMS # # ONIONS # # PEPPERS # # } # class PizzaTopping < GraphQL::Schema::Enum # value :MUSHROOMS # value :ONIONS # value :PEPPERS # end class Enum < GraphQL::Schema::Member extend GraphQL::Schema::Member::ValidatesInput # This is raised when either: # # - A resolver returns a value which doesn't match any of the enum's configured values; # - Or, the resolver returns a value which matches a value, but that value's `authorized?` check returns false. # # In either case, the field should be modified so that the invalid value isn't returned. # # {GraphQL::Schema::Enum} subclasses get their own subclass of this error, so that bug trackers can better show where they came from. class UnresolvedValueError < GraphQL::Error def initialize(value:, enum:, context:, authorized:) fix_message = if authorized == false ", but this value was unauthorized. Update the field or resolver to return a different value in this case (or return `nil`)." else ", but this isn't a valid value for `#{enum.graphql_name}`. Update the field or resolver to return one of `#{enum.graphql_name}`'s values instead." end message = if (cp = context[:current_path]) && (cf = context[:current_field]) "`#{cf.path}` returned `#{value.inspect}` at `#{cp.join(".")}`#{fix_message}" else "`#{value.inspect}` was returned for `#{enum.graphql_name}`#{fix_message}" end super(message) end end # Raised when a {GraphQL::Schema::Enum} is defined to have no values. # This can also happen when all values return false for `.visible?`. class MissingValuesError < GraphQL::Error def initialize(enum_type) @enum_type = enum_type super("Enum types require at least one value, but #{enum_type.graphql_name} didn't provide any for this query. Make sure at least one value is defined and visible for this query.") end end class << self # Define a value for this enum # @option kwargs [String, Symbol] :graphql_name the GraphQL value for this, usually `SCREAMING_CASE` # @option kwargs [String] :description, the GraphQL description for this value, present in documentation # @option kwargs [String] :comment, the GraphQL comment for this value, present in documentation # @option kwargs [::Object] :value the translated Ruby value for this object (defaults to `graphql_name`) # @option kwargs [::Object] :value_method, the method name to fetch `graphql_name` (defaults to `graphql_name.downcase`) # @option kwargs [String] :deprecation_reason if this object is deprecated, include a message here # @param value_method [Symbol, false] A method to generate for this value, or `false` to skip generation # @return [void] # @see {Schema::EnumValue} which handles these inputs by default def value(*args, value_method: nil, **kwargs, &block) kwargs[:owner] = self value = enum_value_class.new(*args, **kwargs, &block) if value_method || (value_methods && value_method != false) generate_value_method(value, value_method) end key = value.graphql_name prev_value = own_values[key] case prev_value when nil own_values[key] = value when GraphQL::Schema::EnumValue own_values[key] = [prev_value, value] when Array prev_value << value else raise "Invariant: Unexpected enum value for #{key.inspect}: #{prev_value.inspect}" end value end # @return [Array] Possible values of this enum def enum_values(context = GraphQL::Query::NullContext.instance) inherited_values = superclass.respond_to?(:enum_values) ? superclass.enum_values(context) : nil visible_values = [] types = Warden.types_from_context(context) own_values.each do |key, values_entry| visible_value = nil if values_entry.is_a?(Array) values_entry.each do |v| if types.visible_enum_value?(v, context) if visible_value.nil? visible_value = v visible_values << v else raise DuplicateNamesError.new( duplicated_name: v.path, duplicated_definition_1: visible_value.inspect, duplicated_definition_2: v.inspect ) end end end elsif types.visible_enum_value?(values_entry, context) visible_values << values_entry end end if inherited_values # Local values take precedence over inherited ones inherited_values.each do |i_val| if !visible_values.any? { |v| v.graphql_name == i_val.graphql_name } visible_values << i_val end end end visible_values end # @return [Array] An unfiltered list of all definitions def all_enum_value_definitions all_defns = if superclass.respond_to?(:all_enum_value_definitions) superclass.all_enum_value_definitions else [] end @own_values && @own_values.each do |_key, value| if value.is_a?(Array) all_defns.concat(value) else all_defns << value end end all_defns end # @return [Hash GraphQL::Schema::EnumValue>] Possible values of this enum, keyed by name. def values(context = GraphQL::Query::NullContext.instance) enum_values(context).each_with_object({}) { |val, obj| obj[val.graphql_name] = val } end # @return [Class] for handling `value(...)` inputs and building `GraphQL::Enum::EnumValue`s out of them def enum_value_class(new_enum_value_class = nil) if new_enum_value_class @enum_value_class = new_enum_value_class elsif defined?(@enum_value_class) && @enum_value_class @enum_value_class else superclass <= GraphQL::Schema::Enum ? superclass.enum_value_class : nil end end def value_methods(new_value = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_value) if @value_methods != nil @value_methods else find_inherited_value(:value_methods, false) end else @value_methods = new_value end end def kind GraphQL::TypeKinds::ENUM end def validate_non_null_input(value_name, ctx, max_errors: nil) allowed_values = ctx.types.enum_values(self) matching_value = allowed_values.find { |v| v.graphql_name == value_name } if matching_value.nil? GraphQL::Query::InputValidationResult.from_problem("Expected #{GraphQL::Language.serialize(value_name)} to be one of: #{allowed_values.map(&:graphql_name).join(', ')}") else nil end # rescue MissingValuesError # nil end # Called by the runtime when a field returns a value to give back to the client. # This method checks that the incoming {value} matches one of the enum's defined values. # @param value [Object] Any value matching the values for this enum. # @param ctx [GraphQL::Query::Context] # @raise [GraphQL::Schema::Enum::UnresolvedValueError] if {value} doesn't match a configured value or if the matching value isn't authorized. # @return [String] The GraphQL-ready string for {value} def coerce_result(value, ctx) types = ctx.types all_values = types ? types.enum_values(self) : values.each_value enum_value = all_values.find { |val| val.value == value } if enum_value && (was_authed = enum_value.authorized?(ctx)) enum_value.graphql_name else raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx, authorized: was_authed) end end # Called by the runtime with incoming string representations from a query. # It will match the string to a configured by name or by Ruby value. # @param value_name [String, Object] A string from a GraphQL query, or a Ruby value matching a `value(..., value: ...)` configuration # @param ctx [GraphQL::Query::Context] # @raise [GraphQL::UnauthorizedEnumValueError] if an {EnumValue} matches but returns false for `.authorized?`. Goes to {Schema.unauthorized_object}. # @return [Object] The Ruby value for the matched {GraphQL::Schema::EnumValue} def coerce_input(value_name, ctx) all_values = ctx.types ? ctx.types.enum_values(self) : values.each_value # This tries matching by incoming GraphQL string, then checks Ruby-defined values if v = (all_values.find { |val| val.graphql_name == value_name } || all_values.find { |val| val.value == value_name }) if v.authorized?(ctx) v.value else raise GraphQL::UnauthorizedEnumValueError.new(type: self, enum_value: v, context: ctx) end else nil end end def inherited(child_class) if child_class.name # Don't assign a custom error class to anonymous classes # because they would end up with names like `#::UnresolvedValueError` which messes up bug trackers child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError)) end child_class.class_exec { @value_methods = nil } super end private def own_values @own_values ||= {} end def generate_value_method(value, configured_value_method) return if configured_value_method == false value_method_name = configured_value_method || value.graphql_name.downcase if respond_to?(value_method_name.to_sym) warn "Failed to define value method for :#{value_method_name}, because " \ "#{value.owner.name || value.owner.graphql_name} already responds to that method. Use `value_method:` to override the method name " \ "or `value_method: false` to disable Enum value method generation." return end define_singleton_method(value_method_name) { value.graphql_name } end end enum_value_class(GraphQL::Schema::EnumValue) end end end graphql-2.6.0/lib/graphql/schema/argument.rb0000644000004100000410000004203715173430257021035 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Argument include GraphQL::Schema::Member::HasPath include GraphQL::Schema::Member::HasAstNode include GraphQL::Schema::Member::HasAuthorization include GraphQL::Schema::Member::HasDirectives include GraphQL::Schema::Member::HasDeprecationReason include GraphQL::Schema::Member::HasValidators include GraphQL::EmptyObjects # @return [String] the GraphQL name for this argument, camelized unless `camelize: false` is provided attr_reader :name alias :graphql_name :name # @return [GraphQL::Schema::Field, Class] The field or input object this argument belongs to attr_reader :owner # @param new_prepare [Method, Proc] # @return [Symbol] A method or proc to call to transform this value before sending it to field resolution method def prepare(new_prepare = NOT_CONFIGURED) if new_prepare != NOT_CONFIGURED @prepare = new_prepare end @prepare end # @return [Symbol] This argument's name in Ruby keyword arguments attr_reader :keyword # @return [Class, Module, nil] If this argument should load an application object, this is the type of object to load attr_reader :loads # @return [Boolean] true if a resolver defined this argument def from_resolver? @from_resolver end # @param arg_name [Symbol] # @param type_expr # @param desc [String] # @param type [Class, Array] Input type; positional argument also accepted # @param name [Symbol] positional argument also accepted # @param loads [Class, Array] A GraphQL type to load for the given ID when one is present # @param definition_block [Proc] Called with the newly-created {Argument} # @param owner [Class] Private, used by GraphQL-Ruby during schema definition # @param required [Boolean, :nullable] if true, this argument is non-null; if false, this argument is nullable. If `:nullable`, then the argument must be provided, though it may be `null`. # @param description [String] # @param default_value [Object] # @param loads [Class, Array] A GraphQL type to load for the given ID when one is present # @param as [Symbol] Override the keyword name when passed to a method # @param prepare [Symbol] A method to call to transform this argument's valuebefore sending it to field resolution # @param camelize [Boolean] if true, the name will be camelized when building the schema # @param from_resolver [Boolean] if true, a Resolver class defined this argument # @param directives [Hash{Class => Hash}] # @param deprecation_reason [String] # @param validates [Hash, nil] Options for building validators, if any should be applied # @param replace_null_with_default [Boolean] if `true`, incoming values of `null` will be replaced with the configured `default_value` # @param comment [String] Private, used by GraphQL-Ruby when parsing GraphQL schema files # @param ast_node [GraphQL::Language::Nodes::InputValueDefinition] Private, used by GraphQL-Ruby when parsing schema files def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, comment: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block) arg_name ||= name @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s) NameValidator.validate!(@name) @type_expr = type_expr || type @description = desc || description @comment = comment @null = required != true @default_value = default_value if replace_null_with_default if !default_value? raise ArgumentError, "`replace_null_with_default: true` requires a default value, please provide one with `default_value: ...`" end @replace_null_with_default = true end @owner = owner @as = as @loads = loads @keyword = as || (arg_name.is_a?(Symbol) ? arg_name : Schema::Member::BuildType.underscore(@name).to_sym) @prepare = prepare @ast_node = ast_node @from_resolver = from_resolver self.deprecation_reason = deprecation_reason if directives directives.each do |dir_class, dir_options| directive(dir_class, **dir_options) end end if validates && !validates.empty? self.validates(validates) end if required == :nullable self.owner.validates(required: { argument: arg_name }) end if definition_block # `self` will still be self, it will also be the first argument to the block: instance_exec(self, &definition_block) end end def inspect "#<#{self.class} #{path}: #{type.to_type_signature}#{description ? " @description=#{description.inspect}" : ""}>" end # @param default_value [Object] The value to use when the client doesn't provide one # @return [Object] the value used when the client doesn't provide a value for this argument def default_value(new_default_value = NOT_CONFIGURED) if new_default_value != NOT_CONFIGURED @default_value = new_default_value end @default_value end # @return [Boolean] True if this argument has a default value def default_value? @default_value != NOT_CONFIGURED end def replace_null_with_default? @replace_null_with_default end attr_writer :description # @return [String] Documentation for this argument def description(text = nil) if text @description = text else @description end end attr_writer :comment # @return [String] Comment for this argument def comment(text = nil) if text @comment = text else @comment end end # @return [String] Deprecation reason for this argument def deprecation_reason(text = nil) if text self.deprecation_reason = text else super() end end def deprecation_reason=(new_reason) validate_deprecated_or_optional(null: @null, deprecation_reason: new_reason) super end def visible?(context) true end def authorizes?(_context) self.method(:authorized?).owner != GraphQL::Schema::Argument end def authorized?(obj, value, ctx) authorized_as_type?(obj, value, ctx, as_type: type) end def authorized_as_type?(obj, value, ctx, as_type:) if value.nil? return true end if as_type.kind.non_null? as_type = as_type.of_type end if as_type.kind.list? value.each do |v| if !authorized_as_type?(obj, v, ctx, as_type: as_type.of_type) return false end end elsif as_type.kind.input_object? return as_type.authorized?(obj, value, ctx) end # None of the early-return conditions were activated, # so this is authorized. true end def type=(new_type) validate_input_type(new_type) # This isn't true for LateBoundTypes, but we can assume those will # be updated via this codepath later in schema setup. if new_type.respond_to?(:non_null?) validate_deprecated_or_optional(null: !new_type.non_null?, deprecation_reason: deprecation_reason) end @type = new_type end def type @type ||= begin parsed_type = begin Member::BuildType.parse_type(@type_expr, null: @null) rescue StandardError => err raise ArgumentError, "Couldn't build type for Argument #{@owner.name}.#{name}: #{err.class.name}: #{err.message}", err.backtrace end # Use the setter method to get validations self.type = parsed_type end end def statically_coercible? return @statically_coercible if defined?(@statically_coercible) requires_parent_object = @prepare.is_a?(String) || @prepare.is_a?(Symbol) || @own_validators @statically_coercible = !requires_parent_object end def freeze statically_coercible? super end # Apply the {prepare} configuration to `value`, using methods from `obj`. # Used by the runtime. # @api private def prepare_value(obj, value, context: nil) if type.unwrap.kind.input_object? value = recursively_prepare_input_object(value, type, context) end Schema::Validator.validate!(validators, obj, context, value) if @prepare.nil? value elsif @prepare.is_a?(String) || @prepare.is_a?(Symbol) if obj.nil? # The problem here is, we _used to_ prepare while building variables. # But now we don't have the runtime object there. # # This will have to be called later, when the runtime object _is_ available. value elsif obj.respond_to?(@prepare) obj.public_send(@prepare, value) elsif owner.respond_to?(@prepare) owner.public_send(@prepare, value, context || obj.context) else raise "Invalid prepare for #{@owner.name}.name: #{@prepare.inspect}. "\ "Could not find prepare method #{@prepare} on #{obj.class} or #{owner}." end elsif @prepare.respond_to?(:call) @prepare.call(value, context || obj.context) else raise "Invalid prepare for #{@owner.name}.name: #{@prepare.inspect}" end end # @api private def coerce_into_values(parent_object, values, context, argument_values) arg_name = graphql_name arg_key = keyword default_used = false if values.key?(arg_name) value = values[arg_name] elsif values.key?(arg_key) value = values[arg_key] elsif default_value? value = default_value default_used = true else # no value at all owner.validate_directive_argument(self, nil) return end if value.nil? && replace_null_with_default? value = default_value default_used = true end loaded_value = nil coerced_value = begin type.coerce_input(value, context) rescue StandardError => err context.schema.handle_or_reraise(context, err) end # If this isn't lazy, then the block returns eagerly and assigns the result here # If it _is_ lazy, then we write the lazy to the hash, then update it later argument_values[arg_key] = context.query.after_lazy(coerced_value) do |resolved_coerced_value| owner.validate_directive_argument(self, resolved_coerced_value) prepared_value = begin prepare_value(parent_object, resolved_coerced_value, context: context) rescue StandardError => err context.schema.handle_or_reraise(context, err) end if loads && !from_resolver? loaded_value = begin load_and_authorize_value(owner, prepared_value, context) rescue StandardError => err context.schema.handle_or_reraise(context, err) end end maybe_loaded_value = loaded_value || prepared_value context.query.after_lazy(maybe_loaded_value) do |resolved_loaded_value| # TODO code smell to access such a deeply-nested constant in a distant module argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new( value: resolved_loaded_value, original_value: resolved_coerced_value, definition: self, default_used: default_used, ) end end end def load_and_authorize_value(load_method_owner, coerced_value, context) if coerced_value.nil? return nil end arg_load_method = "load_#{keyword}" if load_method_owner.respond_to?(arg_load_method) custom_loaded_value = if load_method_owner.is_a?(Class) load_method_owner.public_send(arg_load_method, coerced_value, context) else load_method_owner.public_send(arg_load_method, coerced_value) end context.query.after_lazy(custom_loaded_value) do |custom_value| if loads if type.list? loaded_values = [] context.dataloader.run_isolated do custom_value.each_with_index.map { |custom_val, idx| id = coerced_value[idx] context.dataloader.append_job do loaded_values[idx] = load_method_owner.authorize_application_object(self, id, context, custom_val) end } end context.schema.after_any_lazies(loaded_values, &:itself) else load_method_owner.authorize_application_object(self, coerced_value, context, custom_loaded_value) end else custom_value end end elsif loads if type.list? loaded_values = [] # We want to run these list items all together, # but we also need to wait for the result so we can return it :S context.dataloader.run_isolated do coerced_value.each_with_index { |val, idx| context.dataloader.append_job do loaded_values[idx] = load_method_owner.load_and_authorize_application_object(self, val, context) end } end context.schema.after_any_lazies(loaded_values, &:itself) else load_method_owner.load_and_authorize_application_object(self, coerced_value, context) end else coerced_value end end # @api private def validate_default_value return unless default_value? coerced_default_value = begin # This is weird, but we should accept single-item default values for list-type arguments. # If we used `coerce_isolated_input` below, it would do this for us, but it's not really # the right thing here because we expect default values in application format (Ruby values) # not GraphQL format (scalar values). # # But I don't think Schema::List#coerce_result should apply wrapping to single-item lists. prepped_default_value = if default_value.nil? nil elsif (type.kind.list? || (type.kind.non_null? && type.of_type.list?)) && !default_value.respond_to?(:map) [default_value] else default_value end type.coerce_isolated_result(prepped_default_value) unless prepped_default_value.nil? rescue GraphQL::Schema::Enum::UnresolvedValueError # It raises this, which is helpful at runtime, but not here... default_value end res = type.valid_isolated_input?(coerced_default_value) if !res raise InvalidDefaultValueError.new(self) end end class InvalidDefaultValueError < GraphQL::Error def initialize(argument) message = "`#{argument.path}` has an invalid default value: `#{argument.default_value.inspect}` isn't accepted by `#{argument.type.to_type_signature}`; update the default value or the argument type." super(message) end end private def recursively_prepare_input_object(value, type, context) if type.non_null? type = type.of_type end if type.list? && !value.nil? inner_type = type.of_type value.map { |v| recursively_prepare_input_object(v, inner_type, context) } elsif value.is_a?(GraphQL::Schema::InputObject) value.validate_for(context) value.prepare else value end end def validate_input_type(input_type) if input_type.is_a?(String) || input_type.is_a?(GraphQL::Schema::LateBoundType) # Do nothing; assume this will be validated later elsif input_type.kind.non_null? || input_type.kind.list? validate_input_type(input_type.unwrap) elsif !input_type.kind.input? raise ArgumentError, "Invalid input type for #{path}: #{input_type.graphql_name}. Must be scalar, enum, or input object, not #{input_type.kind.name}." else # It's an input type, we're OK end end def validate_deprecated_or_optional(null:, deprecation_reason:) if deprecation_reason && !null raise ArgumentError, "Required arguments cannot be deprecated: #{path}." end end end end end graphql-2.6.0/lib/graphql/schema/object.rb0000644000004100000410000001265515173430257020464 0ustar www-datawww-data# frozen_string_literal: true require "graphql/query/null_context" module GraphQL class Schema class Object < GraphQL::Schema::Member extend GraphQL::Schema::Member::HasAuthorization extend GraphQL::Schema::Member::HasFields extend GraphQL::Schema::Member::HasInterfaces include Member::HasDataloader # Raised when an Object doesn't have any field defined and hasn't explicitly opted out of this requirement class FieldsAreRequiredError < GraphQL::Error def initialize(object_type) message = "Object types must have fields, but #{object_type.graphql_name} doesn't have any. Define a field for this type, remove it from your schema, or add `has_no_fields(true)` to its definition." super(message) end end # @return [Object] the application object this type is wrapping attr_reader :object # @return [GraphQL::Query::Context] the context instance for this query attr_reader :context # @return [GraphQL::Dataloader] def dataloader context.dataloader end # Call this in a field method to return a value that should be returned to the client # without any further handling by GraphQL. def raw_value(obj) GraphQL::Execution::Interpreter::RawValue.new(obj) end class << self # This is protected so that we can be sure callers use the public method, {.authorized_new} # @see authorized_new to make instances protected :new def wrap_scoped(object, context) scoped_new(object, context) end # This is called by the runtime to return an object to call methods on. def wrap(object, context) authorized_new(object, context) end # Make a new instance of this type _if_ the auth check passes, # otherwise, raise an error. # # Probably only the framework should call this method. # # This might return a {GraphQL::Execution::Lazy} if the user-provided `.authorized?` # hook returns some lazy value (like a Promise). # # The reason that the auth check is in this wrapper method instead of {.new} is because # of how it might return a Promise. It would be weird if `.new` returned a promise; # It would be a headache to try to maintain Promise-y state inside a {Schema::Object} # instance. So, hopefully this wrapper method will do the job. # # @param object [Object] The thing wrapped by this object # @param context [GraphQL::Query::Context] # @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy] # @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false` def authorized_new(object, context) context.query.current_trace.begin_authorized(self, object, context) begin maybe_lazy_auth_val = context.query.current_trace.authorized(query: context.query, type: self, object: object) do begin authorized?(object, context) rescue GraphQL::UnauthorizedError => err context.schema.unauthorized_object(err) rescue StandardError => err context.query.handle_or_reraise(err) end end ensure context.query.current_trace.end_authorized(self, object, context, maybe_lazy_auth_val) end auth_val = if context.schema.lazy?(maybe_lazy_auth_val) GraphQL::Execution::Lazy.new do context.query.current_trace.begin_authorized(self, object, context) context.query.current_trace.authorized_lazy(query: context.query, type: self, object: object) do res = context.schema.sync_lazy(maybe_lazy_auth_val) context.query.current_trace.end_authorized(self, object, context, res) res end end else maybe_lazy_auth_val end context.query.after_lazy(auth_val) do |is_authorized| if is_authorized self.new(object, context) else # It failed the authorization check, so go to the schema's authorized object hook err = GraphQL::UnauthorizedError.new(object: object, type: self, context: context) # If a new value was returned, wrap that instead of the original value begin new_obj = context.schema.unauthorized_object(err) if new_obj self.new(new_obj, context) else nil end end end end end def scoped_new(object, context) self.new(object, context) end end def initialize(object, context) @object = object @context = context end class << self # Set up a type-specific invalid null error to use when this object's non-null fields wrongly return `nil`. # It should help with debugging and bug tracker integrations. def const_missing(name) if name == :InvalidNullError custom_err_class = GraphQL::InvalidNullError.subclass_for(self) const_set(:InvalidNullError, custom_err_class) custom_err_class else super end end def kind GraphQL::TypeKinds::OBJECT end end end end end graphql-2.6.0/lib/graphql/schema/subscription.rb0000644000004100000410000001723115173430257021735 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema # This class can be extended to create fields on your subscription root. # # It provides hooks for the different parts of the subscription lifecycle: # # - `#authorized?`: called before initial subscription and subsequent updates # - `#subscribe`: called for the initial subscription # - `#update`: called for subsequent update # # Also, `#unsubscribe` terminates the subscription. class Subscription < GraphQL::Schema::Resolver extend GraphQL::Schema::Resolver::HasPayloadType extend GraphQL::Schema::Member::HasFields NO_UPDATE = :no_update null false # @api private def initialize(object:, context:, field:) super # Figure out whether this is an update or an initial subscription @mode = context.query.subscription_update? ? :update : :subscribe @subscription_written = false @original_arguments = nil if (subs_ns = context.namespace(:subscriptions)) && (sub_insts = subs_ns[:subscriptions]) sub_insts[context.current_path] = self end end # @api private def resolve_with_support(**args) @original_arguments = args # before `loads:` have been run result = nil unsubscribed = true unsubscribed_result = catch :graphql_subscription_unsubscribed do result = super unsubscribed = false end if unsubscribed if unsubscribed_result context.namespace(:subscriptions)[:final_update] = true unsubscribed_result else context.skip end else result end end # Implement the {Resolve} API. # You can implement this if you want code to run for _both_ the initial subscription # and for later updates. Or, implement {#subscribe} and {#update} def resolve(**args) # Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever # have an unexpected `@mode` public_send("resolve_#{@mode}", **args) end # Wrap the user-defined `#subscribe` hook # @api private def resolve_subscribe(**args) ret_val = !args.empty? ? subscribe(**args) : subscribe if ret_val == :no_response context.skip else ret_val end end # The default implementation returns nothing on subscribe. # Override it to return an object or # `:no_response` to (explicitly) return nothing. def subscribe(args = {}) :no_response end # Wrap the user-provided `#update` hook # @api private def resolve_update(**args) ret_val = !args.empty? ? update(**args) : update if ret_val == NO_UPDATE context.namespace(:subscriptions)[:no_update] = true context.skip else ret_val end end # The default implementation returns the root object. # Override it to return {NO_UPDATE} if you want to # skip updates sometimes. Or override it to return a different object. def update(args = {}) object end # If an argument is flagged with `loads:` and no object is found for it, # remove this subscription (assuming that the object was deleted in the meantime, # or that it became inaccessible). def load_application_object_failed(err) if @mode == :update unsubscribe end super end # Call this to halt execution and remove this subscription from the system # @param update_value [Object] if present, deliver this update before unsubscribing # @return [void] def unsubscribe(update_value = nil) context.namespace(:subscriptions)[:unsubscribed] = true throw :graphql_subscription_unsubscribed, update_value end # Call this method to provide a new subscription_scope; OR # call it without an argument to get the subscription_scope # @param new_scope [Symbol] # @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription. # @return [Symbol] def self.subscription_scope(new_scope = NOT_CONFIGURED, optional: false) if new_scope != NOT_CONFIGURED @subscription_scope = new_scope @subscription_scope_optional = optional elsif defined?(@subscription_scope) @subscription_scope else find_inherited_value(:subscription_scope) end end def self.subscription_scope_optional? if defined?(@subscription_scope_optional) @subscription_scope_optional else find_inherited_value(:subscription_scope_optional, false) end end # This is called during initial subscription to get a "name" for this subscription. # Later, when `.trigger` is called, this will be called again to build another "name". # Any subscribers with matching topic will begin the update flow. # # The default implementation creates a string using the field name, subscription scope, and argument keys and values. # In that implementation, only `.trigger` calls with _exact matches_ result in updates to subscribers. # # To implement a filtered stream-type subscription flow, override this method to return a string with field name and subscription scope. # Then, implement {#update} to compare its arguments to the current `object` and return {NO_UPDATE} when an # update should be filtered out. # # @see {#update} for how to skip updates when an event comes with a matching topic. # @param arguments [Hash Object>] The arguments for this topic, in GraphQL-style (camelized strings) # @param field [GraphQL::Schema::Field] # @param scope [Object, nil] A value corresponding to `.trigger(... scope:)` (for updates) or the `subscription_scope` found in `context` (for initial subscriptions). # @return [String] An identifier corresponding to a stream of updates def self.topic_for(arguments:, field:, scope:) Subscriptions::Serialize.dump_recursive([scope, field.graphql_name, arguments]) end # Calls through to `schema.subscriptions` to register this subscription with the backend. # This is automatically called by GraphQL-Ruby after a query finishes successfully, # but if you need to commit the subscription during `#subscribe`, you can call it there. # (This method also sets a flag showing that this subscription was already written.) # # If you call this method yourself, you may also need to {#unsubscribe} # or call `subscriptions.delete_subscription` to clean up the database if the query crashes with an error # later in execution. # @return [void] def write_subscription if subscription_written? raise GraphQL::Error, "`write_subscription` was called but `#{self.class}#subscription_written?` is already true. Remove a call to `write subscription`." else @subscription_written = true context.schema.subscriptions.write_subscription(context.query, [event]) end nil end # @return [Boolean] `true` if {#write_subscription} was called already def subscription_written? @subscription_written end # @return [Subscriptions::Event] This object is used as a representation of this subscription for the backend def event @event ||= Subscriptions::Event.new( name: field.name, arguments: @original_arguments, context: context, field: field, ) end end end end graphql-2.6.0/lib/graphql/schema/built_in_types.rb0000644000004100000410000000044715173430257022243 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema BUILT_IN_TYPES = { "Int" => GraphQL::Types::Int, "String" => GraphQL::Types::String, "Float" => GraphQL::Types::Float, "Boolean" => GraphQL::Types::Boolean, "ID" => GraphQL::Types::ID, } end end graphql-2.6.0/lib/graphql/schema/late_bound_type.rb0000644000004100000410000000157015173430257022365 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema # A stand-in for a type which will be resolved in a given schema, by name. # TODO: support argument types too, make this a public API somehow # @api Private class LateBoundType attr_reader :name alias :graphql_name :name def initialize(local_name) @name = local_name @to_non_null_type = nil @to_list_type = nil end def unwrap self end def to_non_null_type @to_non_null_type ||= GraphQL::Schema::NonNull.new(self) end def to_list_type @to_list_type ||= GraphQL::Schema::List.new(self) end def to_type_signature name end def inspect "#" end def non_null? false end alias :to_s :inspect end end end graphql-2.6.0/lib/graphql/schema/non_null.rb0000644000004100000410000000340515173430257021033 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema # Represents a non null type in the schema. # Wraps a {Schema::Member} when it is required. # @see {Schema::Member::TypeSystemHelpers#to_non_null_type} class NonNull < GraphQL::Schema::Wrapper include Schema::Member::ValidatesInput # @return [GraphQL::TypeKinds::NON_NULL] def kind GraphQL::TypeKinds::NON_NULL end # @return [true] def non_null? true end # @return [Boolean] True if this type wraps a list type def list? @of_type.list? end def to_type_signature @type_signature ||= -"#{@of_type.to_type_signature}!" end def inspect "#<#{self.class.name} @of_type=#{@of_type.inspect}>" end def validate_input(value, ctx, max_errors: nil) if value.nil? result = GraphQL::Query::InputValidationResult.new result.add_problem("Expected value to not be null") result else of_type.validate_input(value, ctx, max_errors: max_errors) end end # This is for introspection, where it's expected the name will be `null` def graphql_name nil end def coerce_input(value, ctx) # `.validate_input` above is used for variables, but this method is used for arguments if value.nil? raise GraphQL::ExecutionError, "`null` is not a valid input for `#{to_type_signature}`, please provide a value for this argument." end of_type.coerce_input(value, ctx) end def coerce_result(value, ctx) of_type.coerce_result(value, ctx) end # This is for implementing introspection def description nil end end end end graphql-2.6.0/lib/graphql/schema/loader.rb0000644000004100000410000002027215173430257020456 0ustar www-datawww-data# frozen_string_literal: true module GraphQL 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 [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) } 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 directives = [] schema.fetch("directives", []).each do |directive| next if GraphQL::Schema.default_directives.include?(directive.fetch("name")) directives << define_directive(directive, type_resolver) end Class.new(GraphQL::Schema) do add_type_and_traverse(types.values, root: false) orphan_types(types.values.select { |t| t.kind.object? }) directives(directives) description(schema["description"]) 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 NullScalarCoerce = ->(val, _ctx) { val } class << self private def resolve_type(types, type) 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? GraphQL::Schema::LateBoundType.new(type_name) else type end when "LIST" Schema::List.new(resolve_type(types, type.fetch("ofType"))) when "NON_NULL" Schema::NonNull.new(resolve_type(types, type.fetch("ofType"))) else fail GraphQL::RequiredImplementationMissingError, "#{kind} not implemented" end end def extract_default_value(default_value_str, input_value_ast) case input_value_ast when String, Integer, Float, TrueClass, FalseClass input_value_ast when GraphQL::Language::Nodes::Enum input_value_ast.name when GraphQL::Language::Nodes::NullValue nil when GraphQL::Language::Nodes::InputObject input_value_ast.to_h when Array input_value_ast.map { |element| extract_default_value(default_value_str, element) } else raise( "Encountered unexpected type when loading default value. "\ "input_value_ast.class is #{input_value_ast.class} "\ "default_value is #{default_value_str}" ) end end def define_type(type, type_resolver) loader = self case type.fetch("kind") when "ENUM" 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["deprecationReason"], ) end end when "INTERFACE" 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" Class.new(GraphQL::Schema::InputObject) do graphql_name(type["name"]) description(type["description"]) loader.build_arguments(self, type["inputFields"], type_resolver) end 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 when "SCALAR" type_name = type.fetch("name") if (builtin = GraphQL::Schema::BUILT_IN_TYPES[type_name]) builtin else Class.new(GraphQL::Schema::Scalar) do graphql_name(type["name"]) description(type["description"]) specified_by_url(type["specifiedByURL"]) end end when "UNION" 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 def define_directive(directive, type_resolver) loader = self Class.new(GraphQL::Schema::Directive) do graphql_name(directive["name"]) description(directive["description"]) locations(*directive["locations"].map(&:to_sym)) repeatable(directive["isRepeatable"]) loader.build_arguments(self, directive["args"], type_resolver) end end public def build_fields(type_defn, fields, type_resolver) loader = self fields.each do |field_hash| unwrapped_field_hash = field_hash while (of_type = unwrapped_field_hash["ofType"]) unwrapped_field_hash = of_type end type_defn.field( field_hash["name"], type: type_resolver.call(field_hash["type"]), description: field_hash["description"], deprecation_reason: field_hash["deprecationReason"], null: true, camelize: false, connection_extension: nil, ) do if !field_hash["args"].empty? 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"], deprecation_reason: arg["deprecationReason"], required: false, camelize: 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 end graphql-2.6.0/lib/graphql/schema/directive.rb0000644000004100000410000002521715173430257021172 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema # Subclasses of this can influence how {GraphQL::Execution::Interpreter} runs queries. # # - {.include?}: if it returns `false`, the field or fragment will be skipped altogether, as if it were absent # - {.resolve}: Wraps field resolution (so it should call `yield` to continue) class Directive < GraphQL::Schema::Member extend GraphQL::Schema::Member::HasArguments extend GraphQL::Schema::Member::HasArguments::HasDirectiveArguments extend GraphQL::Schema::Member::HasValidators class << self # Directives aren't types, they don't have kinds. undef_method :kind def path "@#{super}" end # Return a name based on the class name, # but downcase the first letter. def default_graphql_name @default_graphql_name ||= begin camelized_name = super.dup camelized_name[0] = camelized_name[0].downcase -camelized_name end end def locations(*new_locations) if !new_locations.empty? is_runtime = false new_locations.each do |new_loc| loc_sym = new_loc.to_sym if !LOCATIONS.include?(loc_sym) raise ArgumentError, "#{self} (#{self.graphql_name}) has an invalid directive location: `locations #{new_loc}` " end is_runtime ||= RUNTIME_LOCATIONS.include?(loc_sym) end @locations = new_locations @is_runtime = is_runtime else @locations ||= (superclass.respond_to?(:locations) ? superclass.locations : []) end end def runtime? @is_runtime end def default_directive(new_default_directive = nil) if new_default_directive != nil @default_directive = new_default_directive elsif @default_directive.nil? @default_directive = (superclass.respond_to?(:default_directive) ? superclass.default_directive : false) else !!@default_directive end end def default_directive? default_directive end # If false, this part of the query won't be evaluated def include?(_object, arguments, context) static_include?(arguments, context) end # Determines whether {Execution::Lookahead} considers the field to be selected def static_include?(_arguments, _context) true end # Continuing is passed as a block; `yield` to continue def resolve(object, arguments, context) yield end # Continuing is passed as a block, yield to continue. def resolve_each(object, arguments, context) yield end def validate!(arguments, context) Schema::Validator.validate!(validators, self, context, arguments) end def on_field? locations.include?(FIELD) end def on_fragment? locations.include?(FRAGMENT_SPREAD) && locations.include?(INLINE_FRAGMENT) end def on_operation? locations.include?(QUERY) && locations.include?(MUTATION) && locations.include?(SUBSCRIPTION) end def repeatable? !!@repeatable end def repeatable(new_value) @repeatable = new_value end private def inherited(subclass) super parent_class = self subclass.class_exec do @default_graphql_name ||= nil @locations = parent_class.locations @is_runtime = parent_class.runtime? @repeatable = false end end end # @return [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class, Module] attr_reader :owner # @return [GraphQL::Interpreter::Arguments] attr_reader :arguments class InvalidArgumentError < GraphQL::Error end def initialize(owner, **arguments) @owner = owner assert_valid_owner # It's be nice if we had the real context here, but we don't. What we _would_ get is: # - error handling # - lazy resolution # Probably, those won't be needed here, since these are configuration arguments, # not runtime arguments. context = Query::NullContext.instance self.class.all_argument_definitions.each do |arg_defn| keyword = arg_defn.keyword arg_type = arg_defn.type if arguments.key?(keyword) value = arguments[keyword] # This is a Ruby-land value; convert it to graphql for validation graphql_value = begin coerce_value = value if arg_type.list? && (!coerce_value.nil?) && (!coerce_value.is_a?(Array)) # When validating inputs, GraphQL accepts a single item # and implicitly converts it to a one-item list. # However, we're using result coercion here to go from Ruby value # to GraphQL value, so it doesn't have that feature. # Keep the GraphQL-type behavior but implement it manually: wrap_type = arg_type while wrap_type.list? if wrap_type.non_null? wrap_type = wrap_type.of_type end wrap_type = wrap_type.of_type coerce_value = [coerce_value] end end arg_type.coerce_isolated_result(coerce_value) rescue GraphQL::Schema::Enum::UnresolvedValueError # Let validation handle this value end else value = graphql_value = nil end result = arg_type.validate_input(graphql_value, context) if !result.valid? raise InvalidArgumentError, "@#{graphql_name}.#{arg_defn.graphql_name} on #{owner.path} is invalid (#{value.inspect}): #{result.problems.first["explanation"]}" end end self.class.validate!(arguments, context) @arguments = self.class.coerce_arguments(nil, arguments, context) if @arguments.is_a?(GraphQL::ExecutionError) raise @arguments end end def graphql_name self.class.graphql_name end LOCATIONS = [ *(RUNTIME_LOCATIONS = [ QUERY = :QUERY, MUTATION = :MUTATION, SUBSCRIPTION = :SUBSCRIPTION, FIELD = :FIELD, FRAGMENT_DEFINITION = :FRAGMENT_DEFINITION, FRAGMENT_SPREAD = :FRAGMENT_SPREAD, INLINE_FRAGMENT = :INLINE_FRAGMENT, VARIABLE_DEFINITION = :VARIABLE_DEFINITION, ]), SCHEMA = :SCHEMA, SCALAR = :SCALAR, OBJECT = :OBJECT, FIELD_DEFINITION = :FIELD_DEFINITION, ARGUMENT_DEFINITION = :ARGUMENT_DEFINITION, INTERFACE = :INTERFACE, UNION = :UNION, ENUM = :ENUM, ENUM_VALUE = :ENUM_VALUE, INPUT_OBJECT = :INPUT_OBJECT, INPUT_FIELD_DEFINITION = :INPUT_FIELD_DEFINITION, ] DEFAULT_DEPRECATION_REASON = 'No longer supported' LOCATION_DESCRIPTIONS = { QUERY: 'Location adjacent to a query operation.', MUTATION: 'Location adjacent to a mutation operation.', SUBSCRIPTION: 'Location adjacent to a subscription operation.', FIELD: 'Location adjacent to a field.', FRAGMENT_DEFINITION: 'Location adjacent to a fragment definition.', FRAGMENT_SPREAD: 'Location adjacent to a fragment spread.', INLINE_FRAGMENT: 'Location adjacent to an inline fragment.', SCHEMA: 'Location adjacent to a schema definition.', SCALAR: 'Location adjacent to a scalar definition.', OBJECT: 'Location adjacent to an object type definition.', FIELD_DEFINITION: 'Location adjacent to a field definition.', ARGUMENT_DEFINITION: 'Location adjacent to an argument definition.', INTERFACE: 'Location adjacent to an interface definition.', UNION: 'Location adjacent to a union definition.', ENUM: 'Location adjacent to an enum definition.', ENUM_VALUE: 'Location adjacent to an enum value definition.', INPUT_OBJECT: 'Location adjacent to an input object type definition.', INPUT_FIELD_DEFINITION: 'Location adjacent to an input object field definition.', VARIABLE_DEFINITION: 'Location adjacent to a variable definition.', } private def assert_valid_owner case @owner when Class if @owner < GraphQL::Schema::Object assert_has_location(OBJECT) elsif @owner < GraphQL::Schema::Union assert_has_location(UNION) elsif @owner < GraphQL::Schema::Enum assert_has_location(ENUM) elsif @owner < GraphQL::Schema::InputObject assert_has_location(INPUT_OBJECT) elsif @owner < GraphQL::Schema::Scalar assert_has_location(SCALAR) elsif @owner < GraphQL::Schema assert_has_location(SCHEMA) elsif @owner < GraphQL::Schema::Resolver assert_has_location(FIELD_DEFINITION) else raise "Unexpected directive owner class: #{@owner}" end when Module assert_has_location(INTERFACE) when GraphQL::Schema::Argument if @owner.owner.is_a?(GraphQL::Schema::Field) assert_has_location(ARGUMENT_DEFINITION) else assert_has_location(INPUT_FIELD_DEFINITION) end when GraphQL::Schema::Field assert_has_location(FIELD_DEFINITION) when GraphQL::Schema::EnumValue assert_has_location(ENUM_VALUE) else raise "Unexpected directive owner: #{@owner.inspect}" end end def assert_has_location(location) if !self.class.locations.include?(location) raise ArgumentError, <<-MD Directive `@#{self.class.graphql_name}` can't be attached to #{@owner.graphql_name} because #{location} isn't included in its locations (#{self.class.locations.join(", ")}). Use `locations(#{location})` to update this directive's definition, or remove it from #{@owner.graphql_name}. MD end end end end end graphql-2.6.0/lib/graphql/schema/member.rb0000644000004100000410000000261015173430257020453 0ustar www-datawww-data# frozen_string_literal: true require 'graphql/schema/member/base_dsl_methods' require 'graphql/schema/member/graphql_type_names' require 'graphql/schema/member/has_ast_node' require 'graphql/schema/member/has_authorization' require 'graphql/schema/member/has_dataloader' require 'graphql/schema/member/has_directives' require 'graphql/schema/member/has_deprecation_reason' require 'graphql/schema/member/has_interfaces' require 'graphql/schema/member/has_path' require 'graphql/schema/member/has_unresolved_type_error' require 'graphql/schema/member/has_validators' require 'graphql/schema/member/relay_shortcuts' require 'graphql/schema/member/scoped' require 'graphql/schema/member/type_system_helpers' require 'graphql/schema/member/validates_input' module GraphQL class Schema # The base class for things that make up the schema, # eg objects, enums, scalars. # # @api private class Member include GraphQLTypeNames extend BaseDSLMethods extend BaseDSLMethods::ConfigurationExtension introspection(false) extend TypeSystemHelpers extend Scoped extend RelayShortcuts extend HasPath extend HasAstNode extend HasDirectives def self.authorizes?(_ctx) false end end end end require 'graphql/schema/member/has_arguments' require 'graphql/schema/member/has_fields' require 'graphql/schema/member/build_type' graphql-2.6.0/lib/graphql/schema/list.rb0000644000004100000410000000456515173430257020172 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema # Represents a list type in the schema. # Wraps a {Schema::Member} as a list type. # @see Schema::Member::TypeSystemHelpers#to_list_type Create a list type from another GraphQL type class List < GraphQL::Schema::Wrapper include Schema::Member::ValidatesInput # @return [GraphQL::TypeKinds::LIST] def kind GraphQL::TypeKinds::LIST end # @return [true] def list? true end def to_type_signature @type_signature ||= -"[#{@of_type.to_type_signature}]" end def authorizes?(ctx) of_type.authorizes?(ctx) end # This is for introspection, where it's expected the name will be `null` def graphql_name nil end # Also for implementing introspection def description nil end def coerce_result(value, ctx) value.map { |i| i.nil? ? nil : of_type.coerce_result(i, ctx) } end def coerce_input(value, ctx) if value.nil? nil else coerced = ensure_array(value).map { |item| item.nil? ? item : of_type.coerce_input(item, ctx) } ctx.schema.after_any_lazies(coerced, &:itself) end end def validate_non_null_input(value, ctx, max_errors: nil) result = GraphQL::Query::InputValidationResult.new ensure_array(value).each_with_index do |item, index| item_result = of_type.validate_input(item, ctx) unless item_result.valid? if max_errors if max_errors == 0 add_max_errors_reached_message(result) break end max_errors -= 1 end result.merge_result!(index, item_result) end end result.valid? ? nil : result end private def ensure_array(value) # `Array({ a: 1 })` makes `[[:a, 1]]`, so do it manually if value.is_a?(Array) value else [value] end end def add_max_errors_reached_message(result) message = "Too many errors processing list variable, max validation error limit reached. Execution aborted" item_result = GraphQL::Query::InputValidationResult.from_problem(message) result.merge_result!(nil, item_result) end end end end graphql-2.6.0/lib/graphql/schema/warden.rb0000644000004100000410000005666715173430257020511 0ustar www-datawww-data# frozen_string_literal: true require 'set' module GraphQL class Schema # Restrict access to a {GraphQL::Schema} with a user-defined `visible?` implementations. # # When validating and executing a query, all access to schema members # should go through a warden. If you access the schema directly, # you may show a client something that it shouldn't be allowed to see. # # @api private class Warden def self.from_context(context) context.warden || PassThruWarden rescue NoMethodError # this might be a hash which won't respond to #warden PassThruWarden end def self.types_from_context(context) context.types || PassThruWarden rescue NoMethodError # this might be a hash which won't respond to #warden PassThruWarden end def self.use(schema) # no-op end # @param visibility_method [Symbol] a Warden method to call for this entry # @param entry [Object, Array] One or more definitions for a given name in a GraphQL Schema # @param context [GraphQL::Query::Context] # @param warden [Warden] # @return [Object] `entry` or one of `entry`'s items if exactly one of them is visible for this context # @return [nil] If neither `entry` nor any of `entry`'s items are visible for this context def self.visible_entry?(visibility_method, entry, context, warden = Warden.from_context(context)) if entry.is_a?(Array) visible_item = nil entry.each do |item| if warden.public_send(visibility_method, item, context) if visible_item.nil? visible_item = item else raise DuplicateNamesError.new( duplicated_name: item.path, duplicated_definition_1: visible_item.inspect, duplicated_definition_2: item.inspect ) end end end visible_item elsif warden.public_send(visibility_method, entry, context) entry else nil end end # This is used when a caller provides a Hash for context. # We want to call the schema's hooks, but we don't have a full-blown warden. # The `context` arguments to these methods exist purely to simplify the code that # calls methods on this object, so it will have everything it needs. class PassThruWarden class << self def visible_field?(field, ctx); field.visible?(ctx); end def visible_argument?(arg, ctx); arg.visible?(ctx); end def visible_type?(type, ctx); type.visible?(ctx); end def visible_enum_value?(ev, ctx); ev.visible?(ctx); end def visible_type_membership?(tm, ctx); tm.visible?(ctx); end def interface_type_memberships(obj_t, ctx); obj_t.interface_type_memberships; end def arguments(owner, ctx); owner.arguments(ctx); end def loadable?(type, ctx); type.visible?(ctx); end def loadable_possible_types(type, ctx); type.possible_types(ctx); end def visibility_profile @visibility_profile ||= Warden::VisibilityProfile.new(self) end end end class NullWarden def initialize(_filter = nil, context:, schema:) @schema = schema @visibility_profile = Warden::VisibilityProfile.new(self) end # No-op, but for compatibility: attr_writer :skip_warning attr_reader :visibility_profile def visible_field?(field_defn, _ctx = nil, owner = nil); true; end def visible_argument?(arg_defn, _ctx = nil); true; end def visible_type?(type_defn, _ctx = nil); true; end def visible_enum_value?(enum_value, _ctx = nil); enum_value.visible?(Query::NullContext.instance); end def visible_type_membership?(type_membership, _ctx = nil); true; end def interface_type_memberships(obj_type, _ctx = nil); obj_type.interface_type_memberships; end def get_type(type_name); @schema.get_type(type_name, Query::NullContext.instance, false); end # rubocop:disable Development/ContextIsPassedCop def arguments(argument_owner, ctx = nil); argument_owner.all_argument_definitions; end def enum_values(enum_defn); enum_defn.enum_values(Query::NullContext.instance); end # rubocop:disable Development/ContextIsPassedCop def get_argument(parent_type, argument_name); parent_type.get_argument(argument_name); end # rubocop:disable Development/ContextIsPassedCop def types; @schema.types; end # rubocop:disable Development/ContextIsPassedCop def root_type_for_operation(op_name); @schema.root_type_for_operation(op_name); end def directives; @schema.directives.values; end def fields(type_defn); type_defn.all_field_definitions; end # rubocop:disable Development/ContextIsPassedCop def get_field(parent_type, field_name); @schema.get_field(parent_type, field_name); end def reachable_type?(type_name); true; end def loadable?(type, _ctx); true; end def loadable_possible_types(abstract_type, _ctx); union_type.possible_types; end def reachable_types; @schema.types.values; end # rubocop:disable Development/ContextIsPassedCop def possible_types(type_defn); @schema.possible_types(type_defn, Query::NullContext.instance, false); end def interfaces(obj_type); obj_type.interfaces; end end def visibility_profile @visibility_profile ||= VisibilityProfile.new(self) end class VisibilityProfile def initialize(warden) @warden = warden end def directives @warden.directives end def directive_exists?(dir_name) @warden.directives.any? { |d| d.graphql_name == dir_name } end def type(name) @warden.get_type(name) end def field(owner, field_name) @warden.get_field(owner, field_name) end def argument(owner, arg_name) @warden.get_argument(owner, arg_name) end def query_root @warden.root_type_for_operation("query") end def mutation_root @warden.root_type_for_operation("mutation") end def subscription_root @warden.root_type_for_operation("subscription") end def arguments(owner) @warden.arguments(owner) end def fields(owner) @warden.fields(owner) end def possible_types(type) @warden.possible_types(type) end def enum_values(enum_type) @warden.enum_values(enum_type) end def all_types @warden.reachable_types end def interfaces(obj_type) @warden.interfaces(obj_type) end def loadable?(t, ctx) # TODO remove ctx here? @warden.loadable?(t, ctx) end def loadable_possible_types(t, ctx) @warden.loadable_possible_types(t, ctx) end def reachable_type?(type_name) !!@warden.reachable_type?(type_name) end def visible_enum_value?(enum_value, ctx = nil) @warden.visible_enum_value?(enum_value, ctx) end end # @param context [GraphQL::Query::Context] # @param schema [GraphQL::Schema] def initialize(context:, schema:) @schema = schema # Cache these to avoid repeated hits to the inheritance chain when one isn't present @query = @schema.query @mutation = @schema.mutation @subscription = @schema.subscription @context = context @visibility_cache = read_through { |m| check_visible(schema, m) } # Initialize all ivars to improve object shape consistency: @types = @visible_types = @reachable_types = @visible_parent_fields = @visible_possible_types = @visible_fields = @visible_arguments = @visible_enum_arrays = @visible_enum_values = @visible_interfaces = @type_visibility = @type_memberships = @visible_and_reachable_type = @unions = @unfiltered_interfaces = @reachable_type_set = @visibility_profile = @loadable_possible_types = nil @skip_warning = schema.plugins.any? { |(plugin, _opts)| plugin == GraphQL::Schema::Warden } end attr_writer :skip_warning # @return [Hash] Visible types in the schema def types @types ||= begin vis_types = {} @schema.types(@context).each do |n, t| if visible_and_reachable_type?(t) vis_types[n] = t end end vis_types end end # @return [Boolean] True if this type is used for `loads:` but not in the schema otherwise and not _explicitly_ hidden. def loadable?(type, _ctx) visible_type?(type) && !referenced?(type) && (type.respond_to?(:interfaces) ? interfaces(type).all? { |i| loadable?(i, _ctx) } : true) end # This abstract type was determined to be used for `loads` only. # All its possible types are valid possibilities here -- no filtering. def loadable_possible_types(abstract_type, _ctx) @loadable_possible_types ||= read_through do |t| if t.is_a?(Class) # union t.possible_types else @schema.possible_types(abstract_type) end end @loadable_possible_types[abstract_type] end # @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`) def get_type(type_name) @visible_types ||= read_through do |name| type_defn = @schema.get_type(name, @context, false) if type_defn && visible_and_reachable_type?(type_defn) type_defn else nil end end @visible_types[type_name] end # @return [Array] Visible and reachable types in the schema def reachable_types @reachable_types ||= reachable_type_set.to_a end # @return Boolean True if the type is visible and reachable in the schema def reachable_type?(type_name) type = get_type(type_name) # rubocop:disable Development/ContextIsPassedCop -- `self` is query-aware type && reachable_type_set.include?(type) end # @return [GraphQL::Field, nil] The field named `field_name` on `parent_type`, if it exists def get_field(parent_type, field_name) @visible_parent_fields ||= read_through do |type| read_through do |f_name| field_defn = @schema.get_field(type, f_name, @context) if field_defn && visible_field?(field_defn, nil, type) field_defn else nil end end end @visible_parent_fields[parent_type][field_name] end # @return [GraphQL::Argument, nil] The argument named `argument_name` on `parent_type`, if it exists and is visible def get_argument(parent_type, argument_name) argument = parent_type.get_argument(argument_name, @context) return argument if argument && visible_argument?(argument, @context) end # @return [Array] The types which may be member of `type_defn` def possible_types(type_defn) @visible_possible_types ||= read_through { |type_defn| pt = @schema.possible_types(type_defn, @context, false) pt.select { |t| visible_and_reachable_type?(t) } } @visible_possible_types[type_defn] end # @param type_defn [GraphQL::ObjectType, GraphQL::InterfaceType] # @return [Array] Fields on `type_defn` def fields(type_defn) @visible_fields ||= read_through { |t| @schema.get_fields(t, @context).values } @visible_fields[type_defn] end # @param argument_owner [GraphQL::Field, GraphQL::InputObjectType] # @return [Array] Visible arguments on `argument_owner` def arguments(argument_owner, ctx = nil) @visible_arguments ||= read_through { |o| args = o.arguments(@context) if !args.empty? args = args.values args.select! { |a| visible_argument?(a, @context) } args else EmptyObjects::EMPTY_ARRAY end } @visible_arguments[argument_owner] end # @return [Array] Visible members of `enum_defn` def enum_values(enum_defn) @visible_enum_arrays ||= read_through { |e| values = e.enum_values(@context) if values.size == 0 raise GraphQL::Schema::Enum::MissingValuesError.new(e) end values } @visible_enum_arrays[enum_defn] end def visible_enum_value?(enum_value, _ctx = nil) @visible_enum_values ||= read_through { |ev| visible?(ev) } @visible_enum_values[enum_value] end # @return [Array] Visible interfaces implemented by `obj_type` def interfaces(obj_type) @visible_interfaces ||= read_through { |t| ints = t.interfaces(@context) if !ints.empty? ints.select! { |i| visible_type?(i) } end ints } @visible_interfaces[obj_type] end def directives @schema.directives.each_value.select { |d| visible?(d) } end def root_type_for_operation(op_name) root_type = @schema.root_type_for_operation(op_name) if root_type && visible?(root_type) root_type else nil end end # @param owner [Class, Module] If provided, confirm that field has the given owner. def visible_field?(field_defn, _ctx = nil, owner = field_defn.owner) # This field is visible in its own right visible?(field_defn) && # This field's return type is visible visible_and_reachable_type?(field_defn.type.unwrap) && # This field is either defined on this object type, # or the interface it's inherited from is also visible ((field_defn.respond_to?(:owner) && field_defn.owner == owner) || field_on_visible_interface?(field_defn, owner)) end def visible_argument?(arg_defn, _ctx = nil) visible?(arg_defn) && visible_and_reachable_type?(arg_defn.type.unwrap) end def visible_type?(type_defn, _ctx = nil) @type_visibility ||= read_through { |type_defn| visible?(type_defn) } @type_visibility[type_defn] end def visible_type_membership?(type_membership, _ctx = nil) visible?(type_membership) end def interface_type_memberships(obj_type, _ctx = nil) @type_memberships ||= read_through do |obj_t| obj_t.interface_type_memberships end @type_memberships[obj_type] end private def visible_and_reachable_type?(type_defn) @visible_and_reachable_type ||= read_through do |type_defn| next false unless visible_type?(type_defn) next true if root_type?(type_defn) || type_defn.introspection? if type_defn.kind.union? !possible_types(type_defn).empty? && (referenced?(type_defn) || orphan_type?(type_defn)) elsif type_defn.kind.interface? if !possible_types(type_defn).empty? true else if @context.respond_to?(:logger) && (logger = @context.logger) logger.debug { "Interface `#{type_defn.graphql_name}` hidden because it has no visible implementers" } end false end else if referenced?(type_defn) true elsif type_defn.kind.object? # Show this object if it belongs to ... interfaces(type_defn).any? { |t| referenced?(t) } || # an interface which is referenced in the schema union_memberships(type_defn).any? { |t| referenced?(t) || orphan_type?(t) } # or a union which is referenced or added via orphan_types else false end end end @visible_and_reachable_type[type_defn] end def union_memberships(obj_type) @unions ||= read_through { |obj_type| @schema.union_memberships(obj_type).select { |u| visible?(u) } } @unions[obj_type] end # We need this to tell whether a field was inherited by an interface # even when that interface is hidden from `#interfaces` def unfiltered_interfaces(type_defn) @unfiltered_interfaces ||= read_through(&:interfaces) @unfiltered_interfaces[type_defn] end # If this field was inherited from an interface, and the field on that interface is _hidden_, # then treat this inherited field as hidden. # (If it _wasn't_ inherited, then don't hide it for this reason.) def field_on_visible_interface?(field_defn, type_defn) if type_defn.kind.object? any_interface_has_field = false any_interface_has_visible_field = false ints = unfiltered_interfaces(type_defn) ints.each do |interface_type| if (iface_field_defn = interface_type.get_field(field_defn.graphql_name, @context)) any_interface_has_field = true if interfaces(type_defn).include?(interface_type) && visible_field?(iface_field_defn, nil, interface_type) any_interface_has_visible_field = true end end end if any_interface_has_field any_interface_has_visible_field else # it's the object's own field true end else true end end def root_type?(type_defn) @query == type_defn || @mutation == type_defn || @subscription == type_defn end def referenced?(type_defn) members = @schema.references_to(type_defn) members.any? { |m| visible?(m) } end def orphan_type?(type_defn) @schema.orphan_types.include?(type_defn) end def visible?(member) @visibility_cache[member] end def read_through Hash.new { |h, k| h[k] = yield(k) }.compare_by_identity end def check_visible(schema, member) if schema.visible?(member, @context) true elsif @skip_warning false else member_s = member.respond_to?(:path) ? member.path : member.inspect member_type = case member when Module if member.respond_to?(:kind) member.kind.name.downcase else "" end when GraphQL::Schema::Field "field" when GraphQL::Schema::EnumValue "enum value" when GraphQL::Schema::Argument "argument" else "" end schema_s = schema.name ? "#{schema.name}'s" : "" schema_name = schema.name ? "#{schema.name}" : "your schema" warn(ADD_WARDEN_WARNING % { schema_s: schema_s, schema_name: schema_name, member: member_s, member_type: member_type }) @skip_warning = true # only warn once per query # If there's no schema name, add the backtrace for additional context: if schema_s == "" puts caller.map { |l| " #{l}"} end false end end ADD_WARDEN_WARNING = <<~WARNING DEPRECATION: %{schema_s} "%{member}" %{member_type} returned `false` for `.visible?` but `GraphQL::Schema::Visibility` isn't configured yet. Address this warning by adding: use GraphQL::Schema::Visibility to the definition for %{schema_name}. (Future GraphQL-Ruby versions won't check `.visible?` methods by default.) Alternatively, for legacy behavior, add: use GraphQL::Schema::Warden # legacy visibility behavior For more information see: https://graphql-ruby.org/authorization/visibility.html WARNING def reachable_type_set return @reachable_type_set if @reachable_type_set @reachable_type_set = Set.new rt_hash = {} unvisited_types = [] ['query', 'mutation', 'subscription'].each do |op_name| root_type = root_type_for_operation(op_name) unvisited_types << root_type if root_type end unvisited_types.concat(@schema.introspection_system.types.values) directives.each do |dir_class| arguments(dir_class).each do |arg_defn| arg_t = arg_defn.type.unwrap if get_type(arg_t.graphql_name) # rubocop:disable Development/ContextIsPassedCop -- `self` is query-aware unvisited_types << arg_t end end end @schema.orphan_types.each do |orphan_type| if get_type(orphan_type.graphql_name) == orphan_type # rubocop:disable Development/ContextIsPassedCop -- `self` is query-aware unvisited_types << orphan_type end end included_interface_possible_types_set = Set.new until unvisited_types.empty? type = unvisited_types.pop visit_type(type, unvisited_types, @reachable_type_set, rt_hash, included_interface_possible_types_set, include_interface_possible_types: false) end @reachable_type_set end def visit_type(type, unvisited_types, visited_type_set, type_by_name_hash, included_interface_possible_types_set, include_interface_possible_types:) if visited_type_set.add?(type) || (include_interface_possible_types && type.kind.interface? && included_interface_possible_types_set.add?(type)) type_by_name = type_by_name_hash[type.graphql_name] ||= type if type_by_name != type name_1, name_2 = [type.inspect, type_by_name.inspect].sort raise DuplicateNamesError.new( duplicated_name: type.graphql_name, duplicated_definition_1: name_1, duplicated_definition_2: name_2 ) end if type.kind.input_object? # recurse into visible arguments arguments(type).each do |argument| argument_type = argument.type.unwrap unvisited_types << argument_type end elsif type.kind.union? # recurse into visible possible types possible_types(type).each do |possible_type| unvisited_types << possible_type end elsif type.kind.fields? if type.kind.object? # recurse into visible implemented interfaces interfaces(type).each do |interface| unvisited_types << interface end elsif include_interface_possible_types possible_types(type).each do |pt| unvisited_types << pt end end # Don't visit interface possible types -- it's not enough to justify visibility # recurse into visible fields fields(type).each do |field| field_type = field.type.unwrap # In this case, if it's an interface, we want to include visit_type(field_type, unvisited_types, visited_type_set, type_by_name_hash, included_interface_possible_types_set, include_interface_possible_types: true) # recurse into visible arguments arguments(field).each do |argument| argument_type = argument.type.unwrap unvisited_types << argument_type end end end end end end end end graphql-2.6.0/lib/graphql/schema/finder.rb0000644000004100000410000001151315173430257020455 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema # Find schema members using string paths # # @example Finding object types # MySchema.find("SomeObjectType") # # @example Finding fields # MySchema.find("SomeObjectType.myField") # # @example Finding arguments # MySchema.find("SomeObjectType.myField.anArgument") # # @example Finding directives # MySchema.find("@include") # class Finder class MemberNotFoundError < ArgumentError; end def initialize(schema) @schema = schema end def find(path) path = path.split(".") type_or_directive = path.shift if type_or_directive.start_with?("@") directive = schema.directives[type_or_directive[1..-1]] if directive.nil? raise MemberNotFoundError, "Could not find directive `#{type_or_directive}` in schema." end return directive if path.empty? find_in_directive(directive, path: path) else type = schema.get_type(type_or_directive) # rubocop:disable Development/ContextIsPassedCop -- build-time if type.nil? raise MemberNotFoundError, "Could not find type `#{type_or_directive}` in schema." end return type if path.empty? find_in_type(type, path: path) end end private attr_reader :schema def find_in_directive(directive, path:) argument_name = path.shift argument = directive.get_argument(argument_name) # rubocop:disable Development/ContextIsPassedCop -- build-time if argument.nil? raise MemberNotFoundError, "Could not find argument `#{argument_name}` on directive #{directive}." end argument end def find_in_type(type, path:) case type.kind.name when "OBJECT" find_in_fields_type(type, kind: "object", path: path) when "INTERFACE" find_in_fields_type(type, kind: "interface", path: path) when "INPUT_OBJECT" find_in_input_object(type, path: path) when "UNION" # Error out if path that was provided is too long # i.e UnionType.PossibleType.aField # Use PossibleType.aField instead. if invalid = path.first raise MemberNotFoundError, "Cannot select union possible type `#{invalid}`. Select the type directly instead." end when "ENUM" find_in_enum_type(type, path: path) else raise "Unexpected find_in_type: #{type.inspect} (#{path})" end end def find_in_fields_type(type, kind:, path:) field_name = path.shift field = schema.get_field(type, field_name) if field.nil? raise MemberNotFoundError, "Could not find field `#{field_name}` on #{kind} type `#{type.graphql_name}`." end return field if path.empty? find_in_field(field, path: path) end def find_in_field(field, path:) argument_name = path.shift argument = field.get_argument(argument_name) # rubocop:disable Development/ContextIsPassedCop -- build-time if argument.nil? raise MemberNotFoundError, "Could not find argument `#{argument_name}` on field `#{field.name}`." end # Error out if path that was provided is too long # i.e Type.field.argument.somethingBad if invalid = path.first raise MemberNotFoundError, "Cannot select member `#{invalid}` on a field." end argument end def find_in_input_object(input_object, path:) field_name = path.shift input_field = input_object.get_argument(field_name) # rubocop:disable Development/ContextIsPassedCop -- build-time if input_field.nil? raise MemberNotFoundError, "Could not find input field `#{field_name}` on input object type `#{input_object.graphql_name}`." end # Error out if path that was provided is too long # i.e InputType.inputField.bad if invalid = path.first raise MemberNotFoundError, "Cannot select member `#{invalid}` on an input field." end input_field end def find_in_enum_type(enum_type, path:) value_name = path.shift enum_value = enum_type.enum_values.find { |v| v.graphql_name == value_name } # rubocop:disable Development/ContextIsPassedCop -- build-time, not runtime if enum_value.nil? raise MemberNotFoundError, "Could not find enum value `#{value_name}` on enum type `#{enum_type.graphql_name}`." end # Error out if path that was provided is too long # i.e Enum.VALUE.wat if invalid = path.first raise MemberNotFoundError, "Cannot select member `#{invalid}` on an enum value." end enum_value end end end end graphql-2.6.0/lib/graphql/schema/visibility/0000755000004100000410000000000015173430257021047 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/schema/visibility/migration.rb0000644000004100000410000001646015173430257023374 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Visibility # You can use this to see how {GraphQL::Schema::Warden} and {GraphQL::Schema::Visibility::Profile} # handle `.visible?` differently in your schema. # # It runs the same method on both implementations and raises an error when the results diverge. # # To fix the error, modify your schema so that both implementations return the same thing. # Or, open an issue on GitHub to discuss the difference. # # This plugin adds overhead to runtime and may cause unexpected crashes -- **don't** use it in production! # # This plugin adds two keys to `context` when running: # # - `visibility_migration_running: true` # - For the {Schema::Warden} which it instantiates, it adds `visibility_migration_warden_running: true`. # # Use those keys to modify your `visible?` behavior as needed. # # Also, in a pinch, you can set `skip_visibility_migration_error: true` in context to turn off this behavior per-query. # (In that case, it uses {Profile} directly.) # # @example Adding this plugin # # use GraphQL::Schema::Visibility, migration_errors: true # class Migration < GraphQL::Schema::Visibility::Profile class RuntimeTypesMismatchError < GraphQL::Error def initialize(method_called, warden_result, profile_result, method_args) super(<<~ERR) Mismatch in types for `##{method_called}(#{method_args.map(&:inspect).join(", ")})`: #{compare_results(warden_result, profile_result)} Update your `.visible?` implementation to make these implementations return the same value. See: https://graphql-ruby.org/authorization/visibility_migration.html ERR end private def compare_results(warden_result, profile_result) if warden_result.is_a?(Array) && profile_result.is_a?(Array) all_results = warden_result | profile_result all_results.sort_by!(&:graphql_name) entries_text = all_results.map { |entry| "#{entry.graphql_name} (#{entry})"} width = entries_text.map(&:size).max yes = " ✔ " no = " " res = "".dup res << "#{"Result".center(width)} Warden Profile \n" all_results.each_with_index do |entry, idx| res << "#{entries_text[idx].ljust(width)}#{warden_result.include?(entry) ? yes : no}#{profile_result.include?(entry) ? yes : no}\n" end res << "\n" else "- Warden returned: #{humanize(warden_result)}\n\n- Visibility::Profile returned: #{humanize(profile_result)}" end end def humanize(val) case val when Array "#{val.size}: #{val.map { |v| humanize(v) }.sort.inspect}" when Module if val.respond_to?(:graphql_name) "#{val.graphql_name} (#{val.inspect})" else val.inspect end else val.inspect end end end def initialize(context:, schema:, name: nil, visibility:) @name = name @skip_error = context[:skip_visibility_migration_error] || context.is_a?(Query::NullContext) || context.is_a?(Hash) @profile_types = GraphQL::Schema::Visibility::Profile.new(context: context, schema: schema, visibility: visibility) if !@skip_error context[:visibility_migration_running] = true warden_ctx_vals = context.to_h.dup warden_ctx_vals[:visibility_migration_warden_running] = true if schema.const_defined?(:WardenCompatSchema, false) # don't use a defn from a superclass warden_schema = schema.const_get(:WardenCompatSchema, false) else warden_schema = Class.new(schema) warden_schema.use_visibility_profile = false # TODO public API warden_schema.send(:add_type_and_traverse, [warden_schema.query, warden_schema.mutation, warden_schema.subscription].compact, root: true) warden_schema.send(:add_type_and_traverse, warden_schema.directives.values + warden_schema.orphan_types, root: false) schema.const_set(:WardenCompatSchema, warden_schema) end warden_ctx = GraphQL::Query::Context.new(query: context.query, values: warden_ctx_vals) warden_ctx.warden = GraphQL::Schema::Warden.new(schema: warden_schema, context: warden_ctx) warden_ctx.warden.skip_warning = true warden_ctx.types = @warden_types = warden_ctx.warden.visibility_profile end end def loaded_types @profile_types.loaded_types end PUBLIC_PROFILE_METHODS = [ :enum_values, :interfaces, :all_types, :all_types_h, :fields, :loadable?, :loadable_possible_types, :type, :arguments, :argument, :directive_exists?, :directives, :field, :query_root, :mutation_root, :possible_types, :subscription_root, :reachable_type?, :visible_enum_value?, ] PUBLIC_PROFILE_METHODS.each do |profile_method| define_method(profile_method) do |*args| call_method_and_compare(profile_method, args) end end def call_method_and_compare(method, args) res_1 = @profile_types.public_send(method, *args) if @skip_error return res_1 end res_2 = @warden_types.public_send(method, *args) normalized_res_1 = res_1.is_a?(Array) ? Set.new(res_1) : res_1 normalized_res_2 = res_2.is_a?(Array) ? Set.new(res_2) : res_2 if !equivalent_schema_members?(normalized_res_1, normalized_res_2) # Raise the errors with the orignally returned values: err = RuntimeTypesMismatchError.new(method, res_2, res_1, args) raise err else res_1 end end def equivalent_schema_members?(member1, member2) if member1.class != member2.class return false end case member1 when Set member1_array = member1.to_a.sort_by(&:graphql_name) member2_array = member2.to_a.sort_by(&:graphql_name) member1_array.each_with_index do |inner_member1, idx| inner_member2 = member2_array[idx] equivalent_schema_members?(inner_member1, inner_member2) end when GraphQL::Schema::Field member1.ensure_loaded member2.ensure_loaded if member1.introspection? && member2.introspection? member1.inspect == member2.inspect else member1 == member2 end when Module if member1.introspection? && member2.introspection? member1.graphql_name == member2.graphql_name else member1 == member2 end else member1 == member2 end end end end end end graphql-2.6.0/lib/graphql/schema/visibility/visit.rb0000644000004100000410000001651315173430257022540 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Visibility class Visit def initialize(schema, &visit_block) @schema = schema @late_bound_types = nil @unvisited_types = nil # These accumulate between calls to prevent re-visiting the same types @visited_types = Set.new.compare_by_identity @visited_directives = Set.new.compare_by_identity @visit_block = visit_block end def entry_point_types ept = [ @schema.query, @schema.mutation, @schema.subscription, *@schema.introspection_system.types.values, *@schema.orphan_types, ] ept.compact! ept end def entry_point_directives @schema.directives.values end def visit_each(types: entry_point_types, directives: entry_point_directives) @unvisited_types && raise("Can't re-enter `visit_each` on this Visit (another visit is already in progress)") @unvisited_types = types @late_bound_types = [] directives_to_visit = directives while !@unvisited_types.empty? || !@late_bound_types.empty? while (type = @unvisited_types.pop) if @visited_types.add?(type) && @visit_block.call(type) directives_to_visit.concat(type.directives) case type.kind.name when "OBJECT", "INTERFACE" type.interface_type_memberships.each do |itm| append_unvisited_type(type, itm.abstract_type) end if type.kind.interface? type.orphan_types.each do |orphan_type| append_unvisited_type(type, orphan_type) end end type.all_field_definitions.each do |field| field.ensure_loaded if @visit_block.call(field) directives_to_visit.concat(field.directives) append_unvisited_type(type, field.type.unwrap) field.all_argument_definitions.each do |argument| if @visit_block.call(argument) directives_to_visit.concat(argument.directives) append_unvisited_type(field, argument.type.unwrap) end end end end when "INPUT_OBJECT" type.all_argument_definitions.each do |argument| if @visit_block.call(argument) directives_to_visit.concat(argument.directives) append_unvisited_type(type, argument.type.unwrap) end end when "UNION" type.type_memberships.each do |tm| append_unvisited_type(type, tm.object_type) end when "ENUM" type.all_enum_value_definitions.each do |val| if @visit_block.call(val) directives_to_visit.concat(val.directives) end end when "SCALAR" # pass -- nothing else to visit else raise "Invariant: unhandled type kind: #{type.kind.inspect}" end end end directives_to_visit.each do |dir| dir_class = dir.is_a?(Class) ? dir : dir.class if @visited_directives.add?(dir_class) && @visit_block.call(dir_class) dir_class.all_argument_definitions.each do |arg_defn| if @visit_block.call(arg_defn) directives_to_visit.concat(arg_defn.directives) append_unvisited_type(dir_class, arg_defn.type.unwrap) end end end end missed_late_types_streak = 0 while (owner, late_type = @late_bound_types.shift) if (late_type.is_a?(String) && (type = Member::BuildType.constantize(type))) || (late_type.is_a?(LateBoundType) && (type = @visited_types.find { |t| t.graphql_name == late_type.graphql_name })) missed_late_types_streak = 0 # might succeed next round update_type_owner(owner, type) append_unvisited_type(owner, type) else # Didn't find it -- keep trying missed_late_types_streak += 1 @late_bound_types << [owner, late_type] if missed_late_types_streak == @late_bound_types.size raise UnresolvedLateBoundTypeError.new(type: late_type) end end end end @unvisited_types = nil nil end private def append_unvisited_type(owner, type) if type.is_a?(LateBoundType) || type.is_a?(String) @late_bound_types << [owner, type] else @unvisited_types << type end end def update_type_owner(owner, type) case owner when Module if owner.kind.union? owner.assign_type_membership_object_type(type) elsif type.kind.interface? new_interfaces = [] owner.interfaces.each do |int_t| if int_t.is_a?(String) && int_t == type.graphql_name new_interfaces << type elsif int_t.is_a?(LateBoundType) && int_t.graphql_name == type.graphql_name new_interfaces << type else # Don't re-add proper interface definitions, # they were probably already added, maybe with options. end end owner.implements(*new_interfaces) new_interfaces.each do |int| pt = @possible_types[int] ||= [] if !pt.include?(owner) && owner.is_a?(Class) pt << owner end int.interfaces.each do |indirect_int| if indirect_int.is_a?(LateBoundType) && (indirect_int_type = get_type(indirect_int.graphql_name)) # rubocop:disable Development/ContextIsPassedCop update_type_owner(owner, indirect_int_type) end end end end when GraphQL::Schema::Argument, GraphQL::Schema::Field orig_type = owner.type # Apply list/non-null wrapper as needed if orig_type.respond_to?(:of_type) transforms = [] while (orig_type.respond_to?(:of_type)) if orig_type.kind.non_null? transforms << :to_non_null_type elsif orig_type.kind.list? transforms << :to_list_type else raise "Invariant: :of_type isn't non-null or list" end orig_type = orig_type.of_type end transforms.reverse_each { |t| type = type.public_send(t) } end owner.type = type else raise "Unexpected update: #{owner.inspect} #{type.inspect}" end end end end end end graphql-2.6.0/lib/graphql/schema/visibility/profile.rb0000644000004100000410000003702315173430257023041 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Visibility # This class filters the types, fields, arguments, enum values, and directives in a schema # based on the given `context`. # # It's like {Warden}, but has some differences: # # - It doesn't use {Schema}'s top-level caches (eg {Schema.references_to}, {Schema.possible_types}, {Schema.types}) # - It doesn't hide Interface or Union types when all their possible types are hidden. (Instead, those types should implement `.visible?` to hide in that case.) # - It checks `.visible?` on root introspection types # - It can be used to cache profiles by name for re-use across queries class Profile # @return [Schema::Visibility::Profile] def self.from_context(ctx, schema) if ctx.respond_to?(:types) && (types = ctx.types).is_a?(self) types else schema.visibility.profile_for(ctx) end end def self.null_profile(context:, schema:) profile = self.new(name: "NullProfile", context: context, schema: schema) profile.instance_variable_set(:@cached_visible, Hash.new { |k, v| k[v] = true }.compare_by_identity) profile end # @return [Symbol, nil] attr_reader :name def freeze @cached_visible.default_proc = nil @cached_visible_fields.default_proc = nil @cached_visible_fields.each do |type, fields| fields.default_proc = nil end @cached_visible_arguments.default_proc = nil @cached_visible_arguments.each do |type, fields| fields.default_proc = nil end @cached_parent_fields.default_proc = nil @cached_parent_fields.each do |type, fields| fields.default_proc = nil end @cached_parent_arguments.default_proc = nil @cached_parent_arguments.each do |type, args| args.default_proc = nil end @cached_possible_types.default_proc = nil @cached_enum_values.default_proc = nil @cached_fields.default_proc = nil @cached_arguments.default_proc = nil @loadable_possible_types.default_proc = nil @cached_field_result.default_proc = nil @cached_field_result.each { |_, h| h.default_proc = nil } @cached_type_result.default_proc = nil super end def initialize(name: nil, context:, schema:, visibility:) @name = name @context = context @schema = schema @visibility = visibility @all_types = {} @all_types_loaded = false @unvisited_types = [] @all_directives = nil @cached_visible = Hash.new { |h, member| h[member] = @schema.visible?(member, @context) }.compare_by_identity @cached_visible_fields = Hash.new { |h, owner| h[owner] = Hash.new do |h2, field| h2[field] = visible_field_for(owner, field) end.compare_by_identity }.compare_by_identity @cached_visible_arguments = Hash.new do |h, owner| h[owner] = Hash.new do |h2, arg| h2[arg] = if @cached_visible[arg] && (arg_type = arg.type.unwrap) && @cached_visible[arg_type] case owner when GraphQL::Schema::Field @cached_visible_fields[owner.owner][owner] when Class @cached_visible[owner] else raise "Unexpected argument owner for `#{arg.path}`: #{owner.inspect}" end else false end end.compare_by_identity end.compare_by_identity @cached_parent_fields = Hash.new do |h, type| h[type] = Hash.new do |h2, field_name| h2[field_name] = type.get_field(field_name, @context) end end.compare_by_identity @cached_parent_arguments = Hash.new do |h, arg_owner| h[arg_owner] = Hash.new do |h2, arg_name| h2[arg_name] = arg_owner.get_argument(arg_name, @context) end end.compare_by_identity @cached_possible_types = Hash.new { |h, type| h[type] = possible_types_for(type) }.compare_by_identity @cached_enum_values = Hash.new do |h, enum_t| values = non_duplicate_items(enum_t.enum_values(@context), @cached_visible) if values.size == 0 raise GraphQL::Schema::Enum::MissingValuesError.new(enum_t) end h[enum_t] = values end.compare_by_identity @cached_fields = Hash.new do |h, owner| h[owner] = non_duplicate_items(owner.all_field_definitions.each(&:ensure_loaded), @cached_visible_fields[owner]) end.compare_by_identity @cached_arguments = Hash.new do |h, owner| h[owner] = non_duplicate_items(owner.all_argument_definitions, @cached_visible_arguments[owner]) end.compare_by_identity @loadable_possible_types = Hash.new { |h, union_type| h[union_type] = union_type.possible_types }.compare_by_identity # Combined cache for field(owner, field_name) — avoids repeated kind check + parent lookup + visibility check @cached_field_result = Hash.new { |h, owner| h[owner] = Hash.new { |h2, field_name| h2[field_name] = compute_field(owner, field_name) } }.compare_by_identity # Cache for type(type_name) — avoids repeated get_type + visibility + referenced? checks @cached_type_result = Hash.new { |h, type_name| h[type_name] = compute_type(type_name) } end def field_on_visible_interface?(field, owner) ints = owner.interface_type_memberships.map(&:abstract_type) field_name = field.graphql_name filtered_ints = interfaces(owner) any_interface_has_field = false any_interface_has_visible_field = false ints.each do |int_t| if (_int_f_defn = @cached_parent_fields[int_t][field_name]) any_interface_has_field = true if filtered_ints.include?(int_t) # TODO cycles, or maybe not necessary since previously checked? && @cached_visible_fields[owner][field] any_interface_has_visible_field = true break end end end if any_interface_has_field any_interface_has_visible_field else true end end def type(type_name) @cached_type_result[type_name] end def field(owner, field_name) @cached_field_result[owner][field_name] end def fields(owner) @cached_fields[owner] end def arguments(owner) @cached_arguments[owner] end def argument(owner, arg_name) arg = @cached_parent_arguments[owner][arg_name] if arg.is_a?(Array) visible_arg = nil arg.each do |arg_defn| if @cached_visible_arguments[owner][arg_defn] if visible_arg.nil? visible_arg = arg_defn else raise_duplicate_definition(visible_arg, arg_defn) end end end visible_arg else if arg && @cached_visible_arguments[owner][arg] arg else nil end end end def possible_types(type) @cached_possible_types[type] end def interfaces(obj_or_int_type) ints = obj_or_int_type.interface_type_memberships .select { |itm| @cached_visible[itm] && @cached_visible[itm.abstract_type] } .map!(&:abstract_type) ints.uniq! # Remove any duplicate interfaces implemented via other interfaces ints end def query_root ((t = @schema.query) && @cached_visible[t]) ? t : nil end def mutation_root ((t = @schema.mutation) && @cached_visible[t]) ? t : nil end def subscription_root ((t = @schema.subscription) && @cached_visible[t]) ? t : nil end def all_types load_all_types @all_types.values end def all_types_h load_all_types @all_types end def enum_values(owner) @cached_enum_values[owner] end def directive_exists?(dir_name) directives.any? { |d| d.graphql_name == dir_name } end def directives @all_directives ||= @visibility.all_directives.select { |dir| @cached_visible[dir] && @visibility.all_references[dir].any? { |ref| ref == true || (@cached_visible[ref] && referenced?(ref)) } } end def loadable?(t, _ctx) @cached_visible[t] && !referenced?(t) end def loadable_possible_types(t, _ctx) @loadable_possible_types[t] end def loaded_types @all_types.values end def reachable_type?(type_name) load_all_types !!@all_types[type_name] end def visible_enum_value?(enum_value, _ctx = nil) @cached_visible[enum_value] end def preload load_all_types @all_types.each do |type_name, type_defn| type(type_name) if type_defn.kind.fields? fields(type_defn).each do |f| field(type_defn, f.graphql_name) arguments(f).each do |arg| argument(f, arg.graphql_name) end end @schema.introspection_system.dynamic_fields.each do |f| field(type_defn, f.graphql_name) end elsif type_defn.kind.input_object? arguments(type_defn).each do |arg| argument(type_defn, arg.graphql_name) end elsif type_defn.kind.enum? enum_values(type_defn) end # Lots more to do here end @schema.introspection_system.entry_points.each do |f| arguments(f).each do |arg| argument(f, arg.graphql_name) end field(@schema.query, f.graphql_name) end @schema.introspection_system.dynamic_fields.each do |f| arguments(f).each do |arg| argument(f, arg.graphql_name) end end end private def compute_type(type_name) t = @visibility.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop if t if t.is_a?(Array) vis_t = nil t.each do |t_defn| if @cached_visible[t_defn] && referenced?(t_defn) if vis_t.nil? vis_t = t_defn else raise_duplicate_definition(vis_t, t_defn) end end end vis_t else if t && @cached_visible[t] && referenced?(t) t else nil end end end end def compute_field(owner, field_name) f = if owner.kind.fields? && (field = @cached_parent_fields[owner][field_name]) field elsif owner == query_root && (entry_point_field = @schema.introspection_system.entry_point(name: field_name)) entry_point_field elsif (dynamic_field = @schema.introspection_system.dynamic_field(name: field_name)) dynamic_field else nil end if f.is_a?(Array) visible_f = nil f.each do |f_defn| if @cached_visible_fields[owner][f_defn] if visible_f.nil? visible_f = f_defn else raise_duplicate_definition(visible_f, f_defn) end end end visible_f&.ensure_loaded elsif f && @cached_visible_fields[owner][f.ensure_loaded] f else nil end end def non_duplicate_items(definitions, visibility_cache) non_dups = [] names = Set.new definitions.each do |defn| if visibility_cache[defn] if !names.add?(defn.graphql_name) dup_defn = non_dups.find { |d| d.graphql_name == defn.graphql_name } raise_duplicate_definition(dup_defn, defn) end non_dups << defn end end non_dups end def raise_duplicate_definition(first_defn, second_defn) raise DuplicateNamesError.new(duplicated_name: first_defn.path, duplicated_definition_1: first_defn.inspect, duplicated_definition_2: second_defn.inspect) end def load_all_types return if @all_types_loaded @all_types_loaded = true visit = Visibility::Visit.new(@schema) do |member| if member.is_a?(Module) && member.respond_to?(:kind) if @cached_visible[member] && referenced?(member) type_name = member.graphql_name if (prev_t = @all_types[type_name]) && !prev_t.equal?(member) raise_duplicate_definition(member, prev_t) end @all_types[type_name] = member true else false end else @cached_visible[member] end end visit.visit_each @all_types.delete_if { |type_name, type_defn| !referenced?(type_defn) } nil end def referenced?(type_defn) @visibility.all_references[type_defn].any? do |ref| case ref when GraphQL::Schema::Argument @cached_visible_arguments[ref.owner][ref] when GraphQL::Schema::Field @cached_visible_fields[ref.owner][ref] when Module @cached_visible[ref] when true true end end end def possible_types_for(type) case type.kind.name when "INTERFACE" pts = [] @visibility.all_interface_type_memberships[type].each do |impl_type, type_memberships| if impl_type.kind.object? && referenced?(impl_type) && @cached_visible[impl_type] if type_memberships.any? { |itm| @cached_visible[itm] } pts << impl_type end end end pts when "UNION" pts = [] type.type_memberships.each { |tm| if @cached_visible[tm] && (ot = tm.object_type) && @cached_visible[ot] && referenced?(ot) pts << ot end } pts when "OBJECT" if @cached_visible[type] [type] else EmptyObjects::EMPTY_ARRAY end else GraphQL::EmptyObjects::EMPTY_ARRAY end end def visible_field_for(owner, field) @cached_visible[field] && (ret_type = field.type.unwrap) && @cached_visible[ret_type] && (owner == field.owner || (!owner.kind.object?) || field_on_visible_interface?(field, owner)) end end end end end graphql-2.6.0/lib/graphql/schema/unique_within_type.rb0000644000004100000410000000212115173430257023132 0ustar www-datawww-data# frozen_string_literal: true require "base64" module GraphQL class Schema module UniqueWithinType class << self attr_accessor :default_id_separator end self.default_id_separator = "-" module_function # @param type_name [String] # @param object_value [Any] # @return [String] a unique, opaque ID generated as a function of the two inputs def encode(type_name, object_value, separator: self.default_id_separator) object_value_str = object_value.to_s if type_name.include?(separator) raise "encode(#{type_name}, #{object_value_str}) contains reserved characters `#{separator}` in the type name" end Base64.strict_encode64([type_name, object_value_str].join(separator)) end # @param node_id [String] A unique ID generated by {.encode} # @return [Array<(String, String)>] The type name & value passed to {.encode} def decode(node_id, separator: self.default_id_separator) GraphQL::Schema::Base64Encoder.decode(node_id).split(separator, 2) end end end end graphql-2.6.0/lib/graphql/schema/resolver/0000755000004100000410000000000015173430257020521 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/schema/resolver/has_payload_type.rb0000644000004100000410000000736615173430257024407 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Resolver # Adds `field(...)` helper to resolvers so that they can # generate payload types. # # Or, an already-defined one can be attached with `payload_type(...)`. module HasPayloadType # Call this method to get the derived return type of the mutation, # or use it as a configuration method to assign a return type # instead of generating one. # @param new_payload_type [Class, nil] If a type definition class is provided, it will be used as the return type of the mutation field # @return [Class] The object type which this mutation returns. def payload_type(new_payload_type = nil) if new_payload_type @payload_type = new_payload_type end @payload_type ||= generate_payload_type end def type(new_type = nil, null: nil) if new_type payload_type(new_type) if !null.nil? self.null(null) end else super() end end alias :type_expr :payload_type def field_class(new_class = nil) if new_class @field_class = new_class elsif defined?(@field_class) && @field_class @field_class else find_inherited_value(:field_class, GraphQL::Schema::Field) end end # An object class to use for deriving return types # @param new_class [Class, nil] Defaults to {GraphQL::Schema::Object} # @return [Class] def object_class(new_class = nil) if new_class if defined?(@payload_type) raise "Can't configure `object_class(...)` after the payload type has already been initialized. Move this configuration higher up the class definition." end @object_class = new_class else @object_class || find_inherited_value(:object_class, GraphQL::Schema::Object) end end NO_INTERFACES = [].freeze def field(*args, **kwargs, &block) pt = payload_type # make sure it's initialized with any inherited fields field_defn = super # Remove any inherited fields to avoid false conflicts at runtime prev_fields = pt.own_fields[field_defn.graphql_name] case prev_fields when GraphQL::Schema::Field if prev_fields.owner != self pt.own_fields.delete(field_defn.graphql_name) end when Array prev_fields.reject! { |f| f.owner != self } if prev_fields.empty? pt.own_fields.delete(field_defn.graphql_name) end end pt.add_field(field_defn, method_conflict_warning: false) field_defn end private # Build a subclass of {.object_class} based on `self`. # This value will be cached as `{.payload_type}`. # Override this hook to customize return type generation. def generate_payload_type resolver_name = graphql_name resolver_fields = all_field_definitions pt = Class.new(object_class) pt.graphql_name("#{resolver_name}Payload") pt.description("Autogenerated return type of #{resolver_name}.") resolver_fields.each do |f| # Reattach the already-defined field here # (The field's `.owner` will still point to the mutation, not the object type, I think) # Don't re-warn about a method conflict. Since this type is generated, it should be fixed in the resolver instead. pt.add_field(f, method_conflict_warning: false) end pt end end end end end graphql-2.6.0/lib/graphql/schema/field.rb0000644000004100000410000012261215173430257020274 0ustar www-datawww-data# frozen_string_literal: true require "graphql/schema/field/connection_extension" require "graphql/schema/field/scope_extension" module GraphQL class Schema class Field include GraphQL::Schema::Member::HasArguments include GraphQL::Schema::Member::HasArguments::FieldConfigured include GraphQL::Schema::Member::HasAstNode include GraphQL::Schema::Member::HasAuthorization include GraphQL::Schema::Member::HasPath include GraphQL::Schema::Member::HasValidators extend GraphQL::Schema::FindInheritedValue include GraphQL::EmptyObjects include GraphQL::Schema::Member::HasDirectives include GraphQL::Schema::Member::HasDeprecationReason class FieldImplementationFailed < GraphQL::Error; end # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided attr_reader :name alias :graphql_name :name attr_writer :description # @return [Symbol] Method or hash key on the underlying object to look up attr_reader :method_sym # @return [String] Method or hash key on the underlying object to look up attr_reader :method_str attr_reader :hash_key attr_reader :dig_keys # @return [Symbol] The method on the type to look up def resolver_method if @resolver_class @resolver_class.resolver_method else @resolver_method end end # @return [String, nil] def deprecation_reason super || @resolver_class&.deprecation_reason end def directives if @resolver_class && !(r_dirs = @resolver_class.directives).empty? if !(own_dirs = super).empty? new_dirs = own_dirs.dup r_dirs.each do |r_dir| if r_dir.class.repeatable? || ( (r_dir_name = r_dir.graphql_name) && (!new_dirs.any? { |d| d.graphql_name == r_dir_name }) ) new_dirs << r_dir end end new_dirs else r_dirs end else super end end # @return [Class] The thing this field was defined on (type, mutation, resolver) attr_accessor :owner # @return [Class] The GraphQL type this field belongs to. (For fields defined on mutations, it's the payload type) def owner_type @owner_type ||= if owner.nil? raise GraphQL::InvariantError, "Field #{original_name.inspect} (graphql name: #{graphql_name.inspect}) has no owner, but all fields should have an owner. How did this happen?!" elsif owner < GraphQL::Schema::Mutation owner.payload_type else owner end end # @return [Symbol] the original name of the field, passed in by the user attr_reader :original_name # @return [Class, nil] The {Schema::Resolver} this field was derived from, if there is one def resolver @resolver_class end # @return [Boolean] Is this field a predefined introspection field? def introspection? @introspection end def inspect "#<#{self.class} #{path}#{!all_argument_definitions.empty? ? "(...)" : ""}: #{type.to_type_signature}>" end alias :mutation :resolver # @return [Boolean] Apply tracing to this field? (Default: skip scalars, this is the override value) attr_reader :trace # @return [String, nil] def subscription_scope @subscription_scope || (@resolver_class.respond_to?(:subscription_scope) ? @resolver_class.subscription_scope : nil) end attr_writer :subscription_scope # Can be set with `connection: true|false` or inferred from a type name ending in `*Connection` # @return [Boolean] if true, this field will be wrapped with Relay connection behavior def connection? if @connection.nil? # Provide default based on type name return_type_name = if @return_type_expr Member::BuildType.to_type_name(@return_type_expr) elsif @resolver_class && @resolver_class.type Member::BuildType.to_type_name(@resolver_class.type) elsif type # As a last ditch, try to force loading the return type: type.unwrap.name end if return_type_name @connection = return_type_name.end_with?("Connection") && return_type_name != "Connection" else # TODO set this when type is set by method false # not loaded yet? end else @connection end end # @return [Boolean] if true, the return type's `.scope_items` method will be applied to this field's return value def scoped? if !@scope.nil? # The default was overridden @scope elsif @return_type_expr # Detect a list return type, but don't call `type` since that may eager-load an otherwise lazy-loaded type @return_type_expr.is_a?(Array) || (@return_type_expr.is_a?(String) && @return_type_expr.include?("[")) || connection? elsif @resolver_class resolver_type = @resolver_class.type_expr resolver_type.is_a?(Array) || (resolver_type.is_a?(String) && resolver_type.include?("[")) || connection? else false end end # This extension is applied to fields when {#connection?} is true. # # You can override it in your base field definition. # @return [Class] A {FieldExtension} subclass for implementing pagination behavior. # @example Configuring a custom extension # class Types::BaseField < GraphQL::Schema::Field # connection_extension(MyCustomExtension) # end def self.connection_extension(new_extension_class = nil) if new_extension_class @connection_extension = new_extension_class else @connection_extension ||= find_inherited_value(:connection_extension, ConnectionExtension) end end # @return Boolean attr_reader :relay_node_field # @return Boolean attr_reader :relay_nodes_field # @return [Boolean] Should we warn if this field's name conflicts with a built-in method? def method_conflict_warning? @method_conflict_warning end # @param name [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API) # @param type [Class, GraphQL::BaseType, Array] The return type of this field # @param owner [Class] The type that this field belongs to # @param null [Boolean] (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null` # @param description [String] Field description # @param comment [String] Field comment # @param deprecation_reason [String] If present, the field is marked "deprecated" with this message # @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`) # @param hash_key [String, Symbol] The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`) # @param dig [Array] The nested hash keys to lookup on the underlying hash to resolve this field using dig # @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`) # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name # @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added. # @param resolve_static [Symbol, true, nil] Used by {Schema.execute_next} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments` # @param resolve_batch [Symbol, true, nil] Used by {Schema.execute_next} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`. # @param resolve_each [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Called on the owner type class with `object, context, **arguments`. # @param resolve_legacy_instance_method [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Calls an instance method on the object type class. # @param dataload [Class, Hash] Shorthand for making dataloader calls # @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results. # @param default_page_size [Integer, nil] For connections, the default number of items to return from this field, or `nil` to return unlimited results. # @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__` # @param resolver_class [Class] (Private) A {Schema::Resolver} which this field was derived from. Use `resolver:` to create a field with a resolver. # @param arguments [{String=>GraphQL::Schema::Argument, Hash}] Arguments for this field (may be added in the block, also) # @param camelize [Boolean] If true, the field name will be camelized when building the schema # @param complexity [Numeric] When provided, set the complexity for this field # @param scope [Boolean] If true, the return type's `.scope_items` method will be called on the return value # @param subscription_scope [Symbol, String] A key in `context` which will be used to scope subscription payloads # @param extensions [Array Object>>] Named extensions to apply to this field (see also {#extension}) # @param directives [Hash{Class => Hash}] Directives to apply to this field # @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field # @param broadcastable [Boolean] Whether or not this field can be distributed in subscription broadcasts # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method # @param validates [Array] Configurations for validating this field # @param fallback_value [Object] A fallback value if the method is not defined # @param dynamic_introspection [Boolean] (Private, used by GraphQL-Ruby) # @param relay_node_field [Boolean] (Private, used by GraphQL-Ruby) # @param relay_nodes_field [Boolean] (Private, used by GraphQL-Ruby) # @param extras [Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>] Extra arguments to be injected into the resolver for this field # @param definition_block [Proc] an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new {Field}. def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, resolve_legacy_instance_method: nil, resolve_static: nil, resolve_each: nil, resolve_batch: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, dataload: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block) if name.nil? raise ArgumentError, "missing first `name` argument or keyword `name:`" end if !(resolver_class) if type.nil? && !block_given? raise ArgumentError, "missing second `type` argument, keyword `type:`, or a block containing `type(...)`" end end @original_name = name name_s = -name.to_s @underscored_name = -Member::BuildType.underscore(name_s) @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s) NameValidator.validate!(@name) @description = description @comment = comment @type = @owner_type = @own_validators = @own_directives = @own_arguments = @arguments_statically_coercible = nil # these will be prepared later if necessary self.deprecation_reason = deprecation_reason if method && hash_key && dig raise ArgumentError, "Provide `method:`, `hash_key:` _or_ `dig:`, not multiple. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}, dig: #{dig.inspect}`)" end if resolver_method if method raise ArgumentError, "Provide `method:` _or_ `resolver_method:`, not both. (called with: `method: #{method.inspect}, resolver_method: #{resolver_method.inspect}`)" end if hash_key || dig raise ArgumentError, "Provide `hash_key:`, `dig:`, _or_ `resolver_method:`, not multiple. (called with: `hash_key: #{hash_key.inspect}, dig: #{dig.inspect}, resolver_method: #{resolver_method.inspect}`)" end end method_name = method || hash_key || name_s @dig_keys = dig if hash_key @hash_key = hash_key @hash_key_str = hash_key.to_s else @hash_key = NOT_CONFIGURED @hash_key_str = NOT_CONFIGURED end @method_str = -method_name.to_s @method_sym = method_name.to_sym @resolver_method = (resolver_method || name_s).to_sym if resolve_static @execution_mode = :resolve_static @execution_mode_key = resolve_static == true ? @method_sym : resolve_static elsif resolve_batch @execution_mode = :resolve_batch @execution_mode_key = resolve_batch == true ? @method_sym : resolve_batch elsif resolve_each @execution_mode = :resolve_each @execution_mode_key = resolve_each == true ? @method_sym : resolve_each elsif hash_key @execution_mode = :hash_key @execution_mode_key = hash_key elsif dig @execution_mode = :dig @execution_mode_key = dig elsif resolver_class @execution_mode = :resolver_class @execution_mode_key = resolver_class elsif resolve_legacy_instance_method @execution_mode = :resolve_legacy_instance_method @execution_mode_key = resolve_legacy_instance_method == true ? @method_sym : resolve_legacy_instance_method elsif dataload @execution_mode = :dataload @execution_mode_key = dataload else @execution_mode = :direct_send @execution_mode_key = @method_sym end @complexity = complexity @dynamic_introspection = dynamic_introspection @return_type_expr = type @return_type_null = if !null.nil? null elsif resolver_class nil else true end @connection = connection @max_page_size = max_page_size @default_page_size = default_page_size @introspection = introspection @extras = extras @broadcastable = broadcastable @resolver_class = resolver_class @scope = scope @trace = trace @relay_node_field = relay_node_field @relay_nodes_field = relay_nodes_field @ast_node = ast_node @method_conflict_warning = method_conflict_warning @fallback_value = fallback_value @definition_block = definition_block arguments.each do |name, arg| case arg when Hash argument(name: name, **arg) when GraphQL::Schema::Argument add_argument(arg) when Array arg.each { |a| add_argument(a) } else raise ArgumentError, "Unexpected argument config (#{arg.class}): #{arg.inspect}" end end @owner = owner @subscription_scope = subscription_scope @extensions = EMPTY_ARRAY @call_after_define = false set_pagination_extensions(connection_extension: NOT_CONFIGURED.equal?(connection_extension) ? self.class.connection_extension : connection_extension) # Do this last so we have as much context as possible when initializing them: if !extensions.empty? self.extensions(extensions) end if resolver_class && !resolver_class.extensions.empty? self.extensions(resolver_class.extensions) end if !directives.empty? directives.each do |(dir_class, options)| self.directive(dir_class, **options) end end if !validates.empty? self.validates(validates) end if @definition_block.nil? self.extensions.each(&:after_define_apply) @call_after_define = true end end # @api private attr_reader :execution_mode_key, :execution_mode # Calls the definition block, if one was given. # This is deferred so that references to the return type # can be lazily evaluated, reducing Rails boot time. # @return [self] # @api private def ensure_loaded if @definition_block if @definition_block.arity == 1 @definition_block.call(self) else instance_exec(self, &@definition_block) end self.extensions.each(&:after_define_apply) @call_after_define = true @definition_block = nil end self end attr_accessor :dynamic_introspection # If true, subscription updates with this field can be shared between viewers # @return [Boolean, nil] # @see GraphQL::Subscriptions::BroadcastAnalyzer def broadcastable? if !NOT_CONFIGURED.equal?(@broadcastable) @broadcastable elsif @resolver_class @resolver_class.broadcastable? else nil end end # @param text [String] # @return [String] def description(text = nil) if text @description = text elsif !NOT_CONFIGURED.equal?(@description) @description elsif @resolver_class @resolver_class.description else nil end end # @param text [String] # @return [String, nil] def comment(text = nil) if text @comment = text elsif !NOT_CONFIGURED.equal?(@comment) @comment elsif @resolver_class @resolver_class.comment else nil end end # Read extension instances from this field, # or add new classes/options to be initialized on this field. # Extensions are executed in the order they are added. # # @example adding an extension # extensions([MyExtensionClass]) # # @example adding multiple extensions # extensions([MyExtensionClass, AnotherExtensionClass]) # # @example adding an extension with options # extensions([MyExtensionClass, { AnotherExtensionClass => { filter: true } }]) # # @param extensions [Array Hash>>] Add extensions to this field. For hash elements, only the first key/value is used. # @return [Array] extensions to apply to this field def extensions(new_extensions = nil) if new_extensions new_extensions.each do |extension_config| if extension_config.is_a?(Hash) extension_class, options = *extension_config.to_a[0] self.extension(extension_class, **options) else self.extension(extension_config) end end end @extensions end # Add `extension` to this field, initialized with `options` if provided. # # @example adding an extension # extension(MyExtensionClass) # # @example adding an extension with options # extension(MyExtensionClass, filter: true) # # @param extension_class [Class] subclass of {Schema::FieldExtension} # @param options [Hash] if provided, given as `options:` when initializing `extension`. # @return [void] def extension(extension_class, **options) extension_inst = extension_class.new(field: self, options: options) if @extensions.frozen? @extensions = @extensions.dup end if @call_after_define extension_inst.after_define_apply end @extensions << extension_inst nil end # Read extras (as symbols) from this field, # or add new extras to be opted into by this field's resolver. # # @param new_extras [Array] Add extras to this field # @return [Array] def extras(new_extras = nil) if new_extras.nil? # Read the value field_extras = @extras if @resolver_class && !@resolver_class.extras.empty? field_extras + @resolver_class.extras else field_extras end else if @extras.frozen? @extras = @extras.dup end # Append to the set of extras on this field @extras.concat(new_extras) end end def calculate_complexity(query:, nodes:, child_complexity:) if respond_to?(:complexity_for) lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner) complexity_for(child_complexity: child_complexity, query: query, lookahead: lookahead) elsif connection? arguments = query.arguments_for(nodes.first, self) max_possible_page_size = nil if arguments.respond_to?(:[]) # It might have been an error if arguments[:first] max_possible_page_size = arguments[:first] end if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size) max_possible_page_size = arguments[:last] end elsif arguments.is_a?(GraphQL::ExecutionError) || arguments.is_a?(GraphQL::UnauthorizedError) raise arguments end if max_possible_page_size.nil? max_possible_page_size = default_page_size || query.schema.default_page_size || max_page_size || query.schema.default_max_page_size end if max_possible_page_size.nil? raise GraphQL::Error, "Can't calculate complexity for #{path}, no `first:`, `last:`, `default_page_size`, `max_page_size` or `default_max_page_size`" else metadata_complexity = 0 lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner) lookahead.selections.each do |next_lookahead| # this includes `pageInfo`, `nodes` and `edges` and any custom fields # TODO this doesn't support procs yet -- unlikely to need it. metadata_complexity += next_lookahead.field.complexity if next_lookahead.name != :nodes && next_lookahead.name != :edges # subfields, eg, for pageInfo -- assumes no subselections metadata_complexity += next_lookahead.selections.size end end # Possible bug: selections on `edges` and `nodes` are _both_ multiplied here. Should they be? items_complexity = child_complexity - metadata_complexity subfields_complexity = (max_possible_page_size * items_complexity) + metadata_complexity # Apply this field's own complexity apply_own_complexity_to(subfields_complexity, query, nodes) end else apply_own_complexity_to(child_complexity, query, nodes) end end def complexity(new_complexity = nil) case new_complexity when Proc if new_complexity.parameters.size != 3 fail( "A complexity proc should always accept 3 parameters: ctx, args, child_complexity. "\ "E.g.: complexity ->(ctx, args, child_complexity) { child_complexity * args[:limit] }" ) else @complexity = new_complexity end when Numeric @complexity = new_complexity when nil if @resolver_class @complexity || @resolver_class.complexity || 1 else @complexity || 1 end else raise("Invalid complexity: #{new_complexity.inspect} on #{@name}") end end # @return [Boolean] True if this field's {#max_page_size} should override the schema default. def has_max_page_size? !NOT_CONFIGURED.equal?(@max_page_size) || (@resolver_class && @resolver_class.has_max_page_size?) end # @return [Integer, nil] Applied to connections if {#has_max_page_size?} def max_page_size if !NOT_CONFIGURED.equal?(@max_page_size) @max_page_size elsif @resolver_class && @resolver_class.has_max_page_size? @resolver_class.max_page_size else nil end end # @return [Boolean] True if this field's {#default_page_size} should override the schema default. def has_default_page_size? !NOT_CONFIGURED.equal?(@default_page_size) || (@resolver_class && @resolver_class.has_default_page_size?) end # @return [Integer, nil] Applied to connections if {#has_default_page_size?} def default_page_size if !NOT_CONFIGURED.equal?(@default_page_size) @default_page_size elsif @resolver_class && @resolver_class.has_default_page_size? @resolver_class.default_page_size else nil end end def freeze type owner_type arguments_statically_coercible? connection? super end class MissingReturnTypeError < GraphQL::Error; end attr_writer :type # Get or set the return type of this field. # # It may return nil if no type was configured or if the given definition block wasn't called yet. # @param new_type [Module, GraphQL::Schema::NonNull, GraphQL::Schema::List] A GraphQL return type # @return [Module, GraphQL::Schema::NonNull, GraphQL::Schema::List, nil] the configured type for this field def type(new_type = NOT_CONFIGURED) if NOT_CONFIGURED.equal?(new_type) if @resolver_class return_type = @return_type_expr || @resolver_class.type_expr if return_type.nil? raise MissingReturnTypeError, "Can't determine the return type for #{self.path} (it has `resolver: #{@resolver_class}`, perhaps that class is missing a `type ...` declaration, or perhaps its type causes a cyclical loading issue)" end nullable = @return_type_null.nil? ? @resolver_class.null : @return_type_null Member::BuildType.parse_type(return_type, null: nullable) elsif !@return_type_expr.nil? @type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null) end else @return_type_expr = new_type # If `type` is set in the definition block, then the `connection_extension: ...` given as a keyword won't be used, hmm... # Also, arguments added by `connection_extension` will clobber anything previously defined, # so `type(...)` should go first. set_pagination_extensions(connection_extension: self.class.connection_extension) end rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err # Let this propagate up raise err rescue StandardError => err raise MissingReturnTypeError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: (#{err.class}) #{err.message}", err.backtrace end def visible?(context) if @resolver_class @resolver_class.visible?(context) else true end end def authorizes?(context) method(:authorized?).owner != GraphQL::Schema::Field || ((args = context.types.arguments(self)) && (args.any? { |a| a.authorizes?(context) })) || (@resolver_class&.authorizes?(context)) || false end def authorized?(object, args, context) if @resolver_class # The resolver _instance_ will check itself during `resolve()` @resolver_class.authorized?(object, context) else if args.size > 0 if (arg_values = context[:current_arguments]) # ^^ that's provided by the interpreter at runtime, and includes info about whether the default value was used or not. using_arg_values = true arg_values = arg_values.argument_values else arg_values = args using_arg_values = false end args = context.types.arguments(self) args.each do |arg| arg_key = arg.keyword if arg_values.key?(arg_key) arg_value = arg_values[arg_key] if using_arg_values if arg_value.default_used? # pass -- no auth required for default used next else application_arg_value = arg_value.value if application_arg_value.is_a?(GraphQL::Execution::Interpreter::Arguments) application_arg_value.keyword_arguments end end else application_arg_value = arg_value end if !arg.authorized?(object, application_arg_value, context) return false end end end end true end end # This method is called by the interpreter for each field. # You can extend it in your base field classes. # @param object [GraphQL::Schema::Object] An instance of some type class, wrapping an application object # @param args [Hash] A symbol-keyed hash of Ruby keyword arguments. (Empty if no args) # @param ctx [GraphQL::Query::Context] def resolve(object, args, query_ctx) # Unwrap the GraphQL object to get the application object. application_object = object.object method_receiver = nil method_to_call = nil method_args = nil @own_validators && Schema::Validator.validate!(validators, application_object, query_ctx, args) query_ctx.query.after_lazy(self.authorized?(application_object, args, query_ctx)) do |is_authorized| if is_authorized with_extensions(object, args, query_ctx) do |obj, ruby_kwargs| method_args = ruby_kwargs if @resolver_class if obj.is_a?(GraphQL::Schema::Object) obj = obj.object end obj = @resolver_class.new(object: obj, context: query_ctx, field: self) end inner_object = obj.object if !NOT_CONFIGURED.equal?(@hash_key) hash_value = if inner_object.is_a?(Hash) inner_object.key?(@hash_key) ? inner_object[@hash_key] : inner_object[@hash_key_str] elsif inner_object.respond_to?(:[]) inner_object[@hash_key] else nil end if hash_value == false hash_value else hash_value || (@fallback_value != NOT_CONFIGURED ? @fallback_value : nil) end elsif obj.respond_to?(resolver_method) method_to_call = resolver_method method_receiver = obj # Call the method with kwargs, if there are any if !ruby_kwargs.empty? obj.public_send(resolver_method, **ruby_kwargs) else obj.public_send(resolver_method) end elsif inner_object.is_a?(Hash) if @dig_keys inner_object.dig(*@dig_keys) elsif inner_object.key?(@method_sym) inner_object[@method_sym] elsif inner_object.key?(@method_str) || !inner_object.default_proc.nil? inner_object[@method_str] elsif @fallback_value != NOT_CONFIGURED @fallback_value else nil end elsif inner_object.respond_to?(@method_sym) method_to_call = @method_sym method_receiver = obj.object if !ruby_kwargs.empty? inner_object.public_send(@method_sym, **ruby_kwargs) else inner_object.public_send(@method_sym) end elsif @fallback_value != NOT_CONFIGURED @fallback_value else raise <<-ERR Failed to implement #{@owner.graphql_name}.#{@name}, tried: - `#{obj.class}##{resolver_method}`, which did not exist - `#{inner_object.class}##{@method_sym}`, which did not exist - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{inner_object}`, but it wasn't a Hash To implement this field, define one of the methods above (and check for typos), or supply a `fallback_value`. ERR end end else raise GraphQL::UnauthorizedFieldError.new(object: application_object, type: object.class, context: query_ctx, field: self) end end rescue GraphQL::UnauthorizedFieldError => err err.field ||= self begin query_ctx.schema.unauthorized_field(err) rescue GraphQL::ExecutionError => err err end rescue GraphQL::UnauthorizedError => err begin query_ctx.schema.unauthorized_object(err) rescue GraphQL::ExecutionError => err err end rescue ArgumentError if method_receiver && method_to_call assert_satisfactory_implementation(method_receiver, method_to_call, method_args) end # if the line above doesn't raise, re-raise raise rescue GraphQL::ExecutionError => err err end # @param ctx [GraphQL::Query::Context] def fetch_extra(extra_name, ctx) if extra_name != :path && extra_name != :ast_node && respond_to?(extra_name) self.public_send(extra_name) elsif ctx.respond_to?(extra_name) ctx.public_send(extra_name) else raise GraphQL::RequiredImplementationMissingError, "Unknown field extra for #{self.path}: #{extra_name.inspect}" end end private def assert_satisfactory_implementation(receiver, method_name, ruby_kwargs) method_defn = receiver.method(method_name) unsatisfied_ruby_kwargs = ruby_kwargs.dup unsatisfied_method_params = [] encountered_keyrest = false method_defn.parameters.each do |(param_type, param_name)| case param_type when :key unsatisfied_ruby_kwargs.delete(param_name) when :keyreq if unsatisfied_ruby_kwargs.key?(param_name) unsatisfied_ruby_kwargs.delete(param_name) else unsatisfied_method_params << "- `#{param_name}:` is required by Ruby, but not by GraphQL. Consider `#{param_name}: nil` instead, or making this argument required in GraphQL." end when :keyrest encountered_keyrest = true when :req unsatisfied_method_params << "- `#{param_name}` is required by Ruby, but GraphQL doesn't pass positional arguments. If it's meant to be a GraphQL argument, use `#{param_name}:` instead. Otherwise, remove it." when :opt, :rest # This is fine, although it will never be present end end if encountered_keyrest unsatisfied_ruby_kwargs.clear end if !unsatisfied_ruby_kwargs.empty? || !unsatisfied_method_params.empty? raise FieldImplementationFailed.new, <<-ERR Failed to call `#{method_name.inspect}` on #{receiver.inspect} because the Ruby method params were incompatible with the GraphQL arguments: #{ unsatisfied_ruby_kwargs .map { |key, value| "- `#{key}: #{value}` was given by GraphQL but not defined in the Ruby method. Add `#{key}:` to the method parameters." } .concat(unsatisfied_method_params) .join("\n") } ERR end end class ExtendedState def initialize(args, object) @arguments = args @object = object @memos = nil @added_extras = nil end attr_accessor :arguments, :object, :memos, :added_extras end # Wrap execution with hooks. # Written iteratively to avoid big stack traces. # @return [Object] Whatever the def with_extensions(obj, args, ctx) if @extensions.empty? yield(obj, args) else # This is a hack to get the _last_ value for extended obj and args, # in case one of the extensions doesn't `yield`. # (There's another implementation that uses multiple-return, but I'm wary of the perf cost of the extra arrays) extended = ExtendedState.new(args, obj) value = run_extensions_before_resolve(obj, args, ctx, extended) do |obj, args| if (added_extras = extended.added_extras) args = args.dup added_extras.each { |e| args.delete(e) } end yield(obj, args) end extended_obj = extended.object extended_args = extended.arguments # rubocop:disable Development/ContextIsPassedCop memos = extended.memos || EMPTY_HASH ctx.query.after_lazy(value) do |resolved_value| idx = 0 @extensions.each do |ext| memo = memos[idx] # TODO after_lazy? resolved_value = ext.after_resolve(object: extended_obj, arguments: extended_args, context: ctx, value: resolved_value, memo: memo) idx += 1 end resolved_value end end end public def run_next_extensions_before_resolve(objs, args, ctx, extended, idx: 0, &block) extension = @extensions[idx] if extension extension.resolve(objects: objs, arguments: args, context: ctx) do |extended_objs, extended_args, memo| if memo memos = extended.memos ||= {} memos[idx] = memo end if (extras = extension.added_extras) ae = extended.added_extras ||= [] ae.concat(extras) end extended.object = extended_objs extended.arguments = extended_args run_next_extensions_before_resolve(extended_objs, extended_args, ctx, extended, idx: idx + 1, &block) end else yield(objs, args) end end private def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0) extension = @extensions[idx] if extension extension.resolve(object: obj, arguments: args, context: ctx) do |extended_obj, extended_args, memo| if memo memos = extended.memos ||= {} memos[idx] = memo end if (extras = extension.added_extras) ae = extended.added_extras ||= [] ae.concat(extras) end extended.object = extended_obj extended.arguments = extended_args run_extensions_before_resolve(extended_obj, extended_args, ctx, extended, idx: idx + 1) { |o, a| yield(o, a) } end else yield(obj, args) end end def apply_own_complexity_to(child_complexity, query, nodes) case (own_complexity = complexity) when Numeric own_complexity + child_complexity when Proc arguments = query.arguments_for(nodes.first, self) if arguments.is_a?(GraphQL::ExecutionError) return child_complexity elsif arguments.respond_to?(:keyword_arguments) arguments = arguments.keyword_arguments end own_complexity.call(query.context, arguments, child_complexity) else raise ArgumentError, "Invalid complexity for #{self.path}: #{own_complexity.inspect}" end end def set_pagination_extensions(connection_extension:) # This should run before connection extension, # but should it run after the definition block? if scoped? self.extension(ScopeExtension, call_after_define: false) end # The problem with putting this after the definition_block # is that it would override arguments if connection? && connection_extension self.extension(connection_extension, call_after_define: false) end end end end end graphql-2.6.0/lib/graphql/schema/enum_value.rb0000644000004100000410000000467315173430257021357 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema # A possible value for an {Enum}. # # You can extend this class to customize enum values in your schema. # # @example custom enum value class # # define a custom class: # class CustomEnumValue < GraphQL::Schema::EnumValue # def initialize(*args) # # arguments to `value(...)` in Enum classes are passed here # super # end # end # # class BaseEnum < GraphQL::Schema::Enum # # use it for these enums: # enum_value_class CustomEnumValue # end class EnumValue < GraphQL::Schema::Member include GraphQL::Schema::Member::HasPath include GraphQL::Schema::Member::HasAstNode include GraphQL::Schema::Member::HasDirectives include GraphQL::Schema::Member::HasDeprecationReason attr_reader :graphql_name # @return [Class] The enum type that owns this value attr_reader :owner def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil, description: nil, comment: nil, value: NOT_CONFIGURED, deprecation_reason: nil, &block) @graphql_name = graphql_name.to_s GraphQL::NameValidator.validate!(@graphql_name) @description = desc || description @comment = comment @value = value == NOT_CONFIGURED ? @graphql_name : value if deprecation_reason self.deprecation_reason = deprecation_reason end @owner = owner @ast_node = ast_node if directives directives.each do |dir_class, dir_options| directive(dir_class, **dir_options) end end if block_given? instance_exec(self, &block) end end def description(new_desc = nil) if new_desc @description = new_desc end @description end def comment(new_comment = nil) if new_comment @comment = new_comment end @comment end def value(new_val = nil) unless new_val.nil? @value = new_val end @value end def inspect "#<#{self.class} #{path} @value=#{@value.inspect}#{description ? " @description=#{description.inspect}" : ""}#{deprecation_reason ? " @deprecation_reason=#{deprecation_reason.inspect}" : ""}>" end def visible?(_ctx); true; end def authorized?(_ctx); true; end end end end graphql-2.6.0/lib/graphql/schema/has_single_input_argument.rb0000644000004100000410000001235115173430257024444 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema module HasSingleInputArgument def resolve_with_support(**inputs) if inputs[:input].is_a?(InputObject) input = inputs[:input].to_kwargs else input = inputs[:input] end new_extras = field ? field.extras : [] all_extras = self.class.extras + new_extras # Transfer these from the top-level hash to the # shortcutted `input:` object all_extras.each do |ext| # It's possible that the `extra` was not passed along by this point, # don't re-add it if it wasn't given here. if inputs.key?(ext) input[ext] = inputs[ext] end end if input # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are. input_kwargs = input.to_h else # Relay Classic Mutations with no `argument`s # don't require `input:` input_kwargs = {} end if !input_kwargs.empty? super(**input_kwargs) else super() end end def self.included(base) base.extend(ClassMethods) end module ClassMethods def dummy @dummy ||= begin d = Class.new(GraphQL::Schema::Resolver) d.graphql_name "#{self.graphql_name}DummyResolver" d.argument_class(self.argument_class) # TODO make this lazier? d.argument(:input, input_type, description: "Parameters for #{self.graphql_name}") d end end def field_arguments(context = GraphQL::Query::NullContext.instance) dummy.arguments(context) end def get_field_argument(name, context = GraphQL::Query::NullContext.instance) dummy.get_argument(name, context) end def own_field_arguments dummy.own_arguments end def any_field_arguments? dummy.any_arguments? end def all_field_argument_definitions dummy.all_argument_definitions end # Also apply this argument to the input type: def argument(*args, own_argument: false, **kwargs, &block) it = input_type # make sure any inherited arguments are already added to it arg = super(*args, **kwargs, &block) # This definition might be overriding something inherited; # if it is, remove the inherited definition so it's not confused at runtime as having multiple definitions prev_args = it.own_arguments[arg.graphql_name] case prev_args when GraphQL::Schema::Argument if prev_args.owner != self it.own_arguments.delete(arg.graphql_name) end when Array prev_args.reject! { |a| a.owner != self } if prev_args.empty? it.own_arguments.delete(arg.graphql_name) end end it.add_argument(arg) arg end # The base class for generated input object types # @param new_class [Class] The base class to use for generating input object definitions # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject}) def input_object_class(new_class = nil) if new_class @input_object_class = new_class end @input_object_class || (superclass.respond_to?(:input_object_class) ? superclass.input_object_class : GraphQL::Schema::InputObject) end # @param new_input_type [Class, nil] If provided, it configures this mutation to accept `new_input_type` instead of generating an input type # @return [Class] The generated {Schema::InputObject} class for this mutation's `input` def input_type(new_input_type = nil) if new_input_type @input_type = new_input_type end @input_type ||= generate_input_type end private # Generate the input type for the `input:` argument # To customize how input objects are generated, override this method # @return [Class] a subclass of {.input_object_class} def generate_input_type mutation_args = all_argument_definitions mutation_class = self Class.new(input_object_class) do class << self def default_graphql_name "#{self.mutation.graphql_name}Input" end def description(new_desc = nil) super || "Autogenerated input type of #{self.mutation.graphql_name}" end end # For compatibility, in case no arguments are defined: has_no_arguments(true) mutation(mutation_class) # these might be inherited: mutation_args.each do |arg| add_argument(arg) end end end end private def authorize_arguments(args, values) # remove the `input` wrapper to match values input_type = args.find { |a| a.graphql_name == "input" }.type.unwrap input_args = context.types.arguments(input_type) super(input_args, values) end end end end graphql-2.6.0/lib/graphql/schema/input_object.rb0000644000004100000410000002611715173430257021701 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class InputObject < GraphQL::Schema::Member extend Forwardable extend GraphQL::Schema::Member::HasArguments extend GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader extend GraphQL::Schema::Member::ValidatesInput extend GraphQL::Schema::Member::HasValidators include GraphQL::Dig # Raised when an InputObject doesn't have any arguments defined and hasn't explicitly opted out of this requirement class ArgumentsAreRequiredError < GraphQL::Error def initialize(input_object_type) message = "Input Object types must have arguments, but #{input_object_type.graphql_name} doesn't have any. Define an argument for this type, remove it from your schema, or add `has_no_arguments(true)` to its definition." super(message) end end # @return [GraphQL::Query::Context] The context for this query attr_reader :context # @return [GraphQL::Execution::Interpereter::Arguments] The underlying arguments instance attr_reader :arguments # Ruby-like hash behaviors, read-only def_delegators :@ruby_style_hash, :keys, :values, :each, :map, :any?, :empty? def initialize(arguments, ruby_kwargs:, context:, defaults_used:) @context = context @ruby_style_hash = ruby_kwargs @arguments = arguments # Apply prepares, not great to have it duplicated here. arg_defns = context ? context.types.arguments(self.class) : self.class.arguments(context).each_value arg_defns.each do |arg_defn| ruby_kwargs_key = arg_defn.keyword if @ruby_style_hash.key?(ruby_kwargs_key) # Weirdly, procs are applied during coercion, but not methods. # Probably because these methods require a `self`. if arg_defn.prepare.is_a?(Symbol) || context.nil? prepared_value = arg_defn.prepare_value(self, @ruby_style_hash[ruby_kwargs_key], context: context) overwrite_argument(ruby_kwargs_key, prepared_value) end end end end def to_h unwrap_value(@ruby_style_hash) end def to_hash to_h end def deconstruct_keys(keys = nil) if keys.nil? @ruby_style_hash else new_h = {} keys.each { |k| @ruby_style_hash.key?(k) && new_h[k] = @ruby_style_hash[k] } new_h end end def prepare self end def unwrap_value(value) case value when Array value.map { |item| unwrap_value(item) } when Hash value.reduce({}) do |h, (key, value)| h.merge!(key => unwrap_value(value)) end when InputObject value.to_h else value end end # Lookup a key on this object, it accepts new-style underscored symbols # Or old-style camelized identifiers. # @param key [Symbol, String] def [](key) if @ruby_style_hash.key?(key) @ruby_style_hash[key] elsif @arguments @arguments[key] else nil end end def key?(key) @ruby_style_hash.key?(key) || (@arguments && @arguments.key?(key)) || false end # A copy of the Ruby-style hash def to_kwargs @ruby_style_hash.dup end # @api private def validate_for(context) object = context[:current_object] # Pass this object's class with `as` so that messages are rendered correctly from inherited validators Schema::Validator.validate!(self.class.validators, object, context, @ruby_style_hash, as: self.class) nil end class << self def authorized?(obj, value, ctx) # Authorize each argument (but this doesn't apply if `prepare` is implemented): if value.respond_to?(:key?) ctx.types.arguments(self).each do |input_obj_arg| if value.key?(input_obj_arg.keyword) && !input_obj_arg.authorized?(obj, value[input_obj_arg.keyword], ctx) return false end end end # It didn't early-return false: true end def one_of if !one_of? if all_argument_definitions.any? { |arg| arg.type.non_null? } raise ArgumentError, "`one_of` may not be used with required arguments -- add `required: false` to argument definitions to use `one_of`" end directive(GraphQL::Schema::Directive::OneOf) end end def one_of? false # Re-defined when `OneOf` is added end def argument(*args, **kwargs, &block) argument_defn = super(*args, **kwargs, &block) if one_of? if argument_defn.type.non_null? raise ArgumentError, "Argument '#{argument_defn.path}' must be nullable because it is part of a OneOf type, add `required: false`." end if argument_defn.default_value? raise ArgumentError, "Argument '#{argument_defn.path}' cannot have a default value because it is part of a OneOf type, remove `default_value: ...`." end end # Add a method access suppress_redefinition_warning do define_accessor_method(argument_defn.keyword) end argument_defn end def kind GraphQL::TypeKinds::INPUT_OBJECT end # @api private INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object." def validate_non_null_input(input, ctx, max_errors: nil) types = ctx.types if input.is_a?(Array) return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) }) end if !(input.respond_to?(:to_h) || input.respond_to?(:to_unsafe_h)) # We're not sure it'll act like a hash, so reject it: return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) }) end result = nil input.each do |argument_name, value| argument = types.argument(self, argument_name) if argument.nil? && ctx.is_a?(Query::NullContext) && argument_name.is_a?(Symbol) # Validating definition directive arguments which come in as Symbols argument = types.arguments(self).find { |arg| arg.keyword == argument_name } end # Items in the input that are unexpected if argument.nil? result ||= Query::InputValidationResult.new result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name]) else # Items in the input that are expected, but have invalid values argument_result = argument.type.validate_input(value, ctx) if !argument_result.valid? result ||= Query::InputValidationResult.new result.merge_result!(argument_name, argument_result) end end end # Check for missing non-null arguments ctx.types.arguments(self).each do |argument| if !input.key?(argument.graphql_name) && argument.type.non_null? && !argument.default_value? result ||= Query::InputValidationResult.new argument_result = argument.type.validate_input(nil, ctx) if !argument_result.valid? result.merge_result!(argument.graphql_name, argument_result) end end end if one_of? if input.size == 1 input.each do |name, value| if value.nil? result ||= Query::InputValidationResult.new result.add_problem("'#{graphql_name}' requires exactly one argument, but '#{name}' was `null`.") end end else result ||= Query::InputValidationResult.new result.add_problem("'#{graphql_name}' requires exactly one argument, but #{input.size} were provided.") end end result end def coerce_input(value, ctx) if value.nil? return nil end arguments = coerce_arguments(nil, value, ctx) ctx.query.after_lazy(arguments) do |resolved_arguments| if resolved_arguments.is_a?(GraphQL::Error) raise resolved_arguments else self.new(resolved_arguments, ruby_kwargs: resolved_arguments.keyword_arguments, context: ctx, defaults_used: nil) end end end # It's funny to think of a _result_ of an input object. # This is used for rendering the default value in introspection responses. def coerce_result(value, ctx) # Allow the application to provide values as :snake_symbols, and convert them to the camelStrings value = value.reduce({}) { |memo, (k, v)| memo[Member::BuildType.camelize(k.to_s)] = v; memo } result = {} arguments(ctx).each do |input_key, input_field_defn| input_value = value[input_key] if value.key?(input_key) result[input_key] = if input_value.nil? nil else input_field_defn.type.coerce_result(input_value, ctx) end end end result end # @param new_has_no_arguments [Boolean] Call with `true` to make this InputObject type ignore the requirement to have any defined arguments. # @return [void] def has_no_arguments(new_has_no_arguments) @has_no_arguments = new_has_no_arguments nil end # @return [Boolean] `true` if `has_no_arguments(true)` was configued def has_no_arguments? @has_no_arguments end def arguments(context = GraphQL::Query::NullContext.instance, require_defined_arguments = true) if require_defined_arguments && !has_no_arguments? && !any_arguments? warn(GraphQL::Schema::InputObject::ArgumentsAreRequiredError.new(self).message + "\n\nThis will raise an error in a future GraphQL-Ruby version.") end super(context, false) end private # Suppress redefinition warning for objectId arguments def suppress_redefinition_warning verbose = $VERBOSE $VERBOSE = nil yield ensure $VERBOSE = verbose end def define_accessor_method(method_name) define_method(method_name) { self[method_name] } alias_method(method_name, method_name) end end private def overwrite_argument(key, value) # Argument keywords come in frozen from the interpreter, dup them before modifying them. if @ruby_style_hash.frozen? @ruby_style_hash = @ruby_style_hash.dup end @ruby_style_hash[key] = value end end end end graphql-2.6.0/lib/graphql/schema/scalar.rb0000644000004100000410000000347415173430257020462 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Scalar < GraphQL::Schema::Member extend GraphQL::Schema::Member::ValidatesInput class << self def coerce_input(val, ctx) val end def coerce_result(val, ctx) val end def kind GraphQL::TypeKinds::SCALAR end def specified_by_url(new_url = nil) if new_url directive(GraphQL::Schema::Directive::SpecifiedBy, url: new_url) elsif (directive = directives.find { |dir| dir.graphql_name == "specifiedBy" }) directive.arguments[:url] # rubocop:disable Development/ContextIsPassedCop elsif superclass.respond_to?(:specified_by_url) superclass.specified_by_url else nil end end def default_scalar(is_default = nil) if !is_default.nil? @default_scalar = is_default end @default_scalar end def default_scalar? @default_scalar ||= false end def validate_non_null_input(value, ctx, max_errors: nil) coerced_result = begin coerce_input(value, ctx) rescue GraphQL::CoercionError => err err rescue StandardError => err ctx.query.handle_or_reraise(err) end if coerced_result.nil? Query::InputValidationResult.from_problem("Could not coerce value #{GraphQL::Language.serialize(value)} to #{graphql_name}") elsif coerced_result.is_a?(GraphQL::CoercionError) Query::InputValidationResult.from_problem(coerced_result.message, message: coerced_result.message, extensions: coerced_result.extensions) else nil end end end end end end graphql-2.6.0/lib/graphql/schema/union.rb0000644000004100000410000000635515173430257020346 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Union < GraphQL::Schema::Member extend GraphQL::Schema::Member::HasUnresolvedTypeError class << self def inherited(child_class) add_unresolved_type_error(child_class) super end def possible_types(*types, context: GraphQL::Query::NullContext.instance, **options) if !types.empty? types.each do |t| assert_valid_union_member(t) type_memberships << type_membership_class.new(self, t, **options) end else visible_types = [] warden = Warden.from_context(context) type_memberships.each do |type_membership| if warden.visible_type_membership?(type_membership, context) visible_types << type_membership.object_type end end visible_types end end def all_possible_types type_memberships.map(&:object_type) end def type_membership_class(membership_class = nil) if membership_class @type_membership_class = membership_class else @type_membership_class || find_inherited_value(:type_membership_class, GraphQL::Schema::TypeMembership) end end def kind GraphQL::TypeKinds::UNION end def type_memberships @type_memberships ||= [] end # Update a type membership whose `.object_type` is a string or late-bound type # so that the type membership's `.object_type` is the given `object_type`. # (This is used for updating the union after the schema as lazily loaded the union member.) # @api private def assign_type_membership_object_type(object_type) assert_valid_union_member(object_type) type_memberships.each { |tm| possible_type = tm.object_type if possible_type.is_a?(String) && (possible_type == object_type.name) # This is a match of Ruby class names, not graphql names, # since strings are used to refer to constants. tm.object_type = object_type elsif possible_type.is_a?(LateBoundType) && possible_type.graphql_name == object_type.graphql_name tm.object_type = object_type end } nil end private def assert_valid_union_member(type_defn) case type_defn when Class if !type_defn.kind.object? raise ArgumentError, "Union possible_types can only be object types (not #{type_defn.kind.name}, #{type_defn.inspect})" end when Module # it's an interface type, defined as a module raise ArgumentError, "Union possible_types can only be object types (not interface types), remove #{type_defn.graphql_name} (#{type_defn.inspect})" when String, GraphQL::Schema::LateBoundType # Ok - assume it will get checked later else raise ArgumentError, "Union possible_types can only be class-based GraphQL types (not #{type_defn.inspect} (#{type_defn.class.name}))." end end end end end end graphql-2.6.0/lib/graphql/schema/introspection_system.rb0000644000004100000410000001153015173430257023511 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class IntrospectionSystem attr_reader :types, :possible_types def initialize(schema) @schema = schema @class_based = !!@schema.is_a?(Class) @built_in_namespace = GraphQL::Introspection @custom_namespace = if @class_based schema.introspection || @built_in_namespace else schema.introspection_namespace || @built_in_namespace end 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 = {}.compare_by_identity type_defns.each do |t| @types[t.graphql_name] = t @possible_types[t] = [t] end @entry_point_fields = if schema.disable_introspection_entry_points? {} else entry_point_fields = get_fields_from_class(class_sym: :EntryPoints) entry_point_fields.delete('__schema') if schema.disable_schema_introspection_entry_point? entry_point_fields.delete('__type') if schema.disable_type_introspection_entry_point? entry_point_fields end @entry_point_fields.each { |k, v| v.dynamic_introspection = true } @dynamic_fields = get_fields_from_class(class_sym: :DynamicFields) @dynamic_fields.each { |k, v| v.dynamic_introspection = true } end def entry_points @entry_point_fields.values end def entry_point(name:) @entry_point_fields[name] end def dynamic_fields @dynamic_fields.values end 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.all_field_definitions.each do |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 type_name = late_bound_type.name @types[type_name] || @schema.get_type(type_name) when GraphQL::Schema::List resolve_late_binding(late_bound_type.of_type).to_list_type when GraphQL::Schema::NonNull 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) const = @custom_namespace.const_get(class_name) dup_type_class(const) rescue NameError # Dup the built-in so that the cached fields aren't shared dup_type_class(@built_in_namespace.const_get(class_name)) end def get_fields_from_class(class_sym:) object_type_defn = load_constant(class_sym) object_type_defn.fields 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 @inner_resolve = inner_resolve end def call(obj, args, ctx) query_ctx = ctx.query.context # Remove the QueryType wrapper if obj.is_a?(GraphQL::Schema::Object) obj = obj.object end wrapped_object = @object_class.wrap(obj, query_ctx) @inner_resolve.call(wrapped_object, args, ctx) end end end end end graphql-2.6.0/lib/graphql/schema/resolver.rb0000644000004100000410000004660015173430257021054 0ustar www-datawww-data# frozen_string_literal: true require "graphql/schema/resolver/has_payload_type" module GraphQL class Schema # A class-based container for field configuration and resolution logic. It supports: # # - Arguments, via `.argument(...)` helper, which will be applied to the field. # - Return type, via `.type(..., null: ...)`, which will be applied to the field. # - Description, via `.description(...)`, which will be applied to the field # - Comment, via `.comment(...)`, which will be applied to the field # - Resolution, via `#resolve(**args)` method, which will be called to resolve the field. # - `#object` and `#context` accessors for use during `#resolve`. # # Resolvers can be attached with the `resolver:` option in a `field(...)` call. # # A resolver's configuration may be overridden with other keywords in the `field(...)` call. # # @see {GraphQL::Schema::Mutation} for a concrete subclass of `Resolver`. # @see {GraphQL::Function} `Resolver` is a replacement for `GraphQL::Function` class Resolver include Schema::Member::GraphQLTypeNames # Really we only need description & comment from here, but: extend Schema::Member::BaseDSLMethods extend GraphQL::Schema::Member::HasArguments extend GraphQL::Schema::Member::HasAuthorization extend GraphQL::Schema::Member::HasValidators include Schema::Member::HasPath extend Schema::Member::HasPath extend Schema::Member::HasDirectives include Schema::Member::HasDataloader extend Schema::Member::HasDeprecationReason # @param object [Object] The application object that this field is being resolved on # @param context [GraphQL::Query::Context] # @param field [GraphQL::Schema::Field] def initialize(object:, context:, field:) @object = object @context = context @field = field # Since this hash is constantly rebuilt, cache it for this call @arguments_by_keyword = {} context.types.arguments(self.class).each do |arg| @arguments_by_keyword[arg.keyword] = arg end @prepared_arguments = nil end attr_accessor :exec_result, :exec_index, :field_resolve_step, :raw_arguments # @return [Object] The application object this field is being resolved on attr_accessor :object # @return [GraphQL::Query::Context] attr_reader :context # @return [GraphQL::Schema::Field] attr_reader :field attr_writer :prepared_arguments def call if self.class < Schema::HasSingleInputArgument @prepared_arguments = @prepared_arguments[:input] end q = context.query trace_objs = [object] q.current_trace.begin_execute_field(field, @prepared_arguments, trace_objs, q) is_ready = ready?(**@prepared_arguments) runner = @field_resolve_step.runner if runner.resolves_lazies && runner.schema.lazy?(is_ready) is_ready, new_return_value = runner.schema.sync_lazy(is_ready) end if is_ready.is_a?(Array) is_ready, new_return_value = is_ready if is_ready != false raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{is_ready.inspect}, #{new_return_value.inspect}]" else new_return_value end end if is_ready begin is_authed, new_return_value = authorized?(**@prepared_arguments) rescue GraphQL::UnauthorizedError => err new_return_value = q.schema.unauthorized_object(err) is_authed = true # the error was handled end end if runner.resolves_lazies && runner.schema.lazy?(is_authed) is_authed, new_return_value = runner.schema.sync_lazy(is_authed) end result = if is_authed Schema::Validator.validate!(self.class.validators, object, context, @prepared_arguments, as: @field) if q.subscription? && @field.owner == context.schema.subscription # This needs to use arguments without `loads:`. TODO extract this into subscription-related code somehow? @original_arguments = @field_resolve_step.runner.input_values[q].argument_values(@field, @field_resolve_step.ast_node.arguments, nil) end call_resolve(@prepared_arguments) elsif new_return_value.nil? err = UnauthorizedFieldError.new(object: object, type: @field_resolve_step.parent_type, context: context, field: @field) context.schema.unauthorized_field(err) else new_return_value end q = context.query q.current_trace.end_execute_field(field, @prepared_arguments, trace_objs, q, [result]) exec_result[exec_index] = result rescue GraphQL::UnauthorizedError => auth_err exec_result[exec_index] = begin context.schema.unauthorized_object(auth_err) rescue GraphQL::ExecutionError => exec_err exec_err end rescue GraphQL::RuntimeError => err exec_result[exec_index] = err rescue StandardError => stderr exec_result[exec_index] = begin context.query.handle_or_reraise(stderr) rescue GraphQL::ExecutionError => ex_err ex_err end ensure field_pending_steps = field_resolve_step.pending_steps field_pending_steps.delete(self) if field_pending_steps.size == 0 && field_resolve_step.field_results field_resolve_step.runner.add_step(field_resolve_step) end end def arguments @prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)") end # This method is _actually_ called by the runtime, # it does some preparation and then eventually calls # the user-defined `#resolve` method. # @api private def resolve_with_support(**args) # First call the ready? hook which may raise raw_ready_val = if !args.empty? ready?(**args) else ready? end context.query.after_lazy(raw_ready_val) do |ready_val| if ready_val.is_a?(Array) is_ready, ready_early_return = ready_val if is_ready != false raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{is_ready.inspect}, #{ready_early_return.inspect}]" else ready_early_return end elsif ready_val # Then call each prepare hook, which may return a different value # for that argument, or may return a lazy object load_arguments_val = load_arguments(args) context.query.after_lazy(load_arguments_val) do |loaded_args| @prepared_arguments = loaded_args Schema::Validator.validate!(self.class.validators, object, context, loaded_args, as: @field) # Then call `authorized?`, which may raise or may return a lazy object raw_authorized_val = if !loaded_args.empty? authorized?(**loaded_args) else authorized? end context.query.after_lazy(raw_authorized_val) do |authorized_val| # If the `authorized?` returned two values, `false, early_return`, # then use the early return value instead of continuing if authorized_val.is_a?(Array) authorized_result, early_return = authorized_val if authorized_result == false early_return else raise "Unexpected result from #authorized? (expected `true`, `false` or `[false, {...}]`): [#{authorized_result.inspect}, #{early_return.inspect}]" end elsif authorized_val # Finally, all the hooks have passed, so resolve it call_resolve(loaded_args) else raise GraphQL::UnauthorizedFieldError.new(context: context, object: object, type: field.owner, field: field) end end end end end end # @api private {GraphQL::Schema::Mutation} uses this to clear the dataloader cache def call_resolve(args_hash) if !args_hash.empty? public_send(self.class.resolve_method, **args_hash) else public_send(self.class.resolve_method) end end # Do the work. Everything happens here. # @return [Object] An object corresponding to the return type def resolve(**args) raise GraphQL::RequiredImplementationMissingError, "#{self.class.name}#resolve should execute the field's logic" end # Called before arguments are prepared. # Implement this hook to make checks before doing any work. # # If it returns a lazy object (like a promise), it will be synced by GraphQL # (but the resulting value won't be used). # # @param args [Hash] The input arguments, if there are any # @raise [GraphQL::ExecutionError] To add an error to the response # @raise [GraphQL::UnauthorizedError] To signal an authorization failure # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.) def ready?(**args) true end # Called after arguments are loaded, but before resolving. # # Override it to check everything before calling the mutation. # @param inputs [Hash] The input arguments # @raise [GraphQL::ExecutionError] To add an error to the response # @raise [GraphQL::UnauthorizedError] To signal an authorization failure # @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.) def authorized?(**inputs) arg_owner = @field # || self.class args = context.types.arguments(arg_owner) authorize_arguments(args, inputs) end def self.authorizes?(context) self.instance_method(:authorized?).owner != GraphQL::Schema::Resolver end # Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type. # # By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}. # # Any value returned here will be used _instead of_ of the loaded object. # @param err [GraphQL::UnauthorizedError] def unauthorized_object(err) raise err end private def authorize_arguments(args, inputs) args.each do |argument| arg_keyword = argument.keyword if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value) auth_result = argument.authorized?(self, arg_value, context) if auth_result.is_a?(Array) # only return this second value if the application returned a second value arg_auth, err = auth_result if !arg_auth return arg_auth, err end elsif auth_result == false return auth_result end end end true end def load_arguments(args) prepared_args = {} prepare_lazies = [] args.each do |key, value| arg_defn = @arguments_by_keyword[key] if arg_defn prepped_value = prepared_args[key] = arg_defn.load_and_authorize_value(self, value, context) if context.schema.lazy?(prepped_value) prepare_lazies << context.query.after_lazy(prepped_value) do |finished_prepped_value| prepared_args[key] = finished_prepped_value end end else # these are `extras:` prepared_args[key] = value end end # Avoid returning a lazy if none are needed if !prepare_lazies.empty? GraphQL::Execution::Lazy.all(prepare_lazies).then { prepared_args } else prepared_args end end def get_argument(name, context = GraphQL::Query::NullContext.instance) self.class.get_argument(name, context) end class << self def field_arguments(context = GraphQL::Query::NullContext.instance) arguments(context) end def any_field_arguments? any_arguments? end def get_field_argument(name, context = GraphQL::Query::NullContext.instance) get_argument(name, context) end def all_field_argument_definitions all_argument_definitions end # Default `:resolve` set below. # @return [Symbol] The method to call on instances of this object to resolve the field def resolve_method(new_method = nil) if new_method @resolve_method = new_method end @resolve_method || (superclass.respond_to?(:resolve_method) ? superclass.resolve_method : :resolve) end # Additional info injected into {#resolve} # @see {GraphQL::Schema::Field#extras} def extras(new_extras = nil) if new_extras @own_extras = new_extras end own_extras = @own_extras || [] own_extras + (superclass.respond_to?(:extras) ? superclass.extras : []) end # If `true` (default), then the return type for this resolver will be nullable. # If `false`, then the return type is non-null. # # @see #type which sets the return type of this field and accepts a `null:` option # @param allow_null [Boolean] Whether or not the response can be null def null(allow_null = nil) if !allow_null.nil? @null = allow_null end @null.nil? ? (superclass.respond_to?(:null) ? superclass.null : true) : @null end def resolver_method(new_method_name = nil) if new_method_name @resolver_method = new_method_name else @resolver_method || :resolve_with_support end end # Call this method to get the return type of the field, # or use it as a configuration method to assign a return type # instead of generating one. # TODO unify with {#null} # @param new_type [Class, Array, nil] If a type definition class is provided, it will be used as the return type of the field # @param null [true, false] Whether or not the field may return `nil` # @return [Class] The type which this field returns. def type(new_type = nil, null: nil) if new_type if null.nil? raise ArgumentError, "required argument `null:` is missing" end @type_expr = new_type @null = null else if type_expr GraphQL::Schema::Member::BuildType.parse_type(type_expr, null: self.null) elsif superclass.respond_to?(:type) superclass.type else nil end end end # Specifies the complexity of the field. Defaults to `1` # @return [Integer, Proc] def complexity(new_complexity = nil) if new_complexity @complexity = new_complexity end @complexity || (superclass.respond_to?(:complexity) ? superclass.complexity : 1) end def broadcastable(new_broadcastable) @broadcastable = new_broadcastable end # @return [Boolean, nil] def broadcastable? if defined?(@broadcastable) @broadcastable else (superclass.respond_to?(:broadcastable?) ? superclass.broadcastable? : nil) end end # Get or set the `max_page_size:` which will be configured for fields using this resolver # (`nil` means "unlimited max page size".) # @param max_page_size [Integer, nil] Set a new value # @return [Integer, nil] The `max_page_size` assigned to fields that use this resolver def max_page_size(new_max_page_size = NOT_CONFIGURED) if new_max_page_size != NOT_CONFIGURED @max_page_size = new_max_page_size elsif defined?(@max_page_size) @max_page_size elsif superclass.respond_to?(:max_page_size) superclass.max_page_size else nil end end # @return [Boolean] `true` if this resolver or a superclass has an assigned `max_page_size` def has_max_page_size? (!!defined?(@max_page_size)) || (superclass.respond_to?(:has_max_page_size?) && superclass.has_max_page_size?) end # Get or set the `default_page_size:` which will be configured for fields using this resolver # (`nil` means "unlimited default page size".) # @param default_page_size [Integer, nil] Set a new value # @return [Integer, nil] The `default_page_size` assigned to fields that use this resolver def default_page_size(new_default_page_size = NOT_CONFIGURED) if new_default_page_size != NOT_CONFIGURED @default_page_size = new_default_page_size elsif defined?(@default_page_size) @default_page_size elsif superclass.respond_to?(:default_page_size) superclass.default_page_size else nil end end # @return [Boolean] `true` if this resolver or a superclass has an assigned `default_page_size` def has_default_page_size? (!!defined?(@default_page_size)) || (superclass.respond_to?(:has_default_page_size?) && superclass.has_default_page_size?) end # A non-normalized type configuration, without `null` applied def type_expr @type_expr || (superclass.respond_to?(:type_expr) ? superclass.type_expr : nil) end # Add an argument to this field's signature, but # also add some preparation hook methods which will be used for this argument # @see {GraphQL::Schema::Argument#initialize} for the signature def argument(*args, **kwargs, &block) # Use `from_resolver: true` to short-circuit the InputObject's own `loads:` implementation # so that we can support `#load_{x}` methods below. super(*args, from_resolver: true, **kwargs) end # Registers new extension # @param extension [Class] Extension class # @param options [Hash] Optional extension options def extension(extension, **options) @own_extensions ||= [] @own_extensions << {extension => options} end # @api private def extensions own_exts = @own_extensions # Jump through some hoops to avoid creating arrays when we don't actually need them if superclass.respond_to?(:extensions) s_exts = superclass.extensions if own_exts if !s_exts.empty? own_exts + s_exts else own_exts end else s_exts end else own_exts || EMPTY_ARRAY end end def inherited(child_class) child_class.description(description) super end private attr_reader :own_extensions end end end end graphql-2.6.0/lib/graphql/schema/timeout.rb0000644000004100000410000001203215173430257020671 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema # This plugin will stop resolving new fields after `max_seconds` have elapsed. # After the time has passed, any remaining fields will be `nil`, with errors added # to the `errors` key. Any already-resolved fields will be in the `data` key, so # you'll get a partial response. # # You can subclass `GraphQL::Schema::Timeout` and override `max_seconds` and/or `handle_timeout` # to provide custom logic when a timeout error occurs. # # Note that this will stop a query _in between_ field resolutions, but # it doesn't interrupt long-running `resolve` functions. Be sure to use # timeout options for external connections. For more info, see # www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/ # # @example Stop resolving fields after 2 seconds # class MySchema < GraphQL::Schema # use GraphQL::Schema::Timeout, max_seconds: 2 # end # # @example Notifying Bugsnag and logging a timeout # class MyTimeout < GraphQL::Schema::Timeout # def handle_timeout(error, query) # Rails.logger.warn("GraphQL Timeout: #{error.message}: #{query.query_string}") # Bugsnag.notify(error, {query_string: query.query_string}) # end # end # # class MySchema < GraphQL::Schema # use MyTimeout, max_seconds: 2 # end # class Timeout def self.use(schema, max_seconds: nil) timeout = self.new(max_seconds: max_seconds) schema.trace_with(self::Trace, timeout: timeout) end def initialize(max_seconds:) @max_seconds = max_seconds end module Trace # @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields def initialize(timeout:, **rest) @timeout = timeout super end def execute_multiplex(multiplex:) multiplex.queries.each do |query| timeout_duration_s = @timeout.max_seconds(query) timeout_state = if timeout_duration_s == false # if the method returns `false`, don't apply a timeout false else now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) timeout_at = now + (timeout_duration_s * 1000) { timeout_at: timeout_at, timed_out: false } end query.context.namespace(@timeout)[:state] = timeout_state end super end def execute_field(query:, field:, **_rest) timeout_state = query.context.namespace(@timeout).fetch(:state) # If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query. if timeout_state == false super elsif Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at) error = GraphQL::Schema::Timeout::TimeoutError.new(field) # Only invoke the timeout callback for the first timeout if !timeout_state[:timed_out] timeout_state[:timed_out] = true @timeout.handle_timeout(error, query) timeout_state = query.context.namespace(@timeout).fetch(:state) end # `handle_timeout` may have set this to be `false` if timeout_state != false error else super end else super end end end # Called at the start of each query. # The default implementation returns the `max_seconds:` value from installing this plugin. # # @param query [GraphQL::Query] The query that's about to run # @return [Numeric, false] The number of seconds after which to interrupt query execution and call {#handle_error}, or `false` to bypass the timeout. def max_seconds(query) @max_seconds end # Invoked when a query times out. # @param error [GraphQL::Schema::Timeout::TimeoutError] # @param query [GraphQL::Error] def handle_timeout(error, query) # override to do something interesting end # Call this method (eg, from {#handle_timeout}) to disable timeout tracking # for the given query. # @param query [GraphQL::Query] # @return [void] def disable_timeout(query) query.context.namespace(self)[:state] = false nil end # This error is raised when a query exceeds `max_seconds`. # Since it's a child of {GraphQL::ExecutionError}, # its message will be added to the response's `errors` key. # # To raise an error that will stop query resolution, use a custom block # to take this error and raise a new one which _doesn't_ descend from {GraphQL::ExecutionError}, # such as `RuntimeError`. class TimeoutError < GraphQL::ExecutionError def initialize(field) super("Timeout on #{field.path}") end end end end end graphql-2.6.0/lib/graphql/schema/printer.rb0000644000004100000410000000640715173430257020677 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema # Used to convert your {GraphQL::Schema} to a GraphQL schema string # # @example print your schema to standard output (via helper) # puts GraphQL::Schema::Printer.print_schema(MySchema) # # @example print your schema to standard output # puts GraphQL::Schema::Printer.new(MySchema).print_schema # # @example print a single type to standard output # class Types::Query < GraphQL::Schema::Object # description "The query root of this schema" # # field :post, Types::Post, null: true # end # # class Types::Post < GraphQL::Schema::Object # description "A blog post" # # field :id, ID, null: false # field :title, String, null: false # field :body, String, null: false # end # # class MySchema < GraphQL::Schema # query(Types::Query) # end # # printer = GraphQL::Schema::Printer.new(MySchema) # puts printer.print_type(Types::Post) # class Printer < GraphQL::Language::Printer attr_reader :schema, :warden # @param schema [GraphQL::Schema] # @param context [Hash] # @param introspection [Boolean] Should include the introspection types in the string? def initialize(schema, context: nil, introspection: false) @document_from_schema = GraphQL::Language::DocumentFromSchemaDefinition.new( schema, context: context, include_introspection_types: introspection, ) @document = @document_from_schema.document @schema = schema end # Return the GraphQL schema string for the introspection type system def self.print_introspection_schema query_root = Class.new(GraphQL::Schema::Object) do graphql_name "Root" field :throwaway_field, String def self.visible?(ctx) false end end schema = Class.new(GraphQL::Schema) { use GraphQL::Schema::Visibility query(query_root) def self.visible?(member, _ctx) member.graphql_name != "Root" end } introspection_schema_ast = GraphQL::Language::DocumentFromSchemaDefinition.new( schema, include_introspection_types: true, include_built_in_directives: true, ).document introspection_schema_ast.to_query_string(printer: IntrospectionPrinter.new) end # Return a GraphQL schema string for the defined types in the schema # @param schema [GraphQL::Schema] # @param context [Hash] # @param only [<#call(member, ctx)>] # @param except [<#call(member, ctx)>] def self.print_schema(schema, **args) printer = new(schema, **args) printer.print_schema end # Return a GraphQL schema string for the defined types in the schema def print_schema print(@document) + "\n" end def print_type(type) node = @document_from_schema.build_type_definition_node(type) print(node) end class IntrospectionPrinter < GraphQL::Language::Printer def print_schema_definition(schema) print_string("schema {\n query: Root\n}") end end end end end graphql-2.6.0/lib/graphql/schema/wrapper.rb0000644000004100000410000000115115173430257020663 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Wrapper include GraphQL::Schema::Member::TypeSystemHelpers # @return [Class, Module] The inner type of this wrapping type, the type of which one or more objects may be present. attr_reader :of_type def initialize(of_type) @of_type = of_type end def unwrap @unwrapped ||= @of_type.unwrap end def freeze unwrap to_type_signature super end def ==(other) self.class == other.class && of_type == other.of_type end end end end graphql-2.6.0/lib/graphql/schema/directive/0000755000004100000410000000000015173430257020636 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/schema/directive/specified_by.rb0000644000004100000410000000070315173430257023610 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member class SpecifiedBy < GraphQL::Schema::Directive description "Exposes a URL that specifies the behavior of this scalar." locations(GraphQL::Schema::Directive::SCALAR) default_directive true argument :url, String, description: "The URL that specifies the behavior of this scalar." end end end end graphql-2.6.0/lib/graphql/schema/directive/deprecated.rb0000644000004100000410000000155615173430257023272 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member class Deprecated < GraphQL::Schema::Directive description "Marks an element of a GraphQL schema as no longer supported." locations(GraphQL::Schema::Directive::FIELD_DEFINITION, GraphQL::Schema::Directive::ENUM_VALUE, GraphQL::Schema::Directive::ARGUMENT_DEFINITION, GraphQL::Schema::Directive::INPUT_FIELD_DEFINITION) reason_description = "Explains why this element was deprecated, usually also including a "\ "suggestion for how to access supported similar data. Formatted "\ "in [Markdown](https://daringfireball.net/projects/markdown/)." argument :reason, String, reason_description, default_value: Directive::DEFAULT_DEPRECATION_REASON, required: false default_directive true end end end end graphql-2.6.0/lib/graphql/schema/directive/one_of.rb0000644000004100000410000000105515173430257022431 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member class OneOf < GraphQL::Schema::Directive description "Requires that exactly one field must be supplied and that field must not be `null`." locations(GraphQL::Schema::Directive::INPUT_OBJECT) default_directive true def initialize(...) super owner.extend(IsOneOf) end module IsOneOf def one_of? true end end end end end end graphql-2.6.0/lib/graphql/schema/directive/feature.rb0000644000004100000410000000540615173430257022623 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member # An example directive to show how you might interact with the runtime. # # This directive might be used along with a server-side feature flag system like Flipper. # # With that system, you could use this directive to exclude parts of a query # if the current viewer doesn't have certain flags enabled. # (So, this flag would be for internal clients, like your iOS app, not third-party API clients.) # # To use it, you have to implement `.enabled?`, for example: # # @example Implementing the Feature directive # # app/graphql/directives/feature.rb # class Directives::Feature < GraphQL::Schema::Directive::Feature # def self.enabled?(flag_name, _obj, context) # # Translate some GraphQL data for Ruby: # flag_key = flag_name.underscore # current_user = context[:viewer] # # Check the feature flag however your app does it: # MyFeatureFlags.enabled?(current_user, flag_key) # end # end # # @example Flagging a part of the query # viewer { # # This field only runs if `.enabled?("recommendationEngine", obj, context)` # # returns true. Otherwise, it's treated as if it didn't exist. # recommendations @feature(flag: "recommendationEngine") { # name # rating # } # } class Feature < Schema::Directive description "Directs the executor to run this only if a certain server-side feature is enabled." locations( GraphQL::Schema::Directive::FIELD, GraphQL::Schema::Directive::FRAGMENT_SPREAD, GraphQL::Schema::Directive::INLINE_FRAGMENT ) argument :flag, String, description: "The name of the feature to check before continuing" # Implement the Directive API def self.include?(object, arguments, context) flag_name = arguments[:flag] self.enabled?(flag_name, object, context) end # Override this method in your app's subclass of this directive. # # @param flag_name [String] The client-provided string of a feature to check # @param object [GraphQL::Schema::Objct] The currently-evaluated GraphQL object instance # @param context [GraphQL::Query::Context] # @return [Boolean] If truthy, execution will continue def self.enabled?(flag_name, object, context) raise GraphQL::RequiredImplementationMissingError, "Implement `.enabled?(flag_name, object, context)` to return true or false for the feature flag (#{flag_name.inspect})" end end end end end graphql-2.6.0/lib/graphql/schema/directive/flagged.rb0000644000004100000410000000450215173430257022555 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member # This is _similar_ to {Directive::Feature}, except it's prescribed by the server, not the client. # # In this case, the server hides types and fields _entirely_, unless the current context has certain `:flags` present. class Flagged < GraphQL::Schema::Directive def initialize(target, **options) if target.is_a?(Module) # This is type class of some kind, `include` will put this module # in between the type class itself and its super class, so `super` will work fine target.include(VisibleByFlag) elsif !target.is_a?(VisibleByFlag) # This is an instance of a base class. `include` won't put this in front of the # base class implementation, so we need to `.prepend`. # `#visible?` could probably be moved to a module and then this could use `include` instead. target.class.prepend(VisibleByFlag) end super end description "Hides this part of the schema unless the named flag is present in context[:flags]" locations( GraphQL::Schema::Directive::FIELD_DEFINITION, GraphQL::Schema::Directive::OBJECT, GraphQL::Schema::Directive::SCALAR, GraphQL::Schema::Directive::ENUM, GraphQL::Schema::Directive::UNION, GraphQL::Schema::Directive::INTERFACE, GraphQL::Schema::Directive::INPUT_OBJECT, GraphQL::Schema::Directive::ENUM_VALUE, GraphQL::Schema::Directive::ARGUMENT_DEFINITION, GraphQL::Schema::Directive::INPUT_FIELD_DEFINITION, ) argument :by, [String], "Flags to check for this schema member" repeatable(true) module VisibleByFlag def self.included(schema_class) schema_class.extend(self) end def visible?(context) if dir = self.directives.find { |d| d.is_a?(Flagged) } relevant_flags = (f = context[:flags]) && dir.arguments[:by] & f # rubocop:disable Development/ContextIsPassedCop -- definition-related relevant_flags && !relevant_flags.empty? && super else super end end end end end end end graphql-2.6.0/lib/graphql/schema/directive/transform.rb0000644000004100000410000000362715173430257023206 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member # An example directive to show how you might interact with the runtime. # # This directive takes the return value of the tagged part of the query, # and if the named transform is whitelisted and applies to the return value, # it's applied by calling a method with that name. # # @example Installing the directive # class MySchema < GraphQL::Schema # directive(GraphQL::Schema::Directive::Transform) # end # # @example Transforming strings # viewer { # username @transform(by: "upcase") # } class Transform < Schema::Directive description "Directs the executor to run named transform on the return value." locations( GraphQL::Schema::Directive::FIELD, ) argument :by, String, description: "The name of the transform to run if applicable" TRANSFORMS = [ "upcase", "downcase", # ?? ] # Implement the Directive API def self.resolve(object, arguments, context) path = context.namespace(:interpreter)[:current_path] return_value = yield transform_name = arguments[:by] if TRANSFORMS.include?(transform_name) && return_value.respond_to?(transform_name) return_value = return_value.public_send(transform_name) response = context.namespace(:interpreter_runtime)[:runtime].final_result *keys, last = path keys.each do |key| if response && (response = response[key]) next else break end end if response response[last] = return_value end nil end end end end end end graphql-2.6.0/lib/graphql/schema/directive/skip.rb0000644000004100000410000000120715173430257022131 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member class Skip < Schema::Directive description "Directs the executor to skip this field or fragment when the `if` argument is true." locations( GraphQL::Schema::Directive::FIELD, GraphQL::Schema::Directive::FRAGMENT_SPREAD, GraphQL::Schema::Directive::INLINE_FRAGMENT ) argument :if, Boolean, description: "Skipped when true." default_directive true def self.static_include?(args, ctx) !args[:if] end end end end end graphql-2.6.0/lib/graphql/schema/directive/include.rb0000644000004100000410000000123515173430257022607 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema class Directive < GraphQL::Schema::Member class Include < GraphQL::Schema::Directive description "Directs the executor to include this field or fragment only when the `if` argument is true." locations( GraphQL::Schema::Directive::FIELD, GraphQL::Schema::Directive::FRAGMENT_SPREAD, GraphQL::Schema::Directive::INLINE_FRAGMENT ) argument :if, Boolean, description: "Included when true." default_directive true def self.static_include?(args, ctx) !!args[:if] end end end end end graphql-2.6.0/lib/graphql/schema/field_extension.rb0000644000004100000410000001473115173430257022372 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Schema # Extend this class to make field-level customizations to resolve behavior. # # When a extension is added to a field with `extension(MyExtension)`, a `MyExtension` instance # is created, and its hooks are applied whenever that field is called. # # The instance is frozen so that instance variables aren't modified during query execution, # which could cause all kinds of issues due to race conditions. class FieldExtension # @return [GraphQL::Schema::Field] attr_reader :field # @return [Object] attr_reader :options # @return [Array, nil] `default_argument`s added, if any were added (otherwise, `nil`) attr_reader :added_default_arguments # Called when the extension is mounted with `extension(name, options)`. # The instance will be frozen to avoid improper use of state during execution. # @param field [GraphQL::Schema::Field] The field where this extension was mounted # @param options [Object] The second argument to `extension`, or `{}` if nothing was passed. def initialize(field:, options:) @field = field @options = options || {} @added_default_arguments = nil apply end class << self # @return [Array(Array, Hash), nil] A list of default argument configs, or `nil` if there aren't any def default_argument_configurations args = superclass.respond_to?(:default_argument_configurations) ? superclass.default_argument_configurations : nil if @own_default_argument_configurations if args args.concat(@own_default_argument_configurations) else args = @own_default_argument_configurations.dup end end args end # @see Argument#initialize # @see HasArguments#argument def default_argument(*argument_args, **argument_kwargs) configs = @own_default_argument_configurations ||= [] configs << [argument_args, argument_kwargs] end # If configured, these `extras` will be added to the field if they aren't already present, # but removed by from `arguments` before the field's `resolve` is called. # (The extras _will_ be present for other extensions, though.) # # @param new_extras [Array] If provided, assign extras used by this extension # @return [Array] any extras assigned to this extension def extras(new_extras = nil) if new_extras @own_extras = new_extras end inherited_extras = self.superclass.respond_to?(:extras) ? superclass.extras : nil if @own_extras if inherited_extras inherited_extras + @own_extras else @own_extras end elsif inherited_extras inherited_extras else GraphQL::EmptyObjects::EMPTY_ARRAY end end end # Called when this extension is attached to a field. # The field definition may be extended during this method. # @return [void] def apply end # Called after the field's definition block has been executed. # (Any arguments from the block are present on `field`) # @return [void] def after_define end # @api private def after_define_apply after_define if (configs = self.class.default_argument_configurations) existing_keywords = field.all_argument_definitions.map(&:keyword) existing_keywords.uniq! @added_default_arguments = [] configs.each do |config| argument_args, argument_kwargs = config arg_name = argument_args[0] if !existing_keywords.include?(arg_name) @added_default_arguments << arg_name field.argument(*argument_args, **argument_kwargs) end end end if !(extras = self.class.extras).empty? @added_extras = extras - field.extras field.extras(@added_extras) else @added_extras = nil end freeze end # @api private attr_reader :added_extras # Called before resolving {#field}. It should either: # # - `yield` values to continue execution; OR # - return something else to shortcut field execution. # # Whatever this method returns will be used for execution. # # @param object [Object] The object the field is being resolved on (not passed by new execution) # @param objects [Array] The objects the field is being resolved on (passed by new execution) # @param arguments [Hash] Ruby keyword arguments for resolving this field # @param context [Query::Context] the context for this query # @yieldparam object_or_objects [Object, Array] The object or objects (new execution) to continue resolving the field on # @yieldparam arguments [Hash] The keyword arguments to continue resolving with # @yieldparam memo [Object] Any extension-specific value which will be passed to {#after_resolve} later # @return [Object] The return value for this field. def resolve(object: nil, objects: nil, arguments:, context:) yield(object.nil? ? objects : object, arguments, nil) end # Called after {#field} was resolved, and after any lazy values (like `Promise`s) were synced, # but before the value was added to the GraphQL response. # # Whatever this hook returns will be used as the return value. # # @param object [Object] The object the field is being resolved on (not passed by new execution) # @param objects [Array] The object the field is being resolved on (passed by new execution) # @param arguments [Hash] Ruby keyword arguments for resolving this field # @param context [Query::Context] the context for this query # @param value [Object] Whatever the field previously returned (not passed by new execution) # @param values [Array] Whatever the field previously returned (passed by new execution) # @param memo [Object] The third value yielded by {#resolve}, or `nil` if there wasn't one # @return [Object] The return value for this field. def after_resolve(object: nil, objects: nil, arguments:, context:, values: nil, value: nil, memo:) value.nil? ? values : value end end end end graphql-2.6.0/lib/graphql/duration_encoding_error.rb0000644000004100000410000000112015173430257022643 0ustar www-datawww-data# frozen_string_literal: true module GraphQL # This error is raised when `Types::ISO8601Duration` is asked to return a value # that cannot be parsed as an ISO8601-formatted duration by ActiveSupport::Duration. # # @see GraphQL::Types::ISO8601Duration which raises this error class DurationEncodingError < GraphQL::RuntimeTypeError # The value which couldn't be encoded attr_reader :duration_value def initialize(value) @duration_value = value super("Duration cannot be parsed: #{value}. \nDuration must be an ISO8601-formatted duration.") end end end graphql-2.6.0/lib/graphql/static_validation.rb0000644000004100000410000000117015173430257021445 0ustar www-datawww-data# frozen_string_literal: true require "graphql/static_validation/error" require "graphql/static_validation/definition_dependencies" require "graphql/static_validation/validator" require "graphql/static_validation/validation_context" require "graphql/static_validation/validation_timeout_error" require "graphql/static_validation/literal_validator" require "graphql/static_validation/base_visitor" rules_glob = File.expand_path("../static_validation/rules/*.rb", __FILE__) Dir.glob(rules_glob).each do |file| require(file) end require "graphql/static_validation/all_rules" require "graphql/static_validation/interpreter_visitor" graphql-2.6.0/lib/graphql/rubocop/0000755000004100000410000000000015173430257017071 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/rubocop/graphql/0000755000004100000410000000000015173430257020527 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/rubocop/graphql/default_null_true.rb0000644000004100000410000000245215173430257024574 0ustar www-datawww-data# frozen_string_literal: true require_relative "base_cop" module GraphQL module Rubocop module GraphQL # Identify (and auto-correct) any field configuration which duplicates # the default `null: true` property. # # `null: true` is default because nullable fields can always be converted # to non-null fields (`null: false`) without a breaking change. (The opposite change, from `null: false` # to `null: true`, change.) # # @example # # Both of these define `name: String` in GraphQL: # # # bad # field :name, String, null: true # # # good # field :name, String # class DefaultNullTrue < BaseCop MSG = "`null: true` is the default and can be removed." def_node_matcher :field_config_with_null_true?, <<-Pattern ( send nil? :field ... (hash $(pair (sym :null) (true)) ...) ) Pattern def on_send(node) field_config_with_null_true?(node) do |null_config| add_offense(null_config) do |corrector| cleaned_node_source = source_without_keyword_argument(node, null_config) corrector.replace(node.source_range, cleaned_node_source) end end end end end end end graphql-2.6.0/lib/graphql/rubocop/graphql/default_required_true.rb0000644000004100000410000000254415173430257025444 0ustar www-datawww-data# frozen_string_literal: true require_relative "./base_cop" module GraphQL module Rubocop module GraphQL # Identify (and auto-correct) any argument configuration which duplicates # the default `required: true` property. # # `required: true` is default because required arguments can always be converted # to optional arguments (`required: false`) without a breaking change. (The opposite change, from `required: false` # to `required: true`, change.) # # @example # # Both of these define `id: ID!` in GraphQL: # # # bad # argument :id, ID, required: true # # # good # argument :id, ID # class DefaultRequiredTrue < BaseCop MSG = "`required: true` is the default and can be removed." def_node_matcher :argument_config_with_required_true?, <<-Pattern ( send {nil? _} :argument ... (hash <$(pair (sym :required) (true)) ...>) ) Pattern def on_send(node) argument_config_with_required_true?(node) do |required_config| add_offense(required_config) do |corrector| cleaned_node_source = source_without_keyword_argument(node, required_config) corrector.replace(node, cleaned_node_source) end end end end end end end graphql-2.6.0/lib/graphql/rubocop/graphql/field_type_in_block.rb0000644000004100000410000001233715173430257025046 0ustar www-datawww-data# frozen_string_literal: true require_relative "./base_cop" module GraphQL module Rubocop module GraphQL # Identify (and auto-correct) any field whose type configuration isn't given # in the configuration block. # # @example # # bad, immediately causes Rails to load `app/graphql/types/thing.rb` # field :thing, Types::Thing # # # good, defers loading until the file is needed # field :thing do # type(Types::Thing) # end # class FieldTypeInBlock < BaseCop MSG = "type configuration can be moved to a block to defer loading the type's file" BUILT_IN_SCALAR_NAMES = ["Float", "Int", "Integer", "String", "ID", "Boolean"] def_node_matcher :field_config_with_inline_type, <<-Pattern ( send {nil? _} :field sym ${const array} ... ) Pattern def_node_matcher :field_config_with_inline_type_and_block, <<-Pattern ( block (send {nil? _} :field sym ${const array} ...) ... (args) _ ) Pattern def on_block(node) ignore_node(node) field_config_with_inline_type_and_block(node) do |type_const| type_const_str = get_type_argument_str(node, type_const) if ignore_inline_type_str?(type_const_str) # Do nothing ... else add_offense(type_const) do |corrector| cleaned_node_source = delete_type_argument(node, type_const) field_indent = determine_field_indent(node) cleaned_node_source.sub!(/(\{|do)/, "\\1\n#{field_indent} type #{type_const_str}") corrector.replace(node, cleaned_node_source) end end end end def on_send(node) return if part_of_ignored_node?(node) field_config_with_inline_type(node) do |type_const| type_const_str = get_type_argument_str(node, type_const) if ignore_inline_type_str?(type_const_str) # Do nothing -- not loading from another file else add_offense(type_const) do |corrector| cleaned_node_source = delete_type_argument(node, type_const) field_indent = determine_field_indent(node) cleaned_node_source += " do\n#{field_indent} type #{type_const_str}\n#{field_indent}end" corrector.replace(node, cleaned_node_source) end end end end private def ignore_inline_type_str?(type_str) if BUILT_IN_SCALAR_NAMES.include?(type_str) true elsif (inner_type_str = type_str.sub(/\[([A-Za-z]+)(, null: (true|false))?\]/, '\1')) && BUILT_IN_SCALAR_NAMES.include?(inner_type_str) true else false end end def get_type_argument_str(send_node, type_const) first_pos = type_const.location.expression.begin_pos end_pos = type_const.location.expression.end_pos node_source = send_node.source_range.source node_first_pos = send_node.location.expression.begin_pos relative_first_pos = first_pos - node_first_pos end_removal_pos = end_pos - node_first_pos node_source[relative_first_pos...end_removal_pos] end def delete_type_argument(send_node, type_const) first_pos = type_const.location.expression.begin_pos end_pos = type_const.location.expression.end_pos node_source = send_node.source_range.source node_first_pos = send_node.location.expression.begin_pos relative_first_pos = first_pos - node_first_pos end_removal_pos = end_pos - node_first_pos begin_removal_pos = relative_first_pos while node_source[begin_removal_pos] != "," begin_removal_pos -= 1 if begin_removal_pos < 1 raise "Invariant: somehow backtracked to beginning of node looking for a comma (node source: #{node_source.inspect})" end end node_source[0...begin_removal_pos] + node_source[end_removal_pos..-1] end def determine_field_indent(send_node) type_defn_node = send_node while (type_defn_node && !(type_defn_node.class_definition? || type_defn_node.module_definition?)) type_defn_node = type_defn_node.parent end if type_defn_node.nil? raise "Invariant: Something went wrong in GraphQL-Ruby, couldn't find surrounding class definition for field (#{send_node}).\n\nPlease report this error on GitHub." end type_defn_source = type_defn_node.source indent_test_idx = send_node.location.expression.begin_pos - type_defn_node.source_range.begin_pos - 1 field_indent = "".dup while type_defn_source[indent_test_idx] == " " field_indent << " " indent_test_idx -= 1 if indent_test_idx == 0 raise "Invariant: somehow backtracted to beginning of class when looking for field indent (source: #{node_source.inspect})" end end field_indent end end end end end graphql-2.6.0/lib/graphql/rubocop/graphql/root_types_in_block.rb0000644000004100000410000000216215173430257025124 0ustar www-datawww-data# frozen_string_literal: true require_relative "./base_cop" module GraphQL module Rubocop module GraphQL # Identify (and auto-correct) any root types in your schema file. # # @example # # bad, immediately causes Rails to load `app/graphql/types/query.rb` # query Types::Query # # # good, defers loading until the file is needed # query { Types::Query } # class RootTypesInBlock < BaseCop MSG = "type configuration can be moved to a block to defer loading the type's file" def_node_matcher :root_type_config_without_block, <<-Pattern ( send nil? {:query :mutation :subscription} const ) Pattern def on_send(node) root_type_config_without_block(node) do add_offense(node) do |corrector| new_node_source = node.source_range.source new_node_source.sub!(/(query|mutation|subscription)/, '\1 {') new_node_source << " }" corrector.replace(node, new_node_source) end end end end end end end graphql-2.6.0/lib/graphql/rubocop/graphql/base_cop.rb0000644000004100000410000000244015173430257022627 0ustar www-datawww-data# frozen_string_literal: true require "rubocop" module GraphQL module Rubocop module GraphQL class BaseCop < RuboCop::Cop::Base extend RuboCop::Cop::AutoCorrector # Return the source of `send_node`, but without the keyword argument represented by `pair_node` def source_without_keyword_argument(send_node, pair_node) # work back to the preceding comma first_pos = pair_node.location.expression.begin_pos end_pos = pair_node.location.expression.end_pos node_source = send_node.source_range.source node_first_pos = send_node.location.expression.begin_pos relative_first_pos = first_pos - node_first_pos relative_last_pos = end_pos - node_first_pos begin_removal_pos = relative_first_pos while node_source[begin_removal_pos] != "," begin_removal_pos -= 1 if begin_removal_pos < 1 raise "Invariant: somehow backtracked to beginning of node looking for a comma (node source: #{node_source.inspect})" end end end_removal_pos = relative_last_pos cleaned_node_source = node_source[0...begin_removal_pos] + node_source[end_removal_pos..-1] cleaned_node_source end end end end end graphql-2.6.0/lib/graphql/integer_decoding_error.rb0000644000004100000410000000110015173430257022437 0ustar www-datawww-data# frozen_string_literal: true module GraphQL # This error is raised when `Types::Int` is given an input value outside of 32-bit integer range. # # For really big integer values, consider `GraphQL::Types::BigInt` # # @see GraphQL::Types::Int which raises this error class IntegerDecodingError < GraphQL::RuntimeTypeError # The value which couldn't be decoded attr_reader :integer_value def initialize(value) @integer_value = value super("Integer out of bounds: #{value}. \nConsider using GraphQL::Types::BigInt instead.") end end end graphql-2.6.0/lib/graphql/dataloader/0000755000004100000410000000000015173430257017520 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/dataloader/request.rb0000644000004100000410000000124315173430257021535 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Dataloader # @see Source#request which returns an instance of this class Request def initialize(source, key) @source = source @key = key end # Call this method to cause the current Fiber to wait for the results of this request. # # @return [Object] the object loaded for `key` def load @source.load(@key) end def load_with_deprecation_warning warn("Returning `.request(...)` from GraphQL::Dataloader is deprecated, use `.load(...)` instead. (See usage of #{@source} with #{@key.inspect}).") load end end end end graphql-2.6.0/lib/graphql/dataloader/null_dataloader.rb0000644000004100000410000000372115173430257023202 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Dataloader # GraphQL-Ruby uses this when Dataloader isn't enabled. # # It runs execution code inline and gathers lazy objects (eg. Promises) # and resolves them during {#run}. class NullDataloader < Dataloader def initialize(*) @lazies_at_depth = Hash.new { |h,k| h[k] = [] } end def freeze @lazies_at_depth.default_proc = nil @lazies_at_depth.freeze super end def run(trace_query_lazy: nil) with_trace_query_lazy(trace_query_lazy) do while !@lazies_at_depth.empty? smallest_depth = nil @lazies_at_depth.each_key do |depth_key| smallest_depth ||= depth_key if depth_key < smallest_depth smallest_depth = depth_key end end if smallest_depth lazies = @lazies_at_depth.delete(smallest_depth) lazies.each(&:value) # resolve these Lazy instances end end end end def run_isolated # Reuse this instance because execution code may already have a reference to _this_ `dataloader` inside the given block. prev_lazies_at_depth = @lazies_at_depth @lazies_at_depth = @lazies_at_depth.dup.clear res = nil append_job { res = yield } run res ensure @lazies_at_depth = prev_lazies_at_depth end def clear_cache; end def yield(_source) raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources." end def append_job(callable = nil) callable ? callable.call : yield nil end def with(*) raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources." end end end end graphql-2.6.0/lib/graphql/dataloader/async_dataloader.rb0000644000004100000410000000757415173430257023357 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Dataloader class AsyncDataloader < Dataloader def yield(source = Fiber[:__graphql_current_dataloader_source]) trace = Fiber[:__graphql_current_multiplex]&.current_trace trace&.dataloader_fiber_yield(source) if (condition = Fiber[:graphql_dataloader_next_tick]) condition.wait else Fiber.yield end trace&.dataloader_fiber_resume(source) nil end def run(trace_query_lazy: nil) trace = Fiber[:__graphql_current_multiplex]&.current_trace jobs_fiber_limit, total_fiber_limit = calculate_fiber_limit job_fibers = [] next_job_fibers = [] source_tasks = [] next_source_tasks = [] first_pass = true sources_condition = Async::Condition.new manager = spawn_fiber do trace&.begin_dataloader(self) while first_pass || !job_fibers.empty? first_pass = false fiber_vars = get_fiber_variables run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace) Sync do |root_task| set_fiber_variables(fiber_vars) while !source_tasks.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) } while (task = (source_tasks.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size + next_source_tasks.size) < total_fiber_limit) && spawn_source_task(root_task, sources_condition, trace)))) if task.alive? root_task.yield # give the source task a chance to run next_source_tasks << task end end sources_condition.signal source_tasks.concat(next_source_tasks) next_source_tasks.clear end end if !@lazies_at_depth.empty? with_trace_query_lazy(trace_query_lazy) do run_next_pending_lazies(job_fibers, trace) run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace) end end end trace&.end_dataloader(self) end manager.resume if manager.alive? raise "Invariant: Manager didn't terminate successfully: #{manager}" end rescue UncaughtThrowError => e throw e.tag, e.value end private def run_pending_steps(job_fibers, next_job_fibers, source_tasks, jobs_fiber_limit, trace) while (f = (job_fibers.shift || (((job_fibers.size + next_job_fibers.size + source_tasks.size) < jobs_fiber_limit) && spawn_job_fiber(trace)))) if f.alive? finished = run_fiber(f) if !finished next_job_fibers << f end end end job_fibers.concat(next_job_fibers) next_job_fibers.clear end def spawn_source_task(parent_task, condition, trace) pending_sources = nil @source_cache.each_value do |source_by_batch_params| source_by_batch_params.each_value do |source| if source.pending? pending_sources ||= [] pending_sources << source end end end if pending_sources fiber_vars = get_fiber_variables parent_task.async do trace&.dataloader_spawn_source_fiber(pending_sources) set_fiber_variables(fiber_vars) Fiber[:graphql_dataloader_next_tick] = condition pending_sources.each do |s| trace&.begin_dataloader_source(s) s.run_pending_keys trace&.end_dataloader_source(s) end cleanup_fiber trace&.dataloader_fiber_exit end end end end end end graphql-2.6.0/lib/graphql/dataloader/active_record_association_source.rb0000644000004100000410000000502115173430257026630 0ustar www-datawww-data# frozen_string_literal: true require "graphql/dataloader/source" require "graphql/dataloader/active_record_source" module GraphQL class Dataloader class ActiveRecordAssociationSource < GraphQL::Dataloader::Source RECORD_SOURCE_CLASS = ActiveRecordSource def initialize(association, scope = nil) @association = association @scope = scope end def self.batch_key_for(association, scope = nil) if scope [association, scope.to_sql] else [association] end end def load(record) if (assoc = record.association(@association)).loaded? assoc.target else super end end def fetch(records) record_classes = Set.new.compare_by_identity associated_classes = Set.new.compare_by_identity scoped_fetch = !@scope.nil? records.each do |record| if scoped_fetch assoc = record.association(@association) assoc.reset end if record_classes.add?(record.class) reflection = record.class.reflect_on_association(@association) if !reflection.polymorphic? && reflection.klass associated_classes.add(reflection.klass) end end end available_records = [] associated_classes.each do |assoc_class| already_loaded_records = dataloader.with(RECORD_SOURCE_CLASS, assoc_class).results.values available_records.concat(already_loaded_records) end ::ActiveRecord::Associations::Preloader.new(records: records, associations: @association, available_records: available_records, scope: @scope).call loaded_associated_records = records.map { |r| assoc = r.association(@association) lar = assoc.target if scoped_fetch assoc.reset end lar } if !scoped_fetch # Don't cache records loaded via scope because they might have reduced `SELECT`s # Could check .select_values here? records_by_model = {} loaded_associated_records.flatten.each do |record| if record updates = records_by_model[record.class] ||= {} updates[record.id] = record end end records_by_model.each do |model_class, updates| dataloader.with(RECORD_SOURCE_CLASS, model_class).merge(updates) end end loaded_associated_records end end end end graphql-2.6.0/lib/graphql/dataloader/active_record_source.rb0000644000004100000410000000253015173430257024236 0ustar www-datawww-data# frozen_string_literal: true require "graphql/dataloader/source" module GraphQL class Dataloader class ActiveRecordSource < GraphQL::Dataloader::Source def initialize(model_class, find_by: model_class.primary_key) @model_class = model_class @find_by = find_by @find_by_many = find_by.is_a?(Array) if @find_by_many @type_for_column = @find_by.map { |fb| @model_class.type_for_attribute(fb) } else @type_for_column = @model_class.type_for_attribute(@find_by) end end def result_key_for(requested_key) normalize_fetch_key(requested_key) end def normalize_fetch_key(requested_key) if @find_by_many requested_key.each_with_index.map do |k, idx| @type_for_column[idx].cast(k) end else @type_for_column.cast(requested_key) end end def fetch(record_ids) records = @model_class.where(@find_by => record_ids) record_lookup = {} if @find_by_many records.each do |r| key = @find_by.map { |fb| r.public_send(fb) } record_lookup[key] = r end else records.each { |r| record_lookup[r.public_send(@find_by)] = r } end record_ids.map { |id| record_lookup[id] } end end end end graphql-2.6.0/lib/graphql/dataloader/source.rb0000644000004100000410000001737215173430257021357 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Dataloader class Source # Called by {Dataloader} to prepare the {Source}'s internal state # @api private def setup(dataloader) # These keys have been requested but haven't been fetched yet @pending = {} # These keys have been passed to `fetch` but haven't been finished yet @fetching = {} # { key => result } @results = {} @dataloader = dataloader end attr_reader :dataloader # @return [Dataloader::Request] a pending request for a value from `key`. Call `.load` on that object to wait for the result. def request(value) res_key = result_key_for(value) if !@results.key?(res_key) @pending[res_key] ||= normalize_fetch_key(value) end Dataloader::Request.new(self, value) end # Implement this method to return a stable identifier if different # key objects should load the same data value. # # @param value [Object] A value passed to `.request` or `.load`, for which a value will be loaded # @return [Object] The key for tracking this pending data def result_key_for(value) value end # Implement this method if varying values given to {load} (etc) should be consolidated # or normalized before being handed off to your {fetch} implementation. # # This is different than {result_key_for} because _that_ method handles unification inside Dataloader's cache, # but this method changes the value passed into {fetch}. # # @param value [Object] The value passed to {load}, {load_all}, {request}, or {request_all} # @return [Object] The value given to {fetch} def normalize_fetch_key(value) value end # @return [Dataloader::Request] a pending request for a values from `keys`. Call `.load` on that object to wait for the results. def request_all(values) values.each do |v| res_key = result_key_for(v) if !@results.key?(res_key) @pending[res_key] ||= normalize_fetch_key(v) end end Dataloader::RequestAll.new(self, values) end # @param value [Object] A loading value which will be passed to {#fetch} if it isn't already in the internal cache. # @return [Object] The result from {#fetch} for `key`. If `key` hasn't been loaded yet, the Fiber will yield until it's loaded. def load(value) result_key = result_key_for(value) if @results.key?(result_key) result_for(result_key) else @pending[result_key] ||= normalize_fetch_key(value) sync([result_key]) result_for(result_key) end end # @param values [Array] Loading keys which will be passed to `#fetch` (or read from the internal cache). # @return [Object] The result from {#fetch} for `keys`. If `keys` haven't been loaded yet, the Fiber will yield until they're loaded. def load_all(values) result_keys = [] pending_keys = [] values.each { |v| k = result_key_for(v) result_keys << k if !@results.key?(k) @pending[k] ||= normalize_fetch_key(v) pending_keys << k end } if !pending_keys.empty? sync(pending_keys) end result_keys.map { |k| result_for(k) } end # Subclasses must implement this method to return a value for each of `keys` # @param keys [Array] keys passed to {#load}, {#load_all}, {#request}, or {#request_all} # @return [Array] A loaded value for each of `keys`. The array must match one-for-one to the list of `keys`. def fetch(keys) # somehow retrieve these from the backend raise "Implement `#{self.class}#fetch(#{keys.inspect}) to return a record for each of the keys" end MAX_ITERATIONS = 1000 # Wait for a batch, if there's anything to batch. # Then run the batch and update the cache. # @return [void] def sync(pending_result_keys) @dataloader.yield(self) iterations = 0 while pending_result_keys.any? { |key| !@results.key?(key) } iterations += 1 if iterations > MAX_ITERATIONS raise "#{self.class}#sync tried #{MAX_ITERATIONS} times to load pending keys (#{pending_result_keys}), but they still weren't loaded. There is likely a circular dependency#{@dataloader.fiber_limit ? " or `fiber_limit: #{@dataloader.fiber_limit}` is set too low" : ""}." end @dataloader.yield(self) end nil end # @return [Boolean] True if this source has any pending requests for data. def pending? !@pending.empty? end # Add these key-value pairs to this source's cache # (future loads will use these merged values). # @param new_results [Hash Object>] key-value pairs to cache in this source # @return [void] def merge(new_results) new_results.each do |new_k, new_v| key = result_key_for(new_k) @results[key] = new_v end nil end # Called by {GraphQL::Dataloader} to resolve and pending requests to this source. # @api private # @return [void] def run_pending_keys if !@fetching.empty? @fetching.each_key { |k| @pending.delete(k) } end return if @pending.empty? fetch_h = @pending @pending = {} @fetching.merge!(fetch_h) results = fetch(fetch_h.values) fetch_h.each_with_index do |(key, _value), idx| @results[key] = results[idx] end nil rescue StandardError => error fetch_h.each_key { |key| @results[key] = error } ensure fetch_h && fetch_h.each_key { |k| @fetching.delete(k) } end # These arguments are given to `dataloader.with(source_class, ...)`. The object # returned from this method is used to de-duplicate batch loads under the hood # by using it as a Hash key. # # By default, the arguments are all put in an Array. To customize how this source's # batches are merged, override this method to return something else. # # For example, if you pass `ActiveRecord::Relation`s to `.with(...)`, you could override # this method to call `.to_sql` on them, thus merging `.load(...)` calls when they apply # to equivalent relations. # # @param batch_args [Array] # @param batch_kwargs [Hash] # @return [Object] def self.batch_key_for(*batch_args, **batch_kwargs) [*batch_args, **batch_kwargs] end # Clear any already-loaded objects for this source # @return [void] def clear_cache @results.clear nil end attr_reader :pending, :results private # Reads and returns the result for the key from the internal cache, or raises an error if the result was an error # @param key [Object] key passed to {#load} or {#load_all} # @return [Object] The result from {#fetch} for `key`. # @api private def result_for(key) if !@results.key?(key) raise GraphQL::InvariantError, <<-ERR Fetching result for a key on #{self.class} that hasn't been loaded yet (#{key.inspect}, loaded: #{@results.keys}) This key should have been loaded already. This is a bug in GraphQL::Dataloader, please report it on GitHub: https://github.com/rmosolgo/graphql-ruby/issues/new. ERR end result = @results[key] if result.is_a?(StandardError) # Dup it because the rescuer may modify it. # (This happens for GraphQL::ExecutionErrors, at least) raise result.dup end result end end end end graphql-2.6.0/lib/graphql/dataloader/request_all.rb0000644000004100000410000000075215173430257022371 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Dataloader # @see Source#request_all which returns an instance of this. class RequestAll < Request def initialize(source, keys) @source = source @keys = keys end # Call this method to cause the current Fiber to wait for the results of this request. # # @return [Array] One object for each of `keys` def load @source.load_all(@keys) end end end end graphql-2.6.0/lib/graphql/execution.rb0000644000004100000410000000106715173430257017754 0ustar www-datawww-data# frozen_string_literal: true require "graphql/execution/directive_checks" require "graphql/execution/next" require "graphql/execution/interpreter" require "graphql/execution/lazy" require "graphql/execution/lookahead" require "graphql/execution/multiplex" require "graphql/execution/errors" module GraphQL module Execution # @api private class Skip < GraphQL::RuntimeError attr_accessor :path def ast_nodes=(_ignored); end def finalize_graphql_result(query, result_data, key) result_data.delete(key) end end end end graphql-2.6.0/lib/graphql/query/0000755000004100000410000000000015173430257016565 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/query/validation_pipeline.rb0000644000004100000410000000741115173430257023134 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Query # Contain the validation pipeline and expose the results. # # 0. Checks in {Query#initialize}: # - Rescue a ParseError, halt if there is one # - Check for selected operation, halt if not found # 1. Validate the AST, halt if errors # 2. Validate the variables, halt if errors # 3. Run query analyzers, halt if errors # # {#valid?} is false if any of the above checks halted the pipeline. # # @api private class ValidationPipeline attr_reader :max_depth, :max_complexity, :validate_timeout_remaining def initialize(query:, parse_error:, operation_name_error:, max_depth:, max_complexity:) @validation_errors = [] @parse_error = parse_error @operation_name_error = operation_name_error @query = query @schema = query.schema @max_depth = max_depth @max_complexity = max_complexity @has_validated = false end # @return [Boolean] does this query have errors that should prevent it from running? def valid? ensure_has_validated @valid end # @return [Array] Static validation errors for the query string def validation_errors ensure_has_validated @validation_errors end def analyzers ensure_has_validated @query_analyzers end def has_validated? @has_validated == true end private # If the pipeline wasn't run yet, run it. # If it was already run, do nothing. def ensure_has_validated return if @has_validated @has_validated = true if @parse_error # This is kind of crazy: we push the parse error into `ctx` # in `def self.parse_error` by default so that users can _opt out_ by redefining that hook. # That means we can't _re-add_ the error here (otherwise we'd either # add it twice _or_ override the user's choice to not add it). # So we just have to know that it was invalid and go from there. @valid = false return elsif @operation_name_error @validation_errors << @operation_name_error else validator = @query.static_validator || @schema.static_validator validation_result = validator.validate(@query, validate: @query.validate, timeout: @schema.validate_timeout, max_errors: @schema.validate_max_errors) @validation_errors.concat(validation_result[:errors]) @validate_timeout_remaining = validation_result[:remaining_timeout] if @validation_errors.empty? @validation_errors.concat(@query.variables.errors) end if @validation_errors.empty? @query_analyzers = build_analyzers( @schema, @max_depth, @max_complexity ) end end @valid = @validation_errors.empty? rescue SystemStackError => err @valid = false @schema.query_stack_error(@query, err) end # If there are max_* values, add them, # otherwise reuse the schema's list of analyzers. def build_analyzers(schema, max_depth, max_complexity) qa = schema.query_analyzers.dup if max_depth || max_complexity # Depending on the analysis engine, we must use different analyzers # remove this once everything has switched over to AST analyzers if max_depth qa << GraphQL::Analysis::MaxQueryDepth end if max_complexity qa << GraphQL::Analysis::MaxQueryComplexity end qa else qa end end end end end graphql-2.6.0/lib/graphql/query/variable_validation_error.rb0000644000004100000410000000253015173430257024322 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Query class VariableValidationError < GraphQL::ExecutionError attr_accessor :value, :validation_result def initialize(variable_ast, type, value, validation_result, msg: nil) @value = value @validation_result = validation_result msg ||= "Variable $#{variable_ast.name} of type #{type.to_type_signature} was provided invalid value" if !problem_fields.empty? msg += " for #{problem_fields.join(", ")}" end super(msg) self.ast_node = variable_ast end def to_h # It is possible there are other extension items in this error, so handle # a one level deep merge explicitly. However beyond that only show the # latest value and problems. super.merge({ "extensions" => { "value" => value, "problems" => validation_result.problems }}) do |key, oldValue, newValue| if oldValue.respond_to?(:merge) oldValue.merge(newValue) else newValue end end end private def problem_fields @problem_fields ||= @validation_result .problems .reject { |problem| problem["path"].empty? } .map { |problem| "#{problem['path'].join('.')} (#{problem['explanation']})" } end end end end graphql-2.6.0/lib/graphql/query/partial.rb0000644000004100000410000001310415173430257020545 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Query # This class is _like_ a {GraphQL::Query}, except it can run on an arbitrary path within a query string. # # It depends on a "parent" {Query}. # # During execution, it calls query-related tracing hooks but passes itself as `query:`. # # The {Partial} will use your {Schema.resolve_type} hook to find the right GraphQL type to use for # `object` in some cases. # # @see Query#run_partials Run via {Query#run_partials} class Partial include Query::Runnable # @param path [Array] A path in `query.query_string` to start executing from # @param object [Object] A starting object for execution # @param query [GraphQL::Query] A full query instance that this partial is based on. Caches are shared. # @param context [Hash] Extra context values to merge into `query.context`, if provided # @param fragment_node [GraphQL::Language::Nodes::InlineFragment, GraphQL::Language::Nodes::FragmentDefinition] def initialize(path: nil, object:, query:, context: nil, fragment_node: nil, type: nil) @path = path @object = object @query = query @schema = query.schema context_vals = @query.context.to_h if context context_vals = context_vals.merge(context) end @context = GraphQL::Query::Context.new(query: self, schema: @query.schema, values: context_vals) @multiplex = nil @result_values = nil @result = nil @finalizers = @top_level_finalizers = nil if fragment_node @ast_nodes = [fragment_node] @root_type = type || raise(ArgumentError, "Pass `type:` when using `node:`") # This is only used when `@leaf` @field_definition = nil elsif path.nil? raise ArgumentError, "`path:` is required if `node:` is not given; add `path:`" else set_type_info_from_path end @leaf = @root_type.unwrap.kind.leaf? end def leaf? @leaf end def root_value object end attr_reader :context, :query, :ast_nodes, :root_type, :object, :field_definition, :path, :schema attr_accessor :multiplex, :result_values class Result < GraphQL::Query::Result def path @query.path end # @return [GraphQL::Query::Partial] def partial @query end end def result @result ||= Result.new(query: self, values: result_values) end def current_trace @query.current_trace end def types @query.types end def resolve_type(...) @query.resolve_type(...) end def variables @query.variables end def fragments @query.fragments end def validate @query.validate end def valid? @query.valid? end def query? true end def run_partials(...) @query.run_partials(...) end def analyzers EmptyObjects::EMPTY_ARRAY end def analysis_errors=(_ignored) # pass end def subscription? @query.subscription? end def selected_operation Language::Nodes::OperationDefinition.new(selections: ast_nodes.flat_map(&:selections)) end def static_errors @query.static_errors end def selected_operation_name @query.selected_operation_name end private def set_type_info_from_path selections = [@query.selected_operation] type = @query.root_type field_defn = nil @path.each do |name_in_doc| if name_in_doc.is_a?(Integer) if type.list? type = type.unwrap next else raise ArgumentError, "Received path with index `#{name_in_doc}`, but type wasn't a list. Type: #{type.to_type_signature}, path: #{@path}" end end next_selections = [] selections.each do |selection| selections_to_check = [] selections_to_check.concat(selection.selections) while (sel = selections_to_check.shift) case sel when GraphQL::Language::Nodes::InlineFragment selections_to_check.concat(sel.selections) when GraphQL::Language::Nodes::FragmentSpread fragment = @query.fragments[sel.name] selections_to_check.concat(fragment.selections) when GraphQL::Language::Nodes::Field if sel.alias == name_in_doc || sel.name == name_in_doc next_selections << sel end else raise "Unexpected selection in partial path: #{sel.class}, #{sel.inspect}" end end end if next_selections.empty? raise ArgumentError, "Path `#{@path.inspect}` is not present in this query. `#{name_in_doc.inspect}` was not found. Try a different path or rewrite the query to include it." end field_name = next_selections.first.name field_defn = @schema.get_field(type, field_name, @query.context) || raise("Invariant: no field called #{field_name} on #{type.graphql_name}") type = field_defn.type if type.non_null? type = type.of_type end selections = next_selections end @ast_nodes = selections @root_type = type @field_definition = field_defn end end end end graphql-2.6.0/lib/graphql/query/fingerprint.rb0000644000004100000410000000134015173430257021437 0ustar www-datawww-data# frozen_string_literal: true require 'digest/sha2' module GraphQL class Query # @api private # @see Query#query_fingerprint # @see Query#variables_fingerprint # @see Query#fingerprint module Fingerprint # Make an obfuscated hash of the given string (either a query string or variables JSON) # @param string [String] # @return [String] A normalized, opaque hash def self.generate(input_str) # Implemented to be: # - Short (and uniform) length # - Stable # - Irreversibly Opaque (don't want to leak variable values) # - URL-friendly bytes = Digest::SHA256.digest(input_str) Base64.urlsafe_encode64(bytes) end end end end graphql-2.6.0/lib/graphql/query/context.rb0000644000004100000410000002056515173430257020606 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Query # Expose some query-specific info to field resolve functions. # It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`. class Context class ExecutionErrors def initialize(ctx) @context = ctx end def add(err_or_msg) err = case err_or_msg when String GraphQL::ExecutionError.new(err_or_msg) when GraphQL::ExecutionError err_or_msg else raise ArgumentError, "expected String or GraphQL::ExecutionError, not #{err_or_msg.class} (#{err_or_msg.inspect})" end # This will assign ast_node and path @context.add_error(err) end alias :>> :add alias :push :add end extend Forwardable include Schema::Member::HasDataloader # @return [Array] errors returned during execution attr_reader :errors # @return [GraphQL::Query] The query whose context this is attr_reader :query # @return [GraphQL::Schema] attr_reader :schema # Make a new context which delegates key lookup to `values` # @param query [GraphQL::Query] the query who owns this context # @param values [Hash] A hash of arbitrary values which will be accessible at query-time def initialize(query:, schema: query.schema, values:) @query = query @schema = schema @provided_values = values || {} # Namespaced storage, where user-provided values are in `nil` namespace: @storage = Hash.new { |h, k| h[k] = {} } @storage[nil] = @provided_values @errors = [] @scoped_context = ScopedContext.new(self) end # Modify this hash to return extensions to client. # @return [Hash] A hash that will be added verbatim to the result hash, as `"extensions" => { ... }` def response_extensions namespace(:__query_result_extensions__) end def dataloader @dataloader ||= self[:dataloader] || (query.multiplex ? query.multiplex.dataloader : schema.dataloader_class.new) end # @api private attr_writer :interpreter # @api private attr_writer :value # @api private attr_reader :scoped_context def []=(key, value) @provided_values[key] = value end def_delegators :@query, :trace def types @types ||= @query.types end attr_writer :types RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path]).freeze # @!method []=(key, value) # Reassign `key` to the hash passed to {Schema#execute} as `context:` # Lookup `key` from the hash passed to {Schema#execute} as `context:` def [](key) if @scoped_context.key?(key) @scoped_context[key] elsif @provided_values.key?(key) @provided_values[key] elsif RUNTIME_METADATA_KEYS.include?(key) if key == :current_path current_path else (current_runtime_state = Fiber[:__graphql_runtime_info]) && (query_runtime_state = current_runtime_state[@query]) && (query_runtime_state.public_send(key)) end else # not found nil end end # Return this value to tell the runtime # to exclude this field from the response altogether def skip GraphQL::Execution::Skip.new end # Add error at query-level. # @param error [GraphQL::ExecutionError] an execution error # @return [void] def add_error(error) if !error.is_a?(GraphQL::RuntimeError) raise TypeError, "expected error to be a GraphQL::RuntimeError, but was #{error.class}" end errors << error nil end # @param value [Object] Any object to be inserted directly into the final response # @return [GraphQL::Execution::Interpreter::RawValue] Return this from the field def raw_value(value) GraphQL::Execution::Interpreter::RawValue.new(value) end # @example Print the GraphQL backtrace during field resolution # puts ctx.backtrace # # @return [GraphQL::Backtrace] The backtrace for this point in query execution def backtrace GraphQL::Backtrace.new(self) end def execution_errors @execution_errors ||= ExecutionErrors.new(self) end def current_path current_runtime_state = Fiber[:__graphql_runtime_info] query_runtime_state = current_runtime_state && current_runtime_state[@query] path = query_runtime_state && (result = query_runtime_state.current_result) && (result.path) if path && (rn = query_runtime_state.current_result_name) path = path.dup path.push(rn) end path end def delete(key) if @scoped_context.key?(key) @scoped_context.delete(key) else @provided_values.delete(key) end end UNSPECIFIED_FETCH_DEFAULT = Object.new def fetch(key, default = UNSPECIFIED_FETCH_DEFAULT) if RUNTIME_METADATA_KEYS.include?(key) (runtime = Fiber[:__graphql_runtime_info]) && (query_runtime_state = runtime[@query]) && (query_runtime_state.public_send(key)) elsif @scoped_context.key?(key) scoped_context[key] elsif @provided_values.key?(key) @provided_values[key] elsif default != UNSPECIFIED_FETCH_DEFAULT default elsif block_given? yield(self, key) else raise KeyError.new(key: key) end end def dig(key, *other_keys) if RUNTIME_METADATA_KEYS.include?(key) (current_runtime_state = Fiber[:__graphql_runtime_info]) && (query_runtime_state = current_runtime_state[@query]) && (obj = query_runtime_state.public_send(key)) && if other_keys.empty? obj else obj.dig(*other_keys) end elsif @scoped_context.key?(key) @scoped_context.dig(key, *other_keys) else @provided_values.dig(key, *other_keys) end end def to_h if (current_scoped_context = @scoped_context.merged_context) @provided_values.merge(current_scoped_context) else @provided_values end end alias :to_hash :to_h def key?(key) @scoped_context.key?(key) || @provided_values.key?(key) end # @return [GraphQL::Schema::Warden] def warden @warden ||= (@query && @query.warden) end # @api private attr_writer :warden # Get an isolated hash for `ns`. Doesn't affect user-provided storage. # @param ns [Object] a usage-specific namespace identifier # @return [Hash] namespaced storage def namespace(ns) if ns == :interpreter self else @storage[ns] end end # @return [Boolean] true if this namespace was accessed before def namespace?(ns) @storage.key?(ns) end def logger @query && @query.logger end def inspect "#<#{self.class} ...>" end def scoped_merge!(hash) @scoped_context.merge!(hash) end def scoped_set!(key, value) scoped_merge!(key => value) nil end # Use this when you need to do a scoped set _inside_ a lazy-loaded (or batch-loaded) # block of code. # # @example using scoped context inside a promise # scoped_ctx = context.scoped # SomeBatchLoader.load(...).then do |thing| # # use a scoped_ctx which was created _before_ dataloading: # scoped_ctx.set!(:thing, thing) # end # @return [Context::Scoped] def scoped Scoped.new(@scoped_context, current_path) end class Scoped def initialize(scoped_context, path) @path = path @scoped_context = scoped_context end def merge!(hash) @scoped_context.merge!(hash, at: @path) end def set!(key, value) @scoped_context.merge!({ key => value }, at: @path) nil end end end end end require "graphql/query/context/scoped_context" graphql-2.6.0/lib/graphql/query/input_validation_result.rb0000644000004100000410000000262115173430257024062 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Query class InputValidationResult attr_accessor :problems def self.from_problem(explanation, path = nil, extensions: nil, message: nil) result = self.new result.add_problem(explanation, path, extensions: extensions, message: message) result end def initialize(valid: true, problems: nil) @valid = valid @problems = problems end def valid? @valid end def add_problem(explanation, path = nil, extensions: nil, message: nil) @problems ||= [] @valid = false problem = { "path" => path || [], "explanation" => explanation } if extensions problem["extensions"] = extensions end if message problem["message"] = message end @problems.push(problem) end def merge_result!(path, inner_result) return if inner_result.nil? || inner_result.valid? if inner_result.problems inner_result.problems.each do |p| item_path = [path, *p["path"]] add_problem(p["explanation"], item_path, message: p["message"], extensions: p["extensions"]) end end # It could have been explicitly set on inner_result (if it had no problems) @valid = false end VALID = self.new VALID.freeze end end end graphql-2.6.0/lib/graphql/query/null_context.rb0000644000004100000410000000172115173430257021631 0ustar www-datawww-data# frozen_string_literal: true require "graphql/query/context" module GraphQL class Query # This object can be `ctx` in places where there is no query class NullContext < Context def self.instance @instance ||= self.new end def self.instance=(new_inst) @instance = new_inst end class NullQuery def after_lazy(value) yield(value) end end class NullSchema < GraphQL::Schema end extend Forwardable attr_reader :schema, :query, :warden, :dataloader def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key?, :to_h def initialize(schema: NullSchema) @query = NullQuery.new @dataloader = GraphQL::Dataloader::NullDataloader.new @schema = schema @warden = Schema::Warden::NullWarden.new(context: self, schema: @schema) @types = @warden.visibility_profile freeze end end end end graphql-2.6.0/lib/graphql/query/result.rb0000644000004100000410000000316315173430257020433 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Query # A result from {Schema#execute}. # It provides the requested data and # access to the {Query} and {Query::Context}. class Result extend Forwardable def initialize(query:, values:) @query = query @to_h = values end # @return [GraphQL::Query] The query that was executed attr_reader :query # @return [Hash] The resulting hash of "data" and/or "errors" attr_reader :to_h def_delegators :@query, :context, :mutation?, :query?, :subscription? def_delegators :@to_h, :[], :keys, :values, :to_json, :as_json # Delegate any hash-like method to the underlying hash. def method_missing(method_name, *args, &block) if @to_h.respond_to?(method_name) @to_h.public_send(method_name, *args, &block) else super end end def respond_to_missing?(method_name, include_private = false) @to_h.respond_to?(method_name) || super end def inspect "#" end # A result is equal to another object when: # # - The other object is a Hash whose value matches `result.to_h` # - The other object is a Result whose value matches `result.to_h` # # (The query is ignored for comparing result equality.) # # @return [Boolean] def ==(other) case other when Hash @to_h == other when Query::Result @to_h == other.to_h else super end end end end end graphql-2.6.0/lib/graphql/query/context/0000755000004100000410000000000015173430257020251 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/query/context/scoped_context.rb0000644000004100000410000000476515173430257023633 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Query class Context class ScopedContext def initialize(query_context) @query_context = query_context @scoped_contexts = nil @all_keys = nil end def merged_context if @scoped_contexts.nil? GraphQL::EmptyObjects::EMPTY_HASH else merged_ctx = {} each_present_path_ctx do |path_ctx| merged_ctx = path_ctx.merge(merged_ctx) end merged_ctx end end def merge!(hash, at: current_path) @all_keys ||= Set.new @all_keys.merge(hash.keys) ctx = @scoped_contexts ||= {} at.each do |path_part| ctx = ctx[path_part] ||= { parent: ctx } end this_scoped_ctx = ctx[:scoped_context] ||= {} this_scoped_ctx.merge!(hash) end def key?(key) if @all_keys && @all_keys.include?(key) each_present_path_ctx do |path_ctx| if path_ctx.key?(key) return true end end end false end def [](key) each_present_path_ctx do |path_ctx| if path_ctx.key?(key) return path_ctx[key] end end nil end def current_path @query_context.current_path || GraphQL::EmptyObjects::EMPTY_ARRAY end def dig(key, *other_keys) each_present_path_ctx do |path_ctx| if path_ctx.key?(key) found_value = path_ctx[key] if !other_keys.empty? return found_value.dig(*other_keys) else return found_value end end end nil end private # Start at the current location, # but look up the tree for previously-assigned scoped values def each_present_path_ctx ctx = @scoped_contexts if ctx.nil? # no-op else current_path.each do |path_part| if ctx.key?(path_part) ctx = ctx[path_part] else break end end while ctx if (scoped_ctx = ctx[:scoped_context]) yield(scoped_ctx) end ctx = ctx[:parent] end end end end end end end graphql-2.6.0/lib/graphql/query/variables.rb0000644000004100000410000000734315173430257021071 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Query # Read-only access to query variables, applying default values if needed. class Variables extend Forwardable # @return [Array] Any errors encountered when parsing the provided variables and literal values attr_reader :errors attr_reader :context def initialize(ctx, ast_variables, provided_variables) schema = ctx.schema @context = ctx @provided_variables = deep_stringify(provided_variables) @errors = [] @storage = ast_variables.each_with_object({}) do |ast_variable, memo| if schema.validate_max_errors && schema.validate_max_errors <= @errors.count add_max_errors_reached_message break end # Find the right value for this variable: # - First, use the value provided at runtime # - Then, fall back to the default value from the query string # If it's still nil, raise an error if it's required. variable_type = schema.type_from_ast(ast_variable.type, context: ctx) if variable_type.nil? || !variable_type.unwrap.kind.input? # Pass -- it will get handled by a validator else variable_name = ast_variable.name default_value = ast_variable.default_value provided_value = @provided_variables[variable_name] value_was_provided = @provided_variables.key?(variable_name) max_errors = schema.validate_max_errors - @errors.count if schema.validate_max_errors begin validation_result = variable_type.validate_input(provided_value, ctx, max_errors: max_errors) if validation_result.valid? if value_was_provided # Add the variable if a value was provided memo[variable_name] = provided_value elsif default_value != nil memo[variable_name] = if default_value.is_a?(Language::Nodes::NullValue) nil else default_value end end end rescue GraphQL::ExecutionError => ex # TODO: This should really include the path to the problematic node in the variable value # like InputValidationResults generated by validate_non_null_input but unfortunately we don't # have this information available in the coerce_input call chain. Note this path is the path # that appears under errors.extensions.problems.path and NOT the result path under errors.path. validation_result = GraphQL::Query::InputValidationResult.from_problem(ex.message) end if !validation_result.valid? @errors << GraphQL::Query::VariableValidationError.new(ast_variable, variable_type, provided_value, validation_result) end end end end def_delegators :@storage, :length, :key?, :[], :fetch, :to_h private def deep_stringify(val) case val when Array val.map { |v| deep_stringify(v) } when Hash new_val = {} val.each do |k, v| new_val[k.to_s] = deep_stringify(v) end new_val else val end end def add_max_errors_reached_message message = "Too many errors processing variables, max validation error limit reached. Execution aborted" validation_result = GraphQL::Query::InputValidationResult.from_problem(message) errors << GraphQL::Query::VariableValidationError.new(nil, nil, nil, validation_result, msg: message) end end end end graphql-2.6.0/lib/graphql/invalid_null_error.rb0000644000004100000410000000364315173430257021644 0ustar www-datawww-data# frozen_string_literal: true module GraphQL # Raised automatically when a field's resolve function returns `nil` # for a non-null field. class InvalidNullError < GraphQL::RuntimeError # @return [GraphQL::BaseType] The owner of {#field} attr_reader :parent_type # @return [GraphQL::Field] The field which failed to return a value attr_reader :field # @return [GraphQL::Language::Nodes::Field] the field where the error occurred def ast_node @ast_nodes.first end attr_reader :ast_nodes # @return [Boolean] indicates an array result caused the error attr_reader :is_from_array attr_accessor :path def initialize(parent_type, field, ast_node_or_nodes, is_from_array: false, path: nil) @parent_type = parent_type @field = field @ast_nodes = Array(ast_node_or_nodes) @is_from_array = is_from_array @path = path # For List elements, identify the non-null error is for an # element and the required element type so it's not ambiguous # whether it was caused by a null instead of the list or a # null element. if @is_from_array super("Cannot return null for non-nullable element of type '#{@field.type.of_type.of_type.to_type_signature}' for #{@parent_type.graphql_name}.#{@field.graphql_name}") else super("Cannot return null for non-nullable field #{@parent_type.graphql_name}.#{@field.graphql_name}") end end class << self attr_accessor :parent_class def subclass_for(parent_class) subclass = Class.new(self) subclass.parent_class = parent_class subclass end def inspect if (name.nil? || parent_class&.name.nil?) && parent_class.respond_to?(:mutation) && (mutation = parent_class.mutation) "#{mutation.inspect}::#{parent_class.graphql_name}::InvalidNullError" else super end end end end end graphql-2.6.0/lib/graphql/relay.rb0000644000004100000410000000010115173430257017051 0ustar www-datawww-data# frozen_string_literal: true require 'graphql/relay/range_add' graphql-2.6.0/lib/graphql/language.rb0000644000004100000410000000714615173430257017540 0ustar www-datawww-data# frozen_string_literal: true require "graphql/language/block_string" require "graphql/language/comment" require "graphql/language/printer" require "graphql/language/sanitized_printer" require "graphql/language/document_from_schema_definition" require "graphql/language/generation" require "graphql/language/lexer" require "graphql/language/nodes" require "graphql/language/cache" require "graphql/language/parser" require "graphql/language/static_visitor" require "graphql/language/visitor" require "graphql/language/definition_slice" require "strscan" module GraphQL module Language # @api private def self.serialize(value) if value.is_a?(Hash) serialized_hash = value.map do |k, v| "#{k}:#{serialize v}" end.join(",") "{#{serialized_hash}}" elsif value.is_a?(Array) serialized_array = value.map do |v| serialize v end.join(",") "[#{serialized_array}]" else JSON.generate(value, quirks_mode: true) end rescue JSON::GeneratorError if Float::INFINITY == value "Infinity" else raise end end # Returns a new string if any single-quoted newlines were escaped. # Otherwise, returns `query_str` unchanged. # @return [String] def self.escape_single_quoted_newlines(query_str) scanner = StringScanner.new(query_str) inside_single_quoted_string = false new_query_str = nil while !scanner.eos? if scanner.skip(/(?:\\"|[^"\n\r]|""")+/m) new_query_str && (new_query_str << scanner.matched) elsif scanner.skip('"') new_query_str && (new_query_str << '"') inside_single_quoted_string = !inside_single_quoted_string elsif scanner.skip("\n") if inside_single_quoted_string new_query_str ||= query_str[0, scanner.pos - 1] new_query_str << '\\n' else new_query_str && (new_query_str << "\n") end elsif scanner.skip("\r") if inside_single_quoted_string new_query_str ||= query_str[0, scanner.pos - 1] new_query_str << '\\r' else new_query_str && (new_query_str << "\r") end elsif scanner.eos? break else raise ArgumentError, "Unmatchable string scanner segment: #{scanner.rest.inspect}" end end new_query_str || query_str end LEADING_REGEX = Regexp.union(" ", *Lexer::Punctuation.constants.map { |const| Lexer::Punctuation.const_get(const) }) # Optimized pattern using: # - Possessive quantifiers (*+, ++) to prevent backtracking in number patterns # - Atomic group (?>...) for IGNORE to prevent backtracking # - Single unified number pattern instead of three alternatives EFFICIENT_NUMBER_REGEXP = /-?(?:0|[1-9][0-9]*+)(?:\.[0-9]++)?(?:[eE][+-]?[0-9]++)?/ EFFICIENT_IGNORE_REGEXP = /(?>[, \r\n\t]+|\#[^\n]*$)*/ MAYBE_INVALID_NUMBER = /\d[_a-zA-Z]/ INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP = %r{ (?#{LEADING_REGEX}) (?#{EFFICIENT_NUMBER_REGEXP}) (?#{Lexer::IDENTIFIER_REGEXP}) #{EFFICIENT_IGNORE_REGEXP} : }x def self.add_space_between_numbers_and_names(query_str) # Fast check for digit followed by identifier char. If this doesn't match, skip the more expensive regexp entirely. return query_str unless query_str.match?(MAYBE_INVALID_NUMBER) return query_str unless query_str.match?(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP) query_str.gsub(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP, "\\k\\k \\k:") end end end graphql-2.6.0/lib/graphql/testing.rb0000644000004100000410000000015415173430257017422 0ustar www-datawww-data# frozen_string_literal: true require "graphql/testing/helpers" require "graphql/testing/mock_action_cable" graphql-2.6.0/lib/graphql/unauthorized_error.rb0000644000004100000410000000240215173430257021675 0ustar www-datawww-data# frozen_string_literal: true module GraphQL # When an `authorized?` hook returns false, this error is used to communicate the failure. # It's passed to {Schema.unauthorized_object}. # # Alternatively, custom code in `authorized?` may raise this error. It will be routed the same way. class UnauthorizedError < GraphQL::RuntimeError # @return [Object] the application object that failed the authorization check attr_reader :object # @return [Class] the GraphQL object type whose `.authorized?` method was called (and returned false) attr_reader :type # @return [GraphQL::Query::Context] the context for the current query attr_accessor :context def initialize(message = nil, object: nil, type: nil, context: nil) if message.nil? && object.nil? && type.nil? raise ArgumentError, "#{self.class.name} requires either a message or keywords" end @path = nil @object = object @type = type @context = context @ast_nodes = nil message ||= "An instance of #{object.class} failed #{type.graphql_name}'s authorization check" super(message) end attr_accessor :path, :ast_nodes def finalize_graphql_result(query, result_data, key) result_data[key] = nil end end end graphql-2.6.0/lib/graphql/unauthorized_field_error.rb0000644000004100000410000000144215173430257023043 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class UnauthorizedFieldError < GraphQL::UnauthorizedError # @return [Field] the field that failed the authorization check attr_accessor :field def initialize(message = nil, object: nil, type: nil, context: nil, field: nil) if message.nil? && [field, type].any?(&:nil?) raise ArgumentError, "#{self.class.name} requires either a message or keywords" end @field = field message ||= begin if object "An instance of #{object.class} failed #{type.name}'s authorization check on field #{field.name}" else "Failed #{type.name}'s authorization check on field #{field.name}" end end super(message, object: object, type: type, context: context) end end end graphql-2.6.0/lib/graphql/subscriptions.rb0000644000004100000410000003120315173430257020653 0ustar www-datawww-data# frozen_string_literal: true require "securerandom" require "graphql/subscriptions/broadcast_analyzer" require "graphql/subscriptions/event" require "graphql/subscriptions/serialize" require "graphql/subscriptions/action_cable_subscriptions" require "graphql/subscriptions/default_subscription_resolve_extension" module GraphQL class Subscriptions # Raised when either: # - the triggered `event_name` doesn't match a field in the schema; or # - one or more arguments don't match the field arguments class InvalidTriggerError < GraphQL::Error end # Raised when either: # - An initial subscription didn't have a value for `context[subscription_scope]` # - Or, an update didn't pass `.trigger(..., scope:)` # When raised, the initial subscription or update fails completely. class SubscriptionScopeMissingError < GraphQL::Error end # @see {Subscriptions#initialize} for options, concrete implementations may add options. def self.use(defn, options = {}) schema = defn.is_a?(Class) ? defn : defn.target if schema.subscriptions(inherited: false) raise ArgumentError, "Can't reinstall subscriptions. #{schema} is using #{schema.subscriptions}, can't also add #{self}" end options[:schema] = schema schema.subscriptions = self.new(**options) schema.add_subscription_extension_if_necessary nil end # @param schema [Class] the GraphQL schema this manager belongs to # @param validate_update [Boolean] If false, then validation is skipped when executing updates def initialize(schema:, validate_update: true, broadcast: false, default_broadcastable: false, **rest) if broadcast schema.query_analyzer(Subscriptions::BroadcastAnalyzer) end @default_broadcastable = default_broadcastable @schema = schema @validate_update = validate_update end # @return [Boolean] Used when fields don't have `broadcastable:` explicitly set attr_reader :default_broadcastable # Fetch subscriptions matching this field + arguments pair # And pass them off to the queue. # @param event_name [String] # @param args [Hash Object] # @param object [Object] # @param scope [Symbol, String] # @param context [Hash] # @return [void] def trigger(event_name, args, object, scope: nil, context: {}) # Make something as context-like as possible, even though there isn't a current query: dummy_query = @schema.query_class.new(@schema, "{ __typename }", validate: false, context: context) context = dummy_query.context event_name = event_name.to_s # Try with the verbatim input first: field = dummy_query.types.field(@schema.subscription, event_name) # rubocop:disable Development/ContextIsPassedCop if field.nil? # And if it wasn't found, normalize it: normalized_event_name = normalize_name(event_name) field = dummy_query.types.field(@schema.subscription, normalized_event_name) # rubocop:disable Development/ContextIsPassedCop if field.nil? raise InvalidTriggerError, "No subscription matching trigger: #{event_name} (looked for #{@schema.subscription.graphql_name}.#{normalized_event_name})" end else # Since we found a field, the original input was already normalized normalized_event_name = event_name end # Normalize symbol-keyed args to strings, try camelizing them # Should this accept a real context somehow? normalized_args = normalize_arguments(normalized_event_name, field, args, @schema.null_context) event = Subscriptions::Event.new( name: normalized_event_name, arguments: normalized_args, field: field, scope: scope, context: context, ) execute_all(event, object) end # `event` was triggered on `object`, and `subscription_id` was subscribed, # so it should be updated. # # Load `subscription_id`'s GraphQL data, re-evaluate the query and return the result. # # @param subscription_id [String] # @param event [GraphQL::Subscriptions::Event] The event which was triggered # @param object [Object] The value for the subscription field # @return [GraphQL::Query::Result] def execute_update(subscription_id, event, object) # Lookup the saved data for this subscription query_data = read_subscription(subscription_id) if query_data.nil? delete_subscription(subscription_id) return nil end # Fetch the required keys from the saved data query_string = query_data.fetch(:query_string) variables = query_data.fetch(:variables) context = query_data.fetch(:context) operation_name = query_data.fetch(:operation_name) execute_options = { query: query_string, context: context, subscription_topic: event.topic, operation_name: operation_name, variables: variables, root_value: object, } # merge event's and query's context together context.merge!(event.context) unless event.context.nil? || context.nil? execute_options[:validate] = validate_update?(**execute_options) result = @schema.execute(**execute_options) subscriptions_context = result.context.namespace(:subscriptions) if subscriptions_context[:no_update] result = nil end if subscriptions_context[:unsubscribed] && !subscriptions_context[:final_update] # `unsubscribe` was called, clean up on our side # The transport should also send `{more: false}` to client delete_subscription(subscription_id) result = nil end result end # Define this method to customize whether to validate # this subscription when executing an update. # # @return [Boolean] defaults to `true`, or false if `validate: false` is provided. def validate_update?(query:, context:, root_value:, subscription_topic:, operation_name:, variables:) @validate_update end # Run the update query for this subscription and deliver it # @see {#execute_update} # @see {#deliver} # @return [void] def execute(subscription_id, event, object) res = execute_update(subscription_id, event, object) if !res.nil? deliver(subscription_id, res) if res.context.namespace(:subscriptions)[:unsubscribed] # `unsubscribe` was called, clean up on our side # The transport should also send `{more: false}` to client delete_subscription(subscription_id) end end end # Event `event` occurred on `object`, # Update all subscribers. # @param event [Subscriptions::Event] # @param object [Object] # @return [void] def execute_all(event, object) raise GraphQL::RequiredImplementationMissingError end # The system wants to send an update to this subscription. # Read its data and return it. # @param subscription_id [String] # @return [Hash] Containing required keys def read_subscription(subscription_id) raise GraphQL::RequiredImplementationMissingError end # A subscription query was re-evaluated, returning `result`. # The result should be send to `subscription_id`. # @param subscription_id [String] # @param result [Hash] # @return [void] def deliver(subscription_id, result) raise GraphQL::RequiredImplementationMissingError end # `query` was executed and found subscriptions to `events`. # Update the database to reflect this new state. # @param query [GraphQL::Query] # @param events [Array] # @return [void] def write_subscription(query, events) raise GraphQL::RequiredImplementationMissingError end # A subscription was terminated server-side. # Clean up the database. # @param subscription_id [String] # @return void. def delete_subscription(subscription_id) raise GraphQL::RequiredImplementationMissingError end # @return [String] A new unique identifier for a subscription def build_id SecureRandom.uuid end # Convert a user-provided event name or argument # to the equivalent in GraphQL. # # By default, it converts the identifier to camelcase. # Override this in a subclass to change the transformation. # # @param event_or_arg_name [String, Symbol] # @return [String] def normalize_name(event_or_arg_name) Schema::Member::BuildType.camelize(event_or_arg_name.to_s) end # @return [Boolean] if true, then a query like this one would be broadcasted def broadcastable?(query_str, **query_options) query = @schema.query_class.new(@schema, query_str, **query_options) if !query.valid? raise "Invalid query: #{query.validation_errors.map(&:to_h).inspect}" end GraphQL::Analysis.analyze_query(query, @schema.query_analyzers) query.context.namespace(:subscriptions)[:subscription_broadcastable] end # Called during execution when a new `subscription ...` operation is received # @param query [GraphQL::Query] # @return [void] def initialize_subscriptions(query) subs_namespace = query.context.namespace(:subscriptions) subs_namespace[:events] = [] subs_namespace[:subscriptions] = {} nil end # Called during execution when a subscription operation has finished # @param query [GraphQL::Query] # @return [void] def finish_subscriptions(query) if (events = query.context.namespace(:subscriptions)[:events]) && !events.empty? write_subscription(query, events) end nil end def finalizer Finalizer.new(self) end class Finalizer include Execution::Finalizer def initialize(subscriptions) @subscriptions = subscriptions end def finalize_graphql_result(query, result_data, result_key) @subscriptions.finish_subscriptions(query) end end private # Recursively normalize `args` as belonging to `arg_owner`: # - convert symbols to strings, # - if needed, camelize the string (using {#normalize_name}) # @param arg_owner [GraphQL::Field, GraphQL::BaseType] # @param args [Hash, Array, Any] some GraphQL input value to coerce as `arg_owner` # @return [Any] normalized arguments value def normalize_arguments(event_name, arg_owner, args, context) case arg_owner when GraphQL::Schema::Field, Class return args if args.nil? if arg_owner.is_a?(Class) && !arg_owner.kind.input_object? # it's a type, but not an input object return args end normalized_args = {} missing_arg_names = [] args.each do |k, v| arg_name = k.to_s arg_defn = arg_owner.get_argument(arg_name, context) if arg_defn normalized_arg_name = arg_name else normalized_arg_name = normalize_name(arg_name) arg_defn = arg_owner.get_argument(normalized_arg_name, context) end if arg_defn if arg_defn.loads normalized_arg_name = arg_defn.keyword.to_s end normalized = normalize_arguments(event_name, arg_defn.type, v, context) normalized_args[normalized_arg_name] = normalized else # Couldn't find a matching argument definition missing_arg_names << arg_name end end # Backfill default values so that trigger arguments # match query arguments. arg_owner.arguments(context).each do |_name, arg_defn| if arg_defn.default_value? && !normalized_args.key?(arg_defn.name) default_value = arg_defn.default_value # We don't have an underlying "object" here, so it can't call methods. # This is broken. normalized_args[arg_defn.name] = arg_defn.prepare_value(nil, default_value, context: context) end end if !missing_arg_names.empty? arg_owner_name = if arg_owner.is_a?(GraphQL::Schema::Field) arg_owner.path elsif arg_owner.is_a?(Class) arg_owner.graphql_name else arg_owner.to_s end raise InvalidTriggerError, "Can't trigger Subscription.#{event_name}, received undefined arguments: #{missing_arg_names.join(", ")}. (Should match arguments of #{arg_owner_name}.)" end normalized_args when GraphQL::Schema::List args&.map { |a| normalize_arguments(event_name, arg_owner.of_type, a, context) } when GraphQL::Schema::NonNull normalize_arguments(event_name, arg_owner.of_type, args, context) else args end end end end graphql-2.6.0/lib/graphql/runtime_type_error.rb0000644000004100000410000000014115173430257021676 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class RuntimeTypeError < GraphQL::Error end end graphql-2.6.0/lib/graphql/coercion_error.rb0000644000004100000410000000014715173430257020761 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class CoercionError < GraphQL::ExecutionError end end graphql-2.6.0/lib/graphql/load_application_object_failed_error.rb0000644000004100000410000000175215173430257025317 0ustar www-datawww-data# frozen_string_literal: true module GraphQL # Raised when a argument is configured with `loads:` and the client provides an `ID`, # but no object is loaded for that ID. # # @see GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader#load_application_object_failed, A hook which you can override in resolvers, mutations and input objects. class LoadApplicationObjectFailedError < GraphQL::ExecutionError # @return [GraphQL::Schema::Argument] the argument definition for the argument that was looked up attr_reader :argument # @return [String] The ID provided by the client attr_reader :id # @return [Object] The value found with this ID attr_reader :object # @return [GraphQL::Query::Context] attr_reader :context def initialize(argument:, id:, object:, context:) @id = id @argument = argument @object = object @context = context super("No object found for `#{argument.graphql_name}: #{id.inspect}`") end end end graphql-2.6.0/lib/graphql/unresolved_type_error.rb0000644000004100000410000000315015173430257022404 0ustar www-datawww-data# frozen_string_literal: true module GraphQL # Error raised when the value provided for a field # can't be resolved to one of the possible types for the field. class UnresolvedTypeError < GraphQL::RuntimeTypeError # @return [Object] The runtime value which couldn't be successfully resolved with `resolve_type` attr_reader :value # @return [GraphQL::Field] The field whose value couldn't be resolved (`field.type` is type which couldn't be resolved) attr_reader :field # @return [GraphQL::BaseType] The owner of `field` attr_reader :parent_type # @return [Object] The return of {Schema#resolve_type} for `value` attr_reader :resolved_type # @return [Array] The allowed options for resolving `value` to `field.type` attr_reader :possible_types def initialize(value, field, parent_type, resolved_type, possible_types) @value = value @field = field @parent_type = parent_type @resolved_type = resolved_type @possible_types = possible_types message = "The value from \"#{field.graphql_name}\" on \"#{parent_type.graphql_name}\" could not be resolved to \"#{field.type.to_type_signature}\". " \ "(Received: `#{resolved_type.inspect}`, Expected: [#{possible_types.map(&:graphql_name).join(", ")}]) " \ "Make sure you have defined a `resolve_type` proc on your schema and that value `#{value.inspect}` " \ "gets resolved to a valid type. You may need to add your type to `orphan_types` if it implements an " \ "interface but isn't a return type of any other field." super(message) end end end graphql-2.6.0/lib/graphql/subscriptions/0000755000004100000410000000000015173430257020327 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/subscriptions/action_cable_subscriptions.rb0000644000004100000410000002430315173430257026250 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Subscriptions # A subscriptions implementation that sends data # as ActionCable broadcastings. # # Some things to keep in mind: # # - No queueing system; ActiveJob should be added # - Take care to reload context when re-delivering the subscription. (see {Query#subscription_update?}) # - Avoid the async ActionCable adapter and use the redis or PostgreSQL adapters instead. Otherwise calling #trigger won't work from background jobs or the Rails console. # # @example Adding ActionCableSubscriptions to your schema # class MySchema < GraphQL::Schema # # ... # use GraphQL::Subscriptions::ActionCableSubscriptions # end # # @example Implementing a channel for GraphQL Subscriptions # class GraphqlChannel < ApplicationCable::Channel # def subscribed # @subscription_ids = [] # end # # def execute(data) # query = data["query"] # variables = ensure_hash(data["variables"]) # operation_name = data["operationName"] # context = { # # Re-implement whatever context methods you need # # in this channel or ApplicationCable::Channel # # current_user: current_user, # # Make sure the channel is in the context # channel: self, # } # # result = MySchema.execute( # query, # context: context, # variables: variables, # operation_name: operation_name # ) # # payload = { # result: result.to_h, # more: result.subscription?, # } # # # Track the subscription here so we can remove it # # on unsubscribe. # if result.context[:subscription_id] # @subscription_ids << result.context[:subscription_id] # end # # transmit(payload) # end # # def unsubscribed # @subscription_ids.each { |sid| # MySchema.subscriptions.delete_subscription(sid) # } # end # # private # # def ensure_hash(ambiguous_param) # case ambiguous_param # when String # if ambiguous_param.present? # ensure_hash(JSON.parse(ambiguous_param)) # else # {} # end # when Hash, ActionController::Parameters # ambiguous_param # when nil # {} # else # raise ArgumentError, "Unexpected parameter: #{ambiguous_param}" # end # end # end # # @see GraphQL::Testing::MockActionCable for test helpers class ActionCableSubscriptions < GraphQL::Subscriptions SUBSCRIPTION_PREFIX = "graphql-subscription:" EVENT_PREFIX = "graphql-event:" # @param serializer [<#dump(obj), #load(string)] Used for serializing messages before handing them to `.broadcast(msg)` # @param namespace [string] Used to namespace events and subscriptions (default: '') def initialize(serializer: Serialize, namespace: '', action_cable: ActionCable, action_cable_coder: ActiveSupport::JSON, **rest) # A per-process map of subscriptions to deliver. # This is provided by Rails, so let's use it @subscriptions = Concurrent::Map.new @events = Concurrent::Map.new do |h, k| h.compute_if_absent(k) do Concurrent::Map.new do |h2, k2| h2.compute_if_absent(k2) { Concurrent::Array.new } end end end @action_cable = action_cable @action_cable_coder = action_cable_coder @serializer = serializer @serialize_with_context = case @serializer.method(:load).arity when 1 false when 2 true else raise ArgumentError, "#{@serializer} must respond to `.load` accepting one or two arguments" end @transmit_ns = namespace super end # An event was triggered; Push the data over ActionCable. # Subscribers will re-evaluate locally. def execute_all(event, object) stream = stream_event_name(event) message = @serializer.dump(object) @action_cable.server.broadcast(stream, message) end # This subscription was re-evaluated. # Send it to the specific stream where this client was waiting. def deliver(subscription_id, result) has_more = !result.context.namespace(:subscriptions)[:final_update] payload = { result: result.to_h, more: has_more } @action_cable.server.broadcast(stream_subscription_name(subscription_id), payload) end # A query was run where these events were subscribed to. # Store them in memory in _this_ ActionCable frontend. # It will receive notifications when events come in # and re-evaluate the query locally. def write_subscription(query, events) unless (channel = query.context[:channel]) raise GraphQL::Error, "This GraphQL Subscription client does not support the transport protocol expected"\ "by the backend Subscription Server implementation (graphql-ruby ActionCableSubscriptions in this case)."\ "Some official client implementation including Apollo (https://graphql-ruby.org/javascript_client/apollo_subscriptions.html), "\ "Relay Modern (https://graphql-ruby.org/javascript_client/relay_subscriptions.html#actioncable)."\ "GraphiQL via `graphiql-rails` may not work out of box (#1051)." end subscription_id = query.context[:subscription_id] ||= build_id stream = stream_subscription_name(subscription_id) channel.stream_from(stream) @subscriptions[subscription_id] = query events.each do |event| # Setup a new listener to run all events with this topic in this process setup_stream(channel, event) # Add this event to the list of events to be updated @events[event.topic][event.fingerprint] << event end end # Every subscribing channel is listening here, but only one of them takes any action. # This is so we can reuse payloads when possible, and make one payload to send to # all subscribers. # # But the problem is, any channel could close at any time, so each channel has to # be ready to take over the primary position. # # To make sure there's always one-and-only-one channel building payloads, # let the listener belonging to the first event on the list be # the one to build and publish payloads. # def setup_stream(channel, initial_event) topic = initial_event.topic event_stream = stream_event_name(initial_event) channel.stream_from(event_stream, coder: @action_cable_coder) do |message| events_by_fingerprint = @events[topic] object = nil events_by_fingerprint.each do |_fingerprint, events| if !events.empty? && events.first == initial_event # The fingerprint has told us that this response should be shared by all subscribers, # so just run it once, then deliver the result to every subscriber first_event = events.first first_subscription_id = first_event.context.fetch(:subscription_id) object ||= load_action_cable_message(message, first_event.context) result = execute_update(first_subscription_id, first_event, object) if !result.nil? # Having calculated the result _once_, send the same payload to all subscribers events.each do |event| subscription_id = event.context.fetch(:subscription_id) deliver(subscription_id, result) end end end end nil end end # This is called to turn an ActionCable-broadcasted string (JSON) # into a query-ready application object. # @param message [String] n ActionCable-broadcasted string (JSON) # @param context [GraphQL::Query::Context] the context of the first event for a given subscription fingerprint def load_action_cable_message(message, context) if @serialize_with_context @serializer.load(message, context) else @serializer.load(message) end end # Return the query from "storage" (in memory) def read_subscription(subscription_id) query = @subscriptions[subscription_id] if query.nil? # This can happen when a subscription is triggered from an unsubscribed channel, # see https://github.com/rmosolgo/graphql-ruby/issues/2478. # (This `nil` is handled by `#execute_update`) nil else { query_string: query.query_string, variables: query.provided_variables, context: query.context.to_h, operation_name: query.operation_name, } end end # The channel was closed, forget about it. def delete_subscription(subscription_id) query = @subscriptions.delete(subscription_id) # In case this came from the server, tell the client to unsubscribe: @action_cable.server.broadcast(stream_subscription_name(subscription_id), { more: false }) # This can be `nil` when `.trigger` happens inside an unsubscribed ActionCable channel, # see https://github.com/rmosolgo/graphql-ruby/issues/2478 if query events = query.context.namespace(:subscriptions)[:events] events.each do |event| ev_by_fingerprint = @events[event.topic] ev_for_fingerprint = ev_by_fingerprint[event.fingerprint] ev_for_fingerprint.delete(event) if ev_for_fingerprint.empty? ev_by_fingerprint.delete(event.fingerprint) end end end end private def stream_subscription_name(subscription_id) [SUBSCRIPTION_PREFIX, @transmit_ns, subscription_id].join end def stream_event_name(event) [EVENT_PREFIX, @transmit_ns, event.topic].join end end end end graphql-2.6.0/lib/graphql/subscriptions/event.rb0000644000004100000410000001463115173430257022002 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Subscriptions # This thing can be: # - Subscribed to by `subscription { ... }` # - Triggered by `MySchema.subscriber.trigger(name, arguments, obj)` # class Event # @return [String] Corresponds to the Subscription root field name attr_reader :name # @return [GraphQL::Execution::Interpreter::Arguments] attr_reader :arguments # @return [GraphQL::Query::Context] attr_reader :context # @return [String] An opaque string which identifies this event, derived from `name` and `arguments` attr_reader :topic def initialize(name:, arguments:, field: nil, context: nil, scope: nil) @name = name @arguments = self.class.arguments_without_field_extras(arguments: arguments, field: field) @context = context field ||= context.field scope_key = field.subscription_scope scope_val = scope || (context && scope_key && context[scope_key]) if scope_key && (subscription = field.resolver) && (subscription.respond_to?(:subscription_scope_optional?)) && !subscription.subscription_scope_optional? && scope_val.nil? raise Subscriptions::SubscriptionScopeMissingError, "#{field.path} (#{subscription}) requires a `scope:` value to trigger updates (Set `subscription_scope ..., optional: true` to disable this requirement)" end @topic = self.class.serialize(name, arguments, field, scope: scope_val, context: context) end # @return [String] an identifier for this unit of subscription def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext.instance) subscription = field.resolver || GraphQL::Schema::Subscription arguments = arguments_without_field_extras(field: field, arguments: arguments) normalized_args = stringify_args(field, arguments.to_h, context) subscription.topic_for(arguments: normalized_args, field: field, scope: scope) end # @return [String] a logical identifier for this event. (Stable when the query is broadcastable.) def fingerprint @fingerprint ||= begin # When this query has been flagged as broadcastable, # use a generalized, stable fingerprint so that # duplicate subscriptions can be evaluated and distributed in bulk. # (`@topic` includes field, args, and subscription scope already.) if @context.namespace(:subscriptions)[:subscription_broadcastable] "#{@topic}/#{@context.query.fingerprint}" else # not broadcastable, build a unique ID for this event @context.schema.subscriptions.build_id end end end class << self def arguments_without_field_extras(arguments:, field:) if !field.extras.empty? arguments = arguments.dup field.extras.each do |extra_key| arguments.delete(extra_key) end end arguments end private # This method does not support cyclic references in the Hash, # nor does it support Hashes whose keys are not sortable # with respect to their peers ( cases where a <=> b might throw an error ) def deep_sort_hash_keys(hash_to_sort) raise ArgumentError.new("Argument must be a Hash") unless hash_to_sort.is_a?(Hash) hash_to_sort.keys.sort.map do |k| if hash_to_sort[k].is_a?(Hash) [k, deep_sort_hash_keys(hash_to_sort[k])] elsif hash_to_sort[k].is_a?(Array) [k, deep_sort_array_hashes(hash_to_sort[k])] else [k, hash_to_sort[k]] end end.to_h end def deep_sort_array_hashes(array_to_inspect) raise ArgumentError.new("Argument must be an Array") unless array_to_inspect.is_a?(Array) array_to_inspect.map do |v| if v.is_a?(Hash) deep_sort_hash_keys(v) elsif v.is_a?(Array) deep_sort_array_hashes(v) else v end end end def stringify_args(arg_owner, args, context) arg_owner = arg_owner.respond_to?(:unwrap) ? arg_owner.unwrap : arg_owner # remove list and non-null wrappers case args when Hash next_args = {} args.each do |k, v| arg_name = k.to_s camelized_arg_name = GraphQL::Schema::Member::BuildType.camelize(arg_name) arg_defn = get_arg_definition(arg_owner, camelized_arg_name, context) arg_defn ||= get_arg_definition(arg_owner, arg_name, context) normalized_arg_name = arg_defn.graphql_name arg_base_type = arg_defn.type.unwrap # In the case where the value being emitted is seen as a "JSON" # type, treat the value as one atomic unit of serialization is_json_definition = arg_base_type && arg_base_type <= GraphQL::Types::JSON if is_json_definition sorted_value = if v.is_a?(Hash) deep_sort_hash_keys(v) elsif v.is_a?(Array) deep_sort_array_hashes(v) else v end next_args[normalized_arg_name] = sorted_value.respond_to?(:to_json) ? sorted_value.to_json : sorted_value else next_args[normalized_arg_name] = stringify_args(arg_base_type, v, context) end end # Make sure they're deeply sorted next_args.sort.to_h when Array args.map { |a| stringify_args(arg_owner, a, context) } when GraphQL::Schema::InputObject stringify_args(arg_owner, args.to_h, context) else if arg_owner.is_a?(Class) && arg_owner < GraphQL::Schema::Enum # `prepare:` may have made the value something other than # a defined value of this enum -- use _that_ in this case. arg_owner.coerce_isolated_input(args) || args else args end end end def get_arg_definition(arg_owner, arg_name, context) context.types.argument(arg_owner, arg_name) || context.types.arguments(arg_owner).find { |v| v.keyword.to_s == arg_name } end end end end end graphql-2.6.0/lib/graphql/subscriptions/serialize.rb0000644000004100000410000001406415173430257022650 0ustar www-datawww-data# frozen_string_literal: true require "set" module GraphQL class Subscriptions # Serialization helpers for passing subscription data around. # @api private module Serialize GLOBALID_KEY = "__gid__" SYMBOL_KEY = "__sym__" SYMBOL_KEYS_KEY = "__sym_keys__" TIMESTAMP_KEY = "__timestamp__" TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%z" # eg '2020-01-01 23:59:59.123456789+05:00' OPEN_STRUCT_KEY = "__ostruct__" module_function # @param str [String] A serialized object from {.dump} # @return [Object] An object equivalent to the one passed to {.dump} def load(str) parsed_obj = JSON.parse(str) load_value(parsed_obj) end # @param obj [Object] Some subscription-related data to dump # @return [String] The stringified object def dump(obj) JSON.generate(dump_value(obj), quirks_mode: true) end # This is for turning objects into subscription scopes. # It's a one-way transformation, can't reload this :'( # @param obj [Object] # @return [String] def dump_recursive(obj) case when obj.is_a?(Array) obj.map { |i| dump_recursive(i) }.join(':') when obj.is_a?(Hash) obj.map { |k, v| "#{dump_recursive(k)}:#{dump_recursive(v)}" }.join(":") when obj.is_a?(GraphQL::Schema::InputObject) dump_recursive(obj.to_h) when obj.respond_to?(:to_gid_param) obj.to_gid_param when obj.respond_to?(:to_param) obj.to_param else obj.to_s end end class << self private # @param value [Object] A parsed JSON object # @return [Object] An object that load Global::Identification recursive def load_value(value) if value.is_a?(Array) is_gids = (v1 = value[0]).is_a?(Hash) && v1.size == 1 && v1[GLOBALID_KEY] if is_gids # Assume it's an array of global IDs ids = value.map { |v| v[GLOBALID_KEY] } GlobalID::Locator.locate_many(ids) else value.map { |item| load_value(item) } end elsif value.is_a?(Hash) if value.size == 1 case value.keys.first # there's only 1 key when GLOBALID_KEY GlobalID::Locator.locate(value[GLOBALID_KEY]) when SYMBOL_KEY value[SYMBOL_KEY].to_sym when TIMESTAMP_KEY timestamp_class_name, *timestamp_args = value[TIMESTAMP_KEY] timestamp_class = Object.const_get(timestamp_class_name) if defined?(ActiveSupport::TimeWithZone) && timestamp_class <= ActiveSupport::TimeWithZone zone_name, timestamp_s = timestamp_args zone = ActiveSupport::TimeZone[zone_name] raise "Zone #{zone_name} not found, unable to deserialize" unless zone zone.strptime(timestamp_s, TIMESTAMP_FORMAT) else timestamp_s = timestamp_args.first timestamp_class.strptime(timestamp_s, TIMESTAMP_FORMAT) end when OPEN_STRUCT_KEY ostruct_values = load_value(value[OPEN_STRUCT_KEY]) OpenStruct.new(ostruct_values) else key = value.keys.first { key => load_value(value[key]) } end else loaded_h = {} sym_keys = value.fetch(SYMBOL_KEYS_KEY, []) value.each do |k, v| if k == SYMBOL_KEYS_KEY next end if sym_keys.include?(k) k = k.to_sym end loaded_h[k] = load_value(v) end loaded_h end else value end end # @param obj [Object] Some subscription-related data to dump # @return [Object] The object that converted Global::Identification def dump_value(obj) if obj.is_a?(Array) obj.map{|item| dump_value(item)} elsif obj.is_a?(Hash) symbol_keys = nil dumped_h = {} obj.each do |k, v| dumped_h[k.to_s] = dump_value(v) if k.is_a?(Symbol) symbol_keys ||= Set.new symbol_keys << k.to_s end end if symbol_keys dumped_h[SYMBOL_KEYS_KEY] = symbol_keys.to_a end dumped_h elsif obj.is_a?(Symbol) { SYMBOL_KEY => obj.to_s } elsif obj.respond_to?(:to_gid_param) {GLOBALID_KEY => obj.to_gid_param} elsif defined?(ActiveSupport::TimeWithZone) && obj.is_a?(ActiveSupport::TimeWithZone) && obj.class.name != Time.name # This handles a case where Rails prior to 7 would # make the class ActiveSupport::TimeWithZone return "Time" for # its name. In Rails 7, it will now return "ActiveSupport::TimeWithZone", # which happens to be incompatible with expectations we have # with what a Time class supports ( notably, strptime in `load_value` ). # # This now passes along the name of the zone, such that a future deserialization # of this string will use the correct time zone from the ActiveSupport TimeZone # list to produce the time. # { TIMESTAMP_KEY => [obj.class.name, obj.time_zone.name, obj.strftime(TIMESTAMP_FORMAT)] } elsif obj.is_a?(Date) || obj.is_a?(Time) # DateTime extends Date; for TimeWithZone, call `.utc` first. { TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] } elsif defined?(OpenStruct) && obj.is_a?(OpenStruct) { OPEN_STRUCT_KEY => dump_value(obj.to_h) } elsif defined?(ActiveRecord::Relation) && obj.is_a?(ActiveRecord::Relation) dump_value(obj.to_a) else obj end end end end end end graphql-2.6.0/lib/graphql/subscriptions/broadcast_analyzer.rb0000644000004100000410000000677315173430257024540 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Subscriptions # Detect whether the current operation: # - Is a subscription operation # - Is completely broadcastable # # Assign the result to `context.namespace(:subscriptions)[:subscription_broadcastable]` # @api private # @see Subscriptions#broadcastable? for a public API class BroadcastAnalyzer < GraphQL::Analysis::Analyzer def initialize(subject) super @default_broadcastable = subject.schema.subscriptions.default_broadcastable # Maybe this will get set to false while analyzing @subscription_broadcastable = true end # Only analyze subscription operations def analyze? @query.subscription? end def on_enter_field(node, parent, visitor) if (@subscription_broadcastable == false) || visitor.skipping? return end current_field = visitor.field_definition current_type = visitor.parent_type_definition apply_broadcastable(current_type, current_field) if current_type.kind.interface? pt = @query.possible_types(current_type) pt.each do |object_type| ot_field = @query.get_field(object_type, current_field.graphql_name) # Inherited fields would be exactly the same object; # only check fields that are overrides of the inherited one if ot_field && ot_field != current_field apply_broadcastable(object_type, ot_field) end end end end # Assign the result to context. # (This method is allowed to return an error, but we don't need to) # @return [void] def result query.context.namespace(:subscriptions)[:subscription_broadcastable] = @subscription_broadcastable nil end private # Modify `@subscription_broadcastable` based on `field_defn`'s configuration (and/or the default value) def apply_broadcastable(owner_type, field_defn) current_field_broadcastable = field_defn.introspection? || field_defn.broadcastable? if current_field_broadcastable.nil? && owner_type.respond_to?(:default_broadcastable?) current_field_broadcastable = owner_type.default_broadcastable? end case current_field_broadcastable when nil query.logger.debug { "`broadcastable: nil` for field: #{field_defn.path}" } # If the value wasn't set, mix in the default value: # - If the default is false and the current value is true, make it false # - If the default is true and the current value is true, it stays true # - If the default is false and the current value is false, keep it false # - If the default is true and the current value is false, keep it false @subscription_broadcastable = @subscription_broadcastable && @default_broadcastable when false query.logger.debug { "`broadcastable: false` for field: #{field_defn.path}" } # One non-broadcastable field is enough to make the whole subscription non-broadcastable @subscription_broadcastable = false when true # Leave `@broadcastable_query` true if it's already true, # but don't _set_ it to true if it was set to false by something else. # Actually, just leave it! else raise ArgumentError, "Unexpected `.broadcastable?` value for #{field_defn.path}: #{current_field_broadcastable}" end end end end end graphql-2.6.0/lib/graphql/subscriptions/default_subscription_resolve_extension.rb0000644000004100000410000000570615173430257030747 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class Subscriptions class DefaultSubscriptionResolveExtension < GraphQL::Schema::FieldExtension def resolve(context:, object: nil, objects: nil, arguments:) if objects has_override_implementation = @field.execution_mode != :direct_send if !has_override_implementation if context.query.subscription_update? objects else objects.map { |o| context.skip } end else yield(objects, arguments) end else has_override_implementation = @field.resolver || object.respond_to?(@field.resolver_method) if !has_override_implementation if context.query.subscription_update? object.object else context.skip end else yield(object, arguments) end end end def after_resolve(values: nil, value: nil, context:, objects: nil, object: nil, arguments:, **rest) if values values.map do |value| self.class.write_subscription(@field, value, arguments, context) end else self.class.write_subscription(@field, value, arguments, context) end end def self.write_subscription(field, value, arguments, context) if value.is_a?(GraphQL::ExecutionError) value elsif field.resolver&.method_defined?(:subscription_written?) && (subscription_namespace = context.namespace(:subscriptions)) && (subscriptions_by_path = subscription_namespace[:subscriptions]) (subscription_instance = subscriptions_by_path[context.current_path]) # If it was already written, don't append this event to be written later if !subscription_instance.subscription_written? events = context.namespace(:subscriptions)[:events] events << subscription_instance.event end value elsif (events = context.namespace(:subscriptions)[:events]) # This is the first execution, so gather an Event # for the backend to register: event = Subscriptions::Event.new( name: field.name, arguments: arguments, context: context, field: field, ) events << event value elsif context.query.subscription_topic == Subscriptions::Event.serialize( field.name, arguments, field, scope: (field.subscription_scope ? context[field.subscription_scope] : nil), ) # This is a subscription update. The resolver returned `skip` if it should be skipped, # or else it returned an object to resolve the update. value else # This is a subscription update, but this event wasn't triggered. context.skip end end end end end graphql-2.6.0/lib/graphql/query.rb0000644000004100000410000004420215173430257017114 0ustar www-datawww-data# frozen_string_literal: true module GraphQL # A combination of query string and {Schema} instance which can be reduced to a {#result}. class Query extend Autoload include Tracing::Traceable extend Forwardable autoload :Context, "graphql/query/context" autoload :Fingerprint, "graphql/query/fingerprint" autoload :NullContext, "graphql/query/null_context" autoload :Partial, "graphql/query/partial" autoload :Result, "graphql/query/result" autoload :Variables, "graphql/query/variables" autoload :InputValidationResult, "graphql/query/input_validation_result" autoload :VariableValidationError, "graphql/query/variable_validation_error" autoload :ValidationPipeline, "graphql/query/validation_pipeline" # Code shared with {Partial} module Runnable def after_lazy(value, &block) if !defined?(@runtime_instance) @runtime_instance = context.namespace(:interpreter_runtime)[:runtime] end if @runtime_instance @runtime_instance.minimal_after_lazy(value, &block) else @schema.after_lazy(value, &block) end end # Node-level cache for calculating arguments. Used during execution and query analysis. # @param ast_node [GraphQL::Language::Nodes::AbstractNode] # @param definition [GraphQL::Schema::Field] # @param parent_object [GraphQL::Schema::Object] # @return [Hash{Symbol => Object}] def arguments_for(ast_node, definition, parent_object: nil) arguments_cache.fetch(ast_node, definition, parent_object) end def arguments_cache @arguments_cache ||= Execution::Interpreter::ArgumentsCache.new(self) end # @api private def handle_or_reraise(err) @schema.handle_or_reraise(context, err) end end include Runnable class OperationNameMissingError < GraphQL::ExecutionError def initialize(name) msg = if name.nil? %|An operation name is required| else %|No operation named "#{name}"| end super(msg) end end attr_reader :schema, :context, :provided_variables # The value for root types attr_accessor :root_value # @return [nil, String] The operation name provided by client or the one inferred from the document. Used to determine which operation to run. attr_accessor :operation_name # @return [Boolean] if false, static validation is skipped (execution behavior for invalid queries is undefined) attr_reader :validate # @param new_validate [Boolean] if false, static validation is skipped. This can't be reasssigned after validation. def validate=(new_validate) if defined?(@validation_pipeline) && @validation_pipeline && @validation_pipeline.has_validated? raise ArgumentError, "Can't reassign Query#validate= after validation has run, remove this assignment." else @validate = new_validate end end # @return [GraphQL::StaticValidation::Validator] if present, the query will validate with these rules. attr_reader :static_validator # @param new_validator [GraphQL::StaticValidation::Validator] if present, the query will validate with these rules. This can't be reasssigned after validation. def static_validator=(new_validator) if defined?(@validation_pipeline) && @validation_pipeline && @validation_pipeline.has_validated? raise ArgumentError, "Can't reassign Query#static_validator= after validation has run, remove this assignment." elsif !new_validator.is_a?(GraphQL::StaticValidation::Validator) raise ArgumentError, "Expected a `GraphQL::StaticValidation::Validator` instance." else @static_validator = new_validator end end attr_writer :query_string # @return [GraphQL::Language::Nodes::Document] def document # It's ok if this hasn't been assigned yet if @query_string || @document with_prepared_ast { @document } else nil end end def inspect "query ..." end # @return [String, nil] The name of the operation to run (may be inferred) def selected_operation_name return nil unless selected_operation selected_operation.name end # @return [String, nil] the triggered event, if this query is a subscription update attr_reader :subscription_topic attr_reader :tracers # Prepare query `query_string` on `schema` # @param schema [GraphQL::Schema] # @param query_string [String] # @param context [#[]] an arbitrary hash of values which you can access in {GraphQL::Field#resolve} # @param variables [Hash] values for `$variables` in the query # @param operation_name [String] if the query string contains many operations, this is the one which should be executed # @param root_value [Object] the object used to resolve fields on the root type # @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value) # @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value) # @param visibility_profile [Symbol] Another way to assign `context[:visibility_profile]` def initialize(schema, query_string = nil, query: nil, document: nil, context: nil, variables: nil, multiplex: nil, validate: true, static_validator: nil, visibility_profile: nil, subscription_topic: nil, operation_name: nil, root_value: nil, max_depth: schema.max_depth, max_complexity: schema.max_complexity, warden: nil, use_visibility_profile: nil) # Even if `variables: nil` is passed, use an empty hash for simpler logic variables ||= {} @multiplex = multiplex @schema = schema @context = schema.context_class.new(query: self, values: context) if visibility_profile @context[:visibility_profile] ||= visibility_profile end if use_visibility_profile.nil? use_visibility_profile = warden ? false : schema.use_visibility_profile? end if use_visibility_profile @visibility_profile = @schema.visibility.profile_for(@context) @warden = Schema::Warden::NullWarden.new(context: @context, schema: @schema) else @visibility_profile = nil @warden = warden end @subscription_topic = subscription_topic @root_value = root_value @fragments = nil @operations = nil @finalizers = @top_level_finalizers = nil @validate = validate self.static_validator = static_validator if static_validator context_tracers = (context ? context.fetch(:tracers, []) : []) @tracers = schema.tracers + context_tracers if !context_tracers.empty? && !(schema.trace_class <= GraphQL::Tracing::CallLegacyTracers) raise ArgumentError, "context[:tracers] are not supported without `trace_with(GraphQL::Tracing::CallLegacyTracers)` in the schema configuration, please add it." end @analysis_errors = [] if variables.is_a?(String) raise ArgumentError, "Query variables should be a Hash, not a String. Try JSON.parse to prepare variables." else @provided_variables = variables || {} end @query_string = query_string || query @document = document if @query_string && @document raise ArgumentError, "Query should only be provided a query string or a document, not both." end if @query_string && !@query_string.is_a?(String) raise ArgumentError, "Query string argument should be a String, got #{@query_string.class.name} instead." end # A two-layer cache of type resolution: # { abstract_type => { value => resolved_type } } @resolved_types_cache = Hash.new do |h1, k1| h1[k1] = Hash.new do |h2, k2| h2[k2] = @schema.resolve_type(k1, k2, @context) end end # Trying to execute a document # with no operations returns an empty hash @ast_variables = [] @mutation = false @operation_name = operation_name @prepared_ast = false @validation_pipeline = nil @max_depth = max_depth @max_complexity = max_complexity @result_values = nil @executed = false @logger = schema.logger_for(context) end # If a document was provided to `GraphQL::Schema#execute` instead of the raw query string, we will need to get it from the document def query_string @query_string ||= (document ? document.to_query_string : nil) end # @return [Symbol, nil] attr_reader :visibility_profile attr_accessor :multiplex # @return [GraphQL::Tracing::Trace] def current_trace @current_trace ||= context[:trace] || (multiplex ? multiplex.current_trace : schema.new_trace(multiplex: multiplex, query: self)) end def subscription_update? @subscription_topic && subscription? end # A lookahead for the root selections of this query # @return [GraphQL::Execution::Lookahead] def lookahead @lookahead ||= begin if selected_operation.nil? GraphQL::Execution::Lookahead::NULL_LOOKAHEAD else GraphQL::Execution::Lookahead.new(query: self, root_type: root_type, ast_nodes: [selected_operation]) end end end # @api private def result_values=(result_hash) if @executed raise "Invariant: Can't reassign result" else @executed = true @result_values = result_hash end end # @api private attr_reader :result_values def fragments with_prepared_ast { @fragments } end def operations with_prepared_ast { @operations } end def path EmptyObjects::EMPTY_ARRAY end # Run subtree partials of this query and return their results. # Each partial is identified with a `path:` and `object:` # where the path references a field in the AST and the object will be treated # as the return value from that field. Subfields of the field named by `path` # will be executed with `object` as the starting point # @param partials_hashes [Array Object}>] Hashes with `path:` and `object:` keys # @return [Array] def run_partials(partials_hashes) partials = partials_hashes.map { |partial_options| Partial.new(query: self, **partial_options) } if context[:__graphql_execute_next] Execution::Next.run_all(@schema, partials, context: @context) else Execution::Interpreter.run_all(@schema, partials, context: @context) end end # Get the result for this query, executing it once # @return [GraphQL::Query::Result] A Hash-like GraphQL response, with `"data"` and/or `"errors"` keys def result if !@executed Execution::Interpreter.run_all(@schema, [self], context: @context) end @result ||= Query::Result.new(query: self, values: @result_values) end def executed? @executed end def static_errors validation_errors + analysis_errors + context.errors end # This is the operation to run for this query. # If more than one operation is present, it must be named at runtime. # @return [GraphQL::Language::Nodes::OperationDefinition, nil] def selected_operation with_prepared_ast { @selected_operation } end # Determine the values for variables of this query, using default values # if a value isn't provided at runtime. # # If some variable is invalid, errors are added to {#validation_errors}. # # @return [GraphQL::Query::Variables] Variables to apply to this query def variables @variables ||= begin with_prepared_ast { GraphQL::Query::Variables.new( @context, @ast_variables, @provided_variables, ) } end end # A version of the given query string, with: # - Variables inlined to the query # - Strings replaced with `` # @return [String, nil] Returns nil if the query is invalid. def sanitized_query_string(inline_variables: true) with_prepared_ast { schema.sanitized_printer.new(self, inline_variables: inline_variables).sanitized_query_string } end # This contains a few components: # # - The selected operation name (or `anonymous`) # - The fingerprint of the query string # - The number of given variables (for readability) # - The fingerprint of the given variables # # This fingerprint can be used to track runs of the same operation-variables combination over time. # # @see operation_fingerprint # @see variables_fingerprint # @return [String] An opaque hash identifying this operation-variables combination def fingerprint @fingerprint ||= "#{operation_fingerprint}/#{variables_fingerprint}" end # @return [String] An opaque hash for identifying this query's given query string and selected operation def operation_fingerprint @operation_fingerprint ||= "#{selected_operation_name || "anonymous"}/#{Fingerprint.generate(query_string || "")}" end # @return [String] An opaque hash for identifying this query's given a variable values (not including defaults) def variables_fingerprint @variables_fingerprint ||= "#{provided_variables.size}/#{Fingerprint.generate(provided_variables.to_json)}" end def validation_pipeline with_prepared_ast { @validation_pipeline } end def_delegators :validation_pipeline, :validation_errors, :analyzers, :ast_analyzers, :max_depth, :max_complexity, :validate_timeout_remaining attr_accessor :analysis_errors def valid? validation_pipeline.valid? && analysis_errors.empty? end def warden with_prepared_ast { @warden } end def get_type(type_name) types.type(type_name) # rubocop:disable Development/ContextIsPassedCop end def get_field(owner, field_name) types.field(owner, field_name) # rubocop:disable Development/ContextIsPassedCop end def possible_types(type) types.possible_types(type) # rubocop:disable Development/ContextIsPassedCop end def root_type_for_operation(op_type) case op_type when "query", nil types.query_root # rubocop:disable Development/ContextIsPassedCop when "mutation" types.mutation_root # rubocop:disable Development/ContextIsPassedCop when "subscription" types.subscription_root # rubocop:disable Development/ContextIsPassedCop else raise ArgumentError, "unexpected root type name: #{op_type.inspect}; expected nil, 'query', 'mutation', or 'subscription'" end end def root_type root_type_for_operation(selected_operation.operation_type) end def types @visibility_profile || warden.visibility_profile end # @param abstract_type [GraphQL::UnionType, GraphQL::InterfaceType] # @param value [Object] Any runtime value # @return [GraphQL::ObjectType, nil] The runtime type of `value` from {Schema#resolve_type} # @see {#possible_types} to apply filtering from `only` / `except` def resolve_type(abstract_type, value = NOT_CONFIGURED) if value.is_a?(Symbol) && value == NOT_CONFIGURED # Old method signature value = abstract_type abstract_type = nil end if value.is_a?(GraphQL::Schema::Object) value = value.object end @resolved_types_cache[abstract_type][value] end def mutation? with_prepared_ast { @mutation } end def query? with_prepared_ast { @query } end def subscription? with_prepared_ast { @subscription } end attr_reader :logger private def find_operation(operations, operation_name) if operation_name.nil? && operations.length == 1 operations.values.first elsif !operations.key?(operation_name) nil else operations.fetch(operation_name) end end def prepare_ast @prepared_ast = true @warden ||= @schema.warden_class.new(schema: @schema, context: @context) parse_error = nil @document ||= begin if query_string GraphQL.parse(query_string, trace: self.current_trace, max_tokens: @schema.max_query_string_tokens) end rescue GraphQL::ParseError => err parse_error = err @schema.parse_error(err, @context) nil end @fragments = {} @operations = {} if @document @document.definitions.each do |part| case part when GraphQL::Language::Nodes::FragmentDefinition @fragments[part.name] = part when GraphQL::Language::Nodes::OperationDefinition @operations[part.name] = part end end elsif parse_error # This will be handled later else parse_error = GraphQL::ExecutionError.new("No query string was present") @context.add_error(parse_error) end # Trying to execute a document # with no operations returns an empty hash @ast_variables = [] @mutation = false @subscription = false operation_name_error = nil if !@operations.empty? @selected_operation = find_operation(@operations, @operation_name) if @selected_operation.nil? operation_name_error = GraphQL::Query::OperationNameMissingError.new(@operation_name) else if @operation_name.nil? @operation_name = @selected_operation.name end @ast_variables = @selected_operation.variables @mutation = @selected_operation.operation_type == "mutation" @query = @selected_operation.operation_type == "query" @subscription = @selected_operation.operation_type == "subscription" end end @validation_pipeline = GraphQL::Query::ValidationPipeline.new( query: self, parse_error: parse_error, operation_name_error: operation_name_error, max_depth: @max_depth, max_complexity: @max_complexity ) end # Since the query string is processed at the last possible moment, # any internal values which depend on it should be accessed within this wrapper. def with_prepared_ast if !@prepared_ast prepare_ast end yield end end end graphql-2.6.0/lib/graphql/dashboard.rb0000644000004100000410000001000115173430257017664 0ustar www-datawww-data# frozen_string_literal: true require 'rails/engine' require 'action_controller' module Graphql # `GraphQL::Dashboard` is a `Rails::Engine`-based dashboard for viewing metadata about your GraphQL schema. # # Pass the class name of your schema when mounting it. # @see GraphQL::Tracing::DetailedTrace DetailedTrace for viewing production traces in the Dashboard # # @example Mounting the Dashboard in your app # mount GraphQL::Dashboard, at: "graphql_dashboard", schema: "MySchema" # # @example Authenticating the Dashboard with HTTP Basic Auth # # config/initializers/graphql_dashboard.rb # GraphQL::Dashboard.middleware.use(Rack::Auth::Basic) do |username, password| # # Compare the provided username/password to an application setting: # ActiveSupport::SecurityUtils.secure_compare(Rails.application.credentials.graphql_dashboard_username, username) && # ActiveSupport::SecurityUtils.secure_compare(Rails.application.credentials.graphql_dashboard_username, password) # end # # @example Custom Rails authentication # # config/initializers/graphql_dashboard.rb # ActiveSupport.on_load(:graphql_dashboard_application_controller) do # # context here is GraphQL::Dashboard::ApplicationController # # before_action do # raise ActionController::RoutingError.new('Not Found') unless current_user&.admin? # end # # def current_user # # load current user # end # end # class Dashboard < Rails::Engine engine_name "graphql_dashboard" isolate_namespace(Graphql::Dashboard) autoload :ApplicationController, "graphql/dashboard/application_controller" autoload :LandingsController, "graphql/dashboard/landings_controller" autoload :StaticsController, "graphql/dashboard/statics_controller" autoload :DetailedTraces, "graphql/dashboard/detailed_traces" autoload :Subscriptions, "graphql/dashboard/subscriptions" autoload :OperationStore, "graphql/dashboard/operation_store" autoload :Limiters, "graphql/dashboard/limiters" routes do root "landings#show" resources :statics, only: :show, constraints: { id: /[0-9A-Za-z\-.]+/ } namespace :detailed_traces do resources :traces, only: [:index, :show, :destroy] do collection do delete :delete_all, to: "traces#delete_all", as: :delete_all end end end namespace :limiters do resources :limiters, only: [:show, :update], param: :name end namespace :operation_store do resources :clients, param: :name do resources :operations, param: :digest, only: [:index] do collection do get :archived, to: "operations#index", archived_status: :archived, as: :archived post :archive, to: "operations#update", modification: :archive, as: :archive post :unarchive, to: "operations#update", modification: :unarchive, as: :unarchive end end end resources :operations, param: :digest, only: [:index, :show] do collection do get :archived, to: "operations#index", archived_status: :archived, as: :archived post :archive, to: "operations#update", modification: :archive, as: :archive post :unarchive, to: "operations#update", modification: :unarchive, as: :unarchive end end resources :index_entries, only: [:index, :show], param: :name, constraints: { name: /[A-Za-z0-9_.]+/} end namespace :subscriptions do resources :topics, only: [:index, :show], param: :name, constraints: { name: /.*/ } resources :subscriptions, only: [:show], constraints: { id: /[a-zA-Z0-9\-]+/ } post "/subscriptions/clear_all", to: "subscriptions#clear_all", as: :clear_all end end end end # Rails expects the engine to be called `Graphql::Dashboard`, # but `GraphQL::Dashboard` is consistent with this gem's naming. # So define both constants to refer to the same class. GraphQL::Dashboard = Graphql::Dashboard graphql-2.6.0/lib/graphql/string_encoding_error.rb0000644000004100000410000000111615173430257022331 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class StringEncodingError < GraphQL::RuntimeTypeError attr_reader :string, :field, :path def initialize(str, context:) @string = str @field = context[:current_field] @path = context[:current_path] message = "String #{str.inspect} was encoded as #{str.encoding}".dup if @path message << " @ #{@path.join(".")}" end if @field message << " (#{@field.path})" end message << ". GraphQL requires an encoding compatible with UTF-8." super(message) end end end graphql-2.6.0/lib/graphql/version.rb0000644000004100000410000000010515173430257017426 0ustar www-datawww-data# frozen_string_literal: true module GraphQL VERSION = "2.6.0" end graphql-2.6.0/lib/graphql/types/0000755000004100000410000000000015173430257016564 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/types/int.rb0000644000004100000410000000165215173430257017707 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types # @see {Types::BigInt} for handling integers outside 32-bit range. class Int < GraphQL::Schema::Scalar description "Represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1." MIN = -(2**31) MAX = (2**31) - 1 def self.coerce_input(value, ctx) return if !value.is_a?(Integer) if value >= MIN && value <= MAX value else err = GraphQL::IntegerDecodingError.new(value) ctx.schema.type_error(err, ctx) end end def self.coerce_result(value, ctx) value = value.to_i if value >= MIN && value <= MAX value else err = GraphQL::IntegerEncodingError.new(value, context: ctx) ctx.schema.type_error(err, ctx) end end default_scalar true end end end graphql-2.6.0/lib/graphql/types/float.rb0000644000004100000410000000073015173430257020216 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types class Float < GraphQL::Schema::Scalar description "Represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point)." def self.coerce_input(value, _ctx) value.is_a?(Numeric) ? value.to_f : nil end def self.coerce_result(value, _ctx) value.to_f end default_scalar true end end end graphql-2.6.0/lib/graphql/types/relay/0000755000004100000410000000000015173430257017700 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/types/relay/edge_behaviors.rb0000644000004100000410000000557715173430257023211 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types module Relay module EdgeBehaviors def self.included(child_class) child_class.description("An edge in a connection.") child_class.field(:cursor, String, null: false, description: "A cursor for use in pagination.") child_class.extend(ClassMethods) child_class.class_exec { self.node_type = nil } child_class.node_nullable(true) child_class.default_broadcastable(nil) end def node if (current_runtime_state = Fiber[:__graphql_runtime_info]) query_runtime_state = current_runtime_state[context.query] query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items? end @object.node end module ClassMethods def inherited(child_class) super child_class.node_type = nil child_class.node_nullable = nil child_class.default_broadcastable(default_broadcastable?) end def default_relay? true end def default_broadcastable? @default_broadcastable end def default_broadcastable(new_value) @default_broadcastable = new_value end # Get or set the Object type that this edge wraps. # # @param node_type [Class] A `Schema::Object` subclass # @param null [Boolean] # @param field_options [Hash] Any extra arguments to pass to the `field :node` configuration def node_type(node_type = nil, null: self.node_nullable, field_options: nil) if node_type @node_type = node_type # Add a default `node` field base_field_options = { name: :node, type: node_type, null: null, description: "The item at the end of the edge.", connection: false, } if field_options base_field_options.merge!(field_options) end field(**base_field_options) end @node_type end def authorized?(obj, ctx) true end def visible?(ctx) node_type.visible?(ctx) end # Set the default `node_nullable` for this class and its child classes. (Defaults to `true`.) # Use `node_nullable(false)` in your base class to make non-null `node` field. def node_nullable(new_value = nil) if new_value.nil? @node_nullable != nil ? @node_nullable : superclass.node_nullable else @node_nullable = new_value end end protected attr_writer :node_type, :node_nullable end end end end end graphql-2.6.0/lib/graphql/types/relay/node.rb0000644000004100000410000000056115173430257021154 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types module Relay # This can be used for Relay's `Node` interface, # or you can take it as inspiration for your own implementation # of the `Node` interface. module Node include GraphQL::Schema::Interface include Types::Relay::NodeBehaviors end end end end graphql-2.6.0/lib/graphql/types/relay/base_connection.rb0000644000004100000410000000321215173430257023354 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types module Relay # Use this to implement Relay connections, or take it as inspiration # for Relay classes in your own app. # # You may wish to copy this code into your own base class, # so you can extend your own `BaseObject` instead of `GraphQL::Schema::Object`. # # @example Implementation a connection and edge # class BaseObject < GraphQL::Schema::Object; end # # # Given some object in your app ... # class Types::Post < BaseObject # end # # # Make a couple of base classes: # class Types::BaseEdge < GraphQL::Types::Relay::BaseEdge; end # class Types::BaseConnection < GraphQL::Types::Relay::BaseConnection; end # # # Then extend them for the object in your app # class Types::PostEdge < Types::BaseEdge # node_type Types::Post # end # # class Types::PostConnection < Types::BaseConnection # edge_type Types::PostEdge, # edges_nullable: true, # edge_nullable: true, # node_nullable: true, # nodes_field: true # # # Alternatively, you can call the class methods followed by your edge type # # edges_nullable true # # edge_nullable true # # node_nullable true # # has_nodes_field true # # edge_type Types::PostEdge # end # # @see Relay::BaseEdge for edge types class BaseConnection < Schema::Object include ConnectionBehaviors end end end end graphql-2.6.0/lib/graphql/types/relay/page_info.rb0000644000004100000410000000036515173430257022160 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types module Relay # The return type of a connection's `pageInfo` field class PageInfo < GraphQL::Schema::Object include PageInfoBehaviors end end end end graphql-2.6.0/lib/graphql/types/relay/has_node_field.rb0000644000004100000410000000235415173430257023154 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types module Relay # Include this module to your root Query type to get a Relay-compliant `node(id: ID!): Node` field that uses the schema's `object_from_id` hook. module HasNodeField def self.included(child_class) child_class.field(**field_options, &field_block) child_class.extend(ExecutionMethods) end module ExecutionMethods def get_relay_node(context, id:) context.schema.object_from_id(id, context) end end def get_relay_node(id:) self.class.get_relay_node(context, id: id) end class << self def field_options { name: "node", type: GraphQL::Types::Relay::Node, null: true, description: "Fetches an object given its ID.", relay_node_field: true, resolver_method: :get_relay_node, resolve_static: :get_relay_node, } end def field_block Proc.new { argument :id, "ID!", description: "ID of the object." } end end end end end end graphql-2.6.0/lib/graphql/types/relay/base_edge.rb0000644000004100000410000000157015173430257022126 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types module Relay # A class-based definition for Relay edges. # # Use this as a parent class in your app, or use it as inspiration for your # own base `Edge` class. # # For example, you may want to extend your own `BaseObject` instead of the # built-in `GraphQL::Schema::Object`. # # @example Making a UserEdge type # # Make a base class for your app # class Types::BaseEdge < GraphQL::Types::Relay::BaseEdge # end # # # Then extend your own base class # class Types::UserEdge < Types::BaseEdge # node_type(Types::User) # end # # @see {Relay::BaseConnection} for connection types class BaseEdge < GraphQL::Schema::Object include Types::Relay::EdgeBehaviors end end end end graphql-2.6.0/lib/graphql/types/relay/page_info_behaviors.rb0000644000004100000410000000201215173430257024211 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types module Relay module PageInfoBehaviors def self.included(child_class) child_class.extend ClassMethods child_class.description "Information about pagination in a connection." child_class.field :has_next_page, Boolean, null: false, description: "When paginating forwards, are there more items?" child_class.field :has_previous_page, Boolean, null: false, description: "When paginating backwards, are there more items?" child_class.field :start_cursor, String, null: true, description: "When paginating backwards, the cursor to continue." child_class.field :end_cursor, String, null: true, description: "When paginating forwards, the cursor to continue." end end module ClassMethods def default_relay? true end def default_broadcastable? true end end end end end graphql-2.6.0/lib/graphql/types/relay/connection_behaviors.rb0000644000004100000410000001773415173430257024442 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types module Relay module ConnectionBehaviors extend Forwardable def_delegators :@object, :cursor_from_node, :parent def self.included(child_class) child_class.extend(ClassMethods) child_class.has_nodes_field(true) child_class.node_nullable(true) child_class.edges_nullable(true) child_class.edge_nullable(true) child_class.module_exec { self.edge_type = nil self.node_type = nil self.edge_class = nil } child_class.default_broadcastable(nil) add_page_info_field(child_class) end module ClassMethods def inherited(child_class) super child_class.has_nodes_field(has_nodes_field) child_class.node_nullable(node_nullable) child_class.edges_nullable(edges_nullable) child_class.edge_nullable(edge_nullable) child_class.edge_type = nil child_class.node_type = nil child_class.edge_class = nil child_class.default_broadcastable(default_broadcastable?) end def default_relay? true end def default_broadcastable? @default_broadcastable end def default_broadcastable(new_value) @default_broadcastable = new_value end # @return [Class] attr_reader :node_type # @return [Class] attr_reader :edge_class # Configure this connection to return `edges` and `nodes` based on `edge_type_class`. # # This method will use the inputs to create: # - `edges` field # - `nodes` field # - description # # It's called when you subclass this base connection, trying to use the # class name to set defaults. You can call it again in the class definition # to override the default (or provide a value, if the default lookup failed). # @param field_options [Hash] Any extra keyword arguments to pass to the `field :edges, ...` and `field :nodes, ...` configurations def edge_type(edge_type_class, edge_class: GraphQL::Pagination::Connection::Edge, node_type: edge_type_class.node_type, nodes_field: self.has_nodes_field, node_nullable: self.node_nullable, edges_nullable: self.edges_nullable, edge_nullable: self.edge_nullable, field_options: nil) # Set this connection's graphql name node_type_name = node_type.graphql_name @node_type = node_type @edge_type = edge_type_class @edge_class = edge_class base_field_options = { name: :edges, type: [edge_type_class, null: edge_nullable], null: edges_nullable, description: "A list of edges.", scope: false, # Assume that the connection was already scoped. connection: false, } if field_options base_field_options.merge!(field_options) end field(**base_field_options) define_nodes_field(node_nullable, field_options: field_options) if nodes_field description("The connection type for #{node_type_name}.") end # Filter this list according to the way its node type would scope them def scope_items(items, context) node_type.scope_items(items, context) end # The connection will skip auth on its nodes if the node_type is configured for that def reauthorize_scoped_objects(new_value = nil) if new_value.nil? if @reauthorize_scoped_objects != nil @reauthorize_scoped_objects else node_type.reauthorize_scoped_objects end else @reauthorize_scoped_objects = new_value end end # Add the shortcut `nodes` field to this connection and its subclasses def nodes_field(node_nullable: self.node_nullable, field_options: nil) define_nodes_field(node_nullable, field_options: field_options) end def authorized?(obj, ctx) true # Let nodes be filtered out end def visible?(ctx) # if this is an abstract base class, there may be no `node_type` node_type ? node_type.visible?(ctx) : super end # Set the default `node_nullable` for this class and its child classes. (Defaults to `true`.) # Use `node_nullable(false)` in your base class to make non-null `node` and `nodes` fields. def node_nullable(new_value = nil) if new_value.nil? defined?(@node_nullable) ? @node_nullable : superclass.node_nullable else @node_nullable = new_value end end # Set the default `edges_nullable` for this class and its child classes. (Defaults to `true`.) # Use `edges_nullable(false)` in your base class to make non-null `edges` fields. def edges_nullable(new_value = nil) if new_value.nil? defined?(@edges_nullable) ? @edges_nullable : superclass.edges_nullable else @edges_nullable = new_value end end # Set the default `edge_nullable` for this class and its child classes. (Defaults to `true`.) # Use `edge_nullable(false)` in your base class to make non-null `edge` fields. def edge_nullable(new_value = nil) if new_value.nil? defined?(@edge_nullable) ? @edge_nullable : superclass.edge_nullable else @edge_nullable = new_value end end # Set the default `nodes_field` for this class and its child classes. (Defaults to `true`.) # Use `nodes_field(false)` in your base class to prevent adding of a nodes field. def has_nodes_field(new_value = nil) if new_value.nil? defined?(@nodes_field) ? @nodes_field : superclass.has_nodes_field else @nodes_field = new_value end end protected attr_writer :edge_type, :node_type, :edge_class private def define_nodes_field(nullable, field_options: nil) base_field_options = { name: :nodes, type: [@node_type, null: nullable], null: nullable, description: "A list of nodes.", connection: false, # Assume that the connection was scoped before this step: scope: false, } if field_options base_field_options.merge!(field_options) end field(**base_field_options) end end class << self def add_page_info_field(obj_type) obj_type.field :page_info, GraphQL::Types::Relay::PageInfo, null: false, description: "Information to aid in pagination." end end def edges # Assume that whatever authorization needed to happen # already happened at the connection level. if (current_runtime_state = Fiber[:__graphql_runtime_info]) query_runtime_state = current_runtime_state[context.query] query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items? end @object.edges end def nodes # Assume that whatever authorization needed to happen # already happened at the connection level. if (current_runtime_state = Fiber[:__graphql_runtime_info]) query_runtime_state = current_runtime_state[context.query] query_runtime_state.was_authorized_by_scope_items = @object.was_authorized_by_scope_items? end @object.nodes end end end end end graphql-2.6.0/lib/graphql/types/relay/node_behaviors.rb0000644000004100000410000000170615173430257023220 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types module Relay module NodeBehaviors def self.included(child_module) child_module.extend(ClassMethods) child_module.extend(ExecutionMethods) child_module.description("An object with an ID.") child_module.field(:id, ID, null: false, description: "ID of the object.", resolver_method: :default_global_id, resolve_each: :default_global_id) end def default_global_id self.class.default_global_id(object, context) end module ClassMethods def default_relay? true end end module ExecutionMethods def default_global_id(object, context) context.schema.id_from_object(object, self, context) end def included(child_class) child_class.extend(ExecutionMethods) end end end end end end graphql-2.6.0/lib/graphql/types/relay/has_nodes_field.rb0000644000004100000410000000245315173430257023337 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types module Relay # Include this module to your root Query type to get a Relay-style `nodes(id: ID!): [Node]` field that uses the schema's `object_from_id` hook. module HasNodesField def self.included(child_class) child_class.field(**field_options, &field_block) child_class.extend(ExecutionMethods) end module ExecutionMethods def get_relay_nodes(context, ids:) ids.map { |id| context.schema.object_from_id(id, context) } end end def get_relay_nodes(ids:) self.class.get_relay_nodes(context, ids: ids) end class << self def field_options { name: "nodes", type: [GraphQL::Types::Relay::Node, null: true], null: false, description: "Fetches a list of objects given a list of IDs.", relay_nodes_field: true, resolver_method: :get_relay_nodes, resolve_static: :get_relay_nodes } end def field_block Proc.new { argument :ids, "[ID!]!", description: "IDs of the objects." } end end end end end end graphql-2.6.0/lib/graphql/types/string.rb0000644000004100000410000000146115173430257020421 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types class String < GraphQL::Schema::Scalar description "Represents textual data as UTF-8 character sequences. This type is most often used by GraphQL to represent free-form human-readable text." def self.coerce_result(value, ctx) str = value.to_s if str.encoding == Encoding::UTF_8 || str.ascii_only? str elsif str.frozen? str.encode(Encoding::UTF_8) else str.encode!(Encoding::UTF_8) end rescue EncodingError err = GraphQL::StringEncodingError.new(str, context: ctx) ctx.schema.type_error(err, ctx) end def self.coerce_input(value, _ctx) value.is_a?(::String) ? value : nil end default_scalar true end end end graphql-2.6.0/lib/graphql/types/big_int.rb0000644000004100000410000000110715173430257020523 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types class BigInt < GraphQL::Schema::Scalar description "Represents non-fractional signed whole numeric values. Since the value may exceed the size of a 32-bit integer, it's encoded as a string." def self.coerce_input(value, _ctx) value && parse_int(value) rescue ArgumentError nil end def self.coerce_result(value, _ctx) value.to_i.to_s end def self.parse_int(value) value.is_a?(Numeric) ? value : Integer(value, 10) end end end end graphql-2.6.0/lib/graphql/types/iso_8601_duration.rb0000644000004100000410000000547015173430257022274 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types # This scalar takes `Duration`s and transmits them as strings, # using ISO 8601 format. ActiveSupport >= 5.0 must be loaded to use # this scalar. # # Use it for fields or arguments as follows: # # field :age, GraphQL::Types::ISO8601Duration, null: false # # argument :interval, GraphQL::Types::ISO8601Duration, null: false # # Alternatively, use this built-in scalar as inspiration for your # own Duration type. class ISO8601Duration < GraphQL::Schema::Scalar description "An ISO 8601-encoded duration" # @return [Integer, nil] def self.seconds_precision # ActiveSupport::Duration precision defaults to whatever input was given @seconds_precision end # @param [Integer, nil] value def self.seconds_precision=(value) @seconds_precision = value end # @param value [ActiveSupport::Duration, String] # @return [String] # @raise [GraphQL::Error] if ActiveSupport::Duration is not defined or if an incompatible object is passed def self.coerce_result(value, _ctx) unless defined?(ActiveSupport::Duration) raise GraphQL::Error, "ActiveSupport >= 5.0 must be loaded to use the built-in ISO8601Duration type." end begin case value when ActiveSupport::Duration value.iso8601(precision: seconds_precision) when ::String ActiveSupport::Duration.parse(value).iso8601(precision: seconds_precision) else # Try calling as ActiveSupport::Duration compatible as a fallback value.iso8601(precision: seconds_precision) end rescue StandardError => error raise GraphQL::Error, "An incompatible object (#{value.class}) was given to #{self}. Make sure that only ActiveSupport::Durations and well-formatted Strings are used with this type. (#{error.message})" end end # @param value [String, ActiveSupport::Duration] # @return [ActiveSupport::Duration, nil] # @raise [GraphQL::Error] if ActiveSupport::Duration is not defined # @raise [GraphQL::DurationEncodingError] if duration cannot be parsed def self.coerce_input(value, ctx) unless defined?(ActiveSupport::Duration) raise GraphQL::Error, "ActiveSupport >= 5.0 must be loaded to use the built-in ISO8601Duration type." end begin if value.is_a?(ActiveSupport::Duration) value elsif value.nil? nil else ActiveSupport::Duration.parse(value) end rescue ArgumentError, TypeError err = GraphQL::DurationEncodingError.new(value) ctx.schema.type_error(err, ctx) end end end end end graphql-2.6.0/lib/graphql/types/json.rb0000644000004100000410000000131415173430257020061 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types # An untyped JSON scalar that maps to Ruby hashes, arrays, strings, integers, floats, booleans and nils. # This should be used judiciously because it subverts the GraphQL type system. # # Use it for fields or arguments as follows: # # field :template_parameters, GraphQL::Types::JSON, null: false # # argument :template_parameters, GraphQL::Types::JSON, null: false # class JSON < GraphQL::Schema::Scalar description "Represents untyped JSON" def self.coerce_input(value, _context) value end def self.coerce_result(value, _context) value end end end end graphql-2.6.0/lib/graphql/types/relay.rb0000644000004100000410000000272515173430257020233 0ustar www-datawww-data# frozen_string_literal: true # behavior modules: require "graphql/types/relay/connection_behaviors" require "graphql/types/relay/edge_behaviors" require "graphql/types/relay/node_behaviors" require "graphql/types/relay/page_info_behaviors" require "graphql/types/relay/has_node_field" require "graphql/types/relay/has_nodes_field" # concrete classes based on the gem defaults: require "graphql/types/relay/page_info" require "graphql/types/relay/base_connection" require "graphql/types/relay/base_edge" require "graphql/types/relay/node" module GraphQL module Types # This module contains some types and fields that could support Relay conventions in GraphQL. # # You can use these classes out of the box if you want, but if you want to use your _own_ # GraphQL extensions along with the features in this code, you could also # open up the source files and copy the relevant methods and configuration into # your own classes. # # For example, the provided object types extend {Types::Relay::BaseObject}, # but you might want to: # # 1. Migrate the extensions from {Types::Relay::BaseObject} into _your app's_ base object # 2. Copy {Relay::BaseConnection}, {Relay::BaseEdge}, etc into _your app_, and # change them to extend _your_ base object. # # Similarly, `BaseField`'s extensions could be migrated to your app # and `Node` could be implemented to mix in your base interface module. module Relay end end end graphql-2.6.0/lib/graphql/types/iso_8601_date.rb0000644000004100000410000000245515173430257021364 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types # This scalar takes `Date`s and transmits them as strings, # using ISO 8601 format. # # Use it for fields or arguments as follows: # # field :published_at, GraphQL::Types::ISO8601Date, null: false # # argument :deliver_at, GraphQL::Types::ISO8601Date, null: false # # Alternatively, use this built-in scalar as inspiration for your # own Date type. class ISO8601Date < GraphQL::Schema::Scalar description "An ISO 8601-encoded date" specified_by_url "https://tools.ietf.org/html/rfc3339" # @param value [Date,Time,DateTime,String] # @return [String] def self.coerce_result(value, _ctx) Date.parse(value.to_s).iso8601 end # @param str_value [String, Date, DateTime, Time] # @return [Date, nil] def self.coerce_input(value, ctx) if value.is_a?(::Date) value elsif value.is_a?(::DateTime) value.to_date elsif value.is_a?(::Time) value.to_date elsif value.nil? nil else Date.iso8601(value) end rescue ArgumentError, TypeError err = GraphQL::DateEncodingError.new(value) ctx.schema.type_error(err, ctx) end end end end graphql-2.6.0/lib/graphql/types/boolean.rb0000644000004100000410000000057715173430257020541 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types class Boolean < GraphQL::Schema::Scalar description "Represents `true` or `false` values." def self.coerce_input(value, _ctx) (value == true || value == false) ? value : nil end def self.coerce_result(value, _ctx) !!value end default_scalar true end end end graphql-2.6.0/lib/graphql/types/id.rb0000644000004100000410000000150515173430257017506 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types class ID < GraphQL::Schema::Scalar graphql_name "ID" description "Represents a unique identifier that is Base64 obfuscated. It is often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"VXNlci0xMA==\"`) or integer (such as `4`) input value will be accepted as an ID." default_scalar true def self.coerce_result(value, _ctx) value.is_a?(::String) ? value : value.to_s end def self.coerce_input(value, _ctx) case value when ::String value when Integer value.to_s else nil end end end end end graphql-2.6.0/lib/graphql/types/iso_8601_date_time.rb0000644000004100000410000000470315173430257022400 0ustar www-datawww-data# frozen_string_literal: true require 'time' module GraphQL module Types # This scalar takes `Time`s and transmits them as strings, # using ISO 8601 format. # # Use it for fields or arguments as follows: # # field :created_at, GraphQL::Types::ISO8601DateTime, null: false # # argument :deliver_at, GraphQL::Types::ISO8601DateTime, null: false # # Alternatively, use this built-in scalar as inspiration for your # own DateTime type. class ISO8601DateTime < GraphQL::Schema::Scalar description "An ISO 8601-encoded datetime" specified_by_url "https://tools.ietf.org/html/rfc3339" # It's not compatible with Rails' default, # i.e. ActiveSupport::JSON::Encoder.time_precision (3 by default) DEFAULT_TIME_PRECISION = 0 # @return [Integer] def self.time_precision @time_precision || DEFAULT_TIME_PRECISION end # @param [Integer] value def self.time_precision=(value) @time_precision = value end # @param value [Time,Date,DateTime,String] # @return [String] def self.coerce_result(value, _ctx) case value when Date return value.to_time.iso8601(time_precision) when ::String return Time.parse(value).iso8601(time_precision) else # Time, DateTime or compatible is given: return value.iso8601(time_precision) end rescue StandardError => error raise GraphQL::Error, "An incompatible object (#{value.class}) was given to #{self}. Make sure that only Times, Dates, DateTimes, and well-formatted Strings are used with this type. (#{error.message})" end # @param str_value [String] # @return [Time] def self.coerce_input(str_value, _ctx) Time.iso8601(str_value) rescue ArgumentError, TypeError begin dt = Date.iso8601(str_value).to_time # For compatibility, continue accepting dates given without times # But without this, it would zero out given any time part of `str_value` (hours and/or minutes) if dt.iso8601.start_with?(str_value) dt elsif str_value.length == 8 && str_value.match?(/\A\d{8}\Z/) # Allow dates that are missing the "-". eg. "20220404" dt else nil end rescue ArgumentError, TypeError # Invalid input nil end end end end end graphql-2.6.0/lib/graphql/current.rb0000644000004100000410000000347115173430257017434 0ustar www-datawww-data# frozen_string_literal: true module GraphQL # This module exposes Fiber-level runtime information. # # It won't work across unrelated fibers, although it will work in child Fibers. # # @example Setting Up ActiveRecord::QueryLogs # # config.active_record.query_log_tags = [ # :namespaced_controller, # :action, # :job, # # ... # { # # GraphQL runtime info: # current_graphql_operation: -> { GraphQL::Current.operation_name }, # current_graphql_field: -> { GraphQL::Current.field&.path }, # current_dataloader_source: -> { GraphQL::Current.dataloader_source_class }, # # ... # }, # ] # module Current # @return [String, nil] Comma-joined operation names for the currently-running {Execution::Multiplex}. `nil` if all operations are anonymous. def self.operation_name if (m = Fiber[:__graphql_current_multiplex]) m.context[:__graphql_current_operation_name] ||= begin names = m.queries.map { |q| q.selected_operation_name } if names.all?(&:nil?) nil else names.join(",") end end else nil end end # @see GraphQL::Field#path for a string identifying this field # @return [GraphQL::Field, nil] The currently-running field, if there is one. def self.field Fiber[:__graphql_runtime_info]&.values&.first&.current_field end # @return [Class, nil] The currently-running {Dataloader::Source} class, if there is one. def self.dataloader_source_class Fiber[:__graphql_current_dataloader_source]&.class end # @return [GraphQL::Dataloader::Source, nil] The currently-running source, if there is one def self.dataloader_source Fiber[:__graphql_current_dataloader_source] end end end graphql-2.6.0/lib/graphql/autoload.rb0000644000004100000410000000211215173430257017551 0ustar www-datawww-data# frozen_string_literal: true module GraphQL # @see GraphQL::Railtie for automatic Rails integration module Autoload # Register a constant named `const_name` to be loaded from `path`. # This is like `Kernel#autoload` but it tracks the constants so they can be eager-loaded with {#eager_load!} # @param const_name [Symbol] # @param path [String] # @return [void] def autoload(const_name, path) @_eagerloaded_constants ||= [] @_eagerloaded_constants << const_name super const_name, path end # Call this to load this constant's `autoload` dependents and continue calling recursively # @return [void] def eager_load! @_eager_loading = true if @_eagerloaded_constants @_eagerloaded_constants.each { |const_name| const_get(const_name) } @_eagerloaded_constants = nil end nil ensure @_eager_loading = false end private # @return [Boolean] `true` if GraphQL-Ruby is currently eager-loading its constants def eager_loading? @_eager_loading ||= false end end end graphql-2.6.0/lib/graphql/date_encoding_error.rb0000644000004100000410000000102115173430257021733 0ustar www-datawww-data# frozen_string_literal: true module GraphQL # This error is raised when `Types::ISO8601Date` is asked to return a value # that cannot be parsed to a Ruby Date. # # @see GraphQL::Types::ISO8601Date which raises this error class DateEncodingError < GraphQL::RuntimeTypeError # The value which couldn't be encoded attr_reader :date_value def initialize(value) @date_value = value super("Date cannot be parsed: #{value}. \nDate must be able to be parsed as a Ruby Date object.") end end end graphql-2.6.0/lib/graphql/backtrace.rb0000644000004100000410000000163115173430257017665 0ustar www-datawww-data# frozen_string_literal: true require "graphql/backtrace/table" require "graphql/backtrace/traced_error" module GraphQL # Wrap unhandled errors with {TracedError}. # # {TracedError} provides a GraphQL backtrace with arguments and return values. # The underlying error is available as {TracedError#cause}. # # @example toggling backtrace annotation # class MySchema < GraphQL::Schema # if Rails.env.development? || Rails.env.test? # use GraphQL::Backtrace # end # end # class Backtrace include Enumerable extend Forwardable def_delegators :to_a, :each, :[] def self.use(schema_defn) schema_defn.using_backtrace = true end def initialize(context, value: nil) @table = Table.new(context, value: value) end def inspect @table.to_table end alias :to_s :inspect def to_a @table.to_backtrace end end end graphql-2.6.0/lib/graphql/pagination/0000755000004100000410000000000015173430257017551 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/pagination/active_record_relation_connection.rb0000644000004100000410000000343315173430257027026 0ustar www-datawww-data# frozen_string_literal: true require "graphql/pagination/relation_connection" module GraphQL module Pagination # Customizes `RelationConnection` to work with `ActiveRecord::Relation`s. class ActiveRecordRelationConnection < Pagination::RelationConnection private def relation_count(relation) int_or_hash = if already_loaded?(relation) relation.size elsif relation.respond_to?(:unscope) relation.unscope(:order).count(:all) else # Rails 3 relation.count end if int_or_hash.is_a?(Integer) int_or_hash else # Grouped relations return count-by-group hashes int_or_hash.length end end def relation_limit(relation) if relation.is_a?(Array) nil else relation.limit_value end end def relation_offset(relation) if relation.is_a?(Array) nil else relation.offset_value end end def null_relation(relation) if relation.respond_to?(:none) relation.none else # Rails 3 relation.where("1=2") end end def set_limit(nodes, limit) if already_loaded?(nodes) nodes.take(limit) else super end end def set_offset(nodes, offset) if already_loaded?(nodes) # If the client sent a bogus cursor beyond the size of the relation, # it might get `nil` from `#[...]`, so return an empty array in that case nodes[offset..-1] || [] else super end end private def already_loaded?(relation) relation.is_a?(Array) || relation.loaded? end end end end graphql-2.6.0/lib/graphql/pagination/relation_connection.rb0000644000004100000410000001716015173430257024137 0ustar www-datawww-data# frozen_string_literal: true require "graphql/pagination/connection" module GraphQL module Pagination # A generic class for working with database query objects. class RelationConnection < Pagination::Connection def nodes load_nodes @nodes end def has_previous_page if @has_previous_page.nil? @has_previous_page = if after_offset && after_offset > 0 true elsif last # See whether there are any nodes _before_ the current offset. # If there _is no_ current offset, then there can't be any nodes before it. # Assume that if the offset is positive, there are nodes before the offset. limited_nodes !(@paged_nodes_offset.nil? || @paged_nodes_offset == 0) else false end end @has_previous_page end def has_next_page if @has_next_page.nil? @has_next_page = if before_offset && before_offset > 0 true elsif first if @nodes && @nodes.count < first false else relation_larger_than(sliced_nodes, @sliced_nodes_offset, first) end else false end end @has_next_page end def cursor_for(item) load_nodes # index in nodes + existing offset + 1 (because it's offset, not index) offset = nodes.index(item) + 1 + (@paged_nodes_offset || 0) - (relation_offset(items) || 0) encode(offset.to_s) end private # @param relation [Object] A database query object # @param _initial_offset [Integer] The number of items already excluded from the relation # @param size [Integer] The value against which we check the relation size # @return [Boolean] True if the number of items in this relation is larger than `size` def relation_larger_than(relation, _initial_offset, size) relation_count(set_limit(relation, size + 1)) == size + 1 end # @param relation [Object] A database query object # @return [Integer, nil] The offset value, or nil if there isn't one def relation_offset(relation) raise "#{self.class}#relation_offset(relation) must return the offset value for a #{relation.class} (#{relation.inspect})" end # @param relation [Object] A database query object # @return [Integer, nil] The limit value, or nil if there isn't one def relation_limit(relation) raise "#{self.class}#relation_limit(relation) must return the limit value for a #{relation.class} (#{relation.inspect})" end # @param relation [Object] A database query object # @return [Integer, nil] The number of items in this relation (hopefully determined without loading all records into memory!) def relation_count(relation) raise "#{self.class}#relation_count(relation) must return the count of records for a #{relation.class} (#{relation.inspect})" end # @param relation [Object] A database query object # @return [Object] A modified query object which will return no records def null_relation(relation) raise "#{self.class}#null_relation(relation) must return an empty relation for a #{relation.class} (#{relation.inspect})" end # @return [Integer] def offset_from_cursor(cursor) decode(cursor).to_i end # Abstract this operation so we can always ignore inputs less than zero. # (Sequel doesn't like it, understandably.) def set_offset(relation, offset_value) if offset_value >= 0 relation.offset(offset_value) else relation.offset(0) end end # Abstract this operation so we can always ignore inputs less than zero. # (Sequel doesn't like it, understandably.) def set_limit(relation, limit_value) if limit_value > 0 relation.limit(limit_value) elsif limit_value == 0 null_relation(relation) else relation end end def calculate_sliced_nodes_parameters if defined?(@sliced_nodes_limit) return else next_offset = relation_offset(items) || 0 relation_limit = relation_limit(items) if after_offset next_offset += after_offset end if before_offset && after_offset if after_offset < before_offset # Get the number of items between the two cursors space_between = before_offset - after_offset - 1 relation_limit = space_between else # The cursors overextend one another to an empty set @sliced_nodes_null_relation = true end elsif before_offset # Use limit to cut off the tail of the relation relation_limit = before_offset - 1 end @sliced_nodes_limit = relation_limit @sliced_nodes_offset = next_offset end end # Apply `before` and `after` to the underlying `items`, # returning a new relation. def sliced_nodes @sliced_nodes ||= begin calculate_sliced_nodes_parameters paginated_nodes = items if @sliced_nodes_null_relation paginated_nodes = null_relation(paginated_nodes) else if @sliced_nodes_limit paginated_nodes = set_limit(paginated_nodes, @sliced_nodes_limit) end if @sliced_nodes_offset paginated_nodes = set_offset(paginated_nodes, @sliced_nodes_offset) end end paginated_nodes end end # @return [Integer, nil] def before_offset @before_offset ||= before && offset_from_cursor(before) end # @return [Integer, nil] def after_offset @after_offset ||= after && offset_from_cursor(after) end # Apply `first` and `last` to `sliced_nodes`, # returning a new relation def limited_nodes @limited_nodes ||= begin calculate_sliced_nodes_parameters if @sliced_nodes_null_relation # it's an empty set return sliced_nodes end relation_limit = @sliced_nodes_limit relation_offset = @sliced_nodes_offset if first && (relation_limit.nil? || relation_limit > first) # `first` would create a stricter limit that the one already applied, so add it relation_limit = first end if last if relation_limit if last <= relation_limit # `last` is a smaller slice than the current limit, so apply it relation_offset += (relation_limit - last) relation_limit = last end else # No limit, so get the last items sliced_nodes_count = relation_count(sliced_nodes) relation_offset += (sliced_nodes_count - [last, sliced_nodes_count].min) relation_limit = last end end @paged_nodes_offset = relation_offset paginated_nodes = items paginated_nodes = set_offset(paginated_nodes, relation_offset) if relation_limit paginated_nodes = set_limit(paginated_nodes, relation_limit) end paginated_nodes end end # Load nodes after applying first/last/before/after, # returns an array of nodes def load_nodes # Return an array so we can consistently use `.index(node)` on it @nodes ||= limited_nodes.to_a end end end end graphql-2.6.0/lib/graphql/pagination/sequel_dataset_connection.rb0000644000004100000410000000117315173430257025322 0ustar www-datawww-data# frozen_string_literal: true require "graphql/pagination/relation_connection" module GraphQL module Pagination # Customizes `RelationConnection` to work with `Sequel::Dataset`s. class SequelDatasetConnection < Pagination::RelationConnection private def relation_offset(relation) relation.opts[:offset] end def relation_limit(relation) relation.opts[:limit] end def relation_count(relation) # Remove order to make it faster relation.order(nil).count end def null_relation(relation) relation.where(false) end end end end graphql-2.6.0/lib/graphql/pagination/connections.rb0000644000004100000410000001322415173430257022422 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Pagination # A schema-level connection wrapper manager. # # Attach as a plugin. # # @example Adding a custom wrapper # class MySchema < GraphQL::Schema # connections.add(MyApp::SearchResults, MyApp::SearchResultsConnection) # end # # @example Removing default connection support for arrays (they can still be manually wrapped) # class MySchema < GraphQL::Schema # connections.delete(Array) # end # # @see {Schema.connections} class Connections class ImplementationMissingError < GraphQL::Error end def initialize(schema:) @schema = schema @wrappers = {} add_default end def add(nodes_class, implementation) @wrappers[nodes_class] = implementation end def delete(nodes_class) @wrappers.delete(nodes_class) end def all_wrappers all_wrappers = {} @schema.ancestors.reverse_each do |schema_class| if schema_class.respond_to?(:connections) && (c = schema_class.connections) all_wrappers.merge!(c.wrappers) end end all_wrappers end def wrapper_for(items, wrappers: all_wrappers) impl = nil items.class.ancestors.each { |cls| impl = wrappers[cls] break if impl } impl end # Used by the runtime to wrap values in connection wrappers. # @api Private def wrap(field, parent, items, arguments, context) return items if GraphQL::Execution::Interpreter::RawValue === items wrappers = context ? context.namespace(:connections)[:all_wrappers] : all_wrappers impl = wrapper_for(items, wrappers: wrappers) if impl impl.new( items, context: context, parent: parent, field: field, max_page_size: field.has_max_page_size? ? field.max_page_size : context.schema.default_max_page_size, default_page_size: field.has_default_page_size? ? field.default_page_size : context.schema.default_page_size, first: arguments[:first], after: arguments[:after], last: arguments[:last], before: arguments[:before], arguments: arguments, edge_class: edge_class_for_field(field), ) else raise ImplementationMissingError, "Couldn't find a connection wrapper for #{items.class} during #{field.path} (#{items.inspect})" end end def populate_connection(field, object, value, original_arguments, context) if value.is_a? GraphQL::ExecutionError # This isn't even going to work because context doesn't have ast_node anymore context.add_error(value) nil elsif value.nil? nil elsif value.is_a?(GraphQL::Pagination::Connection) # update the connection with some things that may not have been provided value.context ||= context value.parent ||= object value.first_value ||= original_arguments[:first] value.after_value ||= original_arguments[:after] value.last_value ||= original_arguments[:last] value.before_value ||= original_arguments[:before] value.arguments ||= original_arguments # rubocop:disable Development/ContextIsPassedCop -- unrelated .arguments method value.field ||= field if field.has_max_page_size? && !value.has_max_page_size_override? value.max_page_size = field.max_page_size end if field.has_default_page_size? && !value.has_default_page_size_override? value.default_page_size = field.default_page_size end if (custom_t = context.schema.connections.edge_class_for_field(field)) value.edge_class = custom_t end value else context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers context.schema.connections.wrap(field, object, value, original_arguments, context) end end # use an override if there is one # @api private def edge_class_for_field(field) conn_type = field.type.unwrap conn_type_edge_type = conn_type.respond_to?(:edge_class) && conn_type.edge_class if conn_type_edge_type && conn_type_edge_type != Pagination::Connection::Edge conn_type_edge_type else nil end end protected attr_reader :wrappers private def add_default add(Array, Pagination::ArrayConnection) if defined?(ActiveRecord::Relation) add(ActiveRecord::Relation, Pagination::ActiveRecordRelationConnection) end if defined?(Sequel::Dataset) add(Sequel::Dataset, Pagination::SequelDatasetConnection) end if defined?(Mongoid::Criteria) add(Mongoid::Criteria, Pagination::MongoidRelationConnection) end # Mongoid 5 and 6 if defined?(Mongoid::Relations::Targets::Enumerable) add(Mongoid::Relations::Targets::Enumerable, Pagination::MongoidRelationConnection) end # Mongoid 7 if defined?(Mongoid::Association::Referenced::HasMany::Targets::Enumerable) add(Mongoid::Association::Referenced::HasMany::Targets::Enumerable, Pagination::MongoidRelationConnection) end # Mongoid 7.3+ if defined?(Mongoid::Association::Referenced::HasMany::Enumerable) add(Mongoid::Association::Referenced::HasMany::Enumerable, Pagination::MongoidRelationConnection) end end end end end graphql-2.6.0/lib/graphql/pagination/array_connection.rb0000644000004100000410000000375315173430257023443 0ustar www-datawww-data# frozen_string_literal: true require "graphql/pagination/connection" module GraphQL module Pagination class ArrayConnection < Pagination::Connection def nodes load_nodes @nodes end def has_previous_page load_nodes @has_previous_page end def has_next_page load_nodes @has_next_page end def cursor_for(item) idx = items.find_index(item) + 1 encode(idx.to_s) end private def index_from_cursor(cursor) decode(cursor).to_i end # Populate all the pagination info _once_, # It doesn't do anything on subsequent calls. def load_nodes @nodes ||= begin sliced_nodes = if before && after end_idx = index_from_cursor(before) - 2 end_idx < 0 ? [] : items[index_from_cursor(after)..end_idx] || [] elsif before end_idx = index_from_cursor(before) - 2 end_idx < 0 ? [] : items[0..end_idx] || [] elsif after items[index_from_cursor(after)..-1] || [] else items end @has_previous_page = if last # There are items preceding the ones in this result sliced_nodes.count > last elsif after # We've paginated into the Array a bit, there are some behind us index_from_cursor(after) > 0 else false end @has_next_page = if before # The original array is longer than the `before` index index_from_cursor(before) < items.length + 1 elsif first # There are more items after these items sliced_nodes.count > first else false end limited_nodes = sliced_nodes limited_nodes = limited_nodes.first(first) if first limited_nodes = limited_nodes.last(last) if last limited_nodes end end end end end graphql-2.6.0/lib/graphql/pagination/mongoid_relation_connection.rb0000644000004100000410000000104615173430257025647 0ustar www-datawww-data# frozen_string_literal: true require "graphql/pagination/relation_connection" module GraphQL module Pagination class MongoidRelationConnection < Pagination::RelationConnection def relation_offset(relation) relation.options.skip end def relation_limit(relation) relation.options.limit end def relation_count(relation) relation.all.count(relation.options.slice(:limit, :skip)) end def null_relation(relation) relation.without_options.none end end end end graphql-2.6.0/lib/graphql/pagination/connection.rb0000644000004100000410000002350415173430257022241 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Pagination # A Connection wraps a list of items and provides cursor-based pagination over it. # # Connections were introduced by Facebook's `Relay` front-end framework, but # proved to be generally useful for GraphQL APIs. When in doubt, use connections # to serve lists (like Arrays, ActiveRecord::Relations) via GraphQL. # # Unlike the previous connection implementation, these default to bidirectional pagination. # # Pagination arguments and context may be provided at initialization or assigned later (see {Schema::Field::ConnectionExtension}). class Connection class PaginationImplementationMissingError < GraphQL::Error end # @return [Object] A list object, from the application. This is the unpaginated value passed into the connection. attr_reader :items # @return [GraphQL::Query::Context] attr_reader :context def context=(new_ctx) @context = new_ctx if @was_authorized_by_scope_items.nil? @was_authorized_by_scope_items = detect_was_authorized_by_scope_items end @context end # @return [Object] the object this collection belongs to attr_accessor :parent # Raw access to client-provided values. (`max_page_size` not applied to first or last.) attr_accessor :before_value, :after_value, :first_value, :last_value # @return [String, nil] the client-provided cursor. `""` is treated as `nil`. def before if defined?(@before) @before else @before = @before_value == "" ? nil : @before_value end end # @return [String, nil] the client-provided cursor. `""` is treated as `nil`. def after if defined?(@after) @after else @after = @after_value == "" ? nil : @after_value end end # @return [Hash Object>] The field arguments from the field that returned this connection attr_accessor :arguments # @param items [Object] some unpaginated collection item, like an `Array` or `ActiveRecord::Relation` # @param context [Query::Context] # @param parent [Object] The object this collection belongs to # @param first [Integer, nil] The limit parameter from the client, if it provided one # @param after [String, nil] A cursor for pagination, if the client provided one # @param last [Integer, nil] Limit parameter from the client, if provided # @param before [String, nil] A cursor for pagination, if the client provided one. # @param arguments [Hash] The arguments to the field that returned the collection wrapped by this connection # @param max_page_size [Integer, nil] A configured value to cap the result size. Applied as `first` if neither first or last are given and no `default_page_size` is set. # @param default_page_size [Integer, nil] A configured value to determine the result size when neither first or last are given. def initialize(items, parent: nil, field: nil, context: nil, first: nil, after: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, last: nil, before: nil, edge_class: nil, arguments: nil) @items = items @parent = parent @context = context @field = field @first_value = first @after_value = after @last_value = last @before_value = before @arguments = arguments @edge_class = edge_class || self.class::Edge # This is only true if the object was _initialized_ with an override # or if one is assigned later. @has_max_page_size_override = max_page_size != NOT_CONFIGURED @max_page_size = if max_page_size == NOT_CONFIGURED nil else max_page_size end @has_default_page_size_override = default_page_size != NOT_CONFIGURED @default_page_size = if default_page_size == NOT_CONFIGURED nil else default_page_size end @was_authorized_by_scope_items = detect_was_authorized_by_scope_items end attr_writer :was_authorized_by_scope_items def was_authorized_by_scope_items? @was_authorized_by_scope_items end def max_page_size=(new_value) @has_max_page_size_override = true @max_page_size = new_value end def max_page_size if @has_max_page_size_override @max_page_size else context.schema.default_max_page_size end end def has_max_page_size_override? @has_max_page_size_override end def default_page_size=(new_value) @has_default_page_size_override = true @default_page_size = new_value end def default_page_size if @has_default_page_size_override @default_page_size else context.schema.default_page_size end end def has_default_page_size_override? @has_default_page_size_override end attr_writer :first # @return [Integer, nil] # A clamped `first` value. # (The underlying instance variable doesn't have limits on it.) # If neither `first` nor `last` is given, but `default_page_size` is # present, default_page_size is used for first. If `default_page_size` # is greater than `max_page_size``, it'll be clamped down to # `max_page_size`. If `default_page_size` is nil, use `max_page_size`. def first @first ||= begin capped = limit_pagination_argument(@first_value, max_page_size) if capped.nil? && last.nil? capped = limit_pagination_argument(default_page_size, max_page_size) || max_page_size end capped end end # This is called by `Relay::RangeAdd` -- it can be overridden # when `item` needs some modifications based on this connection's state. # # @param item [Object] An item newly added to `items` # @return [Edge] def range_add_edge(item) edge_class.new(item, self) end attr_writer :last # @return [Integer, nil] A clamped `last` value. (The underlying instance variable doesn't have limits on it) def last @last ||= limit_pagination_argument(@last_value, max_page_size) end # @return [Array] {nodes}, but wrapped with Edge instances def edges @edges ||= nodes.map { |n| @edge_class.new(n, self) } end # @return [Class] A wrapper class for edges of this connection attr_accessor :edge_class # @return [GraphQL::Schema::Field] The field this connection was returned by attr_accessor :field # @return [Array] A slice of {items}, constrained by {@first_value}/{@after_value}/{@last_value}/{@before_value} def nodes raise PaginationImplementationMissingError, "Implement #{self.class}#nodes to paginate `@items`" end # A dynamic alias for compatibility with {Relay::BaseConnection}. # @deprecated use {#nodes} instead def edge_nodes nodes end # The connection object itself implements `PageInfo` fields def page_info self end # @return [Boolean] True if there are more items after this page def has_next_page raise PaginationImplementationMissingError, "Implement #{self.class}#has_next_page to return the next-page check" end # @return [Boolean] True if there were items before these items def has_previous_page raise PaginationImplementationMissingError, "Implement #{self.class}#has_previous_page to return the previous-page check" end # @return [String] The cursor of the first item in {nodes} def start_cursor nodes.first && cursor_for(nodes.first) end # @return [String] The cursor of the last item in {nodes} def end_cursor nodes.last && cursor_for(nodes.last) end # Return a cursor for this item. # @param item [Object] one of the passed in {items}, taken from {nodes} # @return [String] def cursor_for(item) raise PaginationImplementationMissingError, "Implement #{self.class}#cursor_for(item) to return the cursor for #{item.inspect}" end private def detect_was_authorized_by_scope_items if @context && (current_runtime_state = Fiber[:__graphql_runtime_info]) && (query_runtime_state = current_runtime_state[@context.query]) query_runtime_state.was_authorized_by_scope_items else nil end end # @param argument [nil, Integer] `first` or `last`, as provided by the client # @param max_page_size [nil, Integer] # @return [nil, Integer] `nil` if the input was `nil`, otherwise a value between `0` and `max_page_size` def limit_pagination_argument(argument, max_page_size) if argument if argument < 0 argument = 0 elsif max_page_size && argument > max_page_size argument = max_page_size end end argument end def decode(cursor) context.schema.cursor_encoder.decode(cursor, nonce: true) end def encode(cursor) context.schema.cursor_encoder.encode(cursor, nonce: true) end # A wrapper around paginated items. It includes a {cursor} for pagination # and could be extended with custom relationship-level data. class Edge attr_reader :node def initialize(node, connection) @connection = connection @node = node end def parent @connection.parent end def cursor @cursor ||= @connection.cursor_for(@node) end def was_authorized_by_scope_items? @connection.was_authorized_by_scope_items? end end end end end graphql-2.6.0/lib/graphql/runtime_error.rb0000644000004100000410000000017615173430257020645 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class RuntimeError < Error include GraphQL::Execution::Finalizer end end graphql-2.6.0/lib/graphql/types.rb0000644000004100000410000000115515173430257017113 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Types extend Autoload autoload :Boolean, "graphql/types/boolean" autoload :BigInt, "graphql/types/big_int" autoload :Float, "graphql/types/float" autoload :ID, "graphql/types/id" autoload :Int, "graphql/types/int" autoload :JSON, "graphql/types/json" autoload :String, "graphql/types/string" autoload :ISO8601Date, "graphql/types/iso_8601_date" autoload :ISO8601DateTime, "graphql/types/iso_8601_date_time" autoload :ISO8601Duration, "graphql/types/iso_8601_duration" autoload :Relay, "graphql/types/relay" end end graphql-2.6.0/lib/graphql/execution/0000755000004100000410000000000015173430257017423 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/execution/lazy/0000755000004100000410000000000015173430257020402 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/execution/lazy/lazy_method_map.rb0000644000004100000410000000557715173430257024121 0ustar www-datawww-data# frozen_string_literal: true require 'thread' begin require 'concurrent' rescue LoadError # no problem, we'll fallback to our own map end module GraphQL module Execution class Lazy # {GraphQL::Schema} uses this to match returned values to lazy resolution methods. # Methods may be registered for classes, they apply to its subclasses also. # The result of this lookup is cached for future resolutions. # Instances of this class are thread-safe. # @api private # @see {Schema#lazy?} looks up values from this map class LazyMethodMap def initialize(use_concurrent: defined?(Concurrent::Map)) @storage = use_concurrent ? Concurrent::Map.new : ConcurrentishMap.new end def initialize_copy(other) @storage = other.storage.dup end # @param lazy_class [Class] A class which represents a lazy value (subclasses may also be used) # @param lazy_value_method [Symbol] The method to call on this class to get its value def set(lazy_class, lazy_value_method) @storage[lazy_class] = lazy_value_method end # @param value [Object] an object which may have a `lazy_value_method` registered for its class or superclasses # @return [Symbol, nil] The `lazy_value_method` for this object, or nil def get(value) @storage.compute_if_absent(value.class) { find_superclass_method(value.class) } end def each @storage.each_pair { |k, v| yield(k, v) } end protected attr_reader :storage private def find_superclass_method(value_class) @storage.each_pair { |lazy_class, lazy_value_method| return lazy_value_method if value_class < lazy_class } nil end # Mock the Concurrent::Map API class ConcurrentishMap extend Forwardable # Technically this should be under the mutex too, # but I know it's only used when the lock is already acquired. def_delegators :@storage, :each_pair, :size def initialize @semaphore = Mutex.new # Access to this hash must always be managed by the mutex # since it may be modified at runtime @storage = {} end def []=(key, value) @semaphore.synchronize { @storage[key] = value } end def compute_if_absent(key) @semaphore.synchronize { @storage.fetch(key) { @storage[key] = yield } } end def initialize_copy(other) @semaphore = Mutex.new @storage = other.copy_storage end protected def copy_storage @semaphore.synchronize { @storage.dup } end end end end end end graphql-2.6.0/lib/graphql/execution/load_argument_step.rb0000644000004100000410000000434115173430257023626 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution class LoadArgumentStep def initialize(field_resolve_step:, arguments:, load_receiver:, argument_value:, argument_definition:, argument_key:) @field_resolve_step = field_resolve_step @load_receiver = load_receiver @arguments = arguments @argument_value = argument_value @argument_definition = argument_definition @argument_key = argument_key @loaded_value = nil end def value @loaded_value = @field_resolve_step.sync(@loaded_value) assign_value end def call context = @field_resolve_step.selections_step.query.context @loaded_value = begin @load_receiver.load_and_authorize_application_object(@argument_definition, @argument_value, context) rescue GraphQL::UnauthorizedError => auth_err context.schema.unauthorized_object(auth_err) end if (runner = @field_resolve_step.runner).resolves_lazies && runner.lazy?(@loaded_value) runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self) else assign_value end rescue GraphQL::RuntimeError => err @loaded_value = err assign_value rescue StandardError => stderr @loaded_value = begin context.query.handle_or_reraise(stderr) rescue GraphQL::ExecutionError => ex_err ex_err end assign_value end private def assign_value if @loaded_value.is_a?(GraphQL::RuntimeError) @loaded_value.path = @field_resolve_step.path @field_resolve_step.arguments = @loaded_value else query = @field_resolve_step.selections_step.query query.current_trace.object_loaded(@argument_definition, @loaded_value, query.context) @arguments[@argument_key] = @loaded_value end field_pending_steps = @field_resolve_step.pending_steps field_pending_steps.delete(self) if @field_resolve_step.arguments && field_pending_steps.size == 0 # rubocop:disable Development/ContextIsPassedCop @field_resolve_step.runner.add_step(@field_resolve_step) end end end end end graphql-2.6.0/lib/graphql/execution/runner.rb0000644000004100000410000003545115173430257021271 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution class Runner def initialize(multiplex, authorization:) @multiplex = multiplex @schema = multiplex.schema @steps_queue = [] @runtime_type_at = {}.compare_by_identity @static_type_at = {}.compare_by_identity @finalizers = nil @selected_operation = nil @dataloader = multiplex.context[:dataloader] ||= @schema.dataloader_class.new @resolves_lazies = @schema.resolves_lazies? @input_values = Hash.new do |h, query| h[query] = InputValues.new(query, self) end.compare_by_identity @runtime_directives = nil @schema.directives.each do |name, dir_class| if dir_class.runtime? && name != "include" && name != "skip" @runtime_directives ||= {} @runtime_directives[dir_class.graphql_name] = dir_class end end if @runtime_directives.nil? @uses_runtime_directives = false @runtime_directives = EmptyObjects::EMPTY_HASH else @uses_runtime_directives = true end @lazy_cache = resolves_lazies ? {}.compare_by_identity : nil @authorization = authorization if @authorization @authorizes_cache = Hash.new do |h, query_context| h[query_context] = {}.compare_by_identity end.compare_by_identity end end attr_reader :runtime_directives, :uses_runtime_directives, :finalizer_keys def resolve_type(type, object, query) query.current_trace.begin_resolve_type(type, object, query.context) resolved_type, _ignored_new_value = query.resolve_type(type, object) query.current_trace.end_resolve_type(type, object, query.context, resolved_type) resolved_type end def authorizes?(graphql_definition, query_context) auth_cache = @authorizes_cache[query_context] case (auth_res = auth_cache[graphql_definition]) when nil auth_cache[graphql_definition] = graphql_definition.authorizes?(query_context) else auth_res end end def add_step(step) @dataloader.append_job(step) end attr_reader :authorization, :steps_queue, :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at, :finalizers, :input_values # @return [void] def add_finalizer(query, result_value, key, finalizer) @finalizers ||= {}.compare_by_identity f_for_query = @finalizers[query] ||= {}.compare_by_identity f_for_result = f_for_query[result_value] ||= {}.compare_by_identity if (f = f_for_result[key]) if f.is_a?(Array) f << finalizer else f_for_result[key] = [f, finalizer] end else f_for_result[key] = finalizer end nil end def execute Fiber[:__graphql_current_multiplex] = @multiplex isolated_steps = [[]] trace = @multiplex.current_trace queries = @multiplex.queries multiplex_analyzers = @schema.multiplex_analyzers if @multiplex.max_complexity multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity] end trace.execute_multiplex(multiplex: @multiplex) do trace.begin_analyze_multiplex(@multiplex, multiplex_analyzers) @schema.analysis_engine.analyze_multiplex(@multiplex, multiplex_analyzers) trace.end_analyze_multiplex(@multiplex, multiplex_analyzers) results = [] queries.each do |query| if query.validate && !query.valid? results << { "errors" => query.static_errors.map(&:to_h) } next end root_type = query.root_type if root_type.non_null? root_type = root_type.of_type end root_value = query.root_value if resolves_lazies root_value = schema.sync_lazy(root_value) end trace.execute_query(query: query) do begin_execute(isolated_steps, results, query, root_type, root_value) end end trace.execute_query_lazy(query: nil, multiplex: @multiplex) do while (next_isolated_steps = isolated_steps.shift) next_isolated_steps.each do |step| add_step(step) end @dataloader.run end end queries.each_with_index.map do |query, idx| result = results[idx] fin_result = if (!@finalizers&.key?(query) && query.context.errors.empty?) || !query.valid? result else data = result["data"] data = Finalize.new(query, data, self).run errors = [] query.context.errors.each do |err| if err.respond_to?(:to_h) errors << err.to_h end end res_h = {} if !errors.empty? res_h["errors"] = errors end res_h["data"] = data res_h end query.result_values = fin_result query.result end end ensure Fiber[:__graphql_current_multiplex] = nil end def gather_selections(type_defn, ast_selections, selections_step, query, all_selections, prototype_result, into:) ast_selections.each do |ast_selection| next if !directives_include?(query, ast_selection) case ast_selection when GraphQL::Language::Nodes::Field key = ast_selection.alias || ast_selection.name step = into[key] ||= begin prototype_result[key] = nil FieldResolveStep.new( selections_step: selections_step, key: key, parent_type: type_defn, runner: self, ) end step.append_selection(ast_selection) when GraphQL::Language::Nodes::InlineFragment type_condition = ast_selection.type&.name if type_condition.nil? || type_condition_applies?(query.context, type_defn, type_condition) if uses_runtime_directives && !ast_selection.directives.empty? all_selections << (into = { __node: ast_selection }) all_selections << (prototype_result = {}) end gather_selections(type_defn, ast_selection.selections, selections_step, query, all_selections, prototype_result, into: into) end when GraphQL::Language::Nodes::FragmentSpread fragment_definition = query.fragments[ast_selection.name] type_condition = fragment_definition.type.name if type_condition_applies?(query.context, type_defn, type_condition) if uses_runtime_directives && !ast_selection.directives.empty? all_selections << (into = { __node: ast_selection }) all_selections << (prototype_result = {}) end gather_selections(type_defn, fragment_definition.selections, selections_step, query, all_selections, prototype_result, into: into) end else raise ArgumentError, "Unsupported graphql selection node: #{ast_selection.class} (#{ast_selection.inspect})" end end end def lazy?(object) obj_class = object.class is_lazy = @lazy_cache[obj_class] if is_lazy.nil? is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object) end is_lazy end def type_condition_applies?(context, concrete_type, type_name) if type_name == concrete_type.graphql_name true else abs_t = @schema.get_type(type_name, context) p_types = @schema.possible_types(abs_t, context) c_p_types = @schema.possible_types(concrete_type, context) p_types.any? { |t| c_p_types.include?(t) } end end private def begin_execute(isolated_steps, results, query, root_type, root_value) data = {} selected_operation = query.selected_operation beginning_path = query.path case root_type.kind.name when "OBJECT" if self.authorization && authorizes?(root_type, query.context) query.current_trace.begin_authorized(root_type, root_value, query.context) auth_check = schema.sync_lazy(root_type.authorized?(root_value, query.context)) query.current_trace.end_authorized(root_type, root_value, query.context, auth_check) root_value = if auth_check root_value else begin auth_err = GraphQL::UnauthorizedError.new(object: root_value, type: root_type, context: query.context) new_val = schema.unauthorized_object(auth_err) if new_val auth_check = true end new_val rescue GraphQL::ExecutionError => ex_err # The old runtime didn't add path and ast_nodes to this ex_err.path = beginning_path query.context.add_error(ex_err) nil end end if !auth_check results << {} return end end results << { "data" => data } objects = [root_value] query.current_trace.objects(root_type, objects, query.context) if query.query? isolated_steps[0] << SelectionsStep.new( parent_type: root_type, selections: query.selected_operation.selections, objects: objects, results: [data], path: beginning_path, runner: self, query: query, ) elsif query.mutation? fields = {} all_selections = [fields, (prototype_result = {})] gather_selections(root_type, selected_operation.selections, nil, query, all_selections, prototype_result, into: fields) if all_selections.length > 2 # TODO DRY with SelectionsStep with directive handling raise "Directives on root mutation type not implemented yet" end fields.each_value do |field_resolve_step| isolated_steps << [SelectionsStep.new( clobber: false, # `data` is being shared among several selections steps parent_type: root_type, selections: field_resolve_step.ast_nodes || Array(field_resolve_step.ast_node), objects: objects, results: [data], path: beginning_path, runner: self, query: query, )] end elsif query.subscription? if !query.subscription_update? schema.subscriptions.initialize_subscriptions(query) add_finalizer(query, data, nil, schema.subscriptions.finalizer) end isolated_steps[0] << SelectionsStep.new( parent_type: root_type, selections: selected_operation.selections, objects: objects, results: [data], path: beginning_path, runner: self, query: query, ) else raise ArgumentError, "Unknown operation type (not query, mutation or subscription): #{query.query_string}" end when "UNION", "INTERFACE" resolved_type = resolve_type(root_type, root_value, query) if resolves_lazies resolved_type = schema.sync_lazy(resolved_type) end objects = [root_value] query.current_trace.objects(resolved_type, objects, query.context) runtime_type_at[data] = resolved_type results << { "data" => data } isolated_steps[0] << SelectionsStep.new( parent_type: resolved_type, selections: query.selected_operation.selections, objects: objects, results: [data], path: beginning_path, runner: self, query: query, ) when "LIST" inner_type = root_type.unwrap case inner_type.kind.name when "SCALAR", "ENUM" results << run_isolated_scalar(root_type, query) else list_result = Array.new(root_value.size) { Hash.new.compare_by_identity } results << { "data" => list_result } isolated_steps[0] << SelectionsStep.new( parent_type: inner_type, selections: query.selected_operation.selections, objects: root_value, results: list_result, path: beginning_path, runner: self, query: query, ) end when "SCALAR", "ENUM" results << run_isolated_scalar(root_type, query) else raise "Unhandled root type kind: #{root_type.kind.name.inspect}" end @static_type_at[data] = root_type end def directives_include?(query, ast_selection) if ast_selection.directives.any? { |dir_node| if dir_node.name == "skip" skip_args = @input_values[query].argument_values(GraphQL::Schema::Directive::Skip, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop skip_args[:if] == true elsif dir_node.name == "include" include_args = @input_values[query].argument_values(GraphQL::Schema::Directive::Include, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop include_args[:if] == false end } false else true end end def run_isolated_scalar(type, partial) value = partial.root_value dummy_path = partial.path.dup key = dummy_path.pop is_from_array = key.is_a?(Integer) if lazy?(value) value = @schema.sync_lazy(value) end selections = partial.ast_nodes dummy_ss = SelectionsStep.new( parent_type: nil, selections: selections, objects: nil, results: nil, path: dummy_path, runner: self, query: partial, ) dummy_frs = FieldResolveStep.new( selections_step: dummy_ss, key: key, parent_type: nil, runner: self, ) dummy_frs.static_type = type selections.each { |s| dummy_frs.append_selection(s) } result = is_from_array ? [] : {} dummy_frs.finish_leaf_result(result, key, value, type, partial.context) { "data" => result[key] } end end end end graphql-2.6.0/lib/graphql/execution/next.rb0000644000004100000410000000613615173430257020734 0ustar www-datawww-data# frozen_string_literal: true require "graphql/execution/prepare_object_step" require "graphql/execution/input_values" require "graphql/execution/field_resolve_step" require "graphql/execution/finalize" require "graphql/execution/load_argument_step" require "graphql/execution/runner" require "graphql/execution/selections_step" module GraphQL module Execution module Finalizer attr_accessor :path def finalize_graphql_result(query, result_data, result_key) raise RequiredImplementationMissingError end end module HaltExecution end module PostProcessor def after_resolve(field_results) raise RequiredImplementationMissingError, "#{self.class}#after_resolve should handle `field_results` and return a new value to use" end end module Next module SchemaExtension def execute_next(query_str = nil, context: nil, document: nil, operation_name: nil, variables: nil, root_value: nil, validate: true, visibility_profile: nil) multiplex_context = if context { backtrace: context[:backtrace], tracers: context[:tracers], trace: context[:trace], dataloader: context[:dataloader], trace_mode: context[:trace_mode], } else {} end query_opts = { query: query_str, document: document, context: context, validate: validate, variables: variables, root_value: root_value, operation_name: operation_name, visibility_profile: visibility_profile, } m_results = multiplex_next([query_opts], context: multiplex_context, max_complexity: nil) m_results[0] end def multiplex_next(query_options, context: {}, max_complexity: self.max_complexity) Next.run_all(self, query_options, context: context, max_complexity: max_complexity) end def execution_next_options @execution_next_options || find_inherited_value(:execution_next_options, EmptyObjects::EMPTY_HASH) end attr_writer :execution_next_options end def self.use(schema, authorization: true) schema.extend(SchemaExtension) schema.execution_next_options = { authorization: authorization } end def self.run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity) queries = query_options.map do |opts| query = case opts when Hash schema.query_class.new(schema, nil, **opts) when GraphQL::Query, GraphQL::Query::Partial opts else raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})" end query.context[:__graphql_execute_next] = true query end multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity) runner = Runner.new(multiplex, **schema.execution_next_options) runner.execute end end end end graphql-2.6.0/lib/graphql/execution/selections_step.rb0000644000004100000410000000642515173430257023162 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution class SelectionsStep def initialize(parent_type:, selections:, objects:, results:, runner:, query:, path:, clobber: true) @path = path @parent_type = parent_type @selections = selections @runner = runner @objects = objects @results = results @query = query @graphql_objects = nil @all_selections = nil @clobber = clobber end attr_reader :path, :query, :objects, :results def graphql_objects @graphql_objects ||= @objects.map do |obj| @parent_type.scoped_new(obj, @query.context) end end def call @all_selections = [{}, (prototype_result = {})] @runner.gather_selections(@parent_type, @selections, self, self.query, @all_selections, @all_selections[1], into: @all_selections[0]) continue_selections = [] i = 0 l = @all_selections.length while i < l grouped_selections = @all_selections[i] selections_prototype_result = @all_selections[i + 1] if (directives_owner = grouped_selections.delete(:__node)) directives = directives_owner.directives continue_execution = true directives.each do |dir_node| dir_defn = @runner.runtime_directives[dir_node.name] if dir_defn # not present for `skip` or `include` dir_args = @runner.input_values[query].argument_values(dir_defn, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop result = case directives_owner when Language::Nodes::FragmentSpread dir_defn.resolve_fragment_spread(directives_owner, @parent_type, @objects, dir_args, self.query.context) when Language::Nodes::InlineFragment dir_defn.resolve_inline_fragment(directives_owner, @parent_type, @objects, dir_args, self.query.context) else raise ArgumentError, "Unhandled directive owner (#{directives_owner.class}): #{directives_owner.inspect}" end if result.is_a?(Finalizer) result.path = path @results.each do |r| @runner.add_finalizer(@query, r, nil, result) end if result.is_a?(HaltExecution) continue_execution = false break end end if continue_execution prototype_result.merge!(selections_prototype_result) grouped_selections.each_value { |v| continue_selections << v } end else grouped_selections.each_value { |v| continue_selections << v } end end else grouped_selections.each_value { |v| continue_selections << v } end if @clobber i2 = 0 l2 = @results.length while i2 < l2 @results[i2].replace(prototype_result) i2 += 1 end end continue_selections.each do |frs| @runner.add_step(frs) end i += 2 end end end end end graphql-2.6.0/lib/graphql/execution/field_resolve_step.rb0000644000004100000410000005524215173430257023635 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution class FieldResolveStep def initialize(parent_type:, runner:, key:, selections_step:) @selections_step = selections_step @key = key @parent_type = parent_type @ast_node = @ast_nodes = nil @runner = runner @field_definition = nil @arguments = nil @field_results = nil @path = nil @enqueued_authorization = false @all_next_objects = nil @all_next_results = nil @static_type = nil @next_selections = nil @results = nil @finish_extension_idx = nil @was_scoped = nil @pending_steps = nil @post_processors = @directive_finalizers = nil end attr_reader :ast_node, :key, :parent_type, :selections_step, :runner, :field_definition, :object_is_authorized, :was_scoped, :field_results attr_accessor :pending_steps, :arguments, :static_type def path @path ||= [*@selections_step.path, @key].freeze end def ast_nodes @ast_nodes ||= [@ast_node] end def append_selection(ast_node) if @ast_node.nil? @ast_node = ast_node elsif @ast_nodes.nil? @ast_nodes = [@ast_node, ast_node] else @ast_nodes << ast_node end nil end def value query = @selections_step.query query.current_trace.begin_execute_field(@field_definition, @arguments, @field_results, query) sync(@field_results) query.current_trace.end_execute_field(@field_definition, @arguments, @field_results, query, @field_results) @runner.add_step(self) true end def sync(lazy) if lazy.is_a?(Array) lazy.map! { |l| sync(l)} else @runner.schema.sync_lazy(lazy) end rescue GraphQL::UnauthorizedError => auth_err @runner.schema.unauthorized_object(auth_err) rescue GraphQL::ExecutionError => err err rescue StandardError => stderr begin @selections_step.query.handle_or_reraise(stderr) rescue GraphQL::ExecutionError => ex_err ex_err end end def call if @enqueued_authorization enqueue_next_steps elsif @finish_extension_idx finish_extensions elsif @field_results build_results elsif @arguments execute_field else build_arguments end rescue StandardError => err if @field_definition && !err.message.start_with?("Resolving ") # TODO remove this check ^^^^^^ when NullDataloader isn't recursive raise err, "Resolving #{@field_definition.path}: #{err.message}", err.backtrace else raise end end def add_graphql_error(err) err.path = path err.ast_nodes = ast_nodes @selections_step.query.context.add_error(err) err end def build_arguments query = @selections_step.query field_name = @ast_node.name @field_definition = query.types.field(@parent_type, field_name) || raise("Invariant: no field found for #{@parent_type.to_type_signature}.#{ast_node.name}") arguments = @runner.input_values[query].argument_values(@field_definition, @ast_node.arguments, self) # rubocop:disable Development/ContextIsPassedCop @arguments ||= arguments # may have already been set to an error if (@pending_steps.nil? || @pending_steps.size == 0) && @field_results.nil? # Make sure the arguments flow didn't already call through execute_field end end def execute_field objects = @selections_step.objects @results = @selections_step.results # TODO not as good because only one error? if @arguments.is_a?(GraphQL::RuntimeError) @field_results = Array.new(objects.size, @arguments) build_results return end query = @selections_step.query ctx = query.context if (v = @field_definition.validators).any? # rubocop:disable Development/NoneWithoutBlockCop begin Schema::Validator.validate!(v, nil, ctx, @arguments) rescue GraphQL::RuntimeError => err @field_results = Array.new(objects.size, err) build_results return end end @field_definition.extras.each do |extra| case extra when :lookahead if @arguments.frozen? @arguments = @arguments.dup end @arguments[:lookahead] = Execution::Lookahead.new( query: query, ast_nodes: ast_nodes, field: @field_definition, ) when :ast_node if @arguments.frozen? @arguments = @arguments.dup end @arguments[:ast_node] = ast_node else raise ArgumentError, "This `extra` isn't supported yet: #{extra.inspect}. Open an issue on GraphQL-Ruby to add compatibility for it." end end if @field_definition.dynamic_introspection # TODO break this backwards compat somehow? objects = @selections_step.graphql_objects end if @runner.authorization && @runner.authorizes?(@field_definition, ctx) authorized_objects = [] authorized_results = [] l = objects.size i = 0 while i < l o = objects[i] if @field_definition.authorized?(o, @arguments, ctx) authorized_results << @results[i] authorized_objects << o else begin err = GraphQL::UnauthorizedFieldError.new(object: o, type: @parent_type, context: ctx, field: @field_definition) authorized_objects << query.schema.unauthorized_object(err) authorized_results << @results[i] rescue GraphQL::ExecutionError => exec_err add_graphql_error(exec_err) end end i += 1 end if authorized_objects.size == 0 return end @results = authorized_results else authorized_objects = objects end if @parent_type.default_relay? && authorized_objects.all? { |o| o.respond_to?(:was_authorized_by_scope_items?) && o.was_authorized_by_scope_items? } @was_scoped = true end query.current_trace.begin_execute_field(@field_definition, @arguments, authorized_objects, query) if @runner.uses_runtime_directives if @ast_nodes.nil? || @ast_nodes.size == 1 directives = if !@ast_node.directives.empty? @ast_node.directives else nil end else directives = nil @ast_nodes.each do |n| if (d = n.directives).any? # rubocop:disable Development/NoneWithoutBlockCop directives ||= [] directives.concat(d) end end end if directives directives.each do |dir_node| if (dir_defn = @runner.runtime_directives[dir_node.name]) # TODO: `coerce_arguments` modifies self, assuming it's field arguments. Extract to pure function for use # here and with fragments. dir_args = @runner.input_values[query].argument_values(dir_defn, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop result = dir_defn.resolve_field(ast_nodes, @parent_type, field_definition, authorized_objects, dir_args, ctx) if !result.nil? if result.is_a?(Finalizer) result.path = path @directive_finalizers ||= [] @directive_finalizers << result end if result.is_a?(PostProcessor) @post_processors ||= [] @post_processors << result end if result.is_a?(HaltExecution) @directive_finalizers&.each { |f| @selections_step.results.each { |r| @runner.add_finalizer(query, r, key, f) } } return end end end end end end has_extensions = @field_definition.extensions.size > 0 if has_extensions @extended = GraphQL::Schema::Field::ExtendedState.new(@arguments, authorized_objects) @field_results = @field_definition.run_next_extensions_before_resolve(authorized_objects, @arguments, ctx, @extended) do |objs, args| if (added_extras = @extended.added_extras) args = args.dup added_extras.each { |e| args.delete(e) } end resolve_batch(objs, ctx, args) end @finish_extension_idx = 0 else @field_results = resolve_batch(authorized_objects, ctx, @arguments) end query.current_trace.end_execute_field(@field_definition, @arguments, authorized_objects, query, @field_results) if any_lazy_results? @runner.dataloader.lazy_at_depth(path.size, self) elsif has_extensions finish_extensions elsif @pending_steps.nil? || @pending_steps.empty? build_results end end def any_lazy_results? lazies = false if @runner.resolves_lazies # TODO extract this @field_results.each do |field_result| if @runner.lazy?(field_result) lazies = true break elsif field_result.is_a?(Array) field_result.each do |inner_fr| if @runner.lazy?(inner_fr) break lazies = true end end if lazies break end end end end lazies end def finish_extensions ctx = @selections_step.query.context memos = @extended.memos || EmptyObjects::EMPTY_HASH while ext = @field_definition.extensions[@finish_extension_idx] # These two are hardcoded here because of how they need to interact with runtime metadata. # It would probably be better case ext when Schema::Field::ConnectionExtension conns = ctx.schema.connections @field_results.map!.each_with_index do |value, idx| object = @extended.object[idx] conn = conns.populate_connection(@field_definition, object, value, @arguments, ctx) if conn conn.was_authorized_by_scope_items = @was_scoped end conn end when Schema::Field::ScopeExtension if @was_scoped.nil? if (rt = @field_definition.type.unwrap).respond_to?(:scope_items) @was_scoped = true @field_results.map! { |v| v.nil? ? v : rt.scope_items(v, ctx) } else @was_scoped = false end end else memo = memos[@finish_extension_idx] @field_results = ext.after_resolve(objects: @extended.object, arguments: @extended.arguments, context: ctx, values: @field_results, memo: memo) # rubocop:disable Development/ContextIsPassedCop end @finish_extension_idx += 1 if any_lazy_results? @runner.dataloader.lazy_at_depth(path.size, self) return end end @finish_extension_idx = nil build_results end def build_results return_type = @field_definition.type return_result_type = return_type.unwrap @post_processors&.each do |post_processor| @field_results = post_processor.after_resolve(@field_results) end if return_result_type.kind.composite? @static_type = return_result_type if @ast_nodes @next_selections = [] @ast_nodes.each do |ast_node| @next_selections.concat(ast_node.selections) end else @next_selections = @ast_node.selections end @all_next_objects = [] @all_next_results = [] is_list = return_type.list? is_non_null = return_type.non_null? i = 0 s = @results.size while i < s do result_h = @results[i] result = @field_results[i] i += 1 build_graphql_result(result_h, @key, result, return_type, is_non_null, is_list, false) end @enqueued_authorization = true if @pending_steps.nil? || @pending_steps.size == 0 enqueue_next_steps else # Do nothing -- it will enqueue itself later end else ctx = @selections_step.query.context i = 0 s = @results.size while i < s do result_h = @results[i] field_result = @field_results[i] i += 1 finish_leaf_result(result_h, @key, field_result, return_type, ctx) end end end def finish_leaf_result(result_h, key, field_result, return_type, ctx) final_field_result = if field_result.nil? if return_type.non_null? add_non_null_error(false) else nil end elsif field_result.is_a?(Finalizer) if field_result.is_a?(GraphQL::RuntimeError) add_graphql_error(field_result) else field_result.path = path @runner.add_finalizer(ctx.query, result_h, key, field_result) end else # TODO `nil`s in [T!] types aren't handled return_type.coerce_result(field_result, ctx) end @directive_finalizers&.each { |f| @runner.add_finalizer(ctx.query, result_h, key, f) } result_h[@key] = final_field_result end def enqueue_next_steps if !@all_next_results.empty? @all_next_objects.compact! query = @selections_step.query ctx = query.context if @static_type.kind.abstract? next_objects_by_type = Hash.new { |h, obj_t| h[obj_t] = [] }.compare_by_identity next_results_by_type = Hash.new { |h, obj_t| h[obj_t] = [] }.compare_by_identity @all_next_objects.each_with_index do |next_object, i| result = @all_next_results[i] if (object_type = @runner.runtime_type_at[result]) # OK else object_type = @runner.resolve_type(@static_type, next_object, query) @runner.runtime_type_at[result] = object_type end next_objects_by_type[object_type] << next_object next_results_by_type[object_type] << result end next_objects_by_type.each do |obj_type, next_objects| query.current_trace.objects(obj_type, next_objects, ctx) @runner.add_step(SelectionsStep.new( path: path, parent_type: obj_type, selections: @next_selections, objects: next_objects, results: next_results_by_type[obj_type], runner: @runner, query: query, )) end else query.current_trace.objects(@static_type, @all_next_objects, ctx) @runner.add_step(SelectionsStep.new( path: path, parent_type: @static_type, selections: @next_selections, objects: @all_next_objects, results: @all_next_results, runner: @runner, query: query, )) end end end def authorized_finished(step) @pending_steps.delete(step) if @enqueued_authorization && @pending_steps.size == 0 @runner.add_step(self) end end def add_non_null_error(is_from_array) err = InvalidNullError.new(@parent_type, @field_definition, ast_nodes, is_from_array: is_from_array, path: path) @runner.schema.type_error(err, @selections_step.query.context) end private def build_graphql_result(graphql_result, key, field_result, return_type, is_nn, is_list, is_from_array) # rubocop:disable Metrics/ParameterLists if field_result.nil? if is_nn graphql_result[key] = add_non_null_error(is_from_array) else graphql_result[key] = nil end elsif field_result.is_a?(Finalizer) graphql_result[key] = if field_result.is_a?(GraphQL::RuntimeError) add_graphql_error(field_result) else field_result.path = path @runner.add_finalizer(@selections_step.query, graphql_result, key, field_result) field_result end elsif is_list if is_nn return_type = return_type.of_type end inner_type = return_type.of_type inner_type_nn = inner_type.non_null? inner_type_l = inner_type.list? list_result = graphql_result[key] = [] @directive_finalizers&.each { |f| @runner.add_finalizer(@selections_step.query, list_result, nil, f) } i = 0 s = field_result.size while i < s inner_f_r = field_result[i] build_graphql_result(list_result, i, inner_f_r, inner_type, inner_type_nn, inner_type_l, true) i += 1 end elsif @runner.resolves_lazies || ( @runner.authorization && ( @static_type.kind.object? ? @runner.authorizes?(@static_type, @selections_step.query.context) : ( (runtime_type = (@runner.runtime_type_at[graphql_result] = @runner.resolve_type(@static_type, field_result, @selections_step.query))) && @runner.authorizes?(runtime_type, @selections_step.query.context) ))) obj_step = PrepareObjectStep.new( object: field_result, runner: @runner, field_resolve_step: self, graphql_result: graphql_result, next_objects: @all_next_objects, next_results: @all_next_results, is_non_null: is_nn, key: key, is_from_array: is_from_array, ) ps = @pending_steps ||= [] ps << obj_step @runner.add_step(obj_step) else next_result_h = {}.compare_by_identity @all_next_results << next_result_h @directive_finalizers&.each { |f| @runner.add_finalizer(@selections_step.query, next_result_h, nil, f) } @all_next_objects << field_result @runner.static_type_at[next_result_h] = @static_type graphql_result[key] = next_result_h end end def resolve_batch(objects, context, args_hash) method_receiver = @field_definition.dynamic_introspection ? @field_definition.owner : @parent_type case @field_definition.execution_mode when :resolve_batch begin method_receiver.public_send(@field_definition.execution_mode_key, objects, context, **args_hash) rescue GraphQL::ExecutionError => exec_err Array.new(objects.size, exec_err) end when :resolve_static result = begin method_receiver.public_send(@field_definition.execution_mode_key, context, **args_hash) rescue GraphQL::ExecutionError => err err end Array.new(objects.size, result) when :resolve_each objects.map do |o| method_receiver.public_send(@field_definition.execution_mode_key, o, context, **args_hash) rescue GraphQL::ExecutionError => err err end when :hash_key k = @field_definition.execution_mode_key objects.map { |o| o[k] } when :direct_send m = @field_definition.execution_mode_key objects.map do |o| o.public_send(m, **args_hash) rescue GraphQL::ExecutionError => err err rescue StandardError => stderr begin @selections_step.query.handle_or_reraise(stderr) rescue GraphQL::ExecutionError => ex_err ex_err end end when :dig objects.map { |o| o.dig(*@field_definition.execution_mode_key) } when :dataload if (k = @field_definition.execution_mode_key).is_a?(Class) context.dataload_all(k, objects) elsif (source_class = k[:with]) if (batch_args = k[:by]) context.dataload_all(source_class, *batch_args, objects) else context.dataload_all(source_class, objects) end elsif (model = k[:model]) value_method = k[:using] values = objects.map(&value_method) context.dataload_all_records(model, values, find_by: k[:find_by]) elsif (assoc = k[:association]) if assoc == true assoc = @field_definition.original_name end context.dataload_all_associations(objects, assoc, scope: k[:scope]) else raise ArgumentError, "Unexpected `dataload: ...` configuration: #{k.inspect}" end when :resolver_class results = Array.new(objects.size, nil) ps = @pending_steps ||= [] objects.each_with_index do |o, idx| resolver_inst = @field_definition.resolver.new(object: o, context: context, field: @field_definition) ps << resolver_inst resolver_inst.field_resolve_step = self resolver_inst.prepared_arguments = args_hash resolver_inst.exec_result = results resolver_inst.exec_index = idx @runner.add_step(resolver_inst) resolver_inst end results when :resolve_legacy_instance_method @selections_step.graphql_objects.map do |obj_inst| if @field_definition.dynamic_introspection obj_inst = @owner.wrap(obj_inst, context) end obj_inst.public_send(@field_definition.execution_mode_key, **args_hash) rescue GraphQL::ExecutionError => exec_err exec_err end else raise "Batching execution for #{path} not implemented (execution_mode: #{@execution_mode.inspect}); provide `resolve_static:`, `resolve_batch:`, `hash_key:`, `method:`, or use a compatibility plug-in" end end end end end graphql-2.6.0/lib/graphql/execution/multiplex.rb0000644000004100000410000000315615173430257022000 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution # Execute multiple queries under the same multiplex "umbrella". # They can share a batching context and reduce redundant database hits. # # The flow is: # # - Multiplex instrumentation setup # - Query instrumentation setup # - Analyze the multiplex + each query # - Begin each query # - Resolve lazy values, breadth-first across all queries # - Finish each query (eg, get errors) # - Query instrumentation teardown # - Multiplex instrumentation teardown # # If one query raises an application error, all queries will be in undefined states. # # Validation errors and {GraphQL::ExecutionError}s are handled in isolation: # one of these errors in one query will not affect the other queries. # # @see {Schema#multiplex} for public API # @api private class Multiplex include Tracing::Traceable attr_reader :context, :queries, :schema, :max_complexity, :dataloader, :current_trace def initialize(schema:, queries:, context:, max_complexity:) @schema = schema @queries = queries @queries.each { |q| q.multiplex = self } @context = context @dataloader = @context[:dataloader] ||= @schema.dataloader_class.new @tracers = schema.tracers + (context[:tracers] || EmptyObjects::EMPTY_ARRAY) @max_complexity = max_complexity @current_trace = context[:trace] ||= schema.new_trace(multiplex: self) @logger = nil end def logger @logger ||= @schema.logger_for(context) end end end end graphql-2.6.0/lib/graphql/execution/input_values.rb0000644000004100000410000002245415173430257022475 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution class InputValues def initialize(query, runner) @query = query @runner = runner @variable_values = nil end def variable_values @variable_values ||= begin variable_nodes = @query.selected_operation.variables if variable_nodes.empty? EmptyObjects::EMPTY_HASH else raw_values = @query.provided_variables values = {} variable_nodes.each do |var_node| var_ast_value = if raw_values.key?(var_node.name) raw_values[var_node.name] elsif raw_values.key?(sym_name = var_node.name.to_sym) raw_values[sym_name] elsif !var_node.default_value.nil? var_node.default_value else next end var_type = @runner.schema.type_from_ast(var_node.type, context: @query.context) values[var_node.name] = variable_value(var_ast_value, var_type) end values end end end def argument_values(owner_defn, argument_nodes, field_resolve_step) arg_defns = @query.types.arguments(owner_defn) argument_values = {} arg_defns.each do |argument_definition| arg_ruby_key = argument_definition.keyword arg_graphql_key = argument_definition.graphql_name arg_node = argument_nodes.find { |a| a.name == arg_graphql_key } if arg_node.nil? || (arg_node.value.is_a?(Language::Nodes::VariableIdentifier) && !variable_values.key?(arg_node.value.name)) if argument_definition.default_value? arg_value = value_from_ast(argument_definition.default_value, argument_definition.type) argument_value(argument_values, arg_ruby_key, argument_definition, arg_value, nil, field_resolve_step) end else arg_value = value_from_ast(arg_node.value, argument_definition.type) argument_value(argument_values, arg_ruby_key, argument_definition, arg_value, nil, field_resolve_step) end end argument_values rescue GraphQL::ExecutionError => exec_err exec_err end private def variable_value(value, type) if type.non_null? type = type.of_type end if value.is_a?(Language::Nodes::Enum) value = value.name end if value.nil? nil elsif type.list? inner_type = type.of_type if value.is_a?(Array) value.map { |v| variable_value(v, inner_type) }.freeze else [variable_value(value, inner_type)].freeze end elsif type.kind.input_object? coerced_obj = {} @query.types.arguments(type).each do |arg| arg_key = arg.keyword if value.key?(arg.graphql_name) arg_value = value[arg.graphql_name] elsif value.key?(sym_name = arg.graphql_name.to_sym) arg_value = value[sym_name] elsif arg.default_value? coerced_obj[arg_key] = arg.default_value next else next end coerced_obj[arg_key] = variable_value(arg_value, arg.type) end coerced_obj elsif type.kind.leaf? type.coerce_input(value, @query.context) else raise GraphQL::Error, "Unexpected input type: #{type.graphql_name}." end end def argument_value(argument_values, argument_key, argument_definition, arg_value, override_type, field_resolve_step) treat_as_type = override_type || argument_definition.type if treat_as_type.non_null? treat_as_type = treat_as_type.of_type end if treat_as_type.kind.list? && !arg_value.nil? inner_t = treat_as_type.unwrap arg_value = if arg_value.is_a?(Array) values = Array.new(arg_value.size) arg_value.each_with_index { |inner_v, idx| argument_value(values, idx, argument_definition, inner_v, inner_t, field_resolve_step)} values else values = [nil] argument_value(values, 0, argument_definition, arg_value, inner_t, field_resolve_step) values end end if arg_value && treat_as_type.kind.input_object? arg_defns = @query.types.arguments(treat_as_type) new_arg_value = {} arg_defns.each do |inner_arg_defn| inner_arg_key = inner_arg_defn.keyword if arg_value.is_a?(Hash) if arg_value.key?(inner_arg_key) inner_arg_value = arg_value[inner_arg_key] argument_value(new_arg_value, inner_arg_key, inner_arg_defn, inner_arg_value, nil, field_resolve_step) end else inner_arg_name = inner_arg_defn.graphql_name inner_arg_value = arg_value.arguments.find { |a| a.name == inner_arg_name } # rubocop:disable Development/ContextIsPassedCop if inner_arg_value argument_value(new_arg_value, inner_arg_key, inner_arg_defn, inner_arg_value, nil, field_resolve_step) end end end arg_value = treat_as_type.new(nil, ruby_kwargs: new_arg_value, context: @query.context, defaults_used: nil) end if override_type.nil? # only on root arguments, not list elements arg_value = begin argument_definition.prepare_value(nil, arg_value, context: @query.context) rescue StandardError => err @runner.schema.handle_or_reraise(@query.context, err) end end if field_resolve_step && arg_value && override_type.nil? && argument_definition.loads field_defn = field_resolve_step.field_definition load_receiver = if (r = field_defn.resolver) r.new(field: field_defn, context: @query.context, object: nil) else field_defn end ps = field_resolve_step.pending_steps ||= [] if argument_definition.type.list? results = Array.new(arg_value.size, nil) argument_values[argument_key] = results arg_value.each_with_index do |inner_v, idx| loads_step = LoadArgumentStep.new( field_resolve_step: field_resolve_step, load_receiver: load_receiver, argument_value: inner_v, argument_definition: argument_definition, arguments: results, argument_key: idx, ) ps.push(loads_step) @runner.add_step(loads_step) end else loads_step = LoadArgumentStep.new( field_resolve_step: field_resolve_step, load_receiver: load_receiver, argument_value: arg_value, argument_definition: argument_definition, arguments: argument_values, argument_key: argument_key, ) ps.push(loads_step) @runner.add_step(loads_step) end else argument_values[argument_key] = arg_value end nil end def value_from_ast(value_node, type) if type.non_null? type = type.of_type end if value_node.nil? nil elsif value_node.is_a?(GraphQL::Language::Nodes::VariableIdentifier) variable_values[value_node.name] elsif value_node.is_a?(GraphQL::Language::Nodes::NullValue) nil elsif type.list? inner_type = type.of_type if value_node.is_a?(Array) coerced_items = value_node.map do |inner_value_node| value_from_ast(inner_value_node, inner_type) end coerced_items.freeze else item_value = value_from_ast(value_node, inner_type) [item_value].freeze end elsif type.kind.input_object? coerced_obj = {} arg_nodes_by_name = value_node.arguments.each_with_object({}) do |arg_node, acc| # rubocop:disable Development/ContextIsPassedCop acc[arg_node.name] = arg_node end @query.types.arguments(type).each do |arg| arg_node = arg_nodes_by_name[arg.graphql_name] arg_key = arg.keyword if arg_node.nil? || (arg_node.value.is_a?(Language::Nodes::VariableIdentifier) && !variable_values.key?(arg_node.value.name)) if arg.default_value? coerced_obj[arg_key] = arg.default_value end next end arg_value = value_from_ast(arg_node.value, arg.type) coerced_obj[arg_key] = arg_value end coerced_obj elsif type.kind.leaf? if type.kind.enum? if value_node.is_a?(GraphQL::Language::Nodes::Enum) value_node = value_node.name end end begin type.coerce_input(value_node, @query.context) rescue GraphQL::UnauthorizedEnumValueError => enum_err @runner.schema.unauthorized_object(enum_err) end else raise "Unexpected input type: #{type.to_type_signature}." end end end end end graphql-2.6.0/lib/graphql/execution/interpreter/0000755000004100000410000000000015173430257021766 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/execution/interpreter/arguments.rb0000644000004100000410000000643015173430257024323 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution class Interpreter # A wrapper for argument hashes in GraphQL queries. # # This object is immutable so that the runtime code can be sure that # modifications don't leak from one use to another # # @see GraphQL::Query#arguments_for to get access to these objects. class Arguments extend Forwardable include GraphQL::Dig # The Ruby-style arguments hash, ready for a resolver. # This hash is the one used at runtime. # # @return [Hash] attr_reader :keyword_arguments # @param argument_values [nil, Hash{Symbol => ArgumentValue}] # @param keyword_arguments [nil, Hash{Symbol => Object}] def initialize(keyword_arguments: nil, argument_values:) @empty = argument_values.nil? || argument_values.empty? # This is only present when `extras` have been merged in: if keyword_arguments # This is a little crazy. We expect the `:argument_details` extra to _include extras_, # but the object isn't created until _after_ extras are put together. # So, we have to use a special flag here to say, "at the last minute, add yourself to the keyword args." # # Otherwise: # - We can't access the final Arguments instance _while_ we're preparing extras # - After we _can_ access it, it's frozen, so we can't add anything. # # So, this flag gives us a chance to sneak it in before freezing, _and_ while we have access # to the new Arguments instance itself. if keyword_arguments[:argument_details] == :__arguments_add_self keyword_arguments[:argument_details] = self end @keyword_arguments = keyword_arguments.freeze elsif !@empty @keyword_arguments = {} argument_values.each do |name, arg_val| @keyword_arguments[name] = arg_val.value end @keyword_arguments.freeze else @keyword_arguments = NO_ARGS end @argument_values = argument_values ? argument_values.freeze : NO_ARGS freeze end # @return [Hash{Symbol => ArgumentValue}] attr_reader :argument_values def empty? @empty end def_delegators :keyword_arguments, :key?, :[], :fetch, :keys, :each, :values, :size, :to_h def_delegators :argument_values, :each_value def inspect "#<#{self.class} @keyword_arguments=#{keyword_arguments.inspect}>" end # Create a new arguments instance which includes these extras. # # This is called by the runtime to implement field `extras: [...]` # # @param extra_args [Hash Object>] # @return [Interpreter::Arguments] # @api private def merge_extras(extra_args) self.class.new( argument_values: argument_values, keyword_arguments: keyword_arguments.merge(extra_args) ) end NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH EMPTY = self.new(argument_values: nil, keyword_arguments: NO_ARGS).freeze end end end end graphql-2.6.0/lib/graphql/execution/interpreter/argument_value.rb0000644000004100000410000000206115173430257025330 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution class Interpreter # A container for metadata regarding arguments present in a GraphQL query. # @see Interpreter::Arguments#argument_values for a hash of these objects. class ArgumentValue def initialize(definition:, value:, original_value:, default_used:) @definition = definition @value = value @original_value = original_value @default_used = default_used end # @return [Object] The Ruby-ready value for this Argument attr_reader :value # @return [Object] The value of this argument _before_ `prepare` is applied. attr_reader :original_value # @return [GraphQL::Schema::Argument] The definition instance for this argument attr_reader :definition # @return [Boolean] `true` if the schema-defined `default_value:` was applied in this case. (No client-provided value was present.) def default_used? @default_used end end end end end graphql-2.6.0/lib/graphql/execution/interpreter/runtime.rb0000644000004100000410000013515715173430257024012 0ustar www-datawww-data# frozen_string_literal: true require "graphql/execution/interpreter/runtime/graphql_result" module GraphQL module Execution class Interpreter # I think it would be even better if we could somehow make # `continue_field` not recursive. "Trampolining" it somehow. # # @api private class Runtime class CurrentState def initialize @current_field = nil @current_arguments = nil @current_result_name = nil @current_result = nil @was_authorized_by_scope_items = nil end def current_object @current_result.graphql_application_value end attr_accessor :current_result, :current_result_name, :current_arguments, :current_field, :was_authorized_by_scope_items end # @return [GraphQL::Query] attr_reader :query # @return [Class] attr_reader :schema # @return [GraphQL::Query::Context] attr_reader :context def initialize(query:) @query = query @current_trace = query.current_trace @dataloader = query.multiplex.dataloader @schema = query.schema @context = query.context @response = nil # Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve` @runtime_directive_names = [] noop_resolve_owner = GraphQL::Schema::Directive.singleton_class @schema_directives = schema.directives @schema_directives.each do |name, dir_defn| if dir_defn.method(:resolve).owner != noop_resolve_owner @runtime_directive_names << name end end # { Class => Boolean } @lazy_cache = {}.compare_by_identity end def final_result @response.respond_to?(:graphql_result_data) ? @response.graphql_result_data : @response end def inspect "#<#{self.class.name} response=#{@response.inspect}>" end # @return [void] def run_eager root_type = query.root_type case query when GraphQL::Query ast_node = query.selected_operation selections = ast_node.selections object = query.root_value is_eager = ast_node.operation_type == "mutation" base_path = nil when GraphQL::Query::Partial ast_node = query.ast_nodes.first selections = query.ast_nodes.map(&:selections).inject(&:+) object = query.object is_eager = false base_path = query.path else raise ArgumentError, "Unexpected Runnable, can't execute: #{query.class} (#{query.inspect})" end object = schema.sync_lazy(object) # TODO test query partial with lazy root object runtime_state = get_current_runtime_state case root_type.kind.name when "OBJECT" object_proxy = root_type.wrap(object, context) object_proxy = schema.sync_lazy(object_proxy) if object_proxy.nil? @response = nil else @response = GraphQLResultHash.new(nil, root_type, object_proxy, nil, false, selections, is_eager, ast_node, nil, nil) @response.base_path = base_path runtime_state.current_result = @response call_method_on_directives(:resolve, object, ast_node.directives) do each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys| @response.ordered_result_keys ||= ordered_result_keys if is_selection_array selection_response = GraphQLResultHash.new(nil, root_type, object_proxy, nil, false, selections, is_eager, ast_node, nil, nil) selection_response.ordered_result_keys = ordered_result_keys final_response = @response else selection_response = @response final_response = nil end @dataloader.append_job { evaluate_selections( selections, selection_response, final_response, nil, ) } end end end when "LIST" inner_type = root_type.unwrap case inner_type.kind.name when "SCALAR", "ENUM" result_name = ast_node.alias || ast_node.name field_defn = query.field_definition owner_type = field_defn.owner selection_result = GraphQLResultHash.new(nil, owner_type, nil, nil, false, EmptyObjects::EMPTY_ARRAY, false, ast_node, nil, nil) selection_result.base_path = base_path selection_result.ordered_result_keys = [result_name] runtime_state = get_current_runtime_state runtime_state.current_result = selection_result runtime_state.current_result_name = result_name continue_value = continue_value(object, field_defn, false, ast_node, result_name, selection_result) if HALT != continue_value continue_field(continue_value, owner_type, field_defn, root_type, ast_node, nil, false, nil, nil, result_name, selection_result, false, runtime_state) # rubocop:disable Metrics/ParameterLists end @response = selection_result[result_name] else @response = GraphQLResultArray.new(nil, root_type, nil, nil, false, selections, false, ast_node, nil, nil) @response.base_path = base_path idx = nil object.each do |inner_value| idx ||= 0 this_idx = idx idx += 1 @dataloader.append_job do runtime_state.current_result_name = this_idx runtime_state.current_result = @response continue_field( inner_value, root_type, nil, inner_type, nil, @response.graphql_selections, false, object_proxy, nil, this_idx, @response, false, runtime_state ) end end end when "SCALAR", "ENUM" result_name = ast_node.alias || ast_node.name field_defn = query.field_definition owner_type = field_defn.owner selection_result = GraphQLResultHash.new(nil, owner_type, nil, nil, false, EmptyObjects::EMPTY_ARRAY, false, ast_node, nil, nil) selection_result.ordered_result_keys = [result_name] selection_result.base_path = base_path runtime_state = get_current_runtime_state runtime_state.current_result = selection_result runtime_state.current_result_name = result_name continue_value = continue_value(object, field_defn, false, ast_node, result_name, selection_result) if HALT != continue_value continue_field(continue_value, owner_type, field_defn, query.root_type, ast_node, nil, false, nil, nil, result_name, selection_result, false, runtime_state) # rubocop:disable Metrics/ParameterLists end @response = selection_result[result_name] when "UNION", "INTERFACE" resolved_type, _resolved_obj = resolve_type(root_type, object) resolved_type = schema.sync_lazy(resolved_type) object_proxy = resolved_type.wrap(object, context) object_proxy = schema.sync_lazy(object_proxy) @response = GraphQLResultHash.new(nil, resolved_type, object_proxy, nil, false, selections, false, query.ast_nodes.first, nil, nil) @response.base_path = base_path each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys| @response.ordered_result_keys ||= ordered_result_keys if is_selection_array == true raise "This isn't supported yet" end @dataloader.append_job { evaluate_selections( selections, @response, nil, runtime_state, ) } end else raise "Invariant: unsupported type kind for partial execution: #{root_type.kind.inspect} (#{root_type})" end nil end def each_gathered_selections(response_hash) ordered_result_keys = [] gathered_selections = gather_selections(response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections, nil, {}, ordered_result_keys) ordered_result_keys.uniq! if gathered_selections.is_a?(Array) gathered_selections.each do |item| yield(item, true, ordered_result_keys) end else yield(gathered_selections, false, ordered_result_keys) end end def gather_selections(owner_object, owner_type, selections, selections_to_run, selections_by_name, ordered_result_keys) selections.each do |node| # Skip gathering this if the directive says so if !directives_include?(node, owner_object, owner_type) next end if node.is_a?(GraphQL::Language::Nodes::Field) response_key = node.alias || node.name ordered_result_keys << response_key selections = selections_by_name[response_key] # if there was already a selection of this field, # use an array to hold all selections, # otherwise, use the single node to represent the selection if selections # This field was already selected at least once, # add this node to the list of selections s = Array(selections) s << node selections_by_name[response_key] = s else # No selection was found for this field yet selections_by_name[response_key] = node end else # This is an InlineFragment or a FragmentSpread if !@runtime_directive_names.empty? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) } next_selections = {} next_selections[:graphql_directives] = node.directives if selections_to_run selections_to_run << next_selections else selections_to_run = [] selections_to_run << selections_by_name selections_to_run << next_selections end else next_selections = selections_by_name end case node when GraphQL::Language::Nodes::InlineFragment if node.type type_defn = query.types.type(node.type.name) if query.types.possible_types(type_defn).include?(owner_type) result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys) if !result.equal?(next_selections) selections_to_run = result end end else # it's an untyped fragment, definitely continue result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys) if !result.equal?(next_selections) selections_to_run = result end end when GraphQL::Language::Nodes::FragmentSpread fragment_def = query.fragments[node.name] type_defn = query.types.type(fragment_def.type.name) if query.types.possible_types(type_defn).include?(owner_type) result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections, ordered_result_keys) if !result.equal?(next_selections) selections_to_run = result end end else raise "Invariant: unexpected selection class: #{node.class}" end end end selections_to_run || selections_by_name end NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH # @return [void] def evaluate_selections(gathered_selections, selections_result, target_result, runtime_state) # rubocop:disable Metrics/ParameterLists runtime_state ||= get_current_runtime_state runtime_state.current_result_name = nil runtime_state.current_result = selections_result # This is a less-frequent case; use a fast check since it's often not there. if (directives = gathered_selections[:graphql_directives]) gathered_selections.delete(:graphql_directives) end call_method_on_directives(:resolve, selections_result.graphql_application_value, directives) do gathered_selections.each do |result_name, field_ast_nodes_or_ast_node| # Field resolution may pause the fiber, # so it wouldn't get to the `Resolve` call that happens below. # So instead trigger a run from this outer context. if selections_result.graphql_is_eager @dataloader.clear_cache @dataloader.run_isolated { evaluate_selection( result_name, field_ast_nodes_or_ast_node, selections_result ) @dataloader.clear_cache } else @dataloader.append_job { evaluate_selection( result_name, field_ast_nodes_or_ast_node, selections_result ) } end end if target_result selections_result.merge_into(target_result) end selections_result end end # @return [void] def evaluate_selection(result_name, field_ast_nodes_or_ast_node, selections_result) # rubocop:disable Metrics/ParameterLists return if selections_result.graphql_dead # As a performance optimization, the hash key will be a `Node` if # there's only one selection of the field. But if there are multiple # selections of the field, it will be an Array of nodes if field_ast_nodes_or_ast_node.is_a?(Array) field_ast_nodes = field_ast_nodes_or_ast_node ast_node = field_ast_nodes.first else field_ast_nodes = nil ast_node = field_ast_nodes_or_ast_node end field_name = ast_node.name owner_type = selections_result.graphql_result_type field_defn = query.types.field(owner_type, field_name) # Set this before calling `run_with_directives`, so that the directive can have the latest path runtime_state = get_current_runtime_state runtime_state.current_field = field_defn runtime_state.current_result = selections_result runtime_state.current_result_name = result_name owner_object = selections_result.graphql_application_value if field_defn.dynamic_introspection owner_object = field_defn.owner.wrap(owner_object, context) end if !field_defn.any_arguments? resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY if field_defn.extras.size == 0 evaluate_selection_with_resolved_keyword_args( NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state ) else evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state) end else @query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments| runtime_state = get_current_runtime_state # This might be in a different fiber runtime_state.current_field = field_defn runtime_state.current_arguments = resolved_arguments runtime_state.current_result_name = result_name runtime_state.current_result = selections_result evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state) end end end def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) # rubocop:disable Metrics/ParameterLists after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_arguments, runtime_state| if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError) next if selection_result.collect_result(result_name, resolved_arguments) return_type_non_null = field_defn.type.non_null? continue_value(resolved_arguments, field_defn, return_type_non_null, ast_node, result_name, selection_result) next end kwarg_arguments = if field_defn.extras.empty? if resolved_arguments.empty? # We can avoid allocating the `{ Symbol => Object }` hash in this case NO_ARGS else resolved_arguments.keyword_arguments end else # Bundle up the extras, then make a new arguments instance # that includes the extras, too. extra_args = {} field_defn.extras.each do |extra| case extra when :ast_node extra_args[:ast_node] = ast_node when :execution_errors extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, current_path) when :path extra_args[:path] = current_path when :lookahead if !field_ast_nodes field_ast_nodes = [ast_node] end extra_args[:lookahead] = Execution::Lookahead.new( query: query, ast_nodes: field_ast_nodes, field: field_defn, ) when :argument_details # Use this flag to tell Interpreter::Arguments to add itself # to the keyword args hash _before_ freezing everything. extra_args[:argument_details] = :__arguments_add_self when :parent parent_result = selection_result.graphql_parent extra_args[:parent] = parent_result&.graphql_application_value&.object else extra_args[extra] = field_defn.fetch_extra(extra, context) end end if !extra_args.empty? resolved_arguments = resolved_arguments.merge_extras(extra_args) end resolved_arguments.keyword_arguments end evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) end end def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) # rubocop:disable Metrics/ParameterLists runtime_state.current_field = field_defn runtime_state.current_arguments = resolved_arguments runtime_state.current_result_name = result_name runtime_state.current_result = selection_result # Optimize for the case that field is selected only once if field_ast_nodes.nil? || field_ast_nodes.size == 1 next_selections = ast_node.selections directives = ast_node.directives else next_selections = [] directives = [] field_ast_nodes.each { |f| next_selections.concat(f.selections) directives.concat(f.directives) } end call_method_on_directives(:resolve, object, directives) do if !directives.empty? # This might be executed in a different context; reset this info runtime_state = get_current_runtime_state runtime_state.current_field = field_defn runtime_state.current_arguments = resolved_arguments runtime_state.current_result_name = result_name runtime_state.current_result = selection_result end # Actually call the field resolver and capture the result app_result = begin @current_trace.begin_execute_field(field_defn, object, kwarg_arguments, query) @current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do field_defn.resolve(object, kwarg_arguments, context) end rescue GraphQL::ExecutionError => err err rescue StandardError => err begin query.handle_or_reraise(err) rescue GraphQL::ExecutionError => ex_err ex_err end end @current_trace.end_execute_field(field_defn, object, kwarg_arguments, query, app_result) after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_result, runtime_state| next if selection_result.collect_result(result_name, inner_result) owner_type = selection_result.graphql_result_type return_type = field_defn.type continue_value = continue_value(inner_result, field_defn, return_type.non_null?, ast_node, result_name, selection_result) if HALT != continue_value was_scoped = runtime_state.was_authorized_by_scope_items runtime_state.was_authorized_by_scope_items = nil continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result, was_scoped, runtime_state) else nil end end end # If this field is a root mutation field, immediately resolve # all of its child fields before moving on to the next root mutation field. # (Subselections of this mutation will still be resolved level-by-level.) if selection_result.graphql_is_eager @dataloader.run end end def set_result(selection_result, result_name, value, is_child_result, is_non_null) if !selection_result.graphql_dead if value.nil? && is_non_null # This is an invalid nil that should be propagated # One caller of this method passes a block, # namely when application code returns a `nil` to GraphQL and it doesn't belong there. # The other possibility for reaching here is when a field returns an ExecutionError, so we write # `nil` to the response, not knowing whether it's an invalid `nil` or not. # (And in that case, we don't have to call the schema's handler, since it's not a bug in the application.) # TODO the code is trying to tell me something. yield if block_given? parent = selection_result.graphql_parent if parent.nil? # This is a top-level result hash @response = nil else name_in_parent = selection_result.graphql_result_name is_non_null_in_parent = selection_result.graphql_is_non_null_in_parent set_result(parent, name_in_parent, nil, false, is_non_null_in_parent) set_graphql_dead(selection_result) end elsif is_child_result selection_result.set_child_result(result_name, value) else selection_result.set_leaf(result_name, value) end end end # Mark this node and any already-registered children as dead, # so that it accepts no more writes. def set_graphql_dead(selection_result) case selection_result when GraphQLResultArray selection_result.graphql_dead = true selection_result.values.each { |v| set_graphql_dead(v) } when GraphQLResultHash selection_result.graphql_dead = true selection_result.each { |k, v| set_graphql_dead(v) } else # It's a scalar, no way to mark it dead. end end def current_path st = get_current_runtime_state result = st.current_result path = result && result.path if path && (rn = st.current_result_name) path = path.dup path.push(rn) end path end HALT = Object.new.freeze def continue_value(value, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists case value when nil if is_non_null set_result(selection_result, result_name, nil, false, is_non_null) do # When this comes from a list item, use the parent object: is_from_array = selection_result.is_a?(GraphQLResultArray) parent_type = is_from_array ? selection_result.graphql_parent.graphql_result_type : selection_result.graphql_result_type # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.) err = parent_type::InvalidNullError.new(parent_type, field, ast_node, is_from_array: is_from_array) schema.type_error(err, context) end else set_result(selection_result, result_name, nil, false, is_non_null) end HALT when GraphQL::Error # Handle these cases inside a single `when` # to avoid the overhead of checking three different classes # every time. if value.is_a?(GraphQL::ExecutionError) if selection_result.nil? || !selection_result.graphql_dead value.path ||= current_path value.ast_node ||= ast_node context.errors << value if selection_result set_result(selection_result, result_name, nil, false, is_non_null) end end HALT elsif value.is_a?(GraphQL::UnauthorizedFieldError) value.field ||= field # this hook might raise & crash, or it might return # a replacement value next_value = begin schema.unauthorized_field(value) rescue GraphQL::ExecutionError => err err end continue_value(next_value, field, is_non_null, ast_node, result_name, selection_result) elsif value.is_a?(GraphQL::UnauthorizedError) # this hook might raise & crash, or it might return # a replacement value next_value = begin schema.unauthorized_object(value) rescue GraphQL::ExecutionError => err err end continue_value(next_value, field, is_non_null, ast_node, result_name, selection_result) elsif value.is_a?(GraphQL::Execution::Skip) # It's possible a lazy was already written here case selection_result when GraphQLResultHash selection_result.delete(result_name) when GraphQLResultArray selection_result.graphql_skip_at(result_name) when nil # this can happen with directives else raise "Invariant: unexpected result class #{selection_result.class} (#{selection_result.inspect})" end HALT else # What could this actually _be_? Anyhow, # preserve the default behavior of doing nothing with it. value end when Array # It's an array full of execution errors; add them all. if !value.empty? && value.all?(GraphQL::ExecutionError) list_type_at_all = (field && (field.type.list?)) if selection_result.nil? || !selection_result.graphql_dead value.each_with_index do |error, index| error.ast_node ||= ast_node error.path ||= current_path + (list_type_at_all ? [index] : []) context.errors << error end if selection_result if list_type_at_all result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v } set_result(selection_result, result_name, result_without_errors, false, is_non_null) else set_result(selection_result, result_name, nil, false, is_non_null) end end end HALT else value end when GraphQL::Execution::Interpreter::RawValue # Write raw value directly to the response without resolving nested objects set_result(selection_result, result_name, value.resolve, false, is_non_null) HALT else value end end # The resolver for `field` returned `value`. Continue to execute the query, # treating `value` as `type` (probably the return type of the field). # # Use `next_selections` to resolve object fields, if there are any. # # Location information from `path` and `ast_node`. # # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later def continue_field(value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists if current_type.non_null? current_type = current_type.of_type is_non_null = true end case current_type.kind.name when "SCALAR", "ENUM" r = begin current_type.coerce_result(value, context) rescue GraphQL::ExecutionError => ex_err return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result) rescue StandardError => err begin query.handle_or_reraise(err) rescue GraphQL::ExecutionError => ex_err return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result) end end set_result(selection_result, result_name, r, false, is_non_null) r when "UNION", "INTERFACE" resolved_type_or_lazy = begin resolve_type(current_type, value) rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result) rescue StandardError => err begin query.handle_or_reraise(err) rescue GraphQL::ExecutionError => ex_err return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result) end end after_lazy(resolved_type_or_lazy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_type_result, runtime_state| if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2 resolved_type, resolved_value = resolved_type_result else resolved_type = resolved_type_result resolved_value = value end possible_types = query.types.possible_types(current_type) if !possible_types.include?(resolved_type) parent_type = field.owner_type err_class = current_type::UnresolvedTypeError type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types) schema.type_error(type_error, context) set_result(selection_result, result_name, nil, false, is_non_null) nil else continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result, was_scoped, runtime_state) end end when "OBJECT" object_proxy = begin was_scoped ? current_type.wrap_scoped(value, context) : current_type.wrap(value, context) rescue GraphQL::ExecutionError => err err end after_lazy(object_proxy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_object, runtime_state| continue_value = continue_value(inner_object, field, is_non_null, ast_node, result_name, selection_result) if HALT != continue_value response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false, ast_node, arguments, field) set_result(selection_result, result_name, response_hash, true, is_non_null) each_gathered_selections(response_hash) do |selections, is_selection_array, ordered_result_keys| response_hash.ordered_result_keys ||= ordered_result_keys if is_selection_array this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false, ast_node, arguments, field) this_result.ordered_result_keys = ordered_result_keys final_result = response_hash else this_result = response_hash final_result = nil end evaluate_selections( selections, this_result, final_result, runtime_state, ) end end end when "LIST" inner_type = current_type.of_type # This is true for objects, unions, and interfaces use_dataloader_job = !inner_type.unwrap.kind.input? inner_type_non_null = inner_type.non_null? response_list = GraphQLResultArray.new(result_name, current_type, owner_object, selection_result, is_non_null, next_selections, false, ast_node, arguments, field) set_result(selection_result, result_name, response_list, true, is_non_null) idx = nil list_value = begin begin value.each do |inner_value| idx ||= 0 this_idx = idx idx += 1 if use_dataloader_job @dataloader.append_job do resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, nil) end else resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state) end end response_list rescue NoMethodError => err # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.) if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true) # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug. raise ListResultFailedError.new(value: value, field: field, path: current_path) else # This was some other NoMethodError -- let it bubble to reveal the real error. raise end rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err ex_err rescue StandardError => err begin query.handle_or_reraise(err) rescue GraphQL::ExecutionError => ex_err ex_err end end rescue StandardError => err begin query.handle_or_reraise(err) rescue GraphQL::ExecutionError => ex_err ex_err end end # Detect whether this error came while calling `.each` (before `idx` is set) or while running list *items* (after `idx` is set) error_is_non_null = idx.nil? ? is_non_null : inner_type.non_null? continue_value(list_value, field, error_is_non_null, ast_node, result_name, selection_result) else raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})" end end def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists runtime_state ||= get_current_runtime_state runtime_state.current_result_name = this_idx runtime_state.current_result = response_list call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do # This will update `response_list` with the lazy after_lazy(inner_value, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list, runtime_state: runtime_state) do |inner_inner_value, runtime_state| continue_value = continue_value(inner_inner_value, field, inner_type_non_null, ast_node, this_idx, response_list) if HALT != continue_value continue_field(continue_value, owner_type, field, inner_type, ast_node, response_list.graphql_selections, false, owner_object, arguments, this_idx, response_list, was_scoped, runtime_state) end end end end def call_method_on_directives(method_name, object, directives, &block) return yield if directives.nil? || directives.empty? run_directive(method_name, object, directives, 0, &block) end def run_directive(method_name, object, directives, idx, &block) dir_node = directives[idx] if !dir_node yield else dir_defn = @schema_directives.fetch(dir_node.name) raw_dir_args = arguments(nil, dir_defn, dir_node) if !raw_dir_args.is_a?(GraphQL::ExecutionError) begin dir_defn.validate!(raw_dir_args, context) rescue GraphQL::ExecutionError => err raw_dir_args = err end end dir_args = continue_value( raw_dir_args, # value nil, # field false, # is_non_null dir_node, # ast_node nil, # result_name nil, # selection_result ) if dir_args == HALT nil else dir_defn.public_send(method_name, object, dir_args, context) do run_directive(method_name, object, directives, idx + 1, &block) end end end end # Check {Schema::Directive.include?} for each directive that's present def directives_include?(node, graphql_object, parent_type) node.directives.each do |dir_node| dir_defn = @schema_directives.fetch(dir_node.name) args = arguments(graphql_object, dir_defn, dir_node) if !dir_defn.include?(graphql_object, args, context) return false end end true end def get_current_runtime_state current_state = Fiber[:__graphql_runtime_info] ||= {}.compare_by_identity current_state[@query] ||= CurrentState.new end def minimal_after_lazy(value, &block) if lazy?(value) GraphQL::Execution::Lazy.new do result = @schema.sync_lazy(value) # The returned result might also be lazy, so check it, too minimal_after_lazy(result, &block) end else yield(value) end end # @param obj [Object] Some user-returned value that may want to be batched # @param field [GraphQL::Schema::Field] # @param eager [Boolean] Set to `true` for mutation root fields only # @param trace [Boolean] If `false`, don't wrap this with field tracing # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it. def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, runtime_state:, trace: true, &block) if lazy?(lazy_obj) was_authorized_by_scope_items = runtime_state.was_authorized_by_scope_items lazy = GraphQL::Execution::Lazy.new(field: field) do # This block might be called in a new fiber; # In that case, this will initialize a new state # to avoid conflicting with the parent fiber. runtime_state = get_current_runtime_state runtime_state.current_field = field runtime_state.current_arguments = arguments runtime_state.current_result_name = result_name runtime_state.current_result = result runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items # Wrap the execution of _this_ method with tracing, # but don't wrap the continuation below sync_result = nil inner_obj = begin sync_result = if trace @current_trace.begin_execute_field(field, owner_object, arguments, query) @current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do schema.sync_lazy(lazy_obj) end else schema.sync_lazy(lazy_obj) end rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => ex_err ex_err rescue StandardError => err begin query.handle_or_reraise(err) rescue GraphQL::ExecutionError => ex_err ex_err end ensure if trace @current_trace.end_execute_field(field, owner_object, arguments, query, sync_result) end end yield(inner_obj, runtime_state) end if eager lazy.value else set_result(result, result_name, lazy, false, false) # is_non_null is irrelevant here @dataloader.lazy_at_depth(result.depth, lazy) lazy end else # Don't need to reset state here because it _wasn't_ lazy. yield(lazy_obj, runtime_state) end end def arguments(graphql_object, arg_owner, ast_node) if arg_owner.arguments_statically_coercible? query.arguments_for(ast_node, arg_owner) else # The arguments must be prepared in the context of the given object query.arguments_for(ast_node, arg_owner, parent_object: graphql_object) end end def delete_all_interpreter_context per_query_state = Fiber[:__graphql_runtime_info] if per_query_state per_query_state.delete(@query) if per_query_state.size == 0 Fiber[:__graphql_runtime_info] = nil end end nil end def resolve_type(type, value) @current_trace.begin_resolve_type(type, value, context) resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do query.resolve_type(type, value) end @current_trace.end_resolve_type(type, value, context, resolved_type) if lazy?(resolved_type) GraphQL::Execution::Lazy.new do @current_trace.begin_resolve_type(type, value, context) @current_trace.resolve_type_lazy(query: query, type: type, object: value) do rt = schema.sync_lazy(resolved_type) @current_trace.end_resolve_type(type, value, context, rt) rt end end else [resolved_type, resolved_value] end end def lazy?(object) obj_class = object.class is_lazy = @lazy_cache[obj_class] if is_lazy.nil? is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object) end is_lazy end end end end end graphql-2.6.0/lib/graphql/execution/interpreter/runtime/0000755000004100000410000000000015173430257023451 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/execution/interpreter/runtime/graphql_result.rb0000644000004100000410000002071015173430257027032 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution class Interpreter class Runtime module GraphQLResult def initialize(result_name, result_type, application_value, parent_result, is_non_null_in_parent, selections, is_eager, ast_node, graphql_arguments, graphql_field) # rubocop:disable Metrics/ParameterLists @ast_node = ast_node @graphql_arguments = graphql_arguments @graphql_field = graphql_field @graphql_parent = parent_result @graphql_application_value = application_value @graphql_result_type = result_type if parent_result && parent_result.graphql_dead @graphql_dead = true end @graphql_result_name = result_name @graphql_is_non_null_in_parent = is_non_null_in_parent # Jump through some hoops to avoid creating this duplicate storage if at all possible. @graphql_metadata = nil @graphql_selections = selections @graphql_is_eager = is_eager @base_path = nil end # TODO test full path in Partial attr_writer :base_path def path @path ||= build_path([]) end def build_path(path_array) graphql_result_name && path_array.unshift(graphql_result_name) if @graphql_parent @graphql_parent.build_path(path_array) elsif @base_path @base_path + path_array else path_array end end def depth @depth ||= if @graphql_parent @graphql_parent.depth + 1 else 1 end end attr_accessor :graphql_dead attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent, :graphql_application_value, :graphql_result_type, :graphql_selections, :graphql_is_eager, :ast_node, :graphql_arguments, :graphql_field # @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects) attr_accessor :graphql_result_data end class GraphQLResultHash def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager, _ast_node, _graphql_arguments, graphql_field) # rubocop:disable Metrics/ParameterLists super @graphql_result_data = {} @ordered_result_keys = nil end attr_accessor :ordered_result_keys include GraphQLResult attr_accessor :graphql_merged_into def set_leaf(key, value) # This is a hack. # Basically, this object is merged into the root-level result at some point. # But the problem is, some lazies are created whose closures retain reference to _this_ # object. When those lazies are resolved, they cause an update to this object. # # In order to return a proper top-level result, we have to update that top-level result object. # In order to return a proper partial result (eg, for a directive), we have to update this object, too. # Yowza. if (t = @graphql_merged_into) t.set_leaf(key, value) end before_size = @graphql_result_data.size @graphql_result_data[key] = value after_size = @graphql_result_data.size if after_size > before_size && @ordered_result_keys[before_size] != key fix_result_order end # keep this up-to-date if it's been initialized @graphql_metadata && @graphql_metadata[key] = value value end def set_child_result(key, value) if (t = @graphql_merged_into) t.set_child_result(key, value) end before_size = @graphql_result_data.size @graphql_result_data[key] = value.graphql_result_data after_size = @graphql_result_data.size if after_size > before_size && @ordered_result_keys[before_size] != key fix_result_order end # If we encounter some part of this response that requires metadata tracking, # then create the metadata hash if necessary. It will be kept up-to-date after this. (@graphql_metadata ||= @graphql_result_data.dup)[key] = value value end def delete(key) @graphql_metadata && @graphql_metadata.delete(key) @graphql_result_data.delete(key) end def each (@graphql_metadata || @graphql_result_data).each { |k, v| yield(k, v) } end def values (@graphql_metadata || @graphql_result_data).values end def key?(k) @graphql_result_data.key?(k) end def [](k) (@graphql_metadata || @graphql_result_data)[k] end def merge_into(into_result) self.each do |key, value| case value when GraphQLResultHash next_into = into_result[key] if next_into value.merge_into(next_into) else into_result.set_child_result(key, value) end when GraphQLResultArray # There's no special handling of arrays because currently, there's no way to split the execution # of a list over several concurrent flows. into_result.set_child_result(key, value) else # We have to assume that, since this passed the `fields_will_merge` selection, # that the old and new values are the same. into_result.set_leaf(key, value) end end @graphql_merged_into = into_result end def fix_result_order @ordered_result_keys.each do |k| if @graphql_result_data.key?(k) @graphql_result_data[k] = @graphql_result_data.delete(k) end end end # hook for breadth-first implementations to signal when collecting results. def collect_result(result_name, result_value) false end end class GraphQLResultArray include GraphQLResult def initialize(_result_name, _result_type, _application_value, _parent_result, _is_non_null_in_parent, _selections, _is_eager, _ast_node, _graphql_arguments, graphql_field) # rubocop:disable Metrics/ParameterLists super @graphql_result_data = [] end def graphql_skip_at(index) # Mark this index as dead. It's tricky because some indices may already be storing # `Lazy`s. So the runtime is still holding indexes _before_ skipping, # this object has to coordinate incoming writes to account for any already-skipped indices. @skip_indices ||= [] @skip_indices << index offset_by = @skip_indices.count { |skipped_idx| skipped_idx < index} delete_at_index = index - offset_by @graphql_metadata && @graphql_metadata.delete_at(delete_at_index) @graphql_result_data.delete_at(delete_at_index) end def set_leaf(idx, value) if @skip_indices offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx } idx -= offset_by end @graphql_result_data[idx] = value @graphql_metadata && @graphql_metadata[idx] = value value end def set_child_result(idx, value) if @skip_indices offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx } idx -= offset_by end @graphql_result_data[idx] = value.graphql_result_data # If we encounter some part of this response that requires metadata tracking, # then create the metadata hash if necessary. It will be kept up-to-date after this. (@graphql_metadata ||= @graphql_result_data.dup)[idx] = value value end def values (@graphql_metadata || @graphql_result_data) end def [](idx) (@graphql_metadata || @graphql_result_data)[idx] end end end end end end graphql-2.6.0/lib/graphql/execution/interpreter/handles_raw_value.rb0000644000004100000410000000071415173430257026000 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution class Interpreter # Wrapper for raw values class RawValue include GraphQL::Execution::Finalizer def finalize_graphql_result(query, result_data, result_key) result_data[result_key] = @object end def initialize(obj = nil) @object = obj end def resolve @object end end end end end graphql-2.6.0/lib/graphql/execution/interpreter/arguments_cache.rb0000644000004100000410000000742315173430257025451 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution class Interpreter class ArgumentsCache def initialize(query) @query = query @dataloader = query.context.dataloader @storage = Hash.new do |h, argument_owner| h[argument_owner] = if argument_owner.arguments_statically_coercible? shared_values_cache = {} Hash.new do |h2, ignored_parent_object| h2[ignored_parent_object] = shared_values_cache end.compare_by_identity else Hash.new do |h2, parent_object| h2[parent_object] = {}.compare_by_identity end.compare_by_identity end end.compare_by_identity end def fetch(ast_node, argument_owner, parent_object) # This runs eagerly if no block is given @storage[argument_owner][parent_object][ast_node] ||= begin args_hash = self.class.prepare_args_hash(@query, ast_node) kwarg_arguments = argument_owner.coerce_arguments(parent_object, args_hash, @query.context) @query.after_lazy(kwarg_arguments) do |resolved_args| @storage[argument_owner][parent_object][ast_node] = resolved_args end end end # @yield [Interpreter::Arguments, Lazy] The finally-loaded arguments def dataload_for(ast_node, argument_owner, parent_object, &block) # First, normalize all AST or Ruby values to a plain Ruby hash arg_storage = @storage[argument_owner][parent_object] if (args = arg_storage[ast_node]) yield(args) else args_hash = self.class.prepare_args_hash(@query, ast_node) argument_owner.coerce_arguments(parent_object, args_hash, @query.context) do |resolved_args| arg_storage[ast_node] = resolved_args yield(resolved_args) end end nil end private NO_ARGUMENTS = GraphQL::EmptyObjects::EMPTY_HASH NO_VALUE_GIVEN = NOT_CONFIGURED def self.prepare_args_hash(query, ast_arg_or_hash_or_value) case ast_arg_or_hash_or_value when Hash if ast_arg_or_hash_or_value.empty? return NO_ARGUMENTS end args_hash = {} ast_arg_or_hash_or_value.each do |k, v| args_hash[k] = prepare_args_hash(query, v) end args_hash when Array ast_arg_or_hash_or_value.map { |v| prepare_args_hash(query, v) } when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive if ast_arg_or_hash_or_value.arguments.empty? # rubocop:disable Development/ContextIsPassedCop -- AST-related return NO_ARGUMENTS end args_hash = {} ast_arg_or_hash_or_value.arguments.each do |arg| # rubocop:disable Development/ContextIsPassedCop -- AST-related v = prepare_args_hash(query, arg.value) if v != NO_VALUE_GIVEN args_hash[arg.name] = v end end args_hash when GraphQL::Language::Nodes::VariableIdentifier if query.variables.key?(ast_arg_or_hash_or_value.name) variable_value = query.variables[ast_arg_or_hash_or_value.name] prepare_args_hash(query, variable_value) else NO_VALUE_GIVEN end when GraphQL::Language::Nodes::Enum ast_arg_or_hash_or_value.name when GraphQL::Language::Nodes::NullValue nil else ast_arg_or_hash_or_value end end end end end end graphql-2.6.0/lib/graphql/execution/interpreter/execution_errors.rb0000644000004100000410000000134715173430257025717 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution class Interpreter class ExecutionErrors def initialize(ctx, ast_node, path) @context = ctx @ast_node = ast_node @path = path end def add(err_or_msg) err = case err_or_msg when String GraphQL::ExecutionError.new(err_or_msg) when GraphQL::ExecutionError err_or_msg else raise ArgumentError, "expected String or GraphQL::ExecutionError, not #{err_or_msg.class} (#{err_or_msg.inspect})" end err.ast_node ||= @ast_node err.path ||= @path @context.add_error(err) end end end end end graphql-2.6.0/lib/graphql/execution/interpreter/resolve.rb0000644000004100000410000000725715173430257024005 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution class Interpreter module Resolve # Continue field results in `results` until there's nothing else to continue. # @return [void] # @deprecated Call `dataloader.run` instead def self.resolve_all(results, dataloader) warn "#{self}.#{__method__} is deprecated; Use `dataloader.run` instead.#{caller(1, 5).map { |l| "\n #{l}"}.join}" dataloader.append_job { resolve(results, dataloader) } nil end # @deprecated Call `dataloader.run` instead def self.resolve_each_depth(lazies_at_depth, dataloader) warn "#{self}.#{__method__} is deprecated; Use `dataloader.run` instead.#{caller(1, 5).map { |l| "\n #{l}"}.join}" smallest_depth = nil lazies_at_depth.each_key do |depth_key| smallest_depth ||= depth_key if depth_key < smallest_depth smallest_depth = depth_key end end if smallest_depth lazies = lazies_at_depth.delete(smallest_depth) if !lazies.empty? lazies.each do |l| dataloader.append_job { l.value } end # Run lazies _and_ dataloader, see if more are enqueued dataloader.run resolve_each_depth(lazies_at_depth, dataloader) end end nil end # @deprecated Call `dataloader.run` instead def self.resolve(results, dataloader) warn "#{self}.#{__method__} is deprecated; Use `dataloader.run` instead.#{caller(1, 5).map { |l| "\n #{l}"}.join}" # There might be pending jobs here that _will_ write lazies # into the result hash. We should run them out, so we # can be sure that all lazies will be present in the result hashes. # A better implementation would somehow interleave (or unify) # these approaches. dataloader.run next_results = [] while !results.empty? result_value = results.shift if result_value.is_a?(Runtime::GraphQLResultHash) || result_value.is_a?(Hash) results.concat(result_value.values) next elsif result_value.is_a?(Runtime::GraphQLResultArray) results.concat(result_value.values) next elsif result_value.is_a?(Array) results.concat(result_value) next elsif result_value.is_a?(Lazy) loaded_value = result_value.value if loaded_value.is_a?(Lazy) # Since this field returned another lazy, # add it to the same queue results << loaded_value elsif loaded_value.is_a?(Runtime::GraphQLResultHash) || loaded_value.is_a?(Runtime::GraphQLResultArray) || loaded_value.is_a?(Hash) || loaded_value.is_a?(Array) # Add these values in wholesale -- # they might be modified by later work in the dataloader. next_results << loaded_value end end end if !next_results.empty? # Any pending data loader jobs may populate the # resutl arrays or result hashes accumulated in # `next_results``. Run those **to completion** # before continuing to resolve `next_results`. # (Just `.append_job` doesn't work if any pending # jobs require multiple passes.) dataloader.run dataloader.append_job { resolve(next_results, dataloader) } end nil end end end end end graphql-2.6.0/lib/graphql/execution/lazy.rb0000644000004100000410000000431415173430257020731 0ustar www-datawww-data# frozen_string_literal: true require "graphql/execution/lazy/lazy_method_map" module GraphQL module Execution # This wraps a value which is available, but not yet calculated, like a promise or future. # # Calling `#value` will trigger calculation & return the "lazy" value. # # This is an itty-bitty promise-like object, with key differences: # - It has only two states, not-resolved and resolved # - It has no error-catching functionality # @api private class Lazy attr_reader :field # Create a {Lazy} which will get its inner value by calling the block # @param field [GraphQL::Schema::Field] # @param get_value_func [Proc] a block to get the inner value (later) def initialize(field: nil, &get_value_func) @get_value_func = get_value_func @resolved = false @field = field end # @return [Object] The wrapped value, calling the lazy block if necessary def value if !@resolved @resolved = true v = @get_value_func.call if v.is_a?(Lazy) v = v.value end @value = v end # `SKIP` was made into a subclass of `GraphQL::Error` to improve runtime performance # (fewer clauses in a hot `case` block), but now it requires special handling here. # I think it's still worth it for the performance win, but if the number of special # cases grows, then maybe it's worth rethinking somehow. if @value.is_a?(StandardError) && !@value.is_a?(GraphQL::Execution::Skip) raise @value else @value end end # @return [Lazy] A {Lazy} whose value depends on another {Lazy}, plus any transformations in `block` def then self.class.new { yield(value) } end # @param lazies [Array] Maybe-lazy objects # @return [Lazy] A lazy which will sync all of `lazies` def self.all(lazies) self.new { lazies.map { |l| l.is_a?(Lazy) ? l.value : l } } end # This can be used for fields which _had no_ lazy results # @api private NullResult = Lazy.new(){} NullResult.value end end end graphql-2.6.0/lib/graphql/execution/interpreter.rb0000644000004100000410000001511115173430257022312 0ustar www-datawww-data# frozen_string_literal: true require "fiber" require "graphql/execution/interpreter/argument_value" require "graphql/execution/interpreter/arguments" require "graphql/execution/interpreter/arguments_cache" require "graphql/execution/interpreter/execution_errors" require "graphql/execution/interpreter/runtime" require "graphql/execution/interpreter/resolve" require "graphql/execution/interpreter/handles_raw_value" module GraphQL module Execution class Interpreter class << self # Used internally to signal that the query shouldn't be executed # @api private NO_OPERATION = GraphQL::EmptyObjects::EMPTY_HASH # @param schema [GraphQL::Schema] # @param queries [Array] # @param context [Hash] # @param max_complexity [Integer, nil] # @return [Array] One result per query def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity) queries = query_options.map do |opts| query = case opts when Hash schema.query_class.new(schema, nil, **opts) when GraphQL::Query, GraphQL::Query::Partial opts else raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})" end query end return GraphQL::EmptyObjects::EMPTY_ARRAY if queries.empty? multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity) trace = multiplex.current_trace Fiber[:__graphql_current_multiplex] = multiplex trace.execute_multiplex(multiplex: multiplex) do schema = multiplex.schema queries = multiplex.queries multiplex_analyzers = schema.multiplex_analyzers if multiplex.max_complexity multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity] end trace.begin_analyze_multiplex(multiplex, multiplex_analyzers) schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers) trace.end_analyze_multiplex(multiplex, multiplex_analyzers) begin # Since this is basically the batching context, # share it for a whole multiplex multiplex.context[:interpreter_instance] ||= multiplex.schema.query_execution_strategy(deprecation_warning: false).new # Do as much eager evaluation of the query as possible results = [] queries.each_with_index do |query, idx| multiplex.dataloader.append_job { operation = query.selected_operation result = if operation.nil? || !query.valid? || !query.context.errors.empty? NO_OPERATION else begin # Although queries in a multiplex _share_ an Interpreter instance, # they also have another item of state, which is private to that query # in particular, assign it here: runtime = Runtime.new(query: query) query.context.namespace(:interpreter_runtime)[:runtime] = runtime if query.subscription? && !query.subscription_update? schema.subscriptions.initialize_subscriptions(query) end query.current_trace.execute_query(query: query) do runtime.run_eager end rescue GraphQL::ExecutionError => err query.context.errors << err end end results[idx] = result } end multiplex.dataloader.run(trace_query_lazy: multiplex) # Then, find all errors and assign the result to the query object results.each_with_index do |data_result, idx| query = queries[idx] # Assign the result so that it can be accessed in instrumentation query.result_values = if data_result.equal?(NO_OPERATION) if !query.valid? || !query.context.errors.empty? # A bit weird, but `Query#static_errors` _includes_ `query.context.errors` { "errors" => query.static_errors.map(&:to_h) } else data_result end else if query.subscription? schema.subscriptions.finish_subscriptions(query) end result = {} if !query.context.errors.empty? error_result = query.context.errors.map(&:to_h) result["errors"] = error_result end result["data"] = query.context.namespace(:interpreter_runtime)[:runtime].final_result result end if query.context.namespace?(:__query_result_extensions__) query.result_values["extensions"] = query.context.namespace(:__query_result_extensions__) end # Get the Query::Result, not the Hash results[idx] = query.result end results rescue Exception # TODO rescue at a higher level so it will catch errors in analysis, too # Assign values here so that the query's `@executed` becomes true queries.map { |q| q.result_values ||= {} } raise ensure Fiber[:__graphql_current_multiplex] = nil queries.map { |query| runtime = query.context.namespace(:interpreter_runtime)[:runtime] if runtime runtime.delete_all_interpreter_context end } end end end end class ListResultFailedError < GraphQL::Error def initialize(value:, path:, field:) message = "Failed to build a GraphQL list result for field `#{field.path}` at path `#{path.join(".")}`.\n".dup message << "Expected `#{value.inspect}` (#{value.class}) to implement `.each` to satisfy the GraphQL return type `#{field.type.to_type_signature}`.\n" if field.connection? message << "\nThis field was treated as a Relay-style connection; add `connection: false` to the `field(...)` to disable this behavior." end super(message) end end end end end graphql-2.6.0/lib/graphql/execution/directive_checks.rb0000644000004100000410000000203415173430257023245 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution # Boolean checks for how an AST node's directives should # influence its execution # @api private module DirectiveChecks SKIP = "skip" INCLUDE = "include" module_function # @return [Boolean] Should this node be included in the query? def include?(directive_ast_nodes, query) directive_ast_nodes.each do |directive_ast_node| name = directive_ast_node.name directive_defn = query.schema.directives[name] case name when SKIP args = query.arguments_for(directive_ast_node, directive_defn) if args[:if] == true return false end when INCLUDE args = query.arguments_for(directive_ast_node, directive_defn) if args[:if] == false return false end else # Undefined directive, or one we don't care about end end true end end end end graphql-2.6.0/lib/graphql/execution/errors.rb0000644000004100000410000000734415173430257021274 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution class Errors # Register this handler, updating the # internal handler index to maintain least-to-most specific. # # @param error_class [Class] # @param error_handlers [Hash] # @param error_handler [Proc] # @return [void] def self.register_rescue_from(error_class, error_handlers, error_handler) subclasses_handlers = {} this_level_subclasses = [] # During this traversal, do two things: # - Identify any already-registered subclasses of this error class # and gather them up to be inserted _under_ this class # - Find the point in the index where this handler should be inserted # (That is, _under_ any superclasses, or at top-level, if there are no superclasses registered) while (error_handlers) do this_level_subclasses.clear # First, identify already-loaded handlers that belong # _under_ this one. (That is, they're handlers # for subclasses of `error_class`.) error_handlers.each do |err_class, handler| if err_class < error_class subclasses_handlers[err_class] = handler this_level_subclasses << err_class end end # Any handlers that we'll be moving, delete them from this point in the index this_level_subclasses.each do |err_class| error_handlers.delete(err_class) end # See if any keys in this hash are superclasses of this new class: next_index_point = error_handlers.find { |err_class, handler| error_class < err_class } if next_index_point error_handlers = next_index_point[1][:subclass_handlers] else # this new handler doesn't belong to any sub-handlers, # so insert it in the current set of `handlers` break end end # Having found the point at which to insert this handler, # register it and merge any subclass handlers back in at this point. this_class_handlers = error_handlers[error_class] this_class_handlers[:handler] = error_handler this_class_handlers[:subclass_handlers].merge!(subclasses_handlers) nil end # @return [Proc, nil] The handler for `error_class`, if one was registered on this schema or inherited def self.find_handler_for(schema, error_class) handlers = schema.error_handlers[:subclass_handlers] handler = nil while (handlers) do _err_class, next_handler = handlers.find { |err_class, handler| error_class <= err_class } if next_handler handlers = next_handler[:subclass_handlers] handler = next_handler else # Don't reassign `handler` -- # let the previous assignment carry over outside this block. break end end # check for a handler from a parent class: if schema.superclass.respond_to?(:error_handlers) parent_handler = find_handler_for(schema.superclass, error_class) end # If the inherited handler is more specific than the one defined here, # use it. # If it's a tie (or there is no parent handler), use the one defined here. # If there's an inherited one, but not one defined here, use the inherited one. # Otherwise, there's no handler for this error, return `nil`. if parent_handler && handler && parent_handler[:class] < handler[:class] parent_handler elsif handler handler elsif parent_handler parent_handler else nil end end end end end graphql-2.6.0/lib/graphql/execution/finalize.rb0000644000004100000410000001734515173430257021563 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution class Finalize def initialize(query, data, runner) @query = query @data = data @static_type_at = runner.static_type_at @runner = runner @current_exec_path = query.path.dup @current_result_path = query.path.dup @finalizers = runner.finalizers ? runner.finalizers[query] : {}.compare_by_identity @finalizers_count = 0 @finalizers.each do |key, values| values.each do |key2, values2| case values2 when Array @finalizers_count += values2.size else @finalizers_count += 1 end end end query.context.errors.each do |err| err_path = err.path - @current_exec_path key = err_path.pop targets = [data] while (part = err_path.shift) targets.map! { |t| t[part] } targets.flatten! end targets.each_with_index do |target, idx| if target.is_a?(Hash) if target[key].equal?(err) tf = @finalizers[target] ||= {}.compare_by_identity tf[key] = err @finalizers_count += 1 elsif (arr = target[key]).is_a?(Array) arr.each_with_index do |el, idx| if el.equal?(err) tf = @finalizers[arr] ||= {}.compare_by_identity tf[idx] = err @finalizers_count += 1 end end end end end end end def run if (selected_operation = @query.selected_operation) && @data if @data.is_a?(Hash) check_object_result(@data, @query.root_type, selected_operation.selections) elsif @data.is_a?(Array) check_list_result(@data, @query.root_type, selected_operation.selections) elsif @data.is_a?(Finalizer) dummy_data = {} dummy_key = "__dummy" @data.path = @query.path @data.finalize_graphql_result(@query, dummy_data, dummy_key) dummy_data[dummy_key] else raise ArgumentError, "Unexpected @data: #{@data.inspect}" end else @data end end private def run_finalizers(result_path, finalizer_or_finalizers, result_data, result_key) if finalizer_or_finalizers.is_a?(Array) finalizer_or_finalizers.each { |f| f.path = result_path f.finalize_graphql_result(@query, result_data, result_key) } @finalizers_count -= finalizer_or_finalizers.size else f = finalizer_or_finalizers f.path = result_path f.finalize_graphql_result(@query, result_data, result_key) @finalizers_count -= 1 end end def finalizers(result_value, key) finalizers_for_value = @finalizers[result_value] finalizers_for_value && finalizers_for_value[key] end def check_object_result(result_h, parent_type, ast_selections) if (f = finalizers(result_h, nil)) run_finalizers(@current_result_path.dup, f, result_h, nil) return result_h if @finalizers_count == 0 end if parent_type.kind.abstract? parent_type = @runner.runtime_type_at[result_h] end ast_selections.each do |ast_selection| case ast_selection when Language::Nodes::Field key = ast_selection.alias || ast_selection.name if (f = finalizers(result_h, key)) result_value = result_h[key] run_finalizers(@current_result_path.dup << key, f, result_h, key) new_result_value = result_h.key?(key) ? result_h[key] : :unassigned end next if !(f || result_h.key?(key)) begin @current_exec_path << key @current_result_path << key field_defn = @query.context.types.field(parent_type, ast_selection.name) || raise("Invariant: No field found for #{static_type.to_type_signature}.#{ast_selection.name}") result_type = field_defn.type if (result_type_non_null = result_type.non_null?) result_type = result_type.of_type end if !f result_value = result_h[key] new_result_value = if result_type.list? && result_value check_list_result(result_value, result_type.of_type, ast_selection.selections) elsif !result_type.kind.leaf? && result_value check_object_result(result_value, result_type, ast_selection.selections) else result_value end end if new_result_value.nil? && result_type_non_null return nil elsif :unassigned.equal?(new_result_value) # Do nothing break if @finalizers_count == 0 elsif !new_result_value.equal?(result_value) result_h[key] = new_result_value break if @finalizers_count == 0 end ensure @current_exec_path.pop @current_result_path.pop end when Language::Nodes::InlineFragment static_type_at_result = @static_type_at[result_h] if static_type_at_result && ( (t = ast_selection.type).nil? || @runner.type_condition_applies?(@query.context, static_type_at_result, t.name) ) result_h = check_object_result(result_h, parent_type, ast_selection.selections) end when Language::Nodes::FragmentSpread fragment_defn = @query.document.definitions.find { |defn| defn.is_a?(Language::Nodes::FragmentDefinition) && defn.name == ast_selection.name } static_type_at_result = @static_type_at[result_h] if static_type_at_result && @runner.type_condition_applies?(@query.context, static_type_at_result, fragment_defn.type.name) result_h = check_object_result(result_h, parent_type, fragment_defn.selections) end end end result_h end def check_list_result(result_arr, inner_type, ast_selections) inner_type_non_null = false if inner_type.non_null? inner_type_non_null = true inner_type = inner_type.of_type end new_invalid_null = false if (f = finalizers(result_arr, nil)) run_finalizers(@current_result_path.dup, f, result_arr, nil) return result_arr if @finalizers_count == 0 end result_arr.each_with_index do |result_item, idx| @current_result_path << idx new_result = if (f = finalizers(result_arr, idx)) run_finalizers(@current_result_path.dup, f, result_arr, idx) result_arr[idx] elsif inner_type.list? && result_item check_list_result(result_item, inner_type.of_type, ast_selections) elsif !inner_type.kind.leaf? && result_item check_object_result(result_item, inner_type, ast_selections) else result_item end if new_result.nil? && inner_type_non_null new_invalid_null = true result_arr[idx] = nil break if @finalizers_count == 0 elsif !new_result.equal?(result_item) result_arr[idx] = new_result break if @finalizers_count == 0 end ensure @current_result_path.pop end if new_invalid_null nil else result_arr end end end end end graphql-2.6.0/lib/graphql/execution/prepare_object_step.rb0000644000004100000410000001156715173430257024001 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution class PrepareObjectStep def initialize(object:, runner:, graphql_result:, key:, is_non_null:, field_resolve_step:, next_objects:, next_results:, is_from_array:) @object = object @runner = runner @field_resolve_step = field_resolve_step @is_non_null = is_non_null @next_objects = next_objects @next_results = next_results @graphql_result = graphql_result @resolved_type = nil @authorized_value = nil @authorization_error = nil @key = key @next_step = :resolve_type @is_from_array = is_from_array end def value if @authorized_value query = @field_resolve_step.selections_step.query query.current_trace.begin_authorized(@resolved_type, @object, query.context) @authorized_value = @field_resolve_step.sync(@authorized_value) query.current_trace.end_authorized(@resolved_type, @object, query.context, @authorized_value) elsif @resolved_type ctx = @field_resolve_step.selections_step.query.context st = @field_resolve_step.static_type ctx.query.current_trace.begin_resolve_type(st, @object, ctx) @resolved_type, _ignored_value = @field_resolve_step.sync(@resolved_type) ctx.query.current_trace.end_resolve_type(st, @object, ctx, @resolved_type) end @runner.add_step(self) end def call case @next_step when :resolve_type static_type = @field_resolve_step.static_type if static_type.kind.abstract? ctx = @field_resolve_step.selections_step.query.context ctx.query.current_trace.begin_resolve_type(static_type, @object, ctx) @resolved_type, _ignored_value = @runner.schema.resolve_type(static_type, @object, ctx) ctx.query.current_trace.end_resolve_type(static_type, @object, ctx, @resolved_type) else @resolved_type = static_type end if @runner.resolves_lazies && @runner.lazy?(@resolved_type) @next_step = :authorize @runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self) else authorize end when :authorize authorize when :create_result create_result else raise ArgumentError, "This is a bug, unknown step: #{@next_step.inspect}" end end def authorize if @field_resolve_step.was_scoped && !@resolved_type.reauthorize_scoped_objects @authorized_value = @object create_result return end query = @field_resolve_step.selections_step.query begin query.current_trace.begin_authorized(@resolved_type, @object, query.context) @authorized_value = @resolved_type.authorized?(@object, query.context) query.current_trace.end_authorized(@resolved_type, @object, query.context, @authorized_value) rescue GraphQL::UnauthorizedError => auth_err @authorization_error = auth_err end if @runner.resolves_lazies && @runner.lazy?(@authorized_value) @runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self) @next_step = :create_result else create_result end rescue GraphQL::RuntimeError => err @graphql_result[@key] = @field_resolve_step.add_graphql_error(err) end def create_result if !@authorized_value @authorization_error ||= GraphQL::UnauthorizedError.new(object: @object, type: @resolved_type, context: @field_resolve_step.selections_step.query.context) end if @authorization_error begin new_obj = @runner.schema.unauthorized_object(@authorization_error) if new_obj @authorized_value = true @object = new_obj elsif @is_non_null @graphql_result[@key] = @field_resolve_step.add_non_null_error(@is_from_array) else @graphql_result[@key] = @field_resolve_step.add_graphql_error(@authorization_error) end rescue GraphQL::RuntimeError => err if @is_non_null @graphql_result[@key] = @field_resolve_step.add_non_null_error(@is_from_array) else @graphql_result[@key] = @field_resolve_step.add_graphql_error(err) end end end if @authorized_value next_result_h = {} @next_results << next_result_h @next_objects << @object @graphql_result[@key] = next_result_h @runner.runtime_type_at[next_result_h] = @resolved_type @runner.static_type_at[next_result_h] = @field_resolve_step.static_type end @field_resolve_step.authorized_finished(self) end end end end graphql-2.6.0/lib/graphql/execution/lookahead.rb0000644000004100000410000003625115173430257021706 0ustar www-datawww-data# frozen_string_literal: true module GraphQL module Execution # Lookahead creates a uniform interface to inspect the forthcoming selections. # # It assumes that the AST it's working with is valid. (So, it's safe to use # during execution, but if you're using it directly, be sure to validate first.) # # A field may get access to its lookahead by adding `extras: [:lookahead]` # to its configuration. # # @example looking ahead in a field # field :articles, [Types::Article], null: false, # extras: [:lookahead] # # # For example, imagine a faster database call # # may be issued when only some fields are requested. # # # # Imagine that _full_ fetch must be made to satisfy `fullContent`, # # we can look ahead to see if we need that field. If we do, # # we make the expensive database call instead of the cheap one. # def articles(lookahead:) # if lookahead.selects?(:full_content) # fetch_full_articles(object) # else # fetch_preview_articles(object) # end # end class Lookahead # @param query [GraphQL::Query] # @param ast_nodes [Array, Array] # @param field [GraphQL::Schema::Field] if `ast_nodes` are fields, this is the field definition matching those nodes # @param root_type [Class] if `ast_nodes` are operation definition, this is the root type for that operation def initialize(query:, ast_nodes:, field: nil, root_type: nil, owner_type: nil) @ast_nodes = ast_nodes.freeze @field = field @root_type = root_type @query = query @selected_type = @field ? @field.type.unwrap : root_type @owner_type = owner_type end # @return [Array] attr_reader :ast_nodes # @return [GraphQL::Schema::Field] attr_reader :field # @return [GraphQL::Schema::Object, GraphQL::Schema::Union, GraphQL::Schema::Interface] attr_reader :owner_type # @return [Hash] def arguments if defined?(@arguments) @arguments else @arguments = if @field @query.after_lazy(@query.arguments_for(@ast_nodes.first, @field)) do |args| case args when Execution::Interpreter::Arguments args.keyword_arguments when GraphQL::ExecutionError EmptyObjects::EMPTY_HASH else args end end else nil end end end # True if this node has a selection on `field_name`. # If `field_name` is a String, it is treated as a GraphQL-style (camelized) # field name and used verbatim. If `field_name` is a Symbol, it is # treated as a Ruby-style (underscored) name and camelized before comparing. # # If `arguments:` is provided, each provided key/value will be matched # against the arguments in the next selection. This method will return false # if any of the given `arguments:` are not present and matching in the next selection. # (But, the next selection may contain _more_ than the given arguments.) # @param field_name [String, Symbol] # @param arguments [Hash] Arguments which must match in the selection # @return [Boolean] def selects?(field_name, selected_type: @selected_type, arguments: nil) selection(field_name, selected_type: selected_type, arguments: arguments).selected? end # True if this node has a selection with alias matching `alias_name`. # If `alias_name` is a String, it is treated as a GraphQL-style (camelized) # field name and used verbatim. If `alias_name` is a Symbol, it is # treated as a Ruby-style (underscored) name and camelized before comparing. # # If `arguments:` is provided, each provided key/value will be matched # against the arguments in the next selection. This method will return false # if any of the given `arguments:` are not present and matching in the next selection. # (But, the next selection may contain _more_ than the given arguments.) # @param alias_name [String, Symbol] # @param arguments [Hash] Arguments which must match in the selection # @return [Boolean] def selects_alias?(alias_name, arguments: nil) alias_selection(alias_name, arguments: arguments).selected? end # @return [Boolean] True if this lookahead represents a field that was requested def selected? true end # Like {#selects?}, but can be used for chaining. # It returns a null object (check with {#selected?}) # @param field_name [String, Symbol] # @return [GraphQL::Execution::Lookahead] def selection(field_name, selected_type: @selected_type, arguments: nil) next_field_defn = case field_name when String @query.types.field(selected_type, field_name) when Symbol # Try to avoid the `.to_s` below, if possible all_fields = if selected_type.kind.fields? @query.types.fields(selected_type) else # Handle unions by checking possible @query.types .possible_types(selected_type) .map { |t| @query.types.fields(t) } .tap(&:flatten!) end if (match_by_orig_name = all_fields.find { |f| f.original_name == field_name }) match_by_orig_name else # Symbol#name is only present on 3.0+ sym_s = field_name.respond_to?(:name) ? field_name.name : field_name.to_s guessed_name = Schema::Member::BuildType.camelize(sym_s) @query.types.field(selected_type, guessed_name) end end lookahead_for_selection(next_field_defn, selected_type, arguments) end # Like {#selection}, but for aliases. # It returns a null object (check with {#selected?}) # @return [GraphQL::Execution::Lookahead] def alias_selection(alias_name, selected_type: @selected_type, arguments: nil) alias_cache_key = [alias_name, arguments] return alias_selections[key] if alias_selections.key?(alias_name) alias_node = lookup_alias_node(ast_nodes, alias_name) return NULL_LOOKAHEAD unless alias_node next_field_defn = @query.types.field(selected_type, alias_node.name) alias_arguments = @query.arguments_for(alias_node, next_field_defn) if alias_arguments.is_a?(::GraphQL::Execution::Interpreter::Arguments) alias_arguments = alias_arguments.keyword_arguments end return NULL_LOOKAHEAD if arguments && arguments != alias_arguments alias_selections[alias_cache_key] = lookahead_for_selection(next_field_defn, selected_type, alias_arguments, alias_name) end # Like {#selection}, but for all nodes. # It returns a list of Lookaheads for all Selections # # If `arguments:` is provided, each provided key/value will be matched # against the arguments in each selection. This method will filter the selections # if any of the given `arguments:` do not match the given selection. # # @example getting the name of a selection # def articles(lookahead:) # next_lookaheads = lookahead.selections # => [#, ...] # next_lookaheads.map(&:name) #=> [:full_content, :title] # end # # @param arguments [Hash] Arguments which must match in the selection # @return [Array] def selections(arguments: nil) subselections_by_type = {} subselections_on_type = subselections_by_type[@selected_type] = {} @ast_nodes.each do |node| find_selections(subselections_by_type, subselections_on_type, @selected_type, node.selections, arguments) end subselections = [] subselections_by_type.each do |type, ast_nodes_by_response_key| ast_nodes_by_response_key.each do |response_key, ast_nodes| field_defn = @query.types.field(type, ast_nodes.first.name) lookahead = Lookahead.new(query: @query, ast_nodes: ast_nodes, field: field_defn, owner_type: type) subselections.push(lookahead) end end subselections end # The method name of the field. # It returns the method_sym of the Lookahead's field. # # @example getting the name of a selection # def articles(lookahead:) # article.selection(:full_content).name # => :full_content # # ... # end # # @return [Symbol] def name @field && @field.original_name end def inspect "#" end # This is returned for {Lookahead#selection} when a non-existent field is passed class NullLookahead < Lookahead # No inputs required here. def initialize end def selected? false end def selects?(*) false end def selection(*) NULL_LOOKAHEAD end def selections(*) [] end def inspect "#" end end # A singleton, so that misses don't come with overhead. NULL_LOOKAHEAD = NullLookahead.new private def skipped_by_directive?(ast_selection) ast_selection.directives.each do |directive| dir_defn = @query.schema.directives.fetch(directive.name) directive_class = dir_defn if directive_class dir_args = @query.arguments_for(directive, dir_defn) return true unless directive_class.static_include?(dir_args, @query.context) end end false end def find_selections(subselections_by_type, selections_on_type, selected_type, ast_selections, arguments) ast_selections.each do |ast_selection| next if skipped_by_directive?(ast_selection) case ast_selection when GraphQL::Language::Nodes::Field response_key = ast_selection.alias || ast_selection.name if selections_on_type.key?(response_key) selections_on_type[response_key] << ast_selection elsif arguments.nil? || arguments.empty? selections_on_type[response_key] = [ast_selection] else field_defn = @query.types.field(selected_type, ast_selection.name) if arguments_match?(arguments, field_defn, ast_selection) selections_on_type[response_key] = [ast_selection] end end when GraphQL::Language::Nodes::InlineFragment on_type = selected_type subselections_on_type = selections_on_type if (t = ast_selection.type) # Assuming this is valid, that `t` will be found. on_type = @query.types.type(t.name) subselections_on_type = subselections_by_type[on_type] ||= {} end find_selections(subselections_by_type, subselections_on_type, on_type, ast_selection.selections, arguments) when GraphQL::Language::Nodes::FragmentSpread frag_defn = lookup_fragment(ast_selection) # Again, assuming a valid AST on_type = @query.types.type(frag_defn.type.name) subselections_on_type = subselections_by_type[on_type] ||= {} find_selections(subselections_by_type, subselections_on_type, on_type, frag_defn.selections, arguments) else raise "Invariant: Unexpected selection type: #{ast_selection.class}" end end end # If a selection on `node` matches `field_name` (which is backed by `field_defn`) # and matches the `arguments:` constraints, then add that node to `matches` def find_selected_nodes(node, field_name, field_defn, arguments:, matches:, alias_name: NOT_CONFIGURED) return if skipped_by_directive?(node) case node when GraphQL::Language::Nodes::Field if node.name == field_name && (NOT_CONFIGURED.equal?(alias_name) || node.alias == alias_name) if arguments.nil? || arguments.empty? # No constraint applied matches << node elsif arguments_match?(arguments, field_defn, node) matches << node end end when GraphQL::Language::Nodes::InlineFragment node.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches, alias_name: alias_name) } when GraphQL::Language::Nodes::FragmentSpread frag_defn = lookup_fragment(node) frag_defn.selections.each { |s| find_selected_nodes(s, field_name, field_defn, arguments: arguments, matches: matches, alias_name: alias_name) } else raise "Unexpected selection comparison on #{node.class.name} (#{node})" end end def arguments_match?(arguments, field_defn, field_node) query_kwargs = @query.arguments_for(field_node, field_defn) arguments.all? do |arg_name, arg_value| arg_name_sym = if arg_name.is_a?(String) Schema::Member::BuildType.underscore(arg_name).to_sym else arg_name end # Make sure the constraint is present with a matching value query_kwargs.key?(arg_name_sym) && query_kwargs[arg_name_sym] == arg_value end end def lookahead_for_selection(field_defn, selected_type, arguments, alias_name = NOT_CONFIGURED) return NULL_LOOKAHEAD unless field_defn next_nodes = [] field_name = field_defn.name @ast_nodes.each do |ast_node| ast_node.selections.each do |selection| find_selected_nodes(selection, field_name, field_defn, arguments: arguments, matches: next_nodes, alias_name: alias_name) end end return NULL_LOOKAHEAD if next_nodes.empty? Lookahead.new(query: @query, ast_nodes: next_nodes, field: field_defn, owner_type: selected_type) end def alias_selections return @alias_selections if defined?(@alias_selections) @alias_selections ||= {} end def lookup_alias_node(nodes, name) return if nodes.empty? nodes.flat_map(&:children) .flat_map { |child| unwrap_fragments(child) } .find { |child| child.is_a?(GraphQL::Language::Nodes::Field) && child.alias == name } end def unwrap_fragments(node) case node when GraphQL::Language::Nodes::InlineFragment node.children when GraphQL::Language::Nodes::FragmentSpread lookup_fragment(node).children else [node] end end def lookup_fragment(ast_selection) @query.fragments[ast_selection.name] || raise("Invariant: Can't look ahead to nonexistent fragment #{ast_selection.name} (found: #{@query.fragments.keys})") end end end end graphql-2.6.0/lib/graphql/analysis.rb0000644000004100000410000000634415173430257017577 0ustar www-datawww-data# frozen_string_literal: true require "graphql/analysis/visitor" require "graphql/analysis/analyzer" require "graphql/analysis/field_usage" require "graphql/analysis/query_complexity" require "graphql/analysis/max_query_complexity" require "graphql/analysis/query_depth" require "graphql/analysis/max_query_depth" module GraphQL module Analysis AST = self class TimeoutError < AnalysisError def initialize(...) super("Timeout on validation of query") end end module_function # Analyze a multiplex, and all queries within. # Multiplex analyzers are ran for all queries, keeping state. # Query analyzers are ran per query, without carrying state between queries. # # @param multiplex [GraphQL::Execution::Multiplex] # @param analyzers [Array] # @return [Array] Results from multiplex analyzers def analyze_multiplex(multiplex, analyzers) multiplex_analyzers = analyzers.map { |analyzer| analyzer.new(multiplex) } multiplex.current_trace.analyze_multiplex(multiplex: multiplex) do query_results = multiplex.queries.map do |query| if query.valid? analyze_query( query, query.analyzers, multiplex_analyzers: multiplex_analyzers ) else [] end end multiplex_analyzers.map!(&:result) multiplex_errors = analysis_errors(EmptyObjects::EMPTY_ARRAY, multiplex_analyzers) multiplex.queries.each_with_index do |query, idx| query.analysis_errors = analysis_errors(multiplex_errors, query_results[idx]) end multiplex_analyzers end end # @param query [GraphQL::Query] # @param analyzers [Array] # @return [Array] Results from those analyzers def analyze_query(query, analyzers, multiplex_analyzers: []) query.current_trace.analyze_query(query: query) do query_analyzers = analyzers.map { |analyzer| analyzer.new(query) } query_analyzers.select!(&:analyze?) analyzers_to_run = query_analyzers + multiplex_analyzers if !analyzers_to_run.empty? analyzers_to_run.select!(&:visit?) if !analyzers_to_run.empty? visitor = GraphQL::Analysis::Visitor.new( query: query, analyzers: analyzers_to_run, timeout: query.validate_timeout_remaining, ) visitor.visit if !visitor.rescued_errors.empty? return visitor.rescued_errors end end query_analyzers.map(&:result) else EmptyObjects::EMPTY_ARRAY end end rescue TimeoutError => err [err] rescue GraphQL::UnauthorizedError, GraphQL::ExecutionError # This error was raised during analysis and will be returned the client before execution EmptyObjects::EMPTY_ARRAY end def analysis_errors(parent_errors, results) if !results.empty? results = results.flatten results.select! { |r| r.is_a?(GraphQL::AnalysisError) } end if parent_errors.empty? results else parent_errors + results end end end end graphql-2.6.0/lib/graphql/railtie.rb0000644000004100000410000000114515173430257017377 0ustar www-datawww-data# frozen_string_literal: true module GraphQL # Support {GraphQL::Parser::Cache} and {GraphQL.eager_load!} # # @example Enable the parser cache with default directory # # config.graphql.parser_cache = true # class Railtie < Rails::Railtie config.graphql = ActiveSupport::OrderedOptions.new config.graphql.parser_cache = false config.eager_load_namespaces << GraphQL initializer("graphql.cache") do |app| if config.graphql.parser_cache Language::Parser.cache ||= Language::Cache.new( app.root.join("tmp/cache/graphql") ) end end end end graphql-2.6.0/lib/graphql/rake_task/0000755000004100000410000000000015173430257017364 5ustar www-datawww-datagraphql-2.6.0/lib/graphql/rake_task/validate.rb0000644000004100000410000000464215173430257021510 0ustar www-datawww-data# frozen_string_literal: true module GraphQL class RakeTask extend Rake::DSL desc "Get the checksum of a graphql-pro version and compare it to published versions on GitHub and graphql-ruby.org" task "graphql:pro:validate", [:gem_version] do |t, args| version = args[:gem_version] if version.nil? raise ArgumentError, "A specific version is required, eg `rake graphql:pro:validate[1.12.0]`" end check = "\e[32m✓\e[0m" ex = "\e[31m✘\e[0m" puts "Validating graphql-pro v#{version}" puts " - Checking for graphql-pro credentials..." creds = `bundle config gems.graphql.pro --parseable`[/[a-z0-9]{11}:[a-z0-9]{11}/] if creds.nil? puts " #{ex} failed, please set with `bundle config gems.graphql.pro $MY_CREDENTIALS`" exit(1) else puts " #{check} found" end puts " - Fetching the gem..." fetch_result = `gem fetch graphql-pro -v #{version} --source https://#{creds}@gems.graphql.pro` if fetch_result.empty? puts " #{ex} failed to fetch v#{version}" exit(1) else puts " #{check} fetched" end puts " - Validating digest..." require "digest/sha2" gem_digest = Digest::SHA512.new.hexdigest(File.read("graphql-pro-#{version}.gem")) require "net/http" github_uri = URI("https://raw.githubusercontent.com/rmosolgo/graphql-ruby/master/guides/pro/checksums/graphql-pro-#{version}.txt") # Remove final newline from .txt file github_digest = Net::HTTP.get(github_uri).chomp docs_uri = URI("https://graphql-ruby.org/pro/checksums/graphql-pro-#{version}.txt") docs_digest = Net::HTTP.get(docs_uri).chomp if docs_digest == gem_digest && github_digest == gem_digest puts " #{check} validated from GitHub" puts " #{check} validated from graphql-ruby.org" else puts " #{ex} SHA mismatch:" puts " Downloaded: #{gem_digest}" puts " GitHub: #{github_digest}" puts " graphql-ruby.org: #{docs_digest}" puts "" puts " This download of graphql-pro is invalid, please open an issue:" puts " https://github.com/rmosolgo/graphql-ruby/issues/new?title=graphql-pro%20digest%20mismatch%20(#{version})" exit(1) end puts "\e[32m✔\e[0m graphql-pro #{version} validated successfully!" end end end graphql-2.6.0/.yardopts0000644000004100000410000000016215173430257015061 0ustar www-datawww-data--no-private --markup=markdown --readme=readme.md --title='GraphQL Ruby API Documentation' 'lib/**/*.rb' - '*.md' graphql-2.6.0/readme.md0000644000004100000410000000473615173430257015005 0ustar www-datawww-data# graphql graphql-ruby [![CI Suite](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml/badge.svg)](https://github.com/rmosolgo/graphql-ruby/actions/workflows/ci.yaml) [![Gem Version](https://badge.fury.io/rb/graphql.svg)](https://rubygems.org/gems/graphql) A Ruby implementation of [GraphQL](https://graphql.org/). - [Website](https://graphql-ruby.org/) - [API Documentation](https://www.rubydoc.info/github/rmosolgo/graphql-ruby) - [Newsletter](https://buttondown.email/graphql-ruby) ## Installation Install from RubyGems by adding it to your `Gemfile`, then bundling. ```ruby # Gemfile gem 'graphql' ``` ``` $ bundle install ``` ## Getting Started ``` $ rails generate graphql:install ``` After this, you may need to run `bundle install` again, as by default graphiql-rails is added on installation. Or, see ["Getting Started"](https://graphql-ruby.org/getting_started.html). ## Upgrade I also sell [GraphQL::Pro](https://graphql.pro) which provides several features on top of the GraphQL runtime, including: - [Persisted queries](https://graphql-ruby.org/operation_store/overview) - [API versioning](https://graphql-ruby.org/changesets/overview) - [Streaming payloads](https://graphql-ruby.org/defer/overview) - [Server-side caching](https://graphql-ruby.org/object_cache/overview) - [Rate limiters](https://graphql-ruby.org/limiters/overview) - Subscriptions backends for [Pusher](https://graphql-ruby.org/subscriptions/pusher_implementation) and [Ably](https://graphql-ruby.org/subscriptions/ably_implementation) - Authorization plugins for [Pundit](https://graphql-ruby.org/authorization/pundit_integration) and [CanCan](https://graphql-ruby.org/authorization/can_can_integration) Besides that, Pro customers get email support and an opportunity to support graphql-ruby's development! ## Goals - Implement the GraphQL spec & support a Relay front end - Provide idiomatic, plain-Ruby API with similarities to reference implementation where possible - Support Ruby on Rails and Relay ## Getting Involved - __Say hi & ask questions__ in the #graphql-ruby channel on [Discord](https://discord.com/invite/xud7bH9). - __Report bugs__ by posting a description, full stack trace, and all relevant code in a [GitHub issue](https://github.com/rmosolgo/graphql-ruby/issues). - __Start hacking__ with the [Development guide](https://graphql-ruby.org/development). graphql-2.6.0/MIT-LICENSE0000644000004100000410000000203615173430257014651 0ustar www-datawww-dataCopyright 2015 Robert Mosolgo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. graphql-2.6.0/graphql.gemspec0000644000004100000410000006525315173430257016232 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: graphql 2.6.0 ruby lib Gem::Specification.new do |s| s.name = "graphql".freeze s.version = "2.6.0".freeze s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "bug_tracker_uri" => "https://github.com/rmosolgo/graphql-ruby/issues", "changelog_uri" => "https://github.com/rmosolgo/graphql-ruby/blob/master/CHANGELOG.md", "homepage_uri" => "https://graphql-ruby.org", "mailing_list_uri" => "https://buttondown.email/graphql-ruby", "rubygems_mfa_required" => "true", "source_code_uri" => "https://github.com/rmosolgo/graphql-ruby" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Robert Mosolgo".freeze] s.date = "2026-04-24" s.description = "A plain-Ruby implementation of GraphQL.".freeze s.email = ["rdmosolgo@gmail.com".freeze] s.files = [".yardopts".freeze, "MIT-LICENSE".freeze, "lib/generators/graphql/core.rb".freeze, "lib/generators/graphql/detailed_trace_generator.rb".freeze, "lib/generators/graphql/enum_generator.rb".freeze, "lib/generators/graphql/field_extractor.rb".freeze, "lib/generators/graphql/input_generator.rb".freeze, "lib/generators/graphql/install/mutation_root_generator.rb".freeze, "lib/generators/graphql/install/templates/base_mutation.erb".freeze, "lib/generators/graphql/install/templates/mutation_type.erb".freeze, "lib/generators/graphql/install_generator.rb".freeze, "lib/generators/graphql/interface_generator.rb".freeze, "lib/generators/graphql/loader_generator.rb".freeze, "lib/generators/graphql/mutation_create_generator.rb".freeze, "lib/generators/graphql/mutation_delete_generator.rb".freeze, "lib/generators/graphql/mutation_generator.rb".freeze, "lib/generators/graphql/mutation_update_generator.rb".freeze, "lib/generators/graphql/object_generator.rb".freeze, "lib/generators/graphql/orm_mutations_base.rb".freeze, "lib/generators/graphql/relay.rb".freeze, "lib/generators/graphql/relay_generator.rb".freeze, "lib/generators/graphql/scalar_generator.rb".freeze, "lib/generators/graphql/templates/base_argument.erb".freeze, "lib/generators/graphql/templates/base_connection.erb".freeze, "lib/generators/graphql/templates/base_edge.erb".freeze, "lib/generators/graphql/templates/base_enum.erb".freeze, "lib/generators/graphql/templates/base_field.erb".freeze, "lib/generators/graphql/templates/base_input_object.erb".freeze, "lib/generators/graphql/templates/base_interface.erb".freeze, "lib/generators/graphql/templates/base_object.erb".freeze, "lib/generators/graphql/templates/base_resolver.erb".freeze, "lib/generators/graphql/templates/base_scalar.erb".freeze, "lib/generators/graphql/templates/base_union.erb".freeze, "lib/generators/graphql/templates/create_graphql_detailed_traces.erb".freeze, "lib/generators/graphql/templates/enum.erb".freeze, "lib/generators/graphql/templates/graphql_controller.erb".freeze, "lib/generators/graphql/templates/input.erb".freeze, "lib/generators/graphql/templates/interface.erb".freeze, "lib/generators/graphql/templates/loader.erb".freeze, "lib/generators/graphql/templates/mutation.erb".freeze, "lib/generators/graphql/templates/mutation_create.erb".freeze, "lib/generators/graphql/templates/mutation_delete.erb".freeze, "lib/generators/graphql/templates/mutation_update.erb".freeze, "lib/generators/graphql/templates/node_type.erb".freeze, "lib/generators/graphql/templates/object.erb".freeze, "lib/generators/graphql/templates/query_type.erb".freeze, "lib/generators/graphql/templates/scalar.erb".freeze, "lib/generators/graphql/templates/schema.erb".freeze, "lib/generators/graphql/templates/union.erb".freeze, "lib/generators/graphql/type_generator.rb".freeze, "lib/generators/graphql/union_generator.rb".freeze, "lib/graphql.rb".freeze, "lib/graphql/analysis.rb".freeze, "lib/graphql/analysis/analyzer.rb".freeze, "lib/graphql/analysis/field_usage.rb".freeze, "lib/graphql/analysis/max_query_complexity.rb".freeze, "lib/graphql/analysis/max_query_depth.rb".freeze, "lib/graphql/analysis/query_complexity.rb".freeze, "lib/graphql/analysis/query_depth.rb".freeze, "lib/graphql/analysis/visitor.rb".freeze, "lib/graphql/analysis_error.rb".freeze, "lib/graphql/autoload.rb".freeze, "lib/graphql/backtrace.rb".freeze, "lib/graphql/backtrace/table.rb".freeze, "lib/graphql/backtrace/traced_error.rb".freeze, "lib/graphql/coercion_error.rb".freeze, "lib/graphql/current.rb".freeze, "lib/graphql/dashboard.rb".freeze, "lib/graphql/dashboard/application_controller.rb".freeze, "lib/graphql/dashboard/detailed_traces.rb".freeze, "lib/graphql/dashboard/installable.rb".freeze, "lib/graphql/dashboard/landings_controller.rb".freeze, "lib/graphql/dashboard/limiters.rb".freeze, "lib/graphql/dashboard/operation_store.rb".freeze, "lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css".freeze, "lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js".freeze, "lib/graphql/dashboard/statics/charts.min.css".freeze, "lib/graphql/dashboard/statics/dashboard.css".freeze, "lib/graphql/dashboard/statics/dashboard.js".freeze, "lib/graphql/dashboard/statics/header-icon.png".freeze, "lib/graphql/dashboard/statics/icon.png".freeze, "lib/graphql/dashboard/statics_controller.rb".freeze, "lib/graphql/dashboard/subscriptions.rb".freeze, "lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb".freeze, "lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb".freeze, "lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb".freeze, "lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb".freeze, "lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb".freeze, "lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb".freeze, "lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb".freeze, "lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb".freeze, "lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb".freeze, "lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb".freeze, "lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb".freeze, "lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb".freeze, "lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb".freeze, "lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb".freeze, "lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb".freeze, "lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb".freeze, "lib/graphql/dataloader.rb".freeze, "lib/graphql/dataloader/active_record_association_source.rb".freeze, "lib/graphql/dataloader/active_record_source.rb".freeze, "lib/graphql/dataloader/async_dataloader.rb".freeze, "lib/graphql/dataloader/null_dataloader.rb".freeze, "lib/graphql/dataloader/request.rb".freeze, "lib/graphql/dataloader/request_all.rb".freeze, "lib/graphql/dataloader/source.rb".freeze, "lib/graphql/date_encoding_error.rb".freeze, "lib/graphql/dig.rb".freeze, "lib/graphql/duration_encoding_error.rb".freeze, "lib/graphql/execution.rb".freeze, "lib/graphql/execution/directive_checks.rb".freeze, "lib/graphql/execution/errors.rb".freeze, "lib/graphql/execution/field_resolve_step.rb".freeze, "lib/graphql/execution/finalize.rb".freeze, "lib/graphql/execution/input_values.rb".freeze, "lib/graphql/execution/interpreter.rb".freeze, "lib/graphql/execution/interpreter/argument_value.rb".freeze, "lib/graphql/execution/interpreter/arguments.rb".freeze, "lib/graphql/execution/interpreter/arguments_cache.rb".freeze, "lib/graphql/execution/interpreter/execution_errors.rb".freeze, "lib/graphql/execution/interpreter/handles_raw_value.rb".freeze, "lib/graphql/execution/interpreter/resolve.rb".freeze, "lib/graphql/execution/interpreter/runtime.rb".freeze, "lib/graphql/execution/interpreter/runtime/graphql_result.rb".freeze, "lib/graphql/execution/lazy.rb".freeze, "lib/graphql/execution/lazy/lazy_method_map.rb".freeze, "lib/graphql/execution/load_argument_step.rb".freeze, "lib/graphql/execution/lookahead.rb".freeze, "lib/graphql/execution/multiplex.rb".freeze, "lib/graphql/execution/next.rb".freeze, "lib/graphql/execution/prepare_object_step.rb".freeze, "lib/graphql/execution/runner.rb".freeze, "lib/graphql/execution/selections_step.rb".freeze, "lib/graphql/execution_error.rb".freeze, "lib/graphql/integer_decoding_error.rb".freeze, "lib/graphql/integer_encoding_error.rb".freeze, "lib/graphql/introspection.rb".freeze, "lib/graphql/introspection/base_object.rb".freeze, "lib/graphql/introspection/directive_location_enum.rb".freeze, "lib/graphql/introspection/directive_type.rb".freeze, "lib/graphql/introspection/dynamic_fields.rb".freeze, "lib/graphql/introspection/entry_points.rb".freeze, "lib/graphql/introspection/enum_value_type.rb".freeze, "lib/graphql/introspection/field_type.rb".freeze, "lib/graphql/introspection/input_value_type.rb".freeze, "lib/graphql/introspection/introspection_query.rb".freeze, "lib/graphql/introspection/schema_type.rb".freeze, "lib/graphql/introspection/type_kind_enum.rb".freeze, "lib/graphql/introspection/type_type.rb".freeze, "lib/graphql/invalid_name_error.rb".freeze, "lib/graphql/invalid_null_error.rb".freeze, "lib/graphql/language.rb".freeze, "lib/graphql/language/block_string.rb".freeze, "lib/graphql/language/cache.rb".freeze, "lib/graphql/language/comment.rb".freeze, "lib/graphql/language/definition_slice.rb".freeze, "lib/graphql/language/document_from_schema_definition.rb".freeze, "lib/graphql/language/generation.rb".freeze, "lib/graphql/language/lexer.rb".freeze, "lib/graphql/language/nodes.rb".freeze, "lib/graphql/language/parser.rb".freeze, "lib/graphql/language/printer.rb".freeze, "lib/graphql/language/sanitized_printer.rb".freeze, "lib/graphql/language/static_visitor.rb".freeze, "lib/graphql/language/visitor.rb".freeze, "lib/graphql/load_application_object_failed_error.rb".freeze, "lib/graphql/name_validator.rb".freeze, "lib/graphql/pagination.rb".freeze, "lib/graphql/pagination/active_record_relation_connection.rb".freeze, "lib/graphql/pagination/array_connection.rb".freeze, "lib/graphql/pagination/connection.rb".freeze, "lib/graphql/pagination/connections.rb".freeze, "lib/graphql/pagination/mongoid_relation_connection.rb".freeze, "lib/graphql/pagination/relation_connection.rb".freeze, "lib/graphql/pagination/sequel_dataset_connection.rb".freeze, "lib/graphql/parse_error.rb".freeze, "lib/graphql/query.rb".freeze, "lib/graphql/query/context.rb".freeze, "lib/graphql/query/context/scoped_context.rb".freeze, "lib/graphql/query/fingerprint.rb".freeze, "lib/graphql/query/input_validation_result.rb".freeze, "lib/graphql/query/null_context.rb".freeze, "lib/graphql/query/partial.rb".freeze, "lib/graphql/query/result.rb".freeze, "lib/graphql/query/validation_pipeline.rb".freeze, "lib/graphql/query/variable_validation_error.rb".freeze, "lib/graphql/query/variables.rb".freeze, "lib/graphql/railtie.rb".freeze, "lib/graphql/rake_task.rb".freeze, "lib/graphql/rake_task/validate.rb".freeze, "lib/graphql/relay.rb".freeze, "lib/graphql/relay/range_add.rb".freeze, "lib/graphql/rubocop.rb".freeze, "lib/graphql/rubocop/graphql/base_cop.rb".freeze, "lib/graphql/rubocop/graphql/default_null_true.rb".freeze, "lib/graphql/rubocop/graphql/default_required_true.rb".freeze, "lib/graphql/rubocop/graphql/field_type_in_block.rb".freeze, "lib/graphql/rubocop/graphql/root_types_in_block.rb".freeze, "lib/graphql/runtime_error.rb".freeze, "lib/graphql/runtime_type_error.rb".freeze, "lib/graphql/schema.rb".freeze, "lib/graphql/schema/addition.rb".freeze, "lib/graphql/schema/always_visible.rb".freeze, "lib/graphql/schema/argument.rb".freeze, "lib/graphql/schema/base_64_encoder.rb".freeze, "lib/graphql/schema/build_from_definition.rb".freeze, "lib/graphql/schema/build_from_definition/resolve_map.rb".freeze, "lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb".freeze, "lib/graphql/schema/built_in_types.rb".freeze, "lib/graphql/schema/directive.rb".freeze, "lib/graphql/schema/directive/deprecated.rb".freeze, "lib/graphql/schema/directive/feature.rb".freeze, "lib/graphql/schema/directive/flagged.rb".freeze, "lib/graphql/schema/directive/include.rb".freeze, "lib/graphql/schema/directive/one_of.rb".freeze, "lib/graphql/schema/directive/skip.rb".freeze, "lib/graphql/schema/directive/specified_by.rb".freeze, "lib/graphql/schema/directive/transform.rb".freeze, "lib/graphql/schema/enum.rb".freeze, "lib/graphql/schema/enum_value.rb".freeze, "lib/graphql/schema/field.rb".freeze, "lib/graphql/schema/field/connection_extension.rb".freeze, "lib/graphql/schema/field/scope_extension.rb".freeze, "lib/graphql/schema/field_extension.rb".freeze, "lib/graphql/schema/find_inherited_value.rb".freeze, "lib/graphql/schema/finder.rb".freeze, "lib/graphql/schema/has_single_input_argument.rb".freeze, "lib/graphql/schema/input_object.rb".freeze, "lib/graphql/schema/interface.rb".freeze, "lib/graphql/schema/introspection_system.rb".freeze, "lib/graphql/schema/late_bound_type.rb".freeze, "lib/graphql/schema/list.rb".freeze, "lib/graphql/schema/loader.rb".freeze, "lib/graphql/schema/member.rb".freeze, "lib/graphql/schema/member/base_dsl_methods.rb".freeze, "lib/graphql/schema/member/build_type.rb".freeze, "lib/graphql/schema/member/graphql_type_names.rb".freeze, "lib/graphql/schema/member/has_arguments.rb".freeze, "lib/graphql/schema/member/has_ast_node.rb".freeze, "lib/graphql/schema/member/has_authorization.rb".freeze, "lib/graphql/schema/member/has_dataloader.rb".freeze, "lib/graphql/schema/member/has_deprecation_reason.rb".freeze, "lib/graphql/schema/member/has_directives.rb".freeze, "lib/graphql/schema/member/has_fields.rb".freeze, "lib/graphql/schema/member/has_interfaces.rb".freeze, "lib/graphql/schema/member/has_path.rb".freeze, "lib/graphql/schema/member/has_unresolved_type_error.rb".freeze, "lib/graphql/schema/member/has_validators.rb".freeze, "lib/graphql/schema/member/relay_shortcuts.rb".freeze, "lib/graphql/schema/member/scoped.rb".freeze, "lib/graphql/schema/member/type_system_helpers.rb".freeze, "lib/graphql/schema/member/validates_input.rb".freeze, "lib/graphql/schema/mutation.rb".freeze, "lib/graphql/schema/non_null.rb".freeze, "lib/graphql/schema/object.rb".freeze, "lib/graphql/schema/printer.rb".freeze, "lib/graphql/schema/ractor_shareable.rb".freeze, "lib/graphql/schema/relay_classic_mutation.rb".freeze, "lib/graphql/schema/resolver.rb".freeze, "lib/graphql/schema/resolver/has_payload_type.rb".freeze, "lib/graphql/schema/scalar.rb".freeze, "lib/graphql/schema/subscription.rb".freeze, "lib/graphql/schema/timeout.rb".freeze, "lib/graphql/schema/type_expression.rb".freeze, "lib/graphql/schema/type_membership.rb".freeze, "lib/graphql/schema/union.rb".freeze, "lib/graphql/schema/unique_within_type.rb".freeze, "lib/graphql/schema/validator.rb".freeze, "lib/graphql/schema/validator/all_validator.rb".freeze, "lib/graphql/schema/validator/allow_blank_validator.rb".freeze, "lib/graphql/schema/validator/allow_null_validator.rb".freeze, "lib/graphql/schema/validator/exclusion_validator.rb".freeze, "lib/graphql/schema/validator/format_validator.rb".freeze, "lib/graphql/schema/validator/inclusion_validator.rb".freeze, "lib/graphql/schema/validator/length_validator.rb".freeze, "lib/graphql/schema/validator/numericality_validator.rb".freeze, "lib/graphql/schema/validator/required_validator.rb".freeze, "lib/graphql/schema/visibility.rb".freeze, "lib/graphql/schema/visibility/migration.rb".freeze, "lib/graphql/schema/visibility/profile.rb".freeze, "lib/graphql/schema/visibility/visit.rb".freeze, "lib/graphql/schema/warden.rb".freeze, "lib/graphql/schema/wrapper.rb".freeze, "lib/graphql/static_validation.rb".freeze, "lib/graphql/static_validation/all_rules.rb".freeze, "lib/graphql/static_validation/base_visitor.rb".freeze, "lib/graphql/static_validation/definition_dependencies.rb".freeze, "lib/graphql/static_validation/error.rb".freeze, "lib/graphql/static_validation/interpreter_visitor.rb".freeze, "lib/graphql/static_validation/literal_validator.rb".freeze, "lib/graphql/static_validation/rules/argument_literals_are_compatible.rb".freeze, "lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb".freeze, "lib/graphql/static_validation/rules/argument_names_are_unique.rb".freeze, "lib/graphql/static_validation/rules/argument_names_are_unique_error.rb".freeze, "lib/graphql/static_validation/rules/arguments_are_defined.rb".freeze, "lib/graphql/static_validation/rules/arguments_are_defined_error.rb".freeze, "lib/graphql/static_validation/rules/directives_are_defined.rb".freeze, "lib/graphql/static_validation/rules/directives_are_defined_error.rb".freeze, "lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb".freeze, "lib/graphql/static_validation/rules/directives_are_in_valid_locations_error.rb".freeze, "lib/graphql/static_validation/rules/fields_are_defined_on_type.rb".freeze, "lib/graphql/static_validation/rules/fields_are_defined_on_type_error.rb".freeze, "lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb".freeze, "lib/graphql/static_validation/rules/fields_have_appropriate_selections_error.rb".freeze, "lib/graphql/static_validation/rules/fields_will_merge.rb".freeze, "lib/graphql/static_validation/rules/fields_will_merge_error.rb".freeze, "lib/graphql/static_validation/rules/fragment_names_are_unique.rb".freeze, "lib/graphql/static_validation/rules/fragment_names_are_unique_error.rb".freeze, "lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb".freeze, "lib/graphql/static_validation/rules/fragment_spreads_are_possible_error.rb".freeze, "lib/graphql/static_validation/rules/fragment_types_exist.rb".freeze, "lib/graphql/static_validation/rules/fragment_types_exist_error.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_finite.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_finite_error.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_named.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_named_error.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_on_composite_types_error.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_used.rb".freeze, "lib/graphql/static_validation/rules/fragments_are_used_error.rb".freeze, "lib/graphql/static_validation/rules/input_object_names_are_unique.rb".freeze, "lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb".freeze, "lib/graphql/static_validation/rules/mutation_root_exists.rb".freeze, "lib/graphql/static_validation/rules/mutation_root_exists_error.rb".freeze, "lib/graphql/static_validation/rules/no_definitions_are_present.rb".freeze, "lib/graphql/static_validation/rules/no_definitions_are_present_error.rb".freeze, "lib/graphql/static_validation/rules/not_single_subscription_error.rb".freeze, "lib/graphql/static_validation/rules/one_of_input_objects_are_valid.rb".freeze, "lib/graphql/static_validation/rules/one_of_input_objects_are_valid_error.rb".freeze, "lib/graphql/static_validation/rules/operation_names_are_valid.rb".freeze, "lib/graphql/static_validation/rules/operation_names_are_valid_error.rb".freeze, "lib/graphql/static_validation/rules/query_root_exists.rb".freeze, "lib/graphql/static_validation/rules/query_root_exists_error.rb".freeze, "lib/graphql/static_validation/rules/required_arguments_are_present.rb".freeze, "lib/graphql/static_validation/rules/required_arguments_are_present_error.rb".freeze, "lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb".freeze, "lib/graphql/static_validation/rules/required_input_object_attributes_are_present_error.rb".freeze, "lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb".freeze, "lib/graphql/static_validation/rules/subscription_root_exists_error.rb".freeze, "lib/graphql/static_validation/rules/unique_directives_per_location.rb".freeze, "lib/graphql/static_validation/rules/unique_directives_per_location_error.rb".freeze, "lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb".freeze, "lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed_error.rb".freeze, "lib/graphql/static_validation/rules/variable_names_are_unique.rb".freeze, "lib/graphql/static_validation/rules/variable_names_are_unique_error.rb".freeze, "lib/graphql/static_validation/rules/variable_usages_are_allowed.rb".freeze, "lib/graphql/static_validation/rules/variable_usages_are_allowed_error.rb".freeze, "lib/graphql/static_validation/rules/variables_are_input_types.rb".freeze, "lib/graphql/static_validation/rules/variables_are_input_types_error.rb".freeze, "lib/graphql/static_validation/rules/variables_are_used_and_defined.rb".freeze, "lib/graphql/static_validation/rules/variables_are_used_and_defined_error.rb".freeze, "lib/graphql/static_validation/validation_context.rb".freeze, "lib/graphql/static_validation/validation_timeout_error.rb".freeze, "lib/graphql/static_validation/validator.rb".freeze, "lib/graphql/string_encoding_error.rb".freeze, "lib/graphql/subscriptions.rb".freeze, "lib/graphql/subscriptions/action_cable_subscriptions.rb".freeze, "lib/graphql/subscriptions/broadcast_analyzer.rb".freeze, "lib/graphql/subscriptions/default_subscription_resolve_extension.rb".freeze, "lib/graphql/subscriptions/event.rb".freeze, "lib/graphql/subscriptions/serialize.rb".freeze, "lib/graphql/testing.rb".freeze, "lib/graphql/testing/helpers.rb".freeze, "lib/graphql/testing/mock_action_cable.rb".freeze, "lib/graphql/tracing.rb".freeze, "lib/graphql/tracing/active_support_notifications_trace.rb".freeze, "lib/graphql/tracing/active_support_notifications_tracing.rb".freeze, "lib/graphql/tracing/appoptics_trace.rb".freeze, "lib/graphql/tracing/appoptics_tracing.rb".freeze, "lib/graphql/tracing/appsignal_trace.rb".freeze, "lib/graphql/tracing/appsignal_tracing.rb".freeze, "lib/graphql/tracing/call_legacy_tracers.rb".freeze, "lib/graphql/tracing/data_dog_trace.rb".freeze, "lib/graphql/tracing/data_dog_tracing.rb".freeze, "lib/graphql/tracing/detailed_trace.rb".freeze, "lib/graphql/tracing/detailed_trace/active_record_backend.rb".freeze, "lib/graphql/tracing/detailed_trace/memory_backend.rb".freeze, "lib/graphql/tracing/detailed_trace/redis_backend.rb".freeze, "lib/graphql/tracing/legacy_hooks_trace.rb".freeze, "lib/graphql/tracing/legacy_trace.rb".freeze, "lib/graphql/tracing/monitor_trace.rb".freeze, "lib/graphql/tracing/new_relic_trace.rb".freeze, "lib/graphql/tracing/new_relic_tracing.rb".freeze, "lib/graphql/tracing/notifications_trace.rb".freeze, "lib/graphql/tracing/notifications_tracing.rb".freeze, "lib/graphql/tracing/null_trace.rb".freeze, "lib/graphql/tracing/perfetto_trace.rb".freeze, "lib/graphql/tracing/perfetto_trace/trace.proto".freeze, "lib/graphql/tracing/perfetto_trace/trace_pb.rb".freeze, "lib/graphql/tracing/platform_trace.rb".freeze, "lib/graphql/tracing/platform_tracing.rb".freeze, "lib/graphql/tracing/prometheus_trace.rb".freeze, "lib/graphql/tracing/prometheus_trace/graphql_collector.rb".freeze, "lib/graphql/tracing/prometheus_tracing.rb".freeze, "lib/graphql/tracing/scout_trace.rb".freeze, "lib/graphql/tracing/scout_tracing.rb".freeze, "lib/graphql/tracing/sentry_trace.rb".freeze, "lib/graphql/tracing/statsd_trace.rb".freeze, "lib/graphql/tracing/statsd_tracing.rb".freeze, "lib/graphql/tracing/trace.rb".freeze, "lib/graphql/type_kinds.rb".freeze, "lib/graphql/types.rb".freeze, "lib/graphql/types/big_int.rb".freeze, "lib/graphql/types/boolean.rb".freeze, "lib/graphql/types/float.rb".freeze, "lib/graphql/types/id.rb".freeze, "lib/graphql/types/int.rb".freeze, "lib/graphql/types/iso_8601_date.rb".freeze, "lib/graphql/types/iso_8601_date_time.rb".freeze, "lib/graphql/types/iso_8601_duration.rb".freeze, "lib/graphql/types/json.rb".freeze, "lib/graphql/types/relay.rb".freeze, "lib/graphql/types/relay/base_connection.rb".freeze, "lib/graphql/types/relay/base_edge.rb".freeze, "lib/graphql/types/relay/connection_behaviors.rb".freeze, "lib/graphql/types/relay/edge_behaviors.rb".freeze, "lib/graphql/types/relay/has_node_field.rb".freeze, "lib/graphql/types/relay/has_nodes_field.rb".freeze, "lib/graphql/types/relay/node.rb".freeze, "lib/graphql/types/relay/node_behaviors.rb".freeze, "lib/graphql/types/relay/page_info.rb".freeze, "lib/graphql/types/relay/page_info_behaviors.rb".freeze, "lib/graphql/types/string.rb".freeze, "lib/graphql/unauthorized_enum_value_error.rb".freeze, "lib/graphql/unauthorized_error.rb".freeze, "lib/graphql/unauthorized_field_error.rb".freeze, "lib/graphql/unresolved_type_error.rb".freeze, "lib/graphql/version.rb".freeze, "readme.md".freeze] s.homepage = "https://github.com/rmosolgo/graphql-ruby".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.7.0".freeze) s.rubygems_version = "4.0.6".freeze s.summary = "A GraphQL language and runtime for Ruby".freeze s.specification_version = 4 s.add_runtime_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) s.add_runtime_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, ["~> 2.2".freeze]) s.add_runtime_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, ["~> 1.5.0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) s.add_development_dependency(%q.freeze, [">= 0".freeze]) end