graphql 1.11.10 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +5 -5
  3. data/lib/generators/graphql/relay_generator.rb +63 -0
  4. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  5. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  6. data/lib/generators/graphql/templates/node_type.erb +9 -0
  7. data/lib/generators/graphql/templates/object.erb +1 -1
  8. data/lib/generators/graphql/templates/query_type.erb +1 -3
  9. data/lib/generators/graphql/templates/schema.erb +8 -35
  10. data/lib/graphql/analysis/analyze_query.rb +7 -0
  11. data/lib/graphql/analysis/ast/visitor.rb +9 -1
  12. data/lib/graphql/analysis/ast.rb +11 -2
  13. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  14. data/lib/graphql/backtrace/table.rb +22 -2
  15. data/lib/graphql/backtrace/tracer.rb +40 -9
  16. data/lib/graphql/backtrace.rb +28 -19
  17. data/lib/graphql/backwards_compatibility.rb +1 -0
  18. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  19. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  20. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  21. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  22. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  23. data/lib/graphql/dataloader/request.rb +24 -0
  24. data/lib/graphql/dataloader/request_all.rb +22 -0
  25. data/lib/graphql/dataloader/source.rb +93 -0
  26. data/lib/graphql/dataloader.rb +197 -0
  27. data/lib/graphql/define/assign_global_id_field.rb +1 -1
  28. data/lib/graphql/define/instance_definable.rb +32 -2
  29. data/lib/graphql/define/type_definer.rb +5 -5
  30. data/lib/graphql/deprecated_dsl.rb +5 -0
  31. data/lib/graphql/enum_type.rb +2 -0
  32. data/lib/graphql/execution/errors.rb +4 -0
  33. data/lib/graphql/execution/execute.rb +7 -0
  34. data/lib/graphql/execution/interpreter/arguments.rb +51 -14
  35. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  36. data/lib/graphql/execution/interpreter/runtime.rb +210 -124
  37. data/lib/graphql/execution/interpreter.rb +10 -6
  38. data/lib/graphql/execution/multiplex.rb +20 -6
  39. data/lib/graphql/function.rb +4 -0
  40. data/lib/graphql/input_object_type.rb +2 -0
  41. data/lib/graphql/interface_type.rb +3 -1
  42. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  43. data/lib/graphql/object_type.rb +2 -0
  44. data/lib/graphql/pagination/connection.rb +5 -1
  45. data/lib/graphql/pagination/connections.rb +6 -16
  46. data/lib/graphql/query/context.rb +4 -0
  47. data/lib/graphql/query/serial_execution.rb +1 -0
  48. data/lib/graphql/query/validation_pipeline.rb +1 -1
  49. data/lib/graphql/query.rb +2 -0
  50. data/lib/graphql/relay/base_connection.rb +7 -0
  51. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  52. data/lib/graphql/relay/connection_type.rb +1 -1
  53. data/lib/graphql/relay/mutation.rb +1 -0
  54. data/lib/graphql/relay/node.rb +3 -0
  55. data/lib/graphql/relay/type_extensions.rb +2 -0
  56. data/lib/graphql/scalar_type.rb +2 -0
  57. data/lib/graphql/schema/argument.rb +25 -7
  58. data/lib/graphql/schema/build_from_definition.rb +139 -51
  59. data/lib/graphql/schema/directive/flagged.rb +57 -0
  60. data/lib/graphql/schema/directive.rb +76 -0
  61. data/lib/graphql/schema/enum.rb +3 -0
  62. data/lib/graphql/schema/enum_value.rb +12 -6
  63. data/lib/graphql/schema/field/connection_extension.rb +3 -2
  64. data/lib/graphql/schema/field.rb +28 -9
  65. data/lib/graphql/schema/input_object.rb +33 -22
  66. data/lib/graphql/schema/interface.rb +1 -0
  67. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  68. data/lib/graphql/schema/member/build_type.rb +3 -3
  69. data/lib/graphql/schema/member/has_arguments.rb +24 -6
  70. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  71. data/lib/graphql/schema/member/has_directives.rb +98 -0
  72. data/lib/graphql/schema/member/has_validators.rb +31 -0
  73. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  74. data/lib/graphql/schema/member.rb +4 -0
  75. data/lib/graphql/schema/object.rb +11 -0
  76. data/lib/graphql/schema/printer.rb +5 -4
  77. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  78. data/lib/graphql/schema/resolver.rb +7 -0
  79. data/lib/graphql/schema/subscription.rb +19 -1
  80. data/lib/graphql/schema/timeout_middleware.rb +2 -0
  81. data/lib/graphql/schema/validation.rb +2 -0
  82. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  83. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  84. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  85. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  86. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  87. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  88. data/lib/graphql/schema/validator.rb +163 -0
  89. data/lib/graphql/schema.rb +72 -49
  90. data/lib/graphql/static_validation/base_visitor.rb +0 -3
  91. data/lib/graphql/static_validation/rules/fields_will_merge.rb +4 -4
  92. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
  93. data/lib/graphql/static_validation/validation_context.rb +1 -6
  94. data/lib/graphql/static_validation/validator.rb +12 -14
  95. data/lib/graphql/subscriptions.rb +17 -20
  96. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  97. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  98. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  99. data/lib/graphql/tracing.rb +2 -2
  100. data/lib/graphql/types/relay/base_connection.rb +2 -92
  101. data/lib/graphql/types/relay/base_edge.rb +2 -35
  102. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  103. data/lib/graphql/types/relay/default_relay.rb +27 -0
  104. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  105. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  106. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  107. data/lib/graphql/types/relay/node.rb +2 -4
  108. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  109. data/lib/graphql/types/relay/node_field.rb +1 -19
  110. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  111. data/lib/graphql/types/relay/page_info.rb +2 -14
  112. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  113. data/lib/graphql/types/relay.rb +11 -3
  114. data/lib/graphql/union_type.rb +2 -0
  115. data/lib/graphql/upgrader/member.rb +1 -0
  116. data/lib/graphql/upgrader/schema.rb +1 -0
  117. data/lib/graphql/version.rb +1 -1
  118. data/lib/graphql.rb +38 -4
  119. metadata +31 -6
  120. data/lib/graphql/types/relay/base_field.rb +0 -22
  121. data/lib/graphql/types/relay/base_interface.rb +0 -29
  122. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -7,6 +7,7 @@ module GraphQL
7
7
  extend GraphQL::Schema::Member::HasArguments
8
8
  extend GraphQL::Schema::Member::HasArguments::ArgumentObjectLoader
9
9
  extend GraphQL::Schema::Member::ValidatesInput
10
+ extend GraphQL::Schema::Member::HasValidators
10
11
 
11
12
  include GraphQL::Dig
12
13
 
@@ -37,14 +38,15 @@ module GraphQL
37
38
  load_application_object(arg_defn, loads, value, context)
38
39
  end
39
40
  maybe_lazies << context.schema.after_lazy(loaded_value) do |loaded_value|
40
- @ruby_style_hash[ruby_kwargs_key] = loaded_value
41
+ overwrite_argument(ruby_kwargs_key, loaded_value)
41
42
  end
42
43
  end
43
44
 
44
45
  # Weirdly, procs are applied during coercion, but not methods.
45
46
  # Probably because these methods require a `self`.
46
47
  if arg_defn.prepare.is_a?(Symbol) || context.nil? || !context.interpreter?
47
- @ruby_style_hash[ruby_kwargs_key] = arg_defn.prepare_value(self, @ruby_style_hash[ruby_kwargs_key])
48
+ prepared_value = arg_defn.prepare_value(self, @ruby_style_hash[ruby_kwargs_key])
49
+ overwrite_argument(ruby_kwargs_key, prepared_value)
48
50
  end
49
51
  end
50
52
  end
@@ -74,6 +76,9 @@ module GraphQL
74
76
  def prepare
75
77
  if context
76
78
  context.schema.after_any_lazies(@maybe_lazies) do
79
+ object = context[:current_object]
80
+ # Pass this object's class with `as` so that messages are rendered correctly from inherited validators
81
+ Schema::Validator.validate!(self.class.validators, object, context, @ruby_style_hash, as: self.class)
77
82
  self
78
83
  end
79
84
  else
@@ -168,17 +173,10 @@ module GraphQL
168
173
  return result
169
174
  end
170
175
 
171
- input = begin
172
- input.to_h
173
- rescue
174
- begin
175
- # Handle ActionController::Parameters:
176
- input.to_unsafe_h
177
- rescue
178
- # We're not sure it'll act like a hash, so reject it:
179
- result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
180
- return result
181
- end
176
+ if !(input.respond_to?(:to_h) || input.respond_to?(:to_unsafe_h))
177
+ # We're not sure it'll act like a hash, so reject it:
178
+ result.add_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
179
+ return result
182
180
  end
183
181
 
184
182
  # Inject missing required arguments
@@ -190,16 +188,19 @@ module GraphQL
190
188
  m
191
189
  end
192
190
 
193
- input.merge(missing_required_inputs).each do |argument_name, value|
194
- argument = warden.get_argument(self, argument_name)
195
- # Items in the input that are unexpected
196
- unless argument
197
- result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
198
- next
191
+
192
+ [input, missing_required_inputs].each do |args_to_validate|
193
+ args_to_validate.each do |argument_name, value|
194
+ argument = warden.get_argument(self, argument_name)
195
+ # Items in the input that are unexpected
196
+ unless argument
197
+ result.add_problem("Field is not defined on #{self.graphql_name}", [argument_name])
198
+ next
199
+ end
200
+ # Items in the input that are expected, but have invalid values
201
+ argument_result = argument.type.validate_input(value, ctx)
202
+ result.merge_result!(argument_name, argument_result) unless argument_result.valid?
199
203
  end
200
- # Items in the input that are expected, but have invalid values
201
- argument_result = argument.type.validate_input(value, ctx)
202
- result.merge_result!(argument_name, argument_result) unless argument_result.valid?
203
204
  end
204
205
 
205
206
  result
@@ -240,6 +241,16 @@ module GraphQL
240
241
  result
241
242
  end
242
243
  end
244
+
245
+ private
246
+
247
+ def overwrite_argument(key, value)
248
+ # Argument keywords come in frozen from the interpreter, dup them before modifying them.
249
+ if @ruby_style_hash.frozen?
250
+ @ruby_style_hash = @ruby_style_hash.dup
251
+ end
252
+ @ruby_style_hash[key] = value
253
+ end
243
254
  end
244
255
  end
245
256
  end
@@ -15,6 +15,7 @@ module GraphQL
15
15
  include GraphQL::Schema::Member::Scoped
16
16
  include GraphQL::Schema::Member::HasAstNode
17
17
  include GraphQL::Schema::Member::HasUnresolvedTypeError
18
+ include GraphQL::Schema::Member::HasDirectives
18
19
 
19
20
  # Methods defined in this block will be:
20
21
  # - Added as class methods to this interface
@@ -19,6 +19,7 @@ module GraphQL
19
19
  # @return [String]
20
20
  def graphql_name(new_name = nil)
21
21
  if new_name
22
+ GraphQL::NameValidator.validate!(new_name)
22
23
  @graphql_name = new_name
23
24
  else
24
25
  overridden_graphql_name || default_graphql_name
@@ -60,11 +60,11 @@ module GraphQL
60
60
  parse_type(type_expr.first, null: false)
61
61
  when 2
62
62
  inner_type, nullable_option = type_expr
63
- if nullable_option.keys != [:null] || nullable_option.values != [true]
63
+ if nullable_option.keys != [:null] || (nullable_option[:null] != true && nullable_option[:null] != false)
64
64
  raise ArgumentError, LIST_TYPE_ERROR
65
65
  end
66
66
  list_type = true
67
- parse_type(inner_type, null: true)
67
+ parse_type(inner_type, null: nullable_option[:null])
68
68
  else
69
69
  raise ArgumentError, LIST_TYPE_ERROR
70
70
  end
@@ -75,7 +75,7 @@ module GraphQL
75
75
  if type_expr.respond_to?(:graphql_definition)
76
76
  type_expr
77
77
  else
78
- # Eg `String` => GraphQL::STRING_TYPE
78
+ # Eg `String` => GraphQL::Types::String
79
79
  parse_type(type_expr.name, null: true)
80
80
  end
81
81
  when Proc
@@ -89,7 +89,7 @@ module GraphQL
89
89
  arg_defns = self.arguments
90
90
 
91
91
  if arg_defns.empty?
92
- GraphQL::Execution::Interpreter::Arguments.new(argument_values: nil)
92
+ GraphQL::Execution::Interpreter::Arguments::EMPTY
93
93
  else
94
94
  argument_values = {}
95
95
  arg_lazies = arg_defns.map do |arg_name, arg_defn|
@@ -111,24 +111,28 @@ module GraphQL
111
111
  if has_value
112
112
  loads = arg_defn.loads
113
113
  loaded_value = nil
114
+ coerced_value = context.schema.error_handler.with_error_handling(context) do
115
+ arg_defn.type.coerce_input(value, context)
116
+ end
117
+
118
+ # TODO this should probably be inside after_lazy
114
119
  if loads && !arg_defn.from_resolver?
115
120
  loaded_value = if arg_defn.type.list?
116
- loaded_values = value.map { |val| load_application_object(arg_defn, loads, val, context) }
121
+ loaded_values = coerced_value.map { |val| load_application_object(arg_defn, loads, val, context) }
117
122
  context.schema.after_any_lazies(loaded_values) { |result| result }
118
123
  else
119
- load_application_object(arg_defn, loads, value, context)
124
+ load_application_object(arg_defn, loads, coerced_value, context)
120
125
  end
121
126
  end
122
127
 
123
128
  coerced_value = if loaded_value
124
129
  loaded_value
125
130
  else
126
- context.schema.error_handler.with_error_handling(context) do
127
- arg_defn.type.coerce_input(value, context)
128
- end
131
+ coerced_value
129
132
  end
130
133
 
131
134
  context.schema.after_lazy(coerced_value) do |coerced_value|
135
+ validate_directive_argument(arg_defn, coerced_value)
132
136
  prepared_value = context.schema.error_handler.with_error_handling(context) do
133
137
  arg_defn.prepare_value(parent_object, coerced_value, context: context)
134
138
  end
@@ -140,6 +144,9 @@ module GraphQL
140
144
  default_used: default_used,
141
145
  )
142
146
  end
147
+ else
148
+ # has_value is false
149
+ validate_directive_argument(arg_defn, nil)
143
150
  end
144
151
  end
145
152
 
@@ -151,6 +158,17 @@ module GraphQL
151
158
  end
152
159
  end
153
160
 
161
+ # Usually, this is validated statically by RequiredArgumentsArePresent,
162
+ # but not for directives.
163
+ # TODO apply static validations on schema definitions?
164
+ def validate_directive_argument(arg_defn, value)
165
+ if arg_defn.owner.is_a?(Class) && arg_defn.owner < GraphQL::Schema::Directive
166
+ if value.nil? && arg_defn.type.non_null?
167
+ raise ArgumentError, "#{arg_defn.path} is required, but no value was given"
168
+ end
169
+ end
170
+ end
171
+
154
172
  def arguments_statically_coercible?
155
173
  return @arguments_statically_coercible if defined?(@arguments_statically_coercible)
156
174
 
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ module HasDeprecationReason
7
+ # @return [String, nil] Explains why this member was deprecated (if present, this will be marked deprecated in introspection)
8
+ def deprecation_reason
9
+ dir = self.directives.find { |d| d.is_a?(GraphQL::Schema::Directive::Deprecated) }
10
+ dir && dir.arguments[:reason]
11
+ end
12
+
13
+ # Set the deprecation reason for this member, or remove it by assigning `nil`
14
+ # @param text [String, nil]
15
+ def deprecation_reason=(text)
16
+ if text.nil?
17
+ remove_directive(GraphQL::Schema::Directive::Deprecated)
18
+ else
19
+ directive(GraphQL::Schema::Directive::Deprecated, reason: text)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Member
6
+ module HasDirectives
7
+ # Create an instance of `dir_class` for `self`, using `options`.
8
+ #
9
+ # It removes a previously-attached instance of `dir_class`, if there is one.
10
+ #
11
+ # @return [void]
12
+ def directive(dir_class, **options)
13
+ @own_directives ||= []
14
+ remove_directive(dir_class)
15
+ @own_directives << dir_class.new(self, **options)
16
+ nil
17
+ end
18
+
19
+ # Remove an attached instance of `dir_class`, if there is one
20
+ # @param dir_class [Class<GraphQL::Schema::Directive>]
21
+ # @return [viod]
22
+ def remove_directive(dir_class)
23
+ @own_directives && @own_directives.reject! { |d| d.is_a?(dir_class) }
24
+ nil
25
+ end
26
+
27
+ NO_DIRECTIVES = [].freeze
28
+
29
+ def directives
30
+ case self
31
+ when Class
32
+ inherited_directives = if superclass.respond_to?(:directives)
33
+ superclass.directives
34
+ else
35
+ NO_DIRECTIVES
36
+ end
37
+ if inherited_directives.any? && @own_directives
38
+ dirs = []
39
+ merge_directives(dirs, inherited_directives)
40
+ merge_directives(dirs, @own_directives)
41
+ dirs
42
+ elsif @own_directives
43
+ @own_directives
44
+ elsif inherited_directives.any?
45
+ inherited_directives
46
+ else
47
+ NO_DIRECTIVES
48
+ end
49
+ when Module
50
+ dirs = nil
51
+ self.ancestors.reverse_each do |ancestor|
52
+ if ancestor.respond_to?(:own_directives) &&
53
+ (anc_dirs = ancestor.own_directives).any?
54
+ dirs ||= []
55
+ merge_directives(dirs, anc_dirs)
56
+ end
57
+ end
58
+ if own_directives
59
+ dirs ||= []
60
+ merge_directives(dirs, own_directives)
61
+ end
62
+ dirs || NO_DIRECTIVES
63
+ when HasDirectives
64
+ @own_directives || NO_DIRECTIVES
65
+ else
66
+ raise "Invariant: how could #{self} not be a Class, Module, or instance of HasDirectives?"
67
+ end
68
+ end
69
+
70
+ protected
71
+
72
+ def own_directives
73
+ @own_directives
74
+ end
75
+
76
+ private
77
+
78
+ # Modify `target` by adding items from `dirs` such that:
79
+ # - Any name conflict is overriden by the incoming member of `dirs`
80
+ # - Any other member of `dirs` is appended
81
+ # @param target [Array<GraphQL::Schema::Directive>]
82
+ # @param dirs [Array<GraphQL::Schema::Directive>]
83
+ # @return [void]
84
+ def merge_directives(target, dirs)
85
+ dirs.each do |dir|
86
+ if (idx = target.find_index { |d| d.graphql_name == dir.graphql_name })
87
+ target.slice!(idx)
88
+ target.insert(idx, dir)
89
+ else
90
+ target << dir
91
+ end
92
+ end
93
+ nil
94
+ end
95
+ end
96
+ end
97
+ end
98
+ 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
@@ -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
@@ -4,8 +4,11 @@ require 'graphql/schema/member/base_dsl_methods'
4
4
  require 'graphql/schema/member/cached_graphql_definition'
5
5
  require 'graphql/schema/member/graphql_type_names'
6
6
  require 'graphql/schema/member/has_ast_node'
7
+ require 'graphql/schema/member/has_directives'
8
+ require 'graphql/schema/member/has_deprecation_reason'
7
9
  require 'graphql/schema/member/has_path'
8
10
  require 'graphql/schema/member/has_unresolved_type_error'
11
+ require 'graphql/schema/member/has_validators'
9
12
  require 'graphql/schema/member/relay_shortcuts'
10
13
  require 'graphql/schema/member/scoped'
11
14
  require 'graphql/schema/member/type_system_helpers'
@@ -30,6 +33,7 @@ module GraphQL
30
33
  extend RelayShortcuts
31
34
  extend HasPath
32
35
  extend HasAstNode
36
+ extend HasDirectives
33
37
  end
34
38
  end
35
39
  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
@@ -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`.
@@ -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)
@@ -25,6 +25,22 @@ module GraphQL
25
25
  @mode = context.query.subscription_update? ? :update : :subscribe
26
26
  end
27
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
+
28
44
  # Implement the {Resolve} API
29
45
  def resolve(**args)
30
46
  # Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever
@@ -55,7 +71,8 @@ module GraphQL
55
71
  def resolve_update(**args)
56
72
  ret_val = args.any? ? update(**args) : update
57
73
  if ret_val == :no_update
58
- throw :graphql_no_subscription_update
74
+ context.namespace(:subscriptions)[:no_update] = true
75
+ context.skip
59
76
  else
60
77
  ret_val
61
78
  end
@@ -80,6 +97,7 @@ module GraphQL
80
97
 
81
98
  # Call this to halt execution and remove this subscription from the system
82
99
  def unsubscribe
100
+ context.namespace(:subscriptions)[:unsubscribed] = true
83
101
  throw :graphql_subscription_unsubscribed
84
102
  end
85
103
 
@@ -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)
@@ -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`
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Validator
6
+ # Use this to specifically reject values from an argument.
7
+ #
8
+ # @example disallow certain values
9
+ #
10
+ # argument :favorite_non_prime, Integer, required: true,
11
+ # validates: { exclusion: { in: [2, 3, 5, 7, ... ]} }
12
+ #
13
+ class ExclusionValidator < Validator
14
+ # @param message [String]
15
+ # @param in [Array] The values to reject
16
+ def initialize(message: "%{validated} is reserved", in:, **default_options)
17
+ # `in` is a reserved word, so work around that
18
+ @in_list = binding.local_variable_get(:in)
19
+ @message = message
20
+ super(**default_options)
21
+ end
22
+
23
+ def validate(_object, _context, value)
24
+ if @in_list.include?(value)
25
+ @message
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Validator
6
+ # Use this to assert that string values match (or don't match) the given RegExp.
7
+ #
8
+ # @example requiring input to match a pattern
9
+ #
10
+ # argument :handle, String, required: true,
11
+ # validates: { format: { with: /\A[a-z0-9_]+\Z/ } }
12
+ #
13
+ # @example reject inputs that match a pattern
14
+ #
15
+ # argument :word_that_doesnt_begin_with_a_vowel, String, required: true,
16
+ # validates: { format: { without: /\A[aeiou]/ } }
17
+ #
18
+ # # It's pretty hard to come up with a legitimate use case for `without:`
19
+ #
20
+ class FormatValidator < Validator
21
+ if !String.method_defined?(:match?)
22
+ using GraphQL::StringMatchBackport
23
+ end
24
+
25
+ # @param with [RegExp, nil]
26
+ # @param without [Regexp, nil]
27
+ # @param message [String]
28
+ def initialize(
29
+ with: nil,
30
+ without: nil,
31
+ message: "%{validated} is invalid",
32
+ **default_options
33
+ )
34
+ @with_pattern = with
35
+ @without_pattern = without
36
+ @message = message
37
+ super(**default_options)
38
+ end
39
+
40
+ def validate(_object, _context, value)
41
+ if (@with_pattern && !value.match?(@with_pattern)) ||
42
+ (@without_pattern && value.match?(@without_pattern))
43
+ @message
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ class Validator
6
+ # You can use this to allow certain values for an argument.
7
+ #
8
+ # Usually, a {GraphQL::Schema::Enum} is better for this, because it's self-documenting.
9
+ #
10
+ # @example only allow certain values for an argument
11
+ #
12
+ # argument :favorite_prime, Integer, required: true,
13
+ # validates: { inclusion: { in: [2, 3, 5, 7, 11, ... ] } }
14
+ #
15
+ class InclusionValidator < Validator
16
+ # @param message [String]
17
+ # @param in [Array] The values to allow
18
+ def initialize(in:, message: "%{validated} is not included in the list", **default_options)
19
+ # `in` is a reserved word, so work around that
20
+ @in_list = binding.local_variable_get(:in)
21
+ @message = message
22
+ super(**default_options)
23
+ end
24
+
25
+ def validate(_object, _context, value)
26
+ if !@in_list.include?(value)
27
+ @message
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end