graphql 1.11.3 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +8 -0
  3. data/lib/generators/graphql/install_generator.rb +5 -5
  4. data/lib/generators/graphql/object_generator.rb +2 -0
  5. data/lib/generators/graphql/relay_generator.rb +63 -0
  6. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  7. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  8. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  9. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  10. data/lib/generators/graphql/templates/base_field.erb +2 -0
  11. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  12. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  13. data/lib/generators/graphql/templates/base_mutation.erb +2 -0
  14. data/lib/generators/graphql/templates/base_object.erb +2 -0
  15. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  16. data/lib/generators/graphql/templates/base_union.erb +2 -0
  17. data/lib/generators/graphql/templates/enum.erb +2 -0
  18. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  19. data/lib/generators/graphql/templates/interface.erb +2 -0
  20. data/lib/generators/graphql/templates/loader.erb +2 -0
  21. data/lib/generators/graphql/templates/mutation.erb +2 -0
  22. data/lib/generators/graphql/templates/mutation_type.erb +2 -0
  23. data/lib/generators/graphql/templates/node_type.erb +9 -0
  24. data/lib/generators/graphql/templates/object.erb +3 -1
  25. data/lib/generators/graphql/templates/query_type.erb +3 -3
  26. data/lib/generators/graphql/templates/scalar.erb +2 -0
  27. data/lib/generators/graphql/templates/schema.erb +10 -35
  28. data/lib/generators/graphql/templates/union.erb +3 -1
  29. data/lib/graphql.rb +55 -4
  30. data/lib/graphql/analysis/analyze_query.rb +7 -0
  31. data/lib/graphql/analysis/ast.rb +11 -2
  32. data/lib/graphql/analysis/ast/visitor.rb +9 -1
  33. data/lib/graphql/argument.rb +3 -3
  34. data/lib/graphql/backtrace.rb +28 -19
  35. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  36. data/lib/graphql/backtrace/table.rb +22 -2
  37. data/lib/graphql/backtrace/tracer.rb +40 -8
  38. data/lib/graphql/backwards_compatibility.rb +1 -0
  39. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  40. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  41. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  42. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  43. data/lib/graphql/dataloader.rb +197 -0
  44. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  45. data/lib/graphql/dataloader/request.rb +24 -0
  46. data/lib/graphql/dataloader/request_all.rb +22 -0
  47. data/lib/graphql/dataloader/source.rb +93 -0
  48. data/lib/graphql/define/assign_global_id_field.rb +2 -2
  49. data/lib/graphql/define/instance_definable.rb +32 -2
  50. data/lib/graphql/define/type_definer.rb +5 -5
  51. data/lib/graphql/deprecated_dsl.rb +5 -0
  52. data/lib/graphql/enum_type.rb +2 -0
  53. data/lib/graphql/execution/errors.rb +4 -0
  54. data/lib/graphql/execution/execute.rb +7 -0
  55. data/lib/graphql/execution/interpreter.rb +20 -6
  56. data/lib/graphql/execution/interpreter/arguments.rb +57 -5
  57. data/lib/graphql/execution/interpreter/arguments_cache.rb +8 -0
  58. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  59. data/lib/graphql/execution/interpreter/runtime.rb +251 -138
  60. data/lib/graphql/execution/multiplex.rb +20 -6
  61. data/lib/graphql/function.rb +4 -0
  62. data/lib/graphql/input_object_type.rb +2 -0
  63. data/lib/graphql/integer_decoding_error.rb +17 -0
  64. data/lib/graphql/interface_type.rb +3 -1
  65. data/lib/graphql/introspection.rb +96 -0
  66. data/lib/graphql/introspection/field_type.rb +7 -3
  67. data/lib/graphql/introspection/input_value_type.rb +6 -0
  68. data/lib/graphql/introspection/introspection_query.rb +6 -92
  69. data/lib/graphql/introspection/type_type.rb +7 -3
  70. data/lib/graphql/invalid_null_error.rb +1 -1
  71. data/lib/graphql/language/block_string.rb +24 -5
  72. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  73. data/lib/graphql/language/lexer.rb +7 -3
  74. data/lib/graphql/language/lexer.rl +7 -3
  75. data/lib/graphql/language/nodes.rb +1 -1
  76. data/lib/graphql/language/parser.rb +107 -103
  77. data/lib/graphql/language/parser.y +4 -0
  78. data/lib/graphql/language/sanitized_printer.rb +59 -26
  79. data/lib/graphql/name_validator.rb +6 -7
  80. data/lib/graphql/object_type.rb +2 -0
  81. data/lib/graphql/pagination/connection.rb +5 -1
  82. data/lib/graphql/pagination/connections.rb +15 -17
  83. data/lib/graphql/query.rb +8 -3
  84. data/lib/graphql/query/context.rb +38 -4
  85. data/lib/graphql/query/fingerprint.rb +2 -0
  86. data/lib/graphql/query/serial_execution.rb +1 -0
  87. data/lib/graphql/query/validation_pipeline.rb +4 -1
  88. data/lib/graphql/relay/array_connection.rb +2 -2
  89. data/lib/graphql/relay/base_connection.rb +7 -0
  90. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  91. data/lib/graphql/relay/connection_type.rb +1 -1
  92. data/lib/graphql/relay/mutation.rb +1 -0
  93. data/lib/graphql/relay/node.rb +3 -0
  94. data/lib/graphql/relay/range_add.rb +14 -5
  95. data/lib/graphql/relay/type_extensions.rb +2 -0
  96. data/lib/graphql/scalar_type.rb +2 -0
  97. data/lib/graphql/schema.rb +107 -38
  98. data/lib/graphql/schema/argument.rb +74 -5
  99. data/lib/graphql/schema/build_from_definition.rb +203 -86
  100. data/lib/graphql/schema/default_type_error.rb +2 -0
  101. data/lib/graphql/schema/directive.rb +76 -0
  102. data/lib/graphql/schema/directive/deprecated.rb +1 -1
  103. data/lib/graphql/schema/directive/flagged.rb +57 -0
  104. data/lib/graphql/schema/enum.rb +3 -0
  105. data/lib/graphql/schema/enum_value.rb +12 -6
  106. data/lib/graphql/schema/field.rb +59 -24
  107. data/lib/graphql/schema/field/connection_extension.rb +11 -9
  108. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  109. data/lib/graphql/schema/input_object.rb +38 -25
  110. data/lib/graphql/schema/interface.rb +2 -1
  111. data/lib/graphql/schema/late_bound_type.rb +2 -2
  112. data/lib/graphql/schema/loader.rb +1 -0
  113. data/lib/graphql/schema/member.rb +4 -0
  114. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  115. data/lib/graphql/schema/member/build_type.rb +17 -7
  116. data/lib/graphql/schema/member/has_arguments.rb +70 -51
  117. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  118. data/lib/graphql/schema/member/has_directives.rb +98 -0
  119. data/lib/graphql/schema/member/has_fields.rb +2 -2
  120. data/lib/graphql/schema/member/has_validators.rb +31 -0
  121. data/lib/graphql/schema/member/type_system_helpers.rb +3 -3
  122. data/lib/graphql/schema/object.rb +11 -0
  123. data/lib/graphql/schema/printer.rb +5 -4
  124. data/lib/graphql/schema/relay_classic_mutation.rb +4 -2
  125. data/lib/graphql/schema/resolver.rb +7 -0
  126. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  127. data/lib/graphql/schema/subscription.rb +20 -12
  128. data/lib/graphql/schema/timeout.rb +29 -15
  129. data/lib/graphql/schema/timeout_middleware.rb +2 -0
  130. data/lib/graphql/schema/unique_within_type.rb +1 -2
  131. data/lib/graphql/schema/validation.rb +10 -0
  132. data/lib/graphql/schema/validator.rb +163 -0
  133. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  134. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  135. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  136. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  137. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  138. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  139. data/lib/graphql/schema/warden.rb +2 -3
  140. data/lib/graphql/static_validation.rb +1 -0
  141. data/lib/graphql/static_validation/all_rules.rb +1 -0
  142. data/lib/graphql/static_validation/rules/fields_will_merge.rb +25 -17
  143. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  144. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  145. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  146. data/lib/graphql/static_validation/validator.rb +31 -7
  147. data/lib/graphql/subscriptions.rb +23 -16
  148. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +21 -7
  149. data/lib/graphql/tracing.rb +2 -2
  150. data/lib/graphql/tracing/appoptics_tracing.rb +12 -2
  151. data/lib/graphql/tracing/platform_tracing.rb +4 -2
  152. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  153. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  154. data/lib/graphql/types/int.rb +9 -2
  155. data/lib/graphql/types/iso_8601_date_time.rb +2 -1
  156. data/lib/graphql/types/relay.rb +11 -3
  157. data/lib/graphql/types/relay/base_connection.rb +2 -90
  158. data/lib/graphql/types/relay/base_edge.rb +2 -34
  159. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  160. data/lib/graphql/types/relay/default_relay.rb +27 -0
  161. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  162. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  163. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  164. data/lib/graphql/types/relay/node.rb +2 -4
  165. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  166. data/lib/graphql/types/relay/node_field.rb +1 -19
  167. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  168. data/lib/graphql/types/relay/page_info.rb +2 -14
  169. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  170. data/lib/graphql/types/string.rb +7 -1
  171. data/lib/graphql/unauthorized_error.rb +1 -1
  172. data/lib/graphql/union_type.rb +2 -0
  173. data/lib/graphql/upgrader/member.rb +1 -0
  174. data/lib/graphql/upgrader/schema.rb +1 -0
  175. data/lib/graphql/version.rb +1 -1
  176. data/readme.md +1 -1
  177. metadata +38 -9
  178. data/lib/graphql/types/relay/base_field.rb +0 -22
  179. data/lib/graphql/types/relay/base_interface.rb +0 -29
  180. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -82,9 +82,9 @@ module GraphQL
82
82
  end
83
83
  end
84
84
 
85
- def global_id_field(field_name)
85
+ def global_id_field(field_name, **kwargs)
86
86
  id_resolver = GraphQL::Relay::GlobalIdResolve.new(type: self)
87
- field field_name, "ID", null: false
87
+ field field_name, "ID", **kwargs, null: false
88
88
  define_method(field_name) do
89
89
  id_resolver.call(object, {}, context)
90
90
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Member
5
+ module HasValidators
6
+ include Schema::FindInheritedValue::EmptyObjects
7
+
8
+ # Build {GraphQL::Schema::Validator}s based on the given configuration
9
+ # and use them for this schema member
10
+ # @param validation_config [Hash{Symbol => Hash}]
11
+ # @return [void]
12
+ def validates(validation_config)
13
+ new_validators = GraphQL::Schema::Validator.from_config(self, validation_config)
14
+ @own_validators ||= []
15
+ @own_validators.concat(new_validators)
16
+ nil
17
+ end
18
+
19
+ # @return [Array<GraphQL::Schema::Validator>]
20
+ def validators
21
+ own_validators = @own_validators || EMPTY_ARRAY
22
+ if self.is_a?(Class) && superclass.respond_to?(:validators) && (inherited_validators = superclass.validators).any?
23
+ inherited_validators + own_validators
24
+ else
25
+ own_validators
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -6,12 +6,12 @@ module GraphQL
6
6
  module TypeSystemHelpers
7
7
  # @return [Schema::NonNull] Make a non-null-type representation of this type
8
8
  def to_non_null_type
9
- GraphQL::Schema::NonNull.new(self)
9
+ @to_non_null_type ||= GraphQL::Schema::NonNull.new(self)
10
10
  end
11
11
 
12
12
  # @return [Schema::List] Make a list-type representation of this type
13
13
  def to_list_type
14
- GraphQL::Schema::List.new(self)
14
+ @to_list_type ||= GraphQL::Schema::List.new(self)
15
15
  end
16
16
 
17
17
  # @return [Boolean] true if this is a non-nullable type. A nullable list of non-nullables is considered nullable.
@@ -30,7 +30,7 @@ module GraphQL
30
30
 
31
31
  # @return [GraphQL::TypeKinds::TypeKind]
32
32
  def kind
33
- raise GraphQL::RequiredImplementationMissingError
33
+ raise GraphQL::RequiredImplementationMissingError, "No `.kind` defined for #{self}"
34
34
  end
35
35
  end
36
36
  end
@@ -14,6 +14,17 @@ module GraphQL
14
14
  # @return [GraphQL::Query::Context] the context instance for this query
15
15
  attr_reader :context
16
16
 
17
+ # @return [GraphQL::Dataloader]
18
+ def dataloader
19
+ context.dataloader
20
+ end
21
+
22
+ # Call this in a field method to return a value that should be returned to the client
23
+ # without any further handling by GraphQL.
24
+ def raw_value(obj)
25
+ GraphQL::Execution::Interpreter::RawValue.new(obj)
26
+ end
27
+
17
28
  class << self
18
29
  # This is protected so that we can be sure callers use the public method, {.authorized_new}
19
30
  # @see authorized_new to make instances
@@ -59,14 +59,15 @@ module GraphQL
59
59
 
60
60
  # Return the GraphQL schema string for the introspection type system
61
61
  def self.print_introspection_schema
62
- query_root = ObjectType.define(name: "Root") do
63
- field :throwaway_field, types.String
62
+ query_root = Class.new(GraphQL::Schema::Object) do
63
+ graphql_name "Root"
64
+ field :throwaway_field, String, null: true
64
65
  end
65
- schema = GraphQL::Schema.define(query: query_root)
66
+ schema = Class.new(GraphQL::Schema) { query(query_root) }
66
67
 
67
68
  introspection_schema_ast = GraphQL::Language::DocumentFromSchemaDefinition.new(
68
69
  schema,
69
- except: ->(member, _) { member.name == "Root" },
70
+ except: ->(member, _) { member.graphql_name == "Root" },
70
71
  include_introspection_types: true,
71
72
  include_built_in_directives: true,
72
73
  ).document
@@ -105,7 +105,7 @@ module GraphQL
105
105
  sig = super
106
106
  # Arguments were added at the root, but they should be nested
107
107
  sig[:arguments].clear
108
- sig[:arguments][:input] = { type: input_type, required: true }
108
+ sig[:arguments][:input] = { type: input_type, required: true, description: "Parameters for #{graphql_name}" }
109
109
  sig
110
110
  end
111
111
 
@@ -122,7 +122,9 @@ module GraphQL
122
122
  graphql_name("#{mutation_name}Input")
123
123
  description("Autogenerated input type of #{mutation_name}")
124
124
  mutation(mutation_class)
125
- own_arguments.merge!(mutation_args)
125
+ mutation_args.each do |_name, arg|
126
+ add_argument(arg)
127
+ end
126
128
  argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
127
129
  end
128
130
  end
@@ -24,6 +24,7 @@ module GraphQL
24
24
  # Really we only need description from here, but:
25
25
  extend Schema::Member::BaseDSLMethods
26
26
  extend GraphQL::Schema::Member::HasArguments
27
+ extend GraphQL::Schema::Member::HasValidators
27
28
  include Schema::Member::HasPath
28
29
  extend Schema::Member::HasPath
29
30
 
@@ -49,6 +50,11 @@ module GraphQL
49
50
  # @return [GraphQL::Query::Context]
50
51
  attr_reader :context
51
52
 
53
+ # @return [GraphQL::Dataloader]
54
+ def dataloader
55
+ context.dataloader
56
+ end
57
+
52
58
  # @return [GraphQL::Schema::Field]
53
59
  attr_reader :field
54
60
 
@@ -80,6 +86,7 @@ module GraphQL
80
86
  load_arguments_val = load_arguments(args)
81
87
  context.schema.after_lazy(load_arguments_val) do |loaded_args|
82
88
  @prepared_arguments = loaded_args
89
+ Schema::Validator.validate!(self.class.validators, object, context, loaded_args, as: @field)
83
90
  # Then call `authorized?`, which may raise or may return a lazy object
84
91
  authorized_val = if loaded_args.any?
85
92
  authorized?(**loaded_args)
@@ -44,6 +44,8 @@ module GraphQL
44
44
  end
45
45
  end
46
46
 
47
+ NO_INTERFACES = [].freeze
48
+
47
49
  private
48
50
 
49
51
  # Build a subclass of {.object_class} based on `self`.
@@ -12,16 +12,6 @@ module GraphQL
12
12
  #
13
13
  # Also, `#unsubscribe` terminates the subscription.
14
14
  class Subscription < GraphQL::Schema::Resolver
15
- class EarlyTerminationError < StandardError
16
- end
17
-
18
- # Raised when `unsubscribe` is called; caught by `subscriptions.rb`
19
- class UnsubscribedError < EarlyTerminationError
20
- end
21
-
22
- # Raised when `no_update` is returned; caught by `subscriptions.rb`
23
- class NoUpdateError < EarlyTerminationError
24
- end
25
15
  extend GraphQL::Schema::Resolver::HasPayloadType
26
16
  extend GraphQL::Schema::Member::HasFields
27
17
 
@@ -35,6 +25,22 @@ module GraphQL
35
25
  @mode = context.query.subscription_update? ? :update : :subscribe
36
26
  end
37
27
 
28
+ def resolve_with_support(**args)
29
+ result = nil
30
+ unsubscribed = true
31
+ catch :graphql_subscription_unsubscribed do
32
+ result = super
33
+ unsubscribed = false
34
+ end
35
+
36
+
37
+ if unsubscribed
38
+ context.skip
39
+ else
40
+ result
41
+ end
42
+ end
43
+
38
44
  # Implement the {Resolve} API
39
45
  def resolve(**args)
40
46
  # Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever
@@ -65,7 +71,8 @@ module GraphQL
65
71
  def resolve_update(**args)
66
72
  ret_val = args.any? ? update(**args) : update
67
73
  if ret_val == :no_update
68
- raise NoUpdateError
74
+ context.namespace(:subscriptions)[:no_update] = true
75
+ context.skip
69
76
  else
70
77
  ret_val
71
78
  end
@@ -90,7 +97,8 @@ module GraphQL
90
97
 
91
98
  # Call this to halt execution and remove this subscription from the system
92
99
  def unsubscribe
93
- raise UnsubscribedError
100
+ context.namespace(:subscriptions)[:unsubscribed] = true
101
+ throw :graphql_subscription_unsubscribed
94
102
  end
95
103
 
96
104
  READING_SCOPE = ::Object.new
@@ -7,7 +7,7 @@ module GraphQL
7
7
  # to the `errors` key. Any already-resolved fields will be in the `data` key, so
8
8
  # you'll get a partial response.
9
9
  #
10
- # You can subclass `GraphQL::Schema::Timeout` and override the `handle_timeout` method
10
+ # You can subclass `GraphQL::Schema::Timeout` and override `max_seconds` and/or `handle_timeout`
11
11
  # to provide custom logic when a timeout error occurs.
12
12
  #
13
13
  # Note that this will stop a query _in between_ field resolutions, but
@@ -33,8 +33,6 @@ module GraphQL
33
33
  # end
34
34
  #
35
35
  class Timeout
36
- attr_reader :max_seconds
37
-
38
36
  def self.use(schema, **options)
39
37
  tracer = new(**options)
40
38
  schema.tracer(tracer)
@@ -48,32 +46,39 @@ module GraphQL
48
46
  def trace(key, data)
49
47
  case key
50
48
  when 'execute_multiplex'
51
- timeout_state = {
52
- timeout_at: Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) + max_seconds * 1000,
53
- timed_out: false
54
- }
55
-
56
49
  data.fetch(:multiplex).queries.each do |query|
50
+ timeout_duration_s = max_seconds(query)
51
+ timeout_state = if timeout_duration_s == false
52
+ # if the method returns `false`, don't apply a timeout
53
+ false
54
+ else
55
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
56
+ timeout_at = now + (max_seconds(query) * 1000)
57
+ {
58
+ timeout_at: timeout_at,
59
+ timed_out: false
60
+ }
61
+ end
57
62
  query.context.namespace(self.class)[:state] = timeout_state
58
63
  end
59
64
 
60
65
  yield
61
66
  when 'execute_field', 'execute_field_lazy'
62
- query = data[:context] ? data.fetch(:context).query : data.fetch(:query)
63
- timeout_state = query.context.namespace(self.class).fetch(:state)
64
- if Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
67
+ query_context = data[:context] || data[:query].context
68
+ timeout_state = query_context.namespace(self.class).fetch(:state)
69
+ # If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query.
70
+ if timeout_state != false && Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) > timeout_state.fetch(:timeout_at)
65
71
  error = if data[:context]
66
- context = data.fetch(:context)
67
- GraphQL::Schema::Timeout::TimeoutError.new(context.parent_type, context.field)
72
+ GraphQL::Schema::Timeout::TimeoutError.new(query_context.parent_type, query_context.field)
68
73
  else
69
74
  field = data.fetch(:field)
70
75
  GraphQL::Schema::Timeout::TimeoutError.new(field.owner, field)
71
76
  end
72
77
 
73
78
  # Only invoke the timeout callback for the first timeout
74
- unless timeout_state[:timed_out]
79
+ if !timeout_state[:timed_out]
75
80
  timeout_state[:timed_out] = true
76
- handle_timeout(error, query)
81
+ handle_timeout(error, query_context.query)
77
82
  end
78
83
 
79
84
  error
@@ -85,6 +90,15 @@ module GraphQL
85
90
  end
86
91
  end
87
92
 
93
+ # Called at the start of each query.
94
+ # The default implementation returns the `max_seconds:` value from installing this plugin.
95
+ #
96
+ # @param query [GraphQL::Query] The query that's about to run
97
+ # @return [Integer, false] The number of seconds after which to interrupt query execution and call {#handle_error}, or `false` to bypass the timeout.
98
+ def max_seconds(query)
99
+ @max_seconds
100
+ end
101
+
88
102
  # Invoked when a query times out.
89
103
  # @param error [GraphQL::Schema::Timeout::TimeoutError]
90
104
  # @param query [GraphQL::Error]
@@ -23,6 +23,8 @@ module GraphQL
23
23
  # Bugsnag.notify(timeout_error, {query_string: query_ctx.query.query_string})
24
24
  # end
25
25
  #
26
+ # @api deprecated
27
+ # @see Schema::Timeout
26
28
  class TimeoutMiddleware
27
29
  # @param max_seconds [Numeric] how many seconds the query should be allowed to resolve new fields
28
30
  def initialize(max_seconds:, context_key: nil, &block)
@@ -27,8 +27,7 @@ module GraphQL
27
27
  # @param node_id [String] A unique ID generated by {.encode}
28
28
  # @return [Array<(String, String)>] The type name & value passed to {.encode}
29
29
  def decode(node_id, separator: self.default_id_separator)
30
- # urlsafe_decode64 is for forward compatibility
31
- Base64Bp.urlsafe_decode64(node_id).split(separator, 2)
30
+ GraphQL::Schema::Base64Encoder.decode(node_id).split(separator, 2)
32
31
  end
33
32
  end
34
33
  end
@@ -6,6 +6,8 @@ module GraphQL
6
6
  # Its {RULES} contain objects that respond to `#call(type)`. Rules are
7
7
  # looked up for given types (by class ancestry), then applied to
8
8
  # the object until an error is returned.
9
+ #
10
+ # Remove this in GraphQL-Ruby 2.0 when schema instances are removed.
9
11
  class Validation
10
12
  # Lookup the rules for `object` based on its class,
11
13
  # Then returns an error message or `nil`
@@ -133,6 +135,12 @@ module GraphQL
133
135
  end
134
136
  }
135
137
 
138
+ DEPRECATED_ARGUMENTS_ARE_OPTIONAL = ->(argument) {
139
+ if argument.deprecation_reason && argument.type.non_null?
140
+ "must be optional because it's deprecated"
141
+ end
142
+ }
143
+
136
144
  TYPE_IS_VALID_INPUT_TYPE = ->(type) {
137
145
  outer_type = type.type
138
146
  inner_type = outer_type.respond_to?(:unwrap) ? outer_type.unwrap : nil
@@ -265,8 +273,10 @@ module GraphQL
265
273
  Rules::NAME_IS_STRING,
266
274
  Rules::RESERVED_NAME,
267
275
  Rules::DESCRIPTION_IS_STRING_OR_NIL,
276
+ Rules.assert_property(:deprecation_reason, String, NilClass),
268
277
  Rules::TYPE_IS_VALID_INPUT_TYPE,
269
278
  Rules::DEFAULT_VALUE_IS_VALID_FOR_TYPE,
279
+ Rules::DEPRECATED_ARGUMENTS_ARE_OPTIONAL,
270
280
  ],
271
281
  GraphQL::BaseType => [
272
282
  Rules::NAME_IS_STRING,
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Validator
6
+ # The thing being validated
7
+ # @return [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class<GraphQL::Schema::InputObject>]
8
+ attr_reader :validated
9
+
10
+ # TODO should this implement `if:` and `unless:` ?
11
+ # @param validated [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class<GraphQL::Schema::InputObject>] The argument or argument owner this validator is attached to
12
+ # @param allow_blank [Boolean] if `true`, then objects that respond to `.blank?` and return true for `.blank?` will skip this validation
13
+ # @param allow_null [Boolean] if `true`, then incoming `null`s will skip this validation
14
+ def initialize(validated:, allow_blank: false, allow_null: false)
15
+ @validated = validated
16
+ @allow_blank = allow_blank
17
+ @allow_null = allow_null
18
+ end
19
+
20
+ # @param object [Object] The application object that this argument's field is being resolved for
21
+ # @param context [GraphQL::Query::Context]
22
+ # @param value [Object] The client-provided value for this argument (after parsing and coercing by the input type)
23
+ # @return [nil, Array<String>, String] Error message or messages to add
24
+ def validate(object, context, value)
25
+ raise GraphQL::RequiredImplementationMissingError, "Validator classes should implement #validate"
26
+ end
27
+
28
+ # This is called by the validation system and eventually calls {#validate}.
29
+ # @api private
30
+ def apply(object, context, value)
31
+ if value.nil?
32
+ if @allow_null
33
+ nil # skip this
34
+ else
35
+ "%{validated} can't be null"
36
+ end
37
+ elsif value.respond_to?(:blank?) && value.blank?
38
+ if @allow_blank
39
+ nil # skip this
40
+ else
41
+ "%{validated} can't be blank"
42
+ end
43
+ else
44
+ validate(object, context, value)
45
+ end
46
+ end
47
+
48
+ # This is like `String#%`, but it supports the case that only some of `string`'s
49
+ # values are present in `substitutions`
50
+ def partial_format(string, substitutions)
51
+ substitutions.each do |key, value|
52
+ sub_v = value.is_a?(String) ? value : value.to_s
53
+ string = string.gsub("%{#{key}}", sub_v)
54
+ end
55
+ string
56
+ end
57
+
58
+ # @param schema_member [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class<GraphQL::Schema::InputObject>]
59
+ # @param validates_hash [Hash{Symbol => Hash}, Hash{Class => Hash} nil] A configuration passed as `validates:`
60
+ # @return [Array<Validator>]
61
+ def self.from_config(schema_member, validates_hash)
62
+ if validates_hash.nil? || validates_hash.empty?
63
+ EMPTY_ARRAY
64
+ else
65
+ validates_hash.map do |validator_name, options|
66
+ validator_class = case validator_name
67
+ when Class
68
+ validator_name
69
+ else
70
+ all_validators[validator_name] || raise(ArgumentError, "unknown validation: #{validator_name.inspect}")
71
+ end
72
+ validator_class.new(validated: schema_member, **options)
73
+ end
74
+ end
75
+ end
76
+
77
+ # Add `validator_class` to be initialized when `validates:` is given `name`.
78
+ # (It's initialized with whatever options are given by the key `name`).
79
+ # @param name [Symbol]
80
+ # @param validator_class [Class]
81
+ # @return [void]
82
+ def self.install(name, validator_class)
83
+ all_validators[name] = validator_class
84
+ nil
85
+ end
86
+
87
+ # Remove whatever validator class is {.install}ed at `name`, if there is one
88
+ # @param name [Symbol]
89
+ # @return [void]
90
+ def self.uninstall(name)
91
+ all_validators.delete(name)
92
+ nil
93
+ end
94
+
95
+ class << self
96
+ attr_accessor :all_validators
97
+ end
98
+
99
+ self.all_validators = {}
100
+
101
+ include Schema::FindInheritedValue::EmptyObjects
102
+
103
+ class ValidationFailedError < GraphQL::ExecutionError
104
+ attr_reader :errors
105
+
106
+ def initialize(errors:)
107
+ @errors = errors
108
+ super(errors.join(", "))
109
+ end
110
+ end
111
+
112
+ # @param validators [Array<Validator>]
113
+ # @param object [Object]
114
+ # @param context [Query::Context]
115
+ # @param value [Object]
116
+ # @return [void]
117
+ # @raises [ValidationFailedError]
118
+ def self.validate!(validators, object, context, value, as: nil)
119
+ # Assuming the default case is no errors, reduce allocations in that case.
120
+ # This will be replaced with a mutable array if we actually get any errors.
121
+ all_errors = EMPTY_ARRAY
122
+
123
+ validators.each do |validator|
124
+ validated = as || validator.validated
125
+ errors = validator.apply(object, context, value)
126
+ if errors &&
127
+ (errors.is_a?(Array) && errors != EMPTY_ARRAY) ||
128
+ (errors.is_a?(String))
129
+ if all_errors.frozen? # It's empty
130
+ all_errors = []
131
+ end
132
+ interpolation_vars = { validated: validated.graphql_name }
133
+ if errors.is_a?(String)
134
+ all_errors << (errors % interpolation_vars)
135
+ else
136
+ errors = errors.map { |e| e % interpolation_vars }
137
+ all_errors.concat(errors)
138
+ end
139
+ end
140
+ end
141
+
142
+ if all_errors.any?
143
+ raise ValidationFailedError.new(errors: all_errors)
144
+ end
145
+ nil
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+
152
+ require "graphql/schema/validator/length_validator"
153
+ GraphQL::Schema::Validator.install(:length, GraphQL::Schema::Validator::LengthValidator)
154
+ require "graphql/schema/validator/numericality_validator"
155
+ GraphQL::Schema::Validator.install(:numericality, GraphQL::Schema::Validator::NumericalityValidator)
156
+ require "graphql/schema/validator/format_validator"
157
+ GraphQL::Schema::Validator.install(:format, GraphQL::Schema::Validator::FormatValidator)
158
+ require "graphql/schema/validator/inclusion_validator"
159
+ GraphQL::Schema::Validator.install(:inclusion, GraphQL::Schema::Validator::InclusionValidator)
160
+ require "graphql/schema/validator/exclusion_validator"
161
+ GraphQL::Schema::Validator.install(:exclusion, GraphQL::Schema::Validator::ExclusionValidator)
162
+ require "graphql/schema/validator/required_validator"
163
+ GraphQL::Schema::Validator.install(:required, GraphQL::Schema::Validator::RequiredValidator)