graphql 1.11.6 → 1.12.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +5 -5
  3. data/lib/generators/graphql/object_generator.rb +2 -0
  4. data/lib/generators/graphql/relay_generator.rb +63 -0
  5. data/lib/generators/graphql/templates/base_connection.erb +8 -0
  6. data/lib/generators/graphql/templates/base_edge.erb +8 -0
  7. data/lib/generators/graphql/templates/node_type.erb +9 -0
  8. data/lib/generators/graphql/templates/object.erb +1 -1
  9. data/lib/generators/graphql/templates/query_type.erb +1 -3
  10. data/lib/generators/graphql/templates/schema.erb +8 -35
  11. data/lib/graphql.rb +39 -4
  12. data/lib/graphql/analysis/analyze_query.rb +7 -0
  13. data/lib/graphql/analysis/ast.rb +11 -2
  14. data/lib/graphql/analysis/ast/visitor.rb +9 -1
  15. data/lib/graphql/backtrace.rb +28 -19
  16. data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
  17. data/lib/graphql/backtrace/table.rb +22 -2
  18. data/lib/graphql/backtrace/tracer.rb +40 -9
  19. data/lib/graphql/backwards_compatibility.rb +2 -1
  20. data/lib/graphql/base_type.rb +1 -1
  21. data/lib/graphql/compatibility/execution_specification.rb +1 -0
  22. data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
  23. data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
  24. data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
  25. data/lib/graphql/dataloader.rb +198 -0
  26. data/lib/graphql/dataloader/null_dataloader.rb +21 -0
  27. data/lib/graphql/dataloader/request.rb +24 -0
  28. data/lib/graphql/dataloader/request_all.rb +22 -0
  29. data/lib/graphql/dataloader/source.rb +93 -0
  30. data/lib/graphql/define/assign_global_id_field.rb +1 -1
  31. data/lib/graphql/define/instance_definable.rb +32 -2
  32. data/lib/graphql/define/type_definer.rb +5 -5
  33. data/lib/graphql/deprecated_dsl.rb +7 -2
  34. data/lib/graphql/deprecation.rb +13 -0
  35. data/lib/graphql/enum_type.rb +2 -0
  36. data/lib/graphql/execution/errors.rb +4 -0
  37. data/lib/graphql/execution/execute.rb +7 -0
  38. data/lib/graphql/execution/interpreter.rb +10 -6
  39. data/lib/graphql/execution/interpreter/arguments.rb +57 -5
  40. data/lib/graphql/execution/interpreter/arguments_cache.rb +8 -0
  41. data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
  42. data/lib/graphql/execution/interpreter/runtime.rb +219 -117
  43. data/lib/graphql/execution/multiplex.rb +20 -6
  44. data/lib/graphql/function.rb +4 -0
  45. data/lib/graphql/input_object_type.rb +2 -0
  46. data/lib/graphql/integer_decoding_error.rb +17 -0
  47. data/lib/graphql/interface_type.rb +3 -1
  48. data/lib/graphql/internal_representation/document.rb +2 -2
  49. data/lib/graphql/internal_representation/rewrite.rb +1 -1
  50. data/lib/graphql/invalid_null_error.rb +1 -1
  51. data/lib/graphql/language/document_from_schema_definition.rb +50 -23
  52. data/lib/graphql/object_type.rb +2 -0
  53. data/lib/graphql/pagination/connection.rb +5 -1
  54. data/lib/graphql/pagination/connections.rb +6 -16
  55. data/lib/graphql/query.rb +6 -1
  56. data/lib/graphql/query/arguments.rb +1 -1
  57. data/lib/graphql/query/context.rb +8 -1
  58. data/lib/graphql/query/serial_execution.rb +1 -0
  59. data/lib/graphql/query/validation_pipeline.rb +1 -1
  60. data/lib/graphql/relay/array_connection.rb +2 -2
  61. data/lib/graphql/relay/base_connection.rb +7 -0
  62. data/lib/graphql/relay/connection_instrumentation.rb +4 -4
  63. data/lib/graphql/relay/connection_type.rb +1 -1
  64. data/lib/graphql/relay/mutation.rb +1 -0
  65. data/lib/graphql/relay/node.rb +3 -0
  66. data/lib/graphql/relay/type_extensions.rb +2 -0
  67. data/lib/graphql/scalar_type.rb +2 -0
  68. data/lib/graphql/schema.rb +80 -29
  69. data/lib/graphql/schema/argument.rb +25 -7
  70. data/lib/graphql/schema/build_from_definition.rb +139 -51
  71. data/lib/graphql/schema/default_type_error.rb +2 -0
  72. data/lib/graphql/schema/directive.rb +76 -0
  73. data/lib/graphql/schema/directive/flagged.rb +57 -0
  74. data/lib/graphql/schema/enum.rb +3 -0
  75. data/lib/graphql/schema/enum_value.rb +12 -6
  76. data/lib/graphql/schema/field.rb +50 -22
  77. data/lib/graphql/schema/field/connection_extension.rb +3 -2
  78. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  79. data/lib/graphql/schema/input_object.rb +33 -22
  80. data/lib/graphql/schema/interface.rb +1 -0
  81. data/lib/graphql/schema/member.rb +4 -0
  82. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
  83. data/lib/graphql/schema/member/build_type.rb +3 -3
  84. data/lib/graphql/schema/member/has_arguments.rb +67 -50
  85. data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
  86. data/lib/graphql/schema/member/has_directives.rb +98 -0
  87. data/lib/graphql/schema/member/has_validators.rb +31 -0
  88. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  89. data/lib/graphql/schema/middleware_chain.rb +1 -1
  90. data/lib/graphql/schema/object.rb +11 -0
  91. data/lib/graphql/schema/printer.rb +5 -4
  92. data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
  93. data/lib/graphql/schema/resolver.rb +7 -0
  94. data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
  95. data/lib/graphql/schema/subscription.rb +19 -1
  96. data/lib/graphql/schema/timeout_middleware.rb +3 -1
  97. data/lib/graphql/schema/validation.rb +4 -2
  98. data/lib/graphql/schema/validator.rb +163 -0
  99. data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
  100. data/lib/graphql/schema/validator/format_validator.rb +49 -0
  101. data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
  102. data/lib/graphql/schema/validator/length_validator.rb +57 -0
  103. data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
  104. data/lib/graphql/schema/validator/required_validator.rb +68 -0
  105. data/lib/graphql/static_validation.rb +1 -0
  106. data/lib/graphql/static_validation/all_rules.rb +1 -0
  107. data/lib/graphql/static_validation/rules/fields_will_merge.rb +25 -17
  108. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  109. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  110. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  111. data/lib/graphql/static_validation/validator.rb +32 -9
  112. data/lib/graphql/subscriptions.rb +17 -20
  113. data/lib/graphql/subscriptions/subscription_root.rb +1 -1
  114. data/lib/graphql/tracing.rb +2 -2
  115. data/lib/graphql/tracing/appoptics_tracing.rb +3 -1
  116. data/lib/graphql/tracing/platform_tracing.rb +4 -2
  117. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  118. data/lib/graphql/tracing/skylight_tracing.rb +1 -1
  119. data/lib/graphql/types/int.rb +9 -2
  120. data/lib/graphql/types/relay.rb +11 -3
  121. data/lib/graphql/types/relay/base_connection.rb +2 -91
  122. data/lib/graphql/types/relay/base_edge.rb +2 -34
  123. data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
  124. data/lib/graphql/types/relay/default_relay.rb +27 -0
  125. data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
  126. data/lib/graphql/types/relay/has_node_field.rb +41 -0
  127. data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
  128. data/lib/graphql/types/relay/node.rb +2 -4
  129. data/lib/graphql/types/relay/node_behaviors.rb +15 -0
  130. data/lib/graphql/types/relay/node_field.rb +1 -19
  131. data/lib/graphql/types/relay/nodes_field.rb +1 -19
  132. data/lib/graphql/types/relay/page_info.rb +2 -14
  133. data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
  134. data/lib/graphql/types/string.rb +7 -1
  135. data/lib/graphql/union_type.rb +2 -0
  136. data/lib/graphql/upgrader/member.rb +1 -0
  137. data/lib/graphql/upgrader/schema.rb +1 -0
  138. data/lib/graphql/version.rb +1 -1
  139. data/readme.md +1 -1
  140. metadata +53 -9
  141. data/lib/graphql/types/relay/base_field.rb +0 -22
  142. data/lib/graphql/types/relay/base_interface.rb +0 -29
  143. data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class ValidationTimeoutError < StaticValidation::Error
5
+ def initialize(message, path: nil, nodes: [])
6
+ super(message, path: path, nodes: nodes)
7
+ end
8
+
9
+ # A hash representation of this Message
10
+ def to_h
11
+ extensions = {
12
+ "code" => code
13
+ }
14
+
15
+ super.merge({
16
+ "extensions" => extensions
17
+ })
18
+ end
19
+
20
+ def code
21
+ "validationTimeout"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require "timeout"
3
+
2
4
  module GraphQL
3
5
  module StaticValidation
4
6
  # Initialized with a {GraphQL::Schema}, then it can validate {GraphQL::Language::Nodes::Documents}s based on that schema.
@@ -20,8 +22,10 @@ module GraphQL
20
22
 
21
23
  # Validate `query` against the schema. Returns an array of message hashes.
22
24
  # @param query [GraphQL::Query]
25
+ # @param validate [Boolean]
26
+ # @param timeout [Float] Number of seconds to wait before aborting validation. Any positive number may be used, including Floats to specify fractional seconds.
23
27
  # @return [Array<Hash>]
24
- def validate(query, validate: true)
28
+ def validate(query, validate: true, timeout: nil)
25
29
  query.trace("validate", { validate: validate, query: query }) do
26
30
  can_skip_rewrite = query.context.interpreter? && query.schema.using_ast_analysis? && query.schema.is_a?(Class)
27
31
  errors = if validate == false && can_skip_rewrite
@@ -32,21 +36,31 @@ module GraphQL
32
36
 
33
37
  context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class)
34
38
 
35
- # Attach legacy-style rules.
36
- # Only loop through rules if it has legacy-style rules
37
- unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
38
- legacy_rules.each do |rule_class_or_module|
39
- if rule_class_or_module.method_defined?(:validate)
40
- rule_class_or_module.new.validate(context)
39
+ begin
40
+ # 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.
41
+ # A timeout value of 0 or nil will execute the block without any timeout.
42
+ Timeout::timeout(timeout) do
43
+ # Attach legacy-style rules.
44
+ # Only loop through rules if it has legacy-style rules
45
+ unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
46
+ legacy_rules.each do |rule_class_or_module|
47
+ if rule_class_or_module.method_defined?(:validate)
48
+ GraphQL::Deprecation.warn "Legacy validator rules will be removed from GraphQL-Ruby 2.0, use a module instead (see the built-in rules: https://github.com/rmosolgo/graphql-ruby/tree/master/lib/graphql/static_validation/rules)"
49
+ GraphQL::Deprecation.warn " -> Legacy validator: #{rule_class_or_module}"
50
+ rule_class_or_module.new.validate(context)
51
+ end
52
+ end
41
53
  end
54
+
55
+ context.visitor.visit
42
56
  end
57
+ rescue Timeout::Error
58
+ handle_timeout(query, context)
43
59
  end
44
60
 
45
- context.visitor.visit
46
61
  context.errors
47
62
  end
48
63
 
49
-
50
64
  irep = if errors.empty? && context
51
65
  # Only return this if there are no errors and validation was actually run
52
66
  context.visitor.rewrite_document
@@ -60,6 +74,15 @@ module GraphQL
60
74
  }
61
75
  end
62
76
  end
77
+
78
+ # Invoked when static validation times out.
79
+ # @param query [GraphQL::Query]
80
+ # @param context [GraphQL::StaticValidation::ValidationContext]
81
+ def handle_timeout(query, context)
82
+ context.errors << GraphQL::StaticValidation::ValidationTimeoutError.new(
83
+ "Timeout on validation of query"
84
+ )
85
+ end
63
86
  end
64
87
  end
65
88
  end
@@ -26,7 +26,9 @@ module GraphQL
26
26
 
27
27
  instrumentation = Subscriptions::Instrumentation.new(schema: schema)
28
28
  defn.instrument(:query, instrumentation)
29
- defn.instrument(:field, instrumentation)
29
+ if !schema.is_a?(Class)
30
+ defn.instrument(:field, instrumentation)
31
+ end
30
32
  options[:schema] = schema
31
33
  schema.subscriptions = self.new(**options)
32
34
  schema.add_subscription_extension_if_necessary
@@ -107,31 +109,26 @@ module GraphQL
107
109
  variables = query_data.fetch(:variables)
108
110
  context = query_data.fetch(:context)
109
111
  operation_name = query_data.fetch(:operation_name)
110
- result = nil
111
- # this will be set to `false` unless `.execute` is terminated
112
- # with a `throw :graphql_subscription_unsubscribed`
113
- unsubscribed = true
114
- catch(:graphql_subscription_unsubscribed) do
115
- catch(:graphql_no_subscription_update) do
116
- # Re-evaluate the saved query,
117
- # but if it terminates early with a `throw`,
118
- # it will stay `nil`
119
- result = @schema.execute(
120
- query: query_string,
121
- context: context,
122
- subscription_topic: event.topic,
123
- operation_name: operation_name,
124
- variables: variables,
125
- root_value: object,
126
- )
127
- end
128
- unsubscribed = false
112
+ result = @schema.execute(
113
+ query: query_string,
114
+ context: context,
115
+ subscription_topic: event.topic,
116
+ operation_name: operation_name,
117
+ variables: variables,
118
+ root_value: object,
119
+ )
120
+ subscriptions_context = result.context.namespace(:subscriptions)
121
+ if subscriptions_context[:no_update]
122
+ result = nil
129
123
  end
130
124
 
125
+ unsubscribed = subscriptions_context[:unsubscribed]
126
+
131
127
  if unsubscribed
132
128
  # `unsubscribe` was called, clean up on our side
133
129
  # TODO also send `{more: false}` to client?
134
130
  delete_subscription(subscription_id)
131
+ result = nil
135
132
  end
136
133
 
137
134
  result
@@ -6,7 +6,7 @@ module GraphQL
6
6
  # @deprecated This module is no longer needed.
7
7
  module SubscriptionRoot
8
8
  def self.extended(child_cls)
9
- warn "`extend GraphQL::Subscriptions::SubscriptionRoot` is no longer required; you can remove it from your Subscription type (#{child_cls})"
9
+ GraphQL::Deprecation.warn "`extend GraphQL::Subscriptions::SubscriptionRoot` is no longer required; you can remove it from your Subscription type (#{child_cls})"
10
10
  child_cls.include(InstanceMethods)
11
11
  end
12
12
 
@@ -42,8 +42,8 @@ module GraphQL
42
42
  # execute_multiplex | `{ multiplex: GraphQL::Execution::Multiplex }`
43
43
  # execute_query | `{ query: GraphQL::Query }`
44
44
  # execute_query_lazy | `{ query: GraphQL::Query?, multiplex: GraphQL::Execution::Multiplex? }`
45
- # execute_field | `{ context: GraphQL::Query::Context::FieldResolutionContext?, owner: Class?, field: GraphQL::Schema::Field?, query: GraphQL::Query?, path: Array<String, Integer>?}`
46
- # execute_field_lazy | `{ context: GraphQL::Query::Context::FieldResolutionContext?, owner: Class?, field: GraphQL::Schema::Field?, query: GraphQL::Query?, path: Array<String, Integer>?}`
45
+ # execute_field | `{ owner: Class, field: GraphQL::Schema::Field, query: GraphQL::Query, path: Array<String, Integer>, ast_node: GraphQL::Language::Nodes::Field}`
46
+ # execute_field_lazy | `{ owner: Class, field: GraphQL::Schema::Field, query: GraphQL::Query, path: Array<String, Integer>, ast_node: GraphQL::Language::Nodes::Field}`
47
47
  # authorized | `{ context: GraphQL::Query::Context, type: Class, object: Object, path: Array<String, Integer> }`
48
48
  # authorized_lazy | `{ context: GraphQL::Query::Context, type: Class, object: Object, path: Array<String, Integer> }`
49
49
  # resolve_type | `{ context: GraphQL::Query::Context, type: Class, object: Object, path: Array<String, Integer> }`
@@ -57,7 +57,7 @@ module GraphQL
57
57
  def platform_field_key(type, field)
58
58
  "graphql.#{type.graphql_name}.#{field.graphql_name}"
59
59
  end
60
-
60
+
61
61
  def platform_authorized_key(type)
62
62
  "graphql.authorized.#{type.graphql_name}"
63
63
  end
@@ -112,6 +112,8 @@ module GraphQL
112
112
  graphql_query_string(data[key])
113
113
  when :multiplex
114
114
  graphql_multiplex(data[key])
115
+ when :path
116
+ [key, data[key].join(".")]
115
117
  else
116
118
  [key, data[key]]
117
119
  end
@@ -95,8 +95,10 @@ module GraphQL
95
95
  end
96
96
 
97
97
  def self.use(schema_defn, options = {})
98
- tracer = self.new(options)
99
- schema_defn.instrument(:field, tracer)
98
+ tracer = self.new(**options)
99
+ if !schema_defn.is_a?(Class)
100
+ schema_defn.instrument(:field, tracer)
101
+ end
100
102
  schema_defn.tracer(tracer)
101
103
  end
102
104
 
@@ -16,7 +16,10 @@ module GraphQL
16
16
  end
17
17
 
18
18
  def collect(object)
19
- labels = { key: object['key'], platform_key: object['platform_key'] }
19
+ default_labels = { key: object['key'], platform_key: object['platform_key'] }
20
+ custom = object['custom_labels']
21
+ labels = custom.nil? ? default_labels : default_labels.merge(custom)
22
+
20
23
  @graphql_gauge.observe object['duration'], labels
21
24
  end
22
25
 
@@ -18,7 +18,7 @@ module GraphQL
18
18
  # This is not advised if you run more than one query per HTTP request, for example, with `graphql-client` or multiplexing.
19
19
  # It can also be specified per-query with `context[:set_skylight_endpoint_name]`.
20
20
  def initialize(options = {})
21
- warn("GraphQL::Tracing::SkylightTracing is deprecated, please enable Skylight's GraphQL probe instead: https://www.skylight.io/support/getting-more-from-skylight#graphql.")
21
+ GraphQL::Deprecation.warn("GraphQL::Tracing::SkylightTracing is deprecated and will be removed in GraphQL-Ruby 2.0, please enable Skylight's GraphQL probe instead: https://www.skylight.io/support/getting-more-from-skylight#graphql.")
22
22
  @set_endpoint_name = options.fetch(:set_endpoint_name, false)
23
23
  super
24
24
  end
@@ -9,8 +9,15 @@ module GraphQL
9
9
  MIN = -(2**31)
10
10
  MAX = (2**31) - 1
11
11
 
12
- def self.coerce_input(value, _ctx)
13
- value.is_a?(Integer) ? value : nil
12
+ def self.coerce_input(value, ctx)
13
+ return if !value.is_a?(Integer)
14
+
15
+ if value >= MIN && value <= MAX
16
+ value
17
+ else
18
+ err = GraphQL::IntegerDecodingError.new(value)
19
+ ctx.schema.type_error(err, ctx)
20
+ end
14
21
  end
15
22
 
16
23
  def self.coerce_result(value, ctx)
@@ -1,7 +1,15 @@
1
1
  # frozen_string_literal: true
2
- require "graphql/types/relay/base_field"
3
- require "graphql/types/relay/base_object"
4
- require "graphql/types/relay/base_interface"
2
+
3
+ # behavior modules:
4
+ require "graphql/types/relay/default_relay"
5
+ require "graphql/types/relay/connection_behaviors"
6
+ require "graphql/types/relay/edge_behaviors"
7
+ require "graphql/types/relay/node_behaviors"
8
+ require "graphql/types/relay/page_info_behaviors"
9
+ require "graphql/types/relay/has_node_field"
10
+ require "graphql/types/relay/has_nodes_field"
11
+
12
+ # concrete classes based on the gem defaults:
5
13
  require "graphql/types/relay/page_info"
6
14
  require "graphql/types/relay/base_connection"
7
15
  require "graphql/types/relay/base_edge"
@@ -27,97 +27,8 @@ module GraphQL
27
27
  # end
28
28
  #
29
29
  # @see Relay::BaseEdge for edge types
30
- class BaseConnection < Types::Relay::BaseObject
31
- extend Forwardable
32
- def_delegators :@object, :cursor_from_node, :parent
33
-
34
- class << self
35
- # @return [Class]
36
- attr_reader :node_type
37
-
38
- # @return [Class]
39
- attr_reader :edge_class
40
-
41
- # Configure this connection to return `edges` and `nodes` based on `edge_type_class`.
42
- #
43
- # This method will use the inputs to create:
44
- # - `edges` field
45
- # - `nodes` field
46
- # - description
47
- #
48
- # It's called when you subclass this base connection, trying to use the
49
- # class name to set defaults. You can call it again in the class definition
50
- # to override the default (or provide a value, if the default lookup failed).
51
- def edge_type(edge_type_class, edge_class: GraphQL::Relay::Edge, node_type: edge_type_class.node_type, nodes_field: true, node_nullable: true)
52
- # Set this connection's graphql name
53
- node_type_name = node_type.graphql_name
54
-
55
- @node_type = node_type
56
- @edge_type = edge_type_class
57
- @edge_class = edge_class
58
-
59
- field :edges, [edge_type_class, null: true],
60
- null: true,
61
- description: "A list of edges.",
62
- edge_class: edge_class
63
-
64
- define_nodes_field(node_nullable) if nodes_field
65
-
66
- description("The connection type for #{node_type_name}.")
67
- end
68
-
69
- # Filter this list according to the way its node type would scope them
70
- def scope_items(items, context)
71
- node_type.scope_items(items, context)
72
- end
73
-
74
- # Add the shortcut `nodes` field to this connection and its subclasses
75
- def nodes_field
76
- define_nodes_field
77
- end
78
-
79
- def authorized?(obj, ctx)
80
- true # Let nodes be filtered out
81
- end
82
-
83
- def accessible?(ctx)
84
- node_type.accessible?(ctx)
85
- end
86
-
87
- def visible?(ctx)
88
- node_type.visible?(ctx)
89
- end
90
-
91
- private
92
-
93
- def define_nodes_field(nullable = true)
94
- type = nullable ? [@node_type, null: true] : [@node_type]
95
- field :nodes, type,
96
- null: nullable,
97
- description: "A list of nodes."
98
- end
99
- end
100
-
101
- field :page_info, GraphQL::Types::Relay::PageInfo, null: false, description: "Information to aid in pagination."
102
-
103
- # By default this calls through to the ConnectionWrapper's edge nodes method,
104
- # but sometimes you need to override it to support the `nodes` field
105
- def nodes
106
- @object.edge_nodes
107
- end
108
-
109
- def edges
110
- if @object.is_a?(GraphQL::Pagination::Connection)
111
- @object.edges
112
- elsif context.interpreter?
113
- context.schema.after_lazy(object.edge_nodes) do |nodes|
114
- nodes.map { |n| self.class.edge_class.new(n, object) }
115
- end
116
- else
117
- # This is done by edges_instrumentation
118
- @object.edge_nodes
119
- end
120
- end
30
+ class BaseConnection < Schema::Object
31
+ include ConnectionBehaviors
121
32
  end
122
33
  end
123
34
  end
@@ -21,40 +21,8 @@ module GraphQL
21
21
  # end
22
22
  #
23
23
  # @see {Relay::BaseConnection} for connection types
24
- class BaseEdge < Types::Relay::BaseObject
25
- description "An edge in a connection."
26
-
27
- class << self
28
- # Get or set the Object type that this edge wraps.
29
- #
30
- # @param node_type [Class] A `Schema::Object` subclass
31
- # @param null [Boolean]
32
- def node_type(node_type = nil, null: true)
33
- if node_type
34
- @node_type = node_type
35
- # Add a default `node` field
36
- field :node, node_type, null: null, description: "The item at the end of the edge."
37
- end
38
- @node_type
39
- end
40
-
41
- def authorized?(obj, ctx)
42
- true
43
- end
44
-
45
- def accessible?(ctx)
46
- node_type.accessible?(ctx)
47
- end
48
-
49
- def visible?(ctx)
50
- node_type.visible?(ctx)
51
- end
52
- end
53
-
54
-
55
- field :cursor, String,
56
- null: false,
57
- description: "A cursor for use in pagination."
24
+ class BaseEdge < GraphQL::Schema::Object
25
+ include Types::Relay::EdgeBehaviors
58
26
  end
59
27
  end
60
28
  end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Types
5
+ module Relay
6
+ module ConnectionBehaviors
7
+ extend Forwardable
8
+ def_delegators :@object, :cursor_from_node, :parent
9
+
10
+ def self.included(child_class)
11
+ child_class.extend(ClassMethods)
12
+ child_class.extend(Relay::DefaultRelay)
13
+ child_class.default_relay(true)
14
+ child_class.node_nullable(true)
15
+ add_page_info_field(child_class)
16
+ end
17
+
18
+ module ClassMethods
19
+ # @return [Class]
20
+ attr_reader :node_type
21
+
22
+ # @return [Class]
23
+ attr_reader :edge_class
24
+
25
+ # Configure this connection to return `edges` and `nodes` based on `edge_type_class`.
26
+ #
27
+ # This method will use the inputs to create:
28
+ # - `edges` field
29
+ # - `nodes` field
30
+ # - description
31
+ #
32
+ # It's called when you subclass this base connection, trying to use the
33
+ # class name to set defaults. You can call it again in the class definition
34
+ # to override the default (or provide a value, if the default lookup failed).
35
+ def edge_type(edge_type_class, edge_class: GraphQL::Relay::Edge, node_type: edge_type_class.node_type, nodes_field: true, node_nullable: self.node_nullable)
36
+ # Set this connection's graphql name
37
+ node_type_name = node_type.graphql_name
38
+
39
+ @node_type = node_type
40
+ @edge_type = edge_type_class
41
+ @edge_class = edge_class
42
+
43
+ field :edges, [edge_type_class, null: true],
44
+ null: true,
45
+ description: "A list of edges.",
46
+ legacy_edge_class: edge_class, # This is used by the old runtime only, for EdgesInstrumentation
47
+ connection: false
48
+
49
+ define_nodes_field(node_nullable) if nodes_field
50
+
51
+ description("The connection type for #{node_type_name}.")
52
+ end
53
+
54
+ # Filter this list according to the way its node type would scope them
55
+ def scope_items(items, context)
56
+ node_type.scope_items(items, context)
57
+ end
58
+
59
+ # Add the shortcut `nodes` field to this connection and its subclasses
60
+ def nodes_field(node_nullable: self.node_nullable)
61
+ define_nodes_field(node_nullable)
62
+ end
63
+
64
+ def authorized?(obj, ctx)
65
+ true # Let nodes be filtered out
66
+ end
67
+
68
+ def accessible?(ctx)
69
+ node_type.accessible?(ctx)
70
+ end
71
+
72
+ def visible?(ctx)
73
+ node_type.visible?(ctx)
74
+ end
75
+
76
+ # Set the default `node_nullable` for this class and its child classes. (Defaults to `true`.)
77
+ # Use `node_nullable(false)` in your base class to make non-null `node` and `nodes` fields.
78
+ def node_nullable(new_value = nil)
79
+ if new_value.nil?
80
+ @node_nullable || superclass.node_nullable
81
+ else
82
+ @node_nullable ||= new_value
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def define_nodes_field(nullable)
89
+ field :nodes, [@node_type, null: nullable],
90
+ null: nullable,
91
+ description: "A list of nodes.",
92
+ connection: false
93
+ end
94
+ end
95
+
96
+ class << self
97
+ def add_page_info_field(obj_type)
98
+ obj_type.field :page_info, GraphQL::Types::Relay::PageInfo, null: false, description: "Information to aid in pagination."
99
+ end
100
+ end
101
+
102
+ # By default this calls through to the ConnectionWrapper's edge nodes method,
103
+ # but sometimes you need to override it to support the `nodes` field
104
+ def nodes
105
+ @object.edge_nodes
106
+ end
107
+
108
+ def edges
109
+ if @object.is_a?(GraphQL::Pagination::Connection)
110
+ @object.edges
111
+ elsif context.interpreter?
112
+ context.schema.after_lazy(object.edge_nodes) do |nodes|
113
+ nodes.map { |n| self.class.edge_class.new(n, object) }
114
+ end
115
+ else
116
+ # This is done by edges_instrumentation
117
+ @object.edge_nodes
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end