graphql 1.11.4 → 1.11.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/object_generator.rb +2 -0
  3. data/lib/generators/graphql/templates/union.erb +1 -1
  4. data/lib/graphql.rb +17 -0
  5. data/lib/graphql/argument.rb +3 -3
  6. data/lib/graphql/backtrace/tracer.rb +2 -1
  7. data/lib/graphql/define/assign_global_id_field.rb +2 -2
  8. data/lib/graphql/execution/interpreter.rb +10 -0
  9. data/lib/graphql/execution/interpreter/arguments.rb +21 -6
  10. data/lib/graphql/execution/interpreter/arguments_cache.rb +8 -0
  11. data/lib/graphql/execution/interpreter/runtime.rb +70 -42
  12. data/lib/graphql/integer_decoding_error.rb +17 -0
  13. data/lib/graphql/introspection.rb +96 -0
  14. data/lib/graphql/introspection/field_type.rb +7 -3
  15. data/lib/graphql/introspection/input_value_type.rb +6 -0
  16. data/lib/graphql/introspection/introspection_query.rb +6 -92
  17. data/lib/graphql/introspection/type_type.rb +7 -3
  18. data/lib/graphql/invalid_null_error.rb +1 -1
  19. data/lib/graphql/language/block_string.rb +24 -5
  20. data/lib/graphql/language/lexer.rb +7 -3
  21. data/lib/graphql/language/lexer.rl +7 -3
  22. data/lib/graphql/language/nodes.rb +1 -1
  23. data/lib/graphql/language/parser.rb +107 -103
  24. data/lib/graphql/language/parser.y +4 -0
  25. data/lib/graphql/language/sanitized_printer.rb +59 -26
  26. data/lib/graphql/name_validator.rb +6 -7
  27. data/lib/graphql/pagination/connections.rb +11 -3
  28. data/lib/graphql/query.rb +6 -3
  29. data/lib/graphql/query/context.rb +14 -3
  30. data/lib/graphql/query/validation_pipeline.rb +1 -1
  31. data/lib/graphql/relay/array_connection.rb +2 -2
  32. data/lib/graphql/relay/range_add.rb +14 -5
  33. data/lib/graphql/schema.rb +47 -19
  34. data/lib/graphql/schema/argument.rb +56 -5
  35. data/lib/graphql/schema/build_from_definition.rb +67 -38
  36. data/lib/graphql/schema/default_type_error.rb +2 -0
  37. data/lib/graphql/schema/directive/deprecated.rb +1 -1
  38. data/lib/graphql/schema/field.rb +32 -16
  39. data/lib/graphql/schema/field/connection_extension.rb +8 -7
  40. data/lib/graphql/schema/field/scope_extension.rb +1 -1
  41. data/lib/graphql/schema/input_object.rb +5 -3
  42. data/lib/graphql/schema/interface.rb +1 -1
  43. data/lib/graphql/schema/late_bound_type.rb +2 -2
  44. data/lib/graphql/schema/loader.rb +1 -0
  45. data/lib/graphql/schema/member/build_type.rb +14 -4
  46. data/lib/graphql/schema/member/has_arguments.rb +54 -53
  47. data/lib/graphql/schema/member/has_fields.rb +2 -2
  48. data/lib/graphql/schema/member/type_system_helpers.rb +2 -2
  49. data/lib/graphql/schema/relay_classic_mutation.rb +4 -2
  50. data/lib/graphql/schema/timeout.rb +29 -15
  51. data/lib/graphql/schema/unique_within_type.rb +1 -2
  52. data/lib/graphql/schema/validation.rb +8 -0
  53. data/lib/graphql/static_validation.rb +1 -0
  54. data/lib/graphql/static_validation/all_rules.rb +1 -0
  55. data/lib/graphql/static_validation/rules/fields_will_merge.rb +25 -17
  56. data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
  57. data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
  58. data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
  59. data/lib/graphql/static_validation/validator.rb +29 -7
  60. data/lib/graphql/subscriptions.rb +1 -3
  61. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +21 -7
  62. data/lib/graphql/tracing/platform_tracing.rb +1 -1
  63. data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
  64. data/lib/graphql/types/int.rb +9 -2
  65. data/lib/graphql/types/relay/base_connection.rb +2 -1
  66. data/lib/graphql/types/relay/base_edge.rb +2 -1
  67. data/lib/graphql/types/string.rb +7 -1
  68. data/lib/graphql/unauthorized_error.rb +1 -1
  69. data/lib/graphql/version.rb +1 -1
  70. data/readme.md +1 -1
  71. metadata +7 -3
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ module InputObjectNamesAreUnique
5
+ def on_input_object(node, parent)
6
+ validate_input_fields(node)
7
+ super
8
+ end
9
+
10
+ private
11
+
12
+ def validate_input_fields(node)
13
+ input_field_defns = node.arguments
14
+ input_fields_by_name = Hash.new { |h, k| h[k] = [] }
15
+ input_field_defns.each { |a| input_fields_by_name[a.name] << a }
16
+
17
+ input_fields_by_name.each do |name, defns|
18
+ if defns.size > 1
19
+ error = GraphQL::StaticValidation::InputObjectNamesAreUniqueError.new(
20
+ "There can be only one input field named \"#{name}\"",
21
+ nodes: defns,
22
+ name: name
23
+ )
24
+ add_error(error)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class InputObjectNamesAreUniqueError < StaticValidation::Error
5
+ attr_reader :name
6
+
7
+ def initialize(message, path: nil, nodes: [], name:)
8
+ super(message, path: path, nodes: nodes)
9
+ @name = name
10
+ end
11
+
12
+ # A hash representation of this Message
13
+ def to_h
14
+ extensions = {
15
+ "code" => code,
16
+ "name" => name
17
+ }
18
+
19
+ super.merge({
20
+ "extensions" => extensions
21
+ })
22
+ end
23
+
24
+ def code
25
+ "inputFieldNotUnique"
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -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
@@ -20,8 +20,10 @@ module GraphQL
20
20
 
21
21
  # Validate `query` against the schema. Returns an array of message hashes.
22
22
  # @param query [GraphQL::Query]
23
+ # @param validate [Boolean]
24
+ # @param timeout [Float] Number of seconds to wait before aborting validation. Any positive number may be used, including Floats to specify fractional seconds.
23
25
  # @return [Array<Hash>]
24
- def validate(query, validate: true)
26
+ def validate(query, validate: true, timeout: nil)
25
27
  query.trace("validate", { validate: validate, query: query }) do
26
28
  can_skip_rewrite = query.context.interpreter? && query.schema.using_ast_analysis? && query.schema.is_a?(Class)
27
29
  errors = if validate == false && can_skip_rewrite
@@ -32,18 +34,29 @@ module GraphQL
32
34
 
33
35
  context = GraphQL::StaticValidation::ValidationContext.new(query, visitor_class)
34
36
 
35
- # Attach legacy-style rules
36
- rules_to_use.each do |rule_class_or_module|
37
- if rule_class_or_module.method_defined?(:validate)
38
- rule_class_or_module.new.validate(context)
37
+ begin
38
+ # 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.
39
+ # A timeout value of 0 or nil will execute the block without any timeout.
40
+ Timeout::timeout(timeout) do
41
+ # Attach legacy-style rules.
42
+ # Only loop through rules if it has legacy-style rules
43
+ unless (legacy_rules = rules_to_use - GraphQL::StaticValidation::ALL_RULES).empty?
44
+ legacy_rules.each do |rule_class_or_module|
45
+ if rule_class_or_module.method_defined?(:validate)
46
+ rule_class_or_module.new.validate(context)
47
+ end
48
+ end
49
+ end
50
+
51
+ context.visitor.visit
39
52
  end
53
+ rescue Timeout::Error
54
+ handle_timeout(query, context)
40
55
  end
41
56
 
42
- context.visitor.visit
43
57
  context.errors
44
58
  end
45
59
 
46
-
47
60
  irep = if errors.empty? && context
48
61
  # Only return this if there are no errors and validation was actually run
49
62
  context.visitor.rewrite_document
@@ -57,6 +70,15 @@ module GraphQL
57
70
  }
58
71
  end
59
72
  end
73
+
74
+ # Invoked when static validation times out.
75
+ # @param query [GraphQL::Query]
76
+ # @param context [GraphQL::StaticValidation::ValidationContext]
77
+ def handle_timeout(query, context)
78
+ context.errors << GraphQL::StaticValidation::ValidationTimeoutError.new(
79
+ "Timeout on validation of query"
80
+ )
81
+ end
60
82
  end
61
83
  end
62
84
  end
@@ -4,9 +4,7 @@ require "graphql/subscriptions/broadcast_analyzer"
4
4
  require "graphql/subscriptions/event"
5
5
  require "graphql/subscriptions/instrumentation"
6
6
  require "graphql/subscriptions/serialize"
7
- if defined?(ActionCable)
8
- require "graphql/subscriptions/action_cable_subscriptions"
9
- end
7
+ require "graphql/subscriptions/action_cable_subscriptions"
10
8
  require "graphql/subscriptions/subscription_root"
11
9
  require "graphql/subscriptions/default_subscription_resolve_extension"
12
10
 
@@ -4,7 +4,7 @@ module GraphQL
4
4
  # A subscriptions implementation that sends data
5
5
  # as ActionCable broadcastings.
6
6
  #
7
- # Experimental, some things to keep in mind:
7
+ # Some things to keep in mind:
8
8
  #
9
9
  # - No queueing system; ActiveJob should be added
10
10
  # - Take care to reload context when re-delivering the subscription. (see {Query#subscription_update?})
@@ -86,28 +86,32 @@ module GraphQL
86
86
  EVENT_PREFIX = "graphql-event:"
87
87
 
88
88
  # @param serializer [<#dump(obj), #load(string)] Used for serializing messages before handing them to `.broadcast(msg)`
89
- def initialize(serializer: Serialize, **rest)
89
+ # @param namespace [string] Used to namespace events and subscriptions (default: '')
90
+ def initialize(serializer: Serialize, namespace: '', action_cable: ActionCable, action_cable_coder: ActiveSupport::JSON, **rest)
90
91
  # A per-process map of subscriptions to deliver.
91
92
  # This is provided by Rails, so let's use it
92
93
  @subscriptions = Concurrent::Map.new
93
94
  @events = Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new { |h2, k2| h2[k2] = Concurrent::Array.new } }
95
+ @action_cable = action_cable
96
+ @action_cable_coder = action_cable_coder
94
97
  @serializer = serializer
98
+ @transmit_ns = namespace
95
99
  super
96
100
  end
97
101
 
98
102
  # An event was triggered; Push the data over ActionCable.
99
103
  # Subscribers will re-evaluate locally.
100
104
  def execute_all(event, object)
101
- stream = EVENT_PREFIX + event.topic
105
+ stream = stream_event_name(event)
102
106
  message = @serializer.dump(object)
103
- ActionCable.server.broadcast(stream, message)
107
+ @action_cable.server.broadcast(stream, message)
104
108
  end
105
109
 
106
110
  # This subscription was re-evaluated.
107
111
  # Send it to the specific stream where this client was waiting.
108
112
  def deliver(subscription_id, result)
109
113
  payload = { result: result.to_h, more: true }
110
- ActionCable.server.broadcast(SUBSCRIPTION_PREFIX + subscription_id, payload)
114
+ @action_cable.server.broadcast(stream_subscription_name(subscription_id), payload)
111
115
  end
112
116
 
113
117
  # A query was run where these events were subscribed to.
@@ -117,7 +121,7 @@ module GraphQL
117
121
  def write_subscription(query, events)
118
122
  channel = query.context.fetch(:channel)
119
123
  subscription_id = query.context[:subscription_id] ||= build_id
120
- stream = query.context[:action_cable_stream] ||= SUBSCRIPTION_PREFIX + subscription_id
124
+ stream = stream_subscription_name(subscription_id)
121
125
  channel.stream_from(stream)
122
126
  @subscriptions[subscription_id] = query
123
127
  events.each do |event|
@@ -141,7 +145,7 @@ module GraphQL
141
145
  #
142
146
  def setup_stream(channel, initial_event)
143
147
  topic = initial_event.topic
144
- channel.stream_from(EVENT_PREFIX + topic, coder: ActiveSupport::JSON) do |message|
148
+ channel.stream_from(stream_event_name(initial_event), coder: @action_cable_coder) do |message|
145
149
  object = @serializer.load(message)
146
150
  events_by_fingerprint = @events[topic]
147
151
  events_by_fingerprint.each do |_fingerprint, events|
@@ -197,6 +201,16 @@ module GraphQL
197
201
  end
198
202
  end
199
203
  end
204
+
205
+ private
206
+
207
+ def stream_subscription_name(subscription_id)
208
+ [SUBSCRIPTION_PREFIX, @transmit_ns, subscription_id].join
209
+ end
210
+
211
+ def stream_event_name(event)
212
+ [EVENT_PREFIX, @transmit_ns, event.topic].join
213
+ end
200
214
  end
201
215
  end
202
216
  end
@@ -95,7 +95,7 @@ module GraphQL
95
95
  end
96
96
 
97
97
  def self.use(schema_defn, options = {})
98
- tracer = self.new(options)
98
+ tracer = self.new(**options)
99
99
  schema_defn.instrument(:field, tracer)
100
100
  schema_defn.tracer(tracer)
101
101
  end
@@ -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
 
@@ -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)
@@ -94,7 +94,8 @@ module GraphQL
94
94
  type = nullable ? [@node_type, null: true] : [@node_type]
95
95
  field :nodes, type,
96
96
  null: nullable,
97
- description: "A list of nodes."
97
+ description: "A list of nodes.",
98
+ connection: false
98
99
  end
99
100
  end
100
101
 
@@ -33,7 +33,8 @@ module GraphQL
33
33
  if node_type
34
34
  @node_type = node_type
35
35
  # Add a default `node` field
36
- field :node, node_type, null: null, description: "The item at the end of the edge."
36
+ field :node, node_type, null: null, description: "The item at the end of the edge.",
37
+ connection: false
37
38
  end
38
39
  @node_type
39
40
  end
@@ -7,7 +7,13 @@ module GraphQL
7
7
 
8
8
  def self.coerce_result(value, ctx)
9
9
  str = value.to_s
10
- str.encoding == Encoding::UTF_8 ? str : str.encode(Encoding::UTF_8)
10
+ if str.encoding == Encoding::UTF_8
11
+ str
12
+ elsif str.frozen?
13
+ str.encode(Encoding::UTF_8)
14
+ else
15
+ str.encode!(Encoding::UTF_8)
16
+ end
11
17
  rescue EncodingError
12
18
  err = GraphQL::StringEncodingError.new(str)
13
19
  ctx.schema.type_error(err, ctx)
@@ -22,7 +22,7 @@ module GraphQL
22
22
  @object = object
23
23
  @type = type
24
24
  @context = context
25
- message ||= "An instance of #{object.class} failed #{type.name}'s authorization check"
25
+ message ||= "An instance of #{object.class} failed #{type.graphql_name}'s authorization check"
26
26
  super(message)
27
27
  end
28
28
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.11.4"
3
+ VERSION = "1.11.8"
4
4
  end
data/readme.md CHANGED
@@ -9,7 +9,7 @@
9
9
  A Ruby implementation of [GraphQL](https://graphql.org/).
10
10
 
11
11
  - [Website](https://graphql-ruby.org/)
12
- - [API Documentation](https://www.rubydoc.info/gems/graphql)
12
+ - [API Documentation](https://www.rubydoc.info/github/rmosolgo/graphql-ruby)
13
13
  - [Newsletter](https://tinyletter.com/graphql-ruby)
14
14
 
15
15
  ## Installation
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.11.4
4
+ version: 1.11.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-25 00:00:00.000000000 Z
11
+ date: 2021-02-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -445,6 +445,7 @@ files:
445
445
  - lib/graphql/id_type.rb
446
446
  - lib/graphql/input_object_type.rb
447
447
  - lib/graphql/int_type.rb
448
+ - lib/graphql/integer_decoding_error.rb
448
449
  - lib/graphql/integer_encoding_error.rb
449
450
  - lib/graphql/interface_type.rb
450
451
  - lib/graphql/internal_representation.rb
@@ -650,6 +651,8 @@ files:
650
651
  - lib/graphql/static_validation/rules/fragments_are_on_composite_types_error.rb
651
652
  - lib/graphql/static_validation/rules/fragments_are_used.rb
652
653
  - lib/graphql/static_validation/rules/fragments_are_used_error.rb
654
+ - lib/graphql/static_validation/rules/input_object_names_are_unique.rb
655
+ - lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb
653
656
  - lib/graphql/static_validation/rules/mutation_root_exists.rb
654
657
  - lib/graphql/static_validation/rules/mutation_root_exists_error.rb
655
658
  - lib/graphql/static_validation/rules/no_definitions_are_present.rb
@@ -676,6 +679,7 @@ files:
676
679
  - lib/graphql/static_validation/rules/variables_are_used_and_defined_error.rb
677
680
  - lib/graphql/static_validation/type_stack.rb
678
681
  - lib/graphql/static_validation/validation_context.rb
682
+ - lib/graphql/static_validation/validation_timeout_error.rb
679
683
  - lib/graphql/static_validation/validator.rb
680
684
  - lib/graphql/string_encoding_error.rb
681
685
  - lib/graphql/string_type.rb
@@ -752,7 +756,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
752
756
  - !ruby/object:Gem::Version
753
757
  version: '0'
754
758
  requirements: []
755
- rubygems_version: 3.1.2
759
+ rubygems_version: 3.1.4
756
760
  signing_key:
757
761
  specification_version: 4
758
762
  summary: A GraphQL language and runtime for Ruby