graphql 1.6.4 → 1.6.5

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