graphql 1.8.0.pre4 → 1.8.0.pre5

Sign up to get free protection for your applications and to get access to all the features.
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!