graphql 1.8.0.pre4 → 1.8.0.pre5

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/base_type.rb +10 -27
  3. data/lib/graphql/compatibility/query_parser_specification.rb +7 -0
  4. data/lib/graphql/field.rb +3 -3
  5. data/lib/graphql/internal_representation/node.rb +32 -13
  6. data/lib/graphql/internal_representation/visit.rb +3 -6
  7. data/lib/graphql/language.rb +1 -0
  8. data/lib/graphql/language/block_string.rb +47 -0
  9. data/lib/graphql/language/lexer.rb +129 -68
  10. data/lib/graphql/language/lexer.rl +13 -4
  11. data/lib/graphql/language/nodes.rb +6 -3
  12. data/lib/graphql/language/printer.rb +1 -1
  13. data/lib/graphql/language/token.rb +1 -1
  14. data/lib/graphql/query.rb +1 -1
  15. data/lib/graphql/relay.rb +1 -0
  16. data/lib/graphql/relay/connection_type.rb +5 -3
  17. data/lib/graphql/relay/edge_type.rb +2 -1
  18. data/lib/graphql/relay/type_extensions.rb +30 -0
  19. data/lib/graphql/schema.rb +25 -0
  20. data/lib/graphql/schema/build_from_definition.rb +2 -0
  21. data/lib/graphql/schema/field.rb +3 -0
  22. data/lib/graphql/schema/finder.rb +153 -0
  23. data/lib/graphql/schema/member.rb +3 -1
  24. data/lib/graphql/schema/printer.rb +1 -1
  25. data/lib/graphql/static_validation/rules/fields_will_merge.rb +15 -8
  26. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +11 -1
  27. data/lib/graphql/tracing/data_dog_tracing.rb +13 -9
  28. data/lib/graphql/upgrader/member.rb +19 -8
  29. data/lib/graphql/version.rb +1 -1
  30. data/spec/graphql/backtrace_spec.rb +10 -0
  31. data/spec/graphql/directive_spec.rb +3 -1
  32. data/spec/graphql/language/block_string_spec.rb +70 -0
  33. data/spec/graphql/language/lexer_spec.rb +9 -0
  34. data/spec/graphql/query_spec.rb +1 -1
  35. data/spec/graphql/schema/field_spec.rb +8 -0
  36. data/spec/graphql/schema/finder_spec.rb +135 -0
  37. data/spec/graphql/schema/printer_spec.rb +48 -5
  38. data/spec/graphql/schema_spec.rb +7 -0
  39. data/spec/graphql/upgrader/member_spec.rb +25 -0
  40. metadata +23 -2
@@ -34,6 +34,9 @@
34
34
  RBRACKET = ']';
35
35
  COLON = ':';
36
36
  QUOTE = '"';
37
+ BLOCK_QUOTE = '"""';
38
+ ESCAPED_BLOCK_QUOTE = '\\"""';
39
+ BLOCK_STRING_CHAR = (ESCAPED_BLOCK_QUOTE | ^'"""');
37
40
  ESCAPED_QUOTE = '\\"';
38
41
  STRING_CHAR = (ESCAPED_QUOTE | ^'"');
39
42
  VAR_SIGN = '$';
@@ -44,7 +47,7 @@
44
47
  PIPE = '|';
45
48
 
46
49
  QUOTED_STRING = QUOTE STRING_CHAR* QUOTE;
47
-
50
+ BLOCK_STRING = BLOCK_QUOTE BLOCK_STRING_CHAR* BLOCK_QUOTE;
48
51
  # catch-all for anything else. must be at the bottom for precedence.
49
52
  UNKNOWN_CHAR = /./;
50
53
 
@@ -75,7 +78,8 @@
75
78
  RBRACKET => { emit(:RBRACKET, ts, te, meta) };
76
79
  LBRACKET => { emit(:LBRACKET, ts, te, meta) };
77
80
  COLON => { emit(:COLON, ts, te, meta) };
78
- QUOTED_STRING => { emit_string(ts + 1, te, meta) };
81
+ QUOTED_STRING => { emit_string(ts, te, meta, block: false) };
82
+ BLOCK_STRING => { emit_string(ts, te, meta, block: true) };
79
83
  VAR_SIGN => { emit(:VAR_SIGN, ts, te, meta) };
80
84
  DIR_SIGN => { emit(:DIR_SIGN, ts, te, meta) };
81
85
  ELLIPSIS => { emit(:ELLIPSIS, ts, te, meta) };
@@ -188,8 +192,13 @@ module GraphQL
188
192
  PACK_DIRECTIVE = "c*"
189
193
  UTF_8_ENCODING = "UTF-8"
190
194
 
191
- def self.emit_string(ts, te, meta)
192
- value = meta[:data][ts...te - 1].pack(PACK_DIRECTIVE).force_encoding(UTF_8_ENCODING)
195
+ def self.emit_string(ts, te, meta, block:)
196
+ quotes_length = block ? 3 : 1
197
+ ts += quotes_length
198
+ value = meta[:data][ts...te - quotes_length].pack(PACK_DIRECTIVE).force_encoding(UTF_8_ENCODING)
199
+ if block
200
+ value = GraphQL::Language::BlockString.trim_whitespace(value)
201
+ end
193
202
  if value !~ VALID_STRING
194
203
  meta[:tokens] << token = GraphQL::Language::Token.new(
195
204
  name: :BAD_UNICODE_ESCAPE,
@@ -288,13 +288,16 @@ module GraphQL
288
288
  private
289
289
 
290
290
  def serialize_value_for_hash(value)
291
- if value.is_a? InputObject
291
+ case value
292
+ when InputObject
292
293
  value.to_h
293
- elsif value.is_a? Array
294
+ when Array
294
295
  value.map do |v|
295
296
  serialize_value_for_hash v
296
297
  end
297
- elsif value.is_a? NullValue
298
+ when Enum
299
+ value.name
300
+ when NullValue
298
301
  nil
299
302
  else
300
303
  value
@@ -93,7 +93,7 @@ module GraphQL
93
93
  end
94
94
 
95
95
  def print_input_object(input_object)
96
- print_node(input_object.to_h)
96
+ "{#{input_object.arguments.map { |a| "#{a.name}: #{print(a.value)}" }.join(", ")}}"
97
97
  end
98
98
 
99
99
  def print_list_type(list_type)
@@ -12,7 +12,7 @@ module GraphQL
12
12
 
13
13
  def initialize(value:, name:, line:, col:, prev_token:)
14
14
  @name = name
15
- @value = value
15
+ @value = value.freeze
16
16
  @line = line
17
17
  @col = col
18
18
  @prev_token = prev_token
data/lib/graphql/query.rb CHANGED
@@ -160,7 +160,7 @@ module GraphQL
160
160
  def result
161
161
  if !@executed
162
162
  with_prepared_ast {
163
- Execution::Multiplex.run_queries(@schema, [self])
163
+ Execution::Multiplex.run_queries(@schema, [self], context: @context)
164
164
  }
165
165
  end
166
166
  @result ||= Query::Result.new(query: self, values: @result_values)
data/lib/graphql/relay.rb CHANGED
@@ -15,3 +15,4 @@ require 'graphql/relay/node'
15
15
  require 'graphql/relay/connection_instrumentation'
16
16
  require 'graphql/relay/connection_resolve'
17
17
  require 'graphql/relay/connection_type'
18
+ require 'graphql/relay/type_extensions'
@@ -13,15 +13,17 @@ module GraphQL
13
13
  self.bidirectional_pagination = false
14
14
 
15
15
  # Create a connection which exposes edges of this type
16
- def self.create_type(wrapped_type, edge_type: wrapped_type.edge_type, edge_class: GraphQL::Relay::Edge, nodes_field: ConnectionType.default_nodes_field, &block)
16
+ def self.create_type(wrapped_type, edge_type: nil, edge_class: GraphQL::Relay::Edge, nodes_field: ConnectionType.default_nodes_field, &block)
17
17
  custom_edge_class = edge_class
18
18
 
19
19
  # Any call that would trigger `wrapped_type.ensure_defined`
20
20
  # must be inside this lazy block, otherwise we get weird
21
21
  # cyclical dependency errors :S
22
22
  ObjectType.define do
23
- name("#{wrapped_type.name}Connection")
24
- description("The connection type for #{wrapped_type.name}.")
23
+ type_name = wrapped_type.is_a?(GraphQL::BaseType) ? wrapped_type.name : wrapped_type.graphql_name
24
+ edge_type ||= wrapped_type.edge_type
25
+ name("#{type_name}Connection")
26
+ description("The connection type for #{type_name}.")
25
27
  field :edges, types[edge_type], "A list of edges.", edge_class: custom_edge_class, property: :edge_nodes
26
28
 
27
29
  if nodes_field
@@ -4,7 +4,8 @@ module GraphQL
4
4
  module EdgeType
5
5
  def self.create_type(wrapped_type, name: nil, &block)
6
6
  GraphQL::ObjectType.define do
7
- name("#{wrapped_type.name}Edge")
7
+ type_name = wrapped_type.is_a?(GraphQL::BaseType) ? wrapped_type.name : wrapped_type.graphql_name
8
+ name("#{type_name}Edge")
8
9
  description "An edge in a connection."
9
10
  field :node, wrapped_type, "The item at the end of the edge."
10
11
  field :cursor, !types.String, "A cursor for use in pagination."
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Relay
4
+ # Mixin for Relay-related methods in type objects
5
+ # (used by BaseType and Schema::Member).
6
+ module TypeExtensions
7
+ # @return [GraphQL::ObjectType] The default connection type for this object type
8
+ def connection_type
9
+ @connection_type ||= define_connection
10
+ end
11
+
12
+ # Define a custom connection type for this object type
13
+ # @return [GraphQL::ObjectType]
14
+ def define_connection(**kwargs, &block)
15
+ GraphQL::Relay::ConnectionType.create_type(self, **kwargs, &block)
16
+ end
17
+
18
+ # @return [GraphQL::ObjectType] The default edge type for this object type
19
+ def edge_type
20
+ @edge_type ||= define_edge
21
+ end
22
+
23
+ # Define a custom edge type for this object type
24
+ # @return [GraphQL::ObjectType]
25
+ def define_edge(**kwargs, &block)
26
+ GraphQL::Relay::EdgeType.create_type(self, **kwargs, &block)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -3,6 +3,7 @@ require "graphql/schema/base_64_encoder"
3
3
  require "graphql/schema/catchall_middleware"
4
4
  require "graphql/schema/default_parse_error"
5
5
  require "graphql/schema/default_type_error"
6
+ require "graphql/schema/finder"
6
7
  require "graphql/schema/invalid_type_error"
7
8
  require "graphql/schema/introspection_system"
8
9
  require "graphql/schema/late_bound_type"
@@ -329,6 +330,19 @@ module GraphQL
329
330
  }
330
331
  end
331
332
 
333
+ # Search for a schema member using a string path
334
+ # @example Finding a Field
335
+ # Schema.find("Ensemble.musicians")
336
+ #
337
+ # @see {GraphQL::Schema::Finder} for more examples
338
+ # @param path [String] A dot-separated path to the member
339
+ # @raise [Schema::Finder::MemberNotFoundError] if path could not be found
340
+ # @return [GraphQL::BaseType, GraphQL::Field, GraphQL::Argument, GraphQL::Directive] A GraphQL Schema Member
341
+ def find(path)
342
+ rebuild_artifacts unless defined?(@finder)
343
+ @find_cache[path] ||= @finder.find(path)
344
+ end
345
+
332
346
  # Resolve field named `field_name` for type `parent_type`.
333
347
  # Handles dynamic fields `__typename`, `__type` and `__schema`, too
334
348
  # @param parent_type [String, GraphQL::BaseType]
@@ -641,6 +655,7 @@ module GraphQL
641
655
  schema_defn.query = query
642
656
  schema_defn.mutation = mutation
643
657
  schema_defn.subscription = subscription
658
+ schema_defn.max_complexity = max_complexity
644
659
  schema_defn.max_depth = max_depth
645
660
  schema_defn.default_max_page_size = default_max_page_size
646
661
  schema_defn.orphan_types = orphan_types
@@ -720,6 +735,14 @@ module GraphQL
720
735
  end
721
736
  end
722
737
 
738
+ def max_complexity(max_complexity = nil)
739
+ if max_complexity
740
+ @max_complexity = max_complexity
741
+ else
742
+ @max_complexity
743
+ end
744
+ end
745
+
723
746
  def max_depth(new_max_depth = nil)
724
747
  if new_max_depth
725
748
  @max_depth = new_max_depth
@@ -846,6 +869,8 @@ module GraphQL
846
869
  @instrumented_field_map = traversal.instrumented_field_map
847
870
  @type_reference_map = traversal.type_reference_map
848
871
  @union_memberships = traversal.union_memberships
872
+ @find_cache = {}
873
+ @finder = Finder.new(self)
849
874
  end
850
875
  ensure
851
876
  @rebuilding_artifacts = false
@@ -193,6 +193,8 @@ module GraphQL
193
193
  nil
194
194
  when GraphQL::Language::Nodes::InputObject
195
195
  default_value.to_h
196
+ when Array
197
+ default_value.map { |v| build_default_value(v) }
196
198
  else
197
199
  default_value
198
200
  end
@@ -16,6 +16,9 @@ module GraphQL
16
16
  # @return [Hash{String => GraphQL::Schema::Argument}]
17
17
  attr_reader :arguments
18
18
 
19
+ # @return [Symbol]
20
+ attr_reader :method
21
+
19
22
  def initialize(name, return_type_expr = nil, desc = nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, resolve: nil, introspection: false, extras: [], &definition_block)
20
23
  if !(field || function)
21
24
  if return_type_expr.nil?
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ # Find schema members using string paths
6
+ #
7
+ # @example Finding object types
8
+ # MySchema.find("SomeObjectType")
9
+ #
10
+ # @example Finding fields
11
+ # MySchema.find("SomeObjectType.myField")
12
+ #
13
+ # @example Finding arguments
14
+ # MySchema.find("SomeObjectType.myField.anArgument")
15
+ #
16
+ # @example Finding directives
17
+ # MySchema.find("@include")
18
+ #
19
+ class Finder
20
+ class MemberNotFoundError < ArgumentError; end
21
+
22
+ def initialize(schema)
23
+ @schema = schema
24
+ end
25
+
26
+ def find(path)
27
+ path = path.split(".")
28
+ type_or_directive = path.shift
29
+
30
+ if type_or_directive.start_with?("@")
31
+ directive = schema.directives[type_or_directive[1..-1]]
32
+
33
+ if directive.nil?
34
+ raise MemberNotFoundError, "Could not find directive `#{type_or_directive}` in schema."
35
+ end
36
+
37
+ return directive if path.empty?
38
+
39
+ find_in_directive(directive, path: path)
40
+ else
41
+ type = schema.types[type_or_directive]
42
+
43
+ if type.nil?
44
+ raise MemberNotFoundError, "Could not find type `#{type_or_directive}` in schema."
45
+ end
46
+
47
+ return type if path.empty?
48
+
49
+ find_in_type(type, path: path)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ attr_reader :schema
56
+
57
+ def find_in_directive(directive, path:)
58
+ argument_name = path.shift
59
+ argument = directive.arguments[argument_name]
60
+
61
+ if argument.nil?
62
+ raise MemberNotFoundError, "Could not find argument `#{argument_name}` on directive #{directive}."
63
+ end
64
+
65
+ argument
66
+ end
67
+
68
+ def find_in_type(type, path:)
69
+ case type
70
+ when GraphQL::ObjectType
71
+ find_in_fields_type(type, kind: "object", path: path)
72
+ when GraphQL::InterfaceType
73
+ find_in_fields_type(type, kind: "interface", path: path)
74
+ when GraphQL::InputObjectType
75
+ find_in_input_object(type, path: path)
76
+ when GraphQL::UnionType
77
+ # Error out if path that was provided is too long
78
+ # i.e UnionType.PossibleType.aField
79
+ # Use PossibleType.aField instead.
80
+ if invalid = path.first
81
+ raise MemberNotFoundError, "Cannot select union possible type `#{invalid}`. Select the type directly instead."
82
+ end
83
+ when GraphQL::EnumType
84
+ find_in_enum_type(type, path: path)
85
+ end
86
+ end
87
+
88
+ def find_in_fields_type(type, kind:, path:)
89
+ field_name = path.shift
90
+ field = schema.get_field(type, field_name)
91
+
92
+ if field.nil?
93
+ raise MemberNotFoundError, "Could not find field `#{field_name}` on #{kind} type `#{type}`."
94
+ end
95
+
96
+ return field if path.empty?
97
+
98
+ find_in_field(field, path: path)
99
+ end
100
+
101
+ def find_in_field(field, path:)
102
+ argument_name = path.shift
103
+ argument = field.arguments[argument_name]
104
+
105
+ if argument.nil?
106
+ raise MemberNotFoundError, "Could not find argument `#{argument_name}` on field `#{field.name}`."
107
+ end
108
+
109
+ # Error out if path that was provided is too long
110
+ # i.e Type.field.argument.somethingBad
111
+ if invalid = path.first
112
+ raise MemberNotFoundError, "Cannot select member `#{invalid}` on a field."
113
+ end
114
+
115
+ argument
116
+ end
117
+
118
+ def find_in_input_object(input_object, path:)
119
+ field_name = path.shift
120
+ input_field = input_object.input_fields[field_name]
121
+
122
+ if input_field.nil?
123
+ raise MemberNotFoundError, "Could not find input field `#{field_name}` on input object type `#{input_object}`."
124
+ end
125
+
126
+ # Error out if path that was provided is too long
127
+ # i.e InputType.inputField.bad
128
+ if invalid = path.first
129
+ raise MemberNotFoundError, "Cannot select member `#{invalid}` on an input field."
130
+ end
131
+
132
+ input_field
133
+ end
134
+
135
+ def find_in_enum_type(enum_type, path:)
136
+ value_name = path.shift
137
+ enum_value = enum_type.values[value_name]
138
+
139
+ if enum_value.nil?
140
+ raise MemberNotFoundError, "Could not find enum value `#{value_name}` on enum type `#{enum_type}`."
141
+ end
142
+
143
+ # Error out if path that was provided is too long
144
+ # i.e Enum.VALUE.wat
145
+ if invalid = path.first
146
+ raise MemberNotFoundError, "Cannot select member `#{invalid}` on an enum value."
147
+ end
148
+
149
+ enum_value
150
+ end
151
+ end
152
+ end
153
+ end
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require "graphql/relay/type_extensions"
3
+
2
4
  module GraphQL
3
5
  # The base class for things that make up the schema,
4
6
  # eg objects, enums, scalars.
@@ -35,7 +37,7 @@ module GraphQL
35
37
  include GraphQLTypeNames
36
38
  class << self
37
39
  include CachedGraphQLDefinition
38
-
40
+ include GraphQL::Relay::TypeExtensions
39
41
  # Delegate to the derived type definition if possible.
40
42
  # This is tricky because missing methods cause the definition to be built & cached.
41
43
  def method_missing(method_name, *args, &block)
@@ -100,7 +100,7 @@ module GraphQL
100
100
  if reason.value == GraphQL::Directive::DEFAULT_DEPRECATION_REASON
101
101
  "@deprecated"
102
102
  else
103
- "@deprecated(reason: \"#{reason.value}\")"
103
+ "@deprecated(reason: #{reason.value.to_s.inspect})"
104
104
  end
105
105
  else
106
106
  super
@@ -2,6 +2,9 @@
2
2
  module GraphQL
3
3
  module StaticValidation
4
4
  class FieldsWillMerge
5
+ # Special handling for fields without arguments
6
+ NO_ARGS = {}.freeze
7
+
5
8
  def validate(context)
6
9
  context.each_irep_node do |node|
7
10
  if node.ast_nodes.size > 1
@@ -16,15 +19,19 @@ module GraphQL
16
19
 
17
20
  # Check for incompatible / non-identical arguments on this node:
18
21
  args = node.ast_nodes.map do |n|
19
- n.arguments.reduce({}) do |memo, a|
20
- arg_value = a.value
21
- memo[a.name] = case arg_value
22
- when GraphQL::Language::Nodes::AbstractNode
23
- arg_value.to_query_string
24
- else
25
- GraphQL::Language.serialize(arg_value)
22
+ if n.arguments.any?
23
+ n.arguments.reduce({}) do |memo, a|
24
+ arg_value = a.value
25
+ memo[a.name] = case arg_value
26
+ when GraphQL::Language::Nodes::AbstractNode
27
+ arg_value.to_query_string
28
+ else
29
+ GraphQL::Language.serialize(arg_value)
30
+ end
31
+ memo
26
32
  end
27
- memo
33
+ else
34
+ NO_ARGS
28
35
  end
29
36
  end
30
37
  args.uniq!