graphql 1.6.4 → 1.6.5

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +47 -0
  3. data/lib/generators/graphql/install_generator.rb +15 -20
  4. data/lib/generators/graphql/mutation_generator.rb +31 -1
  5. data/lib/generators/graphql/templates/mutation.erb +2 -2
  6. data/lib/generators/graphql/templates/mutation_type.erb +5 -0
  7. data/lib/generators/graphql/templates/schema.erb +0 -1
  8. data/lib/graphql/argument.rb +6 -5
  9. data/lib/graphql/backwards_compatibility.rb +18 -4
  10. data/lib/graphql/base_type.rb +1 -1
  11. data/lib/graphql/compatibility/execution_specification/counter_schema.rb +1 -1
  12. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +1 -1
  13. data/lib/graphql/compatibility/lazy_execution_specification.rb +9 -2
  14. data/lib/graphql/define.rb +1 -0
  15. data/lib/graphql/define/defined_object_proxy.rb +1 -1
  16. data/lib/graphql/define/no_definition_error.rb +7 -0
  17. data/lib/graphql/enum_type.rb +4 -0
  18. data/lib/graphql/execution/execute.rb +3 -3
  19. data/lib/graphql/execution/field_result.rb +1 -1
  20. data/lib/graphql/execution/lazy/resolve.rb +10 -9
  21. data/lib/graphql/execution/multiplex.rb +6 -5
  22. data/lib/graphql/input_object_type.rb +5 -1
  23. data/lib/graphql/interface_type.rb +12 -3
  24. data/lib/graphql/query.rb +21 -5
  25. data/lib/graphql/query/context.rb +11 -0
  26. data/lib/graphql/schema.rb +48 -27
  27. data/lib/graphql/schema/build_from_definition.rb +1 -1
  28. data/lib/graphql/schema/build_from_definition/resolve_map.rb +2 -2
  29. data/lib/graphql/schema/loader.rb +1 -1
  30. data/lib/graphql/schema/traversal.rb +91 -0
  31. data/lib/graphql/schema/validation.rb +1 -1
  32. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +41 -7
  33. data/lib/graphql/union_type.rb +13 -2
  34. data/lib/graphql/version.rb +1 -1
  35. data/readme.md +1 -3
  36. data/spec/generators/graphql/install_generator_spec.rb +3 -1
  37. data/spec/generators/graphql/mutation_generator_spec.rb +14 -0
  38. data/spec/graphql/analysis/max_query_complexity_spec.rb +12 -1
  39. data/spec/graphql/analysis/query_complexity_spec.rb +1 -1
  40. data/spec/graphql/argument_spec.rb +29 -0
  41. data/spec/graphql/define/assign_argument_spec.rb +4 -4
  42. data/spec/graphql/define/instance_definable_spec.rb +1 -1
  43. data/spec/graphql/enum_type_spec.rb +8 -0
  44. data/spec/graphql/execution/lazy_spec.rb +30 -3
  45. data/spec/graphql/interface_type_spec.rb +44 -0
  46. data/spec/graphql/introspection/schema_type_spec.rb +3 -0
  47. data/spec/graphql/introspection/type_type_spec.rb +1 -0
  48. data/spec/graphql/object_type_spec.rb +8 -3
  49. data/spec/graphql/query/context_spec.rb +18 -0
  50. data/spec/graphql/query/executor_spec.rb +1 -1
  51. data/spec/graphql/query/literal_input_spec.rb +31 -15
  52. data/spec/graphql/query/serial_execution/value_resolution_spec.rb +1 -1
  53. data/spec/graphql/query/variables_spec.rb +25 -1
  54. data/spec/graphql/query_spec.rb +24 -9
  55. data/spec/graphql/relay/mutation_spec.rb +1 -1
  56. data/spec/graphql/schema/build_from_definition_spec.rb +1 -1
  57. data/spec/graphql/schema/loader_spec.rb +1 -1
  58. data/spec/graphql/schema/printer_spec.rb +1 -1
  59. data/spec/graphql/schema/{reduce_types_spec.rb → traversal_spec.rb} +21 -4
  60. data/spec/graphql/schema/warden_spec.rb +1 -1
  61. data/spec/graphql/schema_spec.rb +23 -2
  62. data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +133 -0
  63. data/spec/graphql/union_type_spec.rb +53 -0
  64. data/spec/spec_helper.rb +9 -0
  65. data/spec/support/dummy/data.rb +14 -5
  66. data/spec/support/dummy/schema.rb +46 -5
  67. data/spec/support/star_wars/data.rb +10 -6
  68. data/spec/support/star_wars/schema.rb +5 -2
  69. metadata +8 -7
  70. data/lib/graphql/schema/instrumented_field_map.rb +0 -40
  71. data/lib/graphql/schema/reduce_types.rb +0 -69
  72. data/lib/graphql/schema/type_map.rb +0 -31
@@ -274,10 +274,10 @@ module GraphQL
274
274
  Rules::FIELDS_ARE_VALID,
275
275
  ],
276
276
  GraphQL::Schema => [
277
+ Rules::SCHEMA_INSTRUMENTERS_ARE_VALID,
277
278
  Rules::SCHEMA_CAN_RESOLVE_TYPES,
278
279
  Rules::SCHEMA_CAN_FETCH_IDS,
279
280
  Rules::SCHEMA_CAN_GENERATE_IDS,
280
- Rules::SCHEMA_INSTRUMENTERS_ARE_VALID,
281
281
  ],
282
282
  }
283
283
  end
@@ -7,15 +7,15 @@ module GraphQL
7
7
  def validate(context)
8
8
  # holds { name => ast_node } pairs
9
9
  declared_variables = {}
10
-
11
10
  context.visitor[GraphQL::Language::Nodes::OperationDefinition] << ->(node, parent) {
12
11
  declared_variables = node.variables.each_with_object({}) { |var, memo| memo[var.name] = var }
13
12
  }
14
13
 
15
14
  context.visitor[GraphQL::Language::Nodes::Argument] << ->(node, parent) {
16
- return if !node.value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
17
- arguments = nil
15
+ node_values = Array.wrap(node.value).select { |value| value.is_a? GraphQL::Language::Nodes::VariableIdentifier }
16
+ return if node_values.none?
18
17
 
18
+ arguments = nil
19
19
  case parent
20
20
  when GraphQL::Language::Nodes::Field
21
21
  arguments = context.field_definition.arguments
@@ -29,10 +29,13 @@ module GraphQL
29
29
  else
30
30
  raise("Unexpected argument parent: #{parent}")
31
31
  end
32
- var_defn_ast = declared_variables[node.value.name]
33
- # Might be undefined :(
34
- # VariablesAreUsedAndDefined can't finalize its search until the end of the document.
35
- var_defn_ast && arguments && validate_usage(arguments, node, var_defn_ast, context)
32
+
33
+ node_values.each do |node_value|
34
+ var_defn_ast = declared_variables[node_value.name]
35
+ # Might be undefined :(
36
+ # VariablesAreUsedAndDefined can't finalize its search until the end of the document.
37
+ var_defn_ast && arguments && validate_usage(arguments, node, var_defn_ast, context)
38
+ end
36
39
  }
37
40
  end
38
41
 
@@ -53,6 +56,8 @@ module GraphQL
53
56
  var_inner_type = var_type.unwrap
54
57
  arg_inner_type = arg_defn_type.unwrap
55
58
 
59
+ var_type = wrap_var_type_with_depth_of_arg(var_type, arg_node)
60
+
56
61
  if var_inner_type != arg_inner_type
57
62
  context.errors << create_error("Type mismatch", var_type, ast_var, arg_defn, arg_node, context)
58
63
  elsif list_dimension(var_type) != list_dimension(arg_defn_type)
@@ -66,6 +71,35 @@ module GraphQL
66
71
  message("#{error_message} on variable $#{ast_var.name} and argument #{arg_node.name} (#{var_type.to_s} / #{arg_defn.type.to_s})", arg_node, context: context)
67
72
  end
68
73
 
74
+ def wrap_var_type_with_depth_of_arg(var_type, arg_node)
75
+ arg_node_value = arg_node.value
76
+ return var_type unless arg_node_value.is_a?(Array)
77
+ new_var_type = var_type
78
+
79
+ depth_of_array(arg_node_value).times do
80
+ new_var_type = GraphQL::ListType.new(of_type: new_var_type)
81
+ end
82
+
83
+ new_var_type
84
+ end
85
+
86
+ # @return [Integer] Returns the max depth of `array`, or `0` if it isn't an array at all
87
+ def depth_of_array(array)
88
+ case array
89
+ when Array
90
+ max_child_depth = 0
91
+ array.each do |item|
92
+ item_depth = depth_of_array(item)
93
+ if item_depth > max_child_depth
94
+ max_child_depth = item_depth
95
+ end
96
+ end
97
+ 1 + max_child_depth
98
+ else
99
+ 0
100
+ end
101
+ end
102
+
69
103
  def list_dimension(type)
70
104
  if type.kind.list?
71
105
  1 + list_dimension(type.of_type)
@@ -24,13 +24,16 @@ module GraphQL
24
24
  # }
25
25
  #
26
26
  class UnionType < GraphQL::BaseType
27
- accepts_definitions :possible_types
28
- ensure_defined :possible_types
27
+ accepts_definitions :possible_types, :resolve_type
28
+ ensure_defined :possible_types, :resolve_type, :resolve_type_proc
29
+
30
+ attr_accessor :resolve_type_proc
29
31
 
30
32
  def initialize
31
33
  super
32
34
  @dirty_possible_types = []
33
35
  @clean_possible_types = nil
36
+ @resolve_type_proc = nil
34
37
  end
35
38
 
36
39
  def initialize_copy(other)
@@ -64,6 +67,14 @@ module GraphQL
64
67
  end
65
68
  end
66
69
 
70
+ def resolve_type(value, ctx)
71
+ ctx.query.resolve_type(self, value)
72
+ end
73
+
74
+ def resolve_type=(new_resolve_type_proc)
75
+ @resolve_type_proc = new_resolve_type_proc
76
+ end
77
+
67
78
  protected
68
79
 
69
80
  attr_reader :dirty_possible_types
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.6.4"
3
+ VERSION = "1.6.5"
4
4
  end
data/readme.md CHANGED
@@ -49,6 +49,4 @@ I also sell [GraphQL::Pro](http://graphql.pro) which provides several features o
49
49
 
50
50
  - __Say hi & ask questions__ in the [#ruby channel on Slack](https://graphql-slack.herokuapp.com/) or [on Twitter](https://twitter.com/rmosolgo)!
51
51
  - __Report bugs__ by posting a description, full stack trace, and all relevant code in a [GitHub issue](https://github.com/rmosolgo/graphql-ruby/issues).
52
- - __Features & patches__ are welcome! Consider discussing it in an [issue](https://github.com/rmosolgo/graphql-ruby/issues) or in the [#ruby channel on Slack](https://graphql-slack.herokuapp.com/) to make sure we're on the same page.
53
- - __Run the tests__ with `rake test` or start up guard with `bundle exec guard`.
54
- - __Build the site__ with `rake site:serve`, then visit `http://localhost:4000/graphql-ruby/`.
52
+ - __Start hacking__ with the [Development guide](http://graphql-ruby.org/development).
@@ -37,6 +37,7 @@ class GraphQLGeneratorsInstallGeneratorTest < Rails::Generators::TestCase
37
37
 
38
38
  expected_schema = <<-RUBY
39
39
  DummySchema = GraphQL::Schema.define do
40
+ mutation(Types::MutationType)
40
41
  query(Types::QueryType)
41
42
  end
42
43
  RUBY
@@ -160,8 +161,9 @@ RUBY
160
161
 
161
162
  EXPECTED_RELAY_BATCH_SCHEMA = <<-RUBY
162
163
  DummySchema = GraphQL::Schema.define do
163
- query(Types::QueryType)
164
164
 
165
+ mutation(Types::MutationType)
166
+ query(Types::QueryType)
165
167
  # Relay Object Identification:
166
168
 
167
169
  # Return a string UUID for `object`
@@ -5,6 +5,20 @@ require "generators/graphql/mutation_generator"
5
5
  class GraphQLGeneratorsMutationGeneratorTest < BaseGeneratorTest
6
6
  tests Graphql::Generators::MutationGenerator
7
7
 
8
+ destination File.expand_path("../../../tmp/dummy", File.dirname(__FILE__))
9
+
10
+ setup do
11
+ prepare_destination
12
+ FileUtils.cd(File.expand_path("../../../tmp", File.dirname(__FILE__))) do
13
+ `rm -rf dummy`
14
+ `rails new dummy --skip-active-record --skip-test-unit --skip-spring --skip-bundle`
15
+ end
16
+
17
+ FileUtils.cd(destination_root) do
18
+ `rails g graphql:install`
19
+ end
20
+ end
21
+
8
22
  test "it generates an empty resolver by name" do
9
23
  run_generator(["UpdateName"])
10
24
 
@@ -50,7 +50,7 @@ describe GraphQL::Analysis::MaxQueryComplexity do
50
50
  end
51
51
  end
52
52
 
53
- describe "when complexity is overriden at query-level" do
53
+ describe "when max_complexity is decreased at query-level" do
54
54
  before do
55
55
  Dummy::Schema.max_complexity = 100
56
56
  end
@@ -61,6 +61,17 @@ describe GraphQL::Analysis::MaxQueryComplexity do
61
61
  end
62
62
  end
63
63
 
64
+ describe "when max_complexity is increased at query-level" do
65
+ before do
66
+ Dummy::Schema.max_complexity = 1
67
+ end
68
+ let(:result) { Dummy::Schema.execute(query_string, max_complexity: 10) }
69
+
70
+ it "doesn't error" do
71
+ assert_equal nil, result["errors"]
72
+ end
73
+ end
74
+
64
75
  describe "across a multiplex" do
65
76
  before do
66
77
  Dummy::Schema.max_complexity = 9
@@ -260,7 +260,7 @@ describe GraphQL::Analysis::QueryComplexity do
260
260
  GraphQL::Schema.define(
261
261
  query: query_type,
262
262
  orphan_types: [double_complexity_type],
263
- resolve_type: :pass
263
+ resolve_type: ->(a,b,c) { :pass }
264
264
  )
265
265
  }
266
266
  let(:query_string) {%|
@@ -19,6 +19,18 @@ describe GraphQL::Argument do
19
19
  assert_includes err.message, expected_error
20
20
  end
21
21
 
22
+ it "accepts custom keywords" do
23
+ type = GraphQL::ObjectType.define do
24
+ name "Something"
25
+ field :something, types.String do
26
+ argument "flagged", types.Int, metadata_flag: :flag_1
27
+ end
28
+ end
29
+
30
+ arg = type.fields["something"].arguments["flagged"]
31
+ assert_equal true, arg.metadata[:flag_1]
32
+ end
33
+
22
34
  it "accepts proc type" do
23
35
  argument = GraphQL::Argument.define(name: :favoriteFood, type: -> { GraphQL::STRING_TYPE })
24
36
  assert_equal GraphQL::STRING_TYPE, argument.type
@@ -56,6 +68,14 @@ describe GraphQL::Argument do
56
68
  arg_3 = arg_2.redefine { as :ff }
57
69
  assert_equal arg_3.expose_as, "ff"
58
70
  end
71
+
72
+ it "can be set in the passed block" do
73
+ argument = GraphQL::Argument.define do
74
+ name "arg"
75
+ as "arg_name"
76
+ end
77
+ assert_equal "arg_name", argument.as
78
+ end
59
79
  end
60
80
 
61
81
  describe "prepare" do
@@ -69,5 +89,14 @@ describe GraphQL::Argument do
69
89
  argument = GraphQL::Argument.define(name: :someNumber, type: GraphQL::INT_TYPE)
70
90
  assert_equal argument.prepare(1, nil), 1
71
91
  end
92
+
93
+ it "can be set in the passed block" do
94
+ prepare_proc = Proc.new { |arg, ctx| arg + ctx[:val] }
95
+ argument = GraphQL::Argument.define do
96
+ name "arg"
97
+ prepare prepare_proc
98
+ end
99
+ assert_equal argument.prepare(1, {val: 1}), 2
100
+ end
72
101
  end
73
102
  end
@@ -24,17 +24,17 @@ describe GraphQL::Define::AssignArgument do
24
24
  end
25
25
 
26
26
  it "passing unknown keyword arguments will raise" do
27
- err = assert_raises ArgumentError do
27
+ err = assert_raises GraphQL::Define::NoDefinitionError do
28
28
  define_argument(:a, GraphQL::STRING_TYPE, blah: nil)
29
29
  end
30
30
 
31
- assert_equal 'unknown keyword: blah', err.message
31
+ assert_equal "GraphQL::Argument can't define 'blah'", err.message
32
32
 
33
- err = assert_raises ArgumentError do
33
+ err = assert_raises GraphQL::Define::NoDefinitionError do
34
34
  define_argument(:a, GraphQL::STRING_TYPE, blah: nil, blah2: nil)
35
35
  end
36
36
 
37
- assert_equal 'unknown keywords: blah, blah2', err.message
37
+ assert_equal "GraphQL::Argument can't define 'blah'", err.message
38
38
  end
39
39
 
40
40
  def define_argument(*args)
@@ -185,7 +185,7 @@ describe GraphQL::Define::InstanceDefinable do
185
185
 
186
186
  describe "typos" do
187
187
  it "provides the right class name, method name and line number" do
188
- err = assert_raises(NoMethodError) {
188
+ err = assert_raises(GraphQL::Define::NoDefinitionError) {
189
189
  beet = Garden::Vegetable.define {
190
190
  name "Beet"
191
191
  nonsense :Blah
@@ -110,4 +110,12 @@ describe GraphQL::EnumType do
110
110
  assert_equal(7, enum_2.values.size)
111
111
  end
112
112
  end
113
+
114
+ describe "validates enum value name uniqueness" do
115
+ it "raises an exception when adding a duplicate enum value name" do
116
+ assert_raises "Enum value names must be unique. `COW` already exists." do
117
+ enum.add_value(GraphQL::EnumType::EnumValue.define(name: "COW"))
118
+ end
119
+ end
120
+ end
113
121
  end
@@ -17,12 +17,24 @@ describe GraphQL::Execution::Lazy do
17
17
  value
18
18
  nestedSum(value: 7) {
19
19
  value
20
+ nestedSum(value: 1) {
21
+ value
22
+ nestedSum(value: -50) {
23
+ value
24
+ }
25
+ }
20
26
  }
21
27
  }
22
28
  b: nestedSum(value: 2) {
23
29
  value
24
30
  nestedSum(value: 11) {
25
31
  value
32
+ nestedSum(value: 2) {
33
+ value
34
+ nestedSum(value: -50) {
35
+ value
36
+ }
37
+ }
26
38
  }
27
39
  }
28
40
 
@@ -35,9 +47,24 @@ describe GraphQL::Execution::Lazy do
35
47
  |
36
48
 
37
49
  expected_data = {
38
- "a"=>{"value"=>14, "nestedSum"=>{"value"=>46}},
39
- "b"=>{"value"=>14, "nestedSum"=>{"value"=>46}},
40
- "c"=>[{"nestedSum"=>{"value"=>14}}, {"nestedSum"=>{"value"=>14}}],
50
+ "a"=>{"value"=>14, "nestedSum"=>{
51
+ "value"=>46,
52
+ "nestedSum"=>{
53
+ "value"=>95,
54
+ "nestedSum"=>{"value"=>90}
55
+ }
56
+ }},
57
+ "b"=>{"value"=>14, "nestedSum"=>{
58
+ "value"=>46,
59
+ "nestedSum"=>{
60
+ "value"=>95,
61
+ "nestedSum"=>{"value"=>90}
62
+ }
63
+ }},
64
+ "c"=>[
65
+ {"nestedSum"=>{"value"=>14}},
66
+ {"nestedSum"=>{"value"=>14}}
67
+ ],
41
68
  }
42
69
 
43
70
  assert_equal expected_data, res["data"]
@@ -93,4 +93,48 @@ describe GraphQL::InterfaceType do
93
93
  assert_equal 4, interface_2.fields.size
94
94
  end
95
95
  end
96
+
97
+ describe "#resolve_type" do
98
+ let(:result) { Dummy::Schema.execute(query_string) }
99
+ let(:query_string) {%|
100
+ {
101
+ allEdible {
102
+ __typename
103
+ ... on Milk {
104
+ milkFatContent: fatContent
105
+ }
106
+ ... on Cheese {
107
+ cheeseFatContent: fatContent
108
+ }
109
+ }
110
+
111
+ allEdibleAsMilk {
112
+ __typename
113
+ ... on Milk {
114
+ fatContent
115
+ }
116
+ }
117
+ }
118
+ |}
119
+
120
+ it 'returns correct types for general schema and specific interface' do
121
+ expected_result = {
122
+ # Uses schema-level resolve_type
123
+ "allEdible"=>[
124
+ {"__typename"=>"Cheese", "cheeseFatContent"=>0.19},
125
+ {"__typename"=>"Cheese", "cheeseFatContent"=>0.3},
126
+ {"__typename"=>"Cheese", "cheeseFatContent"=>0.065},
127
+ {"__typename"=>"Milk", "milkFatContent"=>0.04}
128
+ ],
129
+ # Uses type-level resolve_type
130
+ "allEdibleAsMilk"=>[
131
+ {"__typename"=>"Milk", "fatContent"=>0.19},
132
+ {"__typename"=>"Milk", "fatContent"=>0.3},
133
+ {"__typename"=>"Milk", "fatContent"=>0.065},
134
+ {"__typename"=>"Milk", "fatContent"=>0.04}
135
+ ]
136
+ }
137
+ assert_equal expected_result, result["data"]
138
+ end
139
+ end
96
140
  end
@@ -19,8 +19,11 @@ describe GraphQL::Introspection::SchemaType do
19
19
  "types" => Dummy::Schema.types.values.map { |t| t.name.nil? ? (p t; raise("no name for #{t}")) : {"name" => t.name} },
20
20
  "queryType"=>{
21
21
  "fields"=>[
22
+ {"name"=>"allAnimal"},
23
+ {"name"=>"allAnimalAsCow"},
22
24
  {"name"=>"allDairy"},
23
25
  {"name"=>"allEdible"},
26
+ {"name"=>"allEdibleAsMilk"},
24
27
  {"name"=>"cheese"},
25
28
  {"name"=>"cow"},
26
29
  {"name"=>"dairy"},
@@ -41,6 +41,7 @@ describe GraphQL::Introspection::TypeType do
41
41
  "milkType"=>{
42
42
  "interfaces"=>[
43
43
  {"name"=>"Edible"},
44
+ {"name"=>"EdibleAsMilk"},
44
45
  {"name"=>"AnimalProduct"},
45
46
  {"name"=>"LocalProduct"},
46
47
  ],
@@ -17,7 +17,12 @@ describe GraphQL::ObjectType do
17
17
 
18
18
  describe "interfaces" do
19
19
  it "may have interfaces" do
20
- assert_equal([Dummy::EdibleInterface, Dummy::AnimalProductInterface, Dummy::LocalProductInterface], type.interfaces)
20
+ assert_equal([
21
+ Dummy::EdibleInterface,
22
+ Dummy::EdibleAsMilkInterface,
23
+ Dummy::AnimalProductInterface,
24
+ Dummy::LocalProductInterface
25
+ ], type.interfaces)
21
26
  end
22
27
 
23
28
  it "raises if the interfaces arent an array" do
@@ -129,8 +134,8 @@ describe GraphQL::ObjectType do
129
134
 
130
135
  type_2.fields["nonsense"] = GraphQL::Field.define(name: "nonsense", type: type)
131
136
 
132
- assert_equal 3, type.interfaces.size
133
- assert_equal 4, type_2.interfaces.size
137
+ assert_equal 4, type.interfaces.size
138
+ assert_equal 5, type_2.interfaces.size
134
139
  assert_equal 8, type.fields.size
135
140
  assert_equal 9, type_2.fields.size
136
141
  end