graphql 0.18.11 → 0.18.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1878fc3aef9f2315438da1dc338f7a6254ed83ca
4
- data.tar.gz: 1f894a00b663c83b7b1bb33f2fda113dd8851f1e
3
+ metadata.gz: fbb584f225769c48d6c7d9878ff6d237068f0c5b
4
+ data.tar.gz: cf2c4052c0b5facfcf788524bf6a9b3b1b89b167
5
5
  SHA512:
6
- metadata.gz: d14b02503088c21f6795c91a8b7eccfa7ed9d7cac10f06b388368ec3a1730e32e504166da603de22c2303a952af0778d1bb8190f0620df1bbb24dd9bb23a2c88
7
- data.tar.gz: 790684129a750601ce3fb2cd71455ba1c6afece4f4be047f260161436e2cfca3f01c3d2cc9dd147f3f03eeb2bdac5d3c1bcb44acd420b40c99158e23eca650e7
6
+ metadata.gz: 4d09502f2198d2e0cc265db03d49bc80061c999e23471615622793b8d7ef25cea10e7a9fbd4ff48302f01453ae36652cb8c9b6fcca1ceac8c8c17578d3f2beb7
7
+ data.tar.gz: 81eb4cd1c6d3f00f9770a6929da390caac133266b89bd3c1ad8737ce8fd53a4cd750942db35332c34ba856559c1bfa53f33e82777a8b88b08eec74fda9921e2e
@@ -3,3 +3,4 @@ require "graphql/analysis/max_query_depth"
3
3
  require "graphql/analysis/query_complexity"
4
4
  require "graphql/analysis/query_depth"
5
5
  require "graphql/analysis/analyze_query"
6
+ require "graphql/analysis/field_usage"
@@ -0,0 +1,44 @@
1
+ module GraphQL
2
+ module Analysis
3
+ # A query reducer for tracking both field usage and deprecated field usage.
4
+ #
5
+ # @example Logging field usage and deprecated field usage
6
+ # Schema.query_analyzers << GraphQL::Analysis::FieldUsage.new { |query, used_fields, used_deprecated_fields|
7
+ # puts "Used GraphQL fields: #{used_fields.join(', ')}"
8
+ # puts "Used deprecated GraphQL fields: #{used_deprecated_fields.join(', ')}"
9
+ # }
10
+ # Schema.execute(query_str)
11
+ # # Used GraphQL fields: Cheese.id, Cheese.fatContent, Query.cheese
12
+ # # Used deprecated GraphQL fields: Cheese.fatContent
13
+ #
14
+ class FieldUsage
15
+ def initialize(&block)
16
+ @field_usage_handler = block
17
+ end
18
+
19
+ def initial_value(query)
20
+ {
21
+ query: query,
22
+ used_fields: Set.new,
23
+ used_deprecated_fields: Set.new
24
+ }
25
+ end
26
+
27
+ def call(memo, visit_type, irep_node)
28
+ if irep_node.ast_node.is_a?(GraphQL::Language::Nodes::Field) && visit_type == :leave
29
+ irep_node.definitions.each do |type_defn, field_defn|
30
+ field = "#{type_defn.name}.#{field_defn.name}"
31
+ memo[:used_fields] << field
32
+ memo[:used_deprecated_fields] << field if field_defn.deprecation_reason
33
+ end
34
+ end
35
+
36
+ memo
37
+ end
38
+
39
+ def final_value(memo)
40
+ @field_usage_handler.call(memo[:query], memo[:used_fields].to_a, memo[:used_deprecated_fields].to_a)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -33,7 +33,7 @@ module GraphQL
33
33
 
34
34
  visitor[Nodes::OperationDefinition].enter << -> (ast_node, prev_ast_node) {
35
35
  node = Node.new(
36
- return_type: context.type_definition.unwrap,
36
+ return_type: context.type_definition && context.type_definition.unwrap,
37
37
  ast_node: ast_node,
38
38
  name: ast_node.name,
39
39
  parent: nil,
@@ -20,6 +20,12 @@ module GraphQL
20
20
  @argument_values[key.to_s]
21
21
  end
22
22
 
23
+ # @param key [String, Symbol] name of value to access
24
+ # @return [Boolean] true if the argument was present in this field
25
+ def key?(key)
26
+ @argument_values.key?(key.to_s)
27
+ end
28
+
23
29
  # Get the original Ruby hash
24
30
  # @return [Hash] the original values hash
25
31
  def to_h
@@ -16,14 +16,25 @@ module GraphQL
16
16
  values_hash = {}
17
17
  argument_defns.each do |arg_name, arg_defn|
18
18
  ast_arg = ast_arguments.find { |ast_arg| ast_arg.name == arg_name }
19
- arg_value = nil
20
- if ast_arg
21
- arg_value = coerce(arg_defn.type, ast_arg.value, variables)
22
- end
23
- if arg_value.nil?
24
- arg_value = arg_defn.type.coerce_input(arg_defn.default_value)
19
+ arg_default_value = arg_defn.type.coerce_input(arg_defn.default_value)
20
+ if ast_arg.nil? && arg_default_value.nil?
21
+ # If it wasn't in the document,
22
+ # and there's no provided default,
23
+ # then don't pass it to the resolve function
24
+ next
25
+ else
26
+ arg_value = nil
27
+
28
+ if ast_arg
29
+ arg_value = coerce(arg_defn.type, ast_arg.value, variables)
30
+ end
31
+
32
+ if arg_value.nil?
33
+ arg_value = arg_default_value
34
+ end
35
+
36
+ values_hash[arg_name] = arg_value
25
37
  end
26
- values_hash[arg_name] = arg_value
27
38
  end
28
39
  GraphQL::Query::Arguments.new(values_hash)
29
40
  end
@@ -24,6 +24,8 @@ module GraphQL
24
24
  GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTyped,
25
25
  GraphQL::StaticValidation::VariablesAreUsedAndDefined,
26
26
  GraphQL::StaticValidation::VariableUsagesAreAllowed,
27
+ GraphQL::StaticValidation::MutationRootExists,
28
+ GraphQL::StaticValidation::SubscriptionRootExists,
27
29
  ]
28
30
  end
29
31
  end
@@ -0,0 +1,20 @@
1
+ module GraphQL
2
+ module StaticValidation
3
+ class MutationRootExists
4
+ include GraphQL::StaticValidation::Message::MessageHelper
5
+
6
+ def validate(context)
7
+ return if context.schema.mutation
8
+
9
+ visitor = context.visitor
10
+
11
+ visitor[GraphQL::Language::Nodes::OperationDefinition].enter << -> (ast_node, prev_ast_node) {
12
+ if ast_node.operation_type == 'mutation'
13
+ context.errors << message('Schema is not configured for mutations', ast_node, context: context)
14
+ return GraphQL::Language::Visitor::SKIP
15
+ end
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module GraphQL
2
+ module StaticValidation
3
+ class SubscriptionRootExists
4
+ include GraphQL::StaticValidation::Message::MessageHelper
5
+
6
+ def validate(context)
7
+ return if context.schema.subscription
8
+
9
+ visitor = context.visitor
10
+
11
+ visitor[GraphQL::Language::Nodes::OperationDefinition].enter << -> (ast_node, prev_ast_node) {
12
+ if ast_node.operation_type == 'subscription'
13
+ context.errors << message('Schema is not configured for subscriptions', ast_node, context: context)
14
+ return GraphQL::Language::Visitor::SKIP
15
+ end
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,3 +1,3 @@
1
1
  module GraphQL
2
- VERSION = "0.18.11"
2
+ VERSION = "0.18.12"
3
3
  end
data/readme.md CHANGED
@@ -137,3 +137,16 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
137
137
  - If the new edge isn't a member of the connection's objects, raise a nice error
138
138
  - Missing Enum value should raise a descriptive error, not "key not found"
139
139
  - `args` should whitelist keys -- if you request a key that isn't defined for the field, it should 💥
140
+ - Fix middleware
141
+ - Handle out-of-bounds lookup, eg `graphql-batch`
142
+ - Handle non-serial execution, eg `@defer`
143
+ - Support non-instance-eval `.define`, eg `.define { |defn| ... }`
144
+ - First-class promise support
145
+ - like `graphql-batch` but more local
146
+ - support promises in connection resolves
147
+ - Add immutable transformation API to AST
148
+ - Support working with AST as data
149
+ - Adding fields to selections (`__typename` can go anywhere, others are type-specific)
150
+ - Renaming fragments from local names to unique names
151
+ - Support AST subclasses? This would be hard, I think classes are used as hash keys in many places.
152
+ - Support object deep-copy (schema, type, field, argument)? To support multiple schemas based on the same types.
@@ -0,0 +1,61 @@
1
+ require "spec_helper"
2
+
3
+ describe GraphQL::Analysis::FieldUsage do
4
+ let(:result) { [] }
5
+ let(:field_usage_analyzer) { GraphQL::Analysis::FieldUsage.new { |query, used_fields, used_deprecated_fields| result << query << used_fields << used_deprecated_fields } }
6
+ let(:reduce_result) { GraphQL::Analysis.analyze_query(query, [field_usage_analyzer]) }
7
+ let(:query) { GraphQL::Query.new(DummySchema, query_string, variables: variables) }
8
+ let(:variables) { {} }
9
+
10
+ describe "query with deprecated fields" do
11
+ let(:query_string) {%|
12
+ query {
13
+ cheese(id: 1) {
14
+ id
15
+ fatContent
16
+ }
17
+ }
18
+ |}
19
+
20
+ it "returns query in reduced result" do
21
+ reduce_result
22
+ assert_equal query, result[0]
23
+ end
24
+
25
+ it "keeps track of used fields" do
26
+ reduce_result
27
+ assert_equal ['Cheese.id', 'Cheese.fatContent', 'Query.cheese'], result[1]
28
+ end
29
+
30
+ it "keeps track of deprecated fields" do
31
+ reduce_result
32
+ assert_equal ['Cheese.fatContent'], result[2]
33
+ end
34
+ end
35
+
36
+ describe "query with deprecated fields used more than once" do
37
+ let(:query_string) {%|
38
+ query {
39
+ cheese1: cheese(id: 1) {
40
+ id
41
+ fatContent
42
+ }
43
+
44
+ cheese2: cheese(id: 2) {
45
+ id
46
+ fatContent
47
+ }
48
+ }
49
+ |}
50
+
51
+ it "omits duplicate usage of a field" do
52
+ reduce_result
53
+ assert_equal ['Cheese.id', 'Cheese.fatContent', 'Query.cheese'], result[1]
54
+ end
55
+
56
+ it "omits duplicate usage of a deprecated field" do
57
+ reduce_result
58
+ assert_equal ['Cheese.fatContent'], result[2]
59
+ end
60
+ end
61
+ end
@@ -22,4 +22,86 @@ describe GraphQL::Query::Arguments do
22
22
  it "returns original Ruby hash values with to_h" do
23
23
  assert_equal({ a: 1, b: 2, c: { d: 3, e: 4 } }, arguments.to_h)
24
24
  end
25
+
26
+ describe "#key?" do
27
+ let(:arg_values) { [] }
28
+ let(:schema) {
29
+ arg_values_array = arg_values
30
+
31
+ test_input_type = GraphQL::InputObjectType.define do
32
+ name "TestInput"
33
+ argument :a, types.Int
34
+ argument :b, types.Int, default_value: 2
35
+ argument :c, types.Int
36
+ argument :d, types.Int
37
+ end
38
+
39
+ query = GraphQL::ObjectType.define do
40
+ name "Query"
41
+ field :argTest, types.Int do
42
+ argument :a, types.Int
43
+ argument :b, types.Int, default_value: 2
44
+ argument :c, types.Int
45
+ argument :d, test_input_type
46
+ resolve -> (obj, args, ctx) {
47
+ arg_values_array << args
48
+ 1
49
+ }
50
+ end
51
+ end
52
+
53
+ GraphQL::Schema.define(query: query)
54
+ }
55
+
56
+ it "detects missing keys by string or symbol" do
57
+ assert_equal true, arguments.key?(:a)
58
+ assert_equal true, arguments.key?("a")
59
+ assert_equal false, arguments.key?(:f)
60
+ assert_equal false, arguments.key?("f")
61
+ end
62
+
63
+ it "works from query literals" do
64
+ schema.execute("{ argTest(a: 1) }")
65
+
66
+ last_args = arg_values.last
67
+
68
+ assert_equal true, last_args.key?(:a)
69
+ # This is present from default value:
70
+ assert_equal true, last_args.key?(:b)
71
+ assert_equal false, last_args.key?(:c)
72
+ assert_equal({"a" => 1, "b" => 2}, last_args.to_h)
73
+ end
74
+
75
+ it "works from variables" do
76
+ variables = { "arg" => { "a" => 1, "d" => nil } }
77
+ schema.execute("query ArgTest($arg: TestInput){ argTest(d: $arg) }", variables: variables)
78
+
79
+ test_inputs = arg_values.last["d"]
80
+
81
+ assert_equal true, test_inputs.key?(:a)
82
+ # This is present from default value:
83
+ assert_equal true, test_inputs.key?(:b)
84
+
85
+ assert_equal false, test_inputs.key?(:c)
86
+ # This _was_ present in the variables,
87
+ # but it was nil, which is not allowed in GraphQL
88
+ assert_equal false, test_inputs.key?(:d)
89
+
90
+ assert_equal({"a" => 1, "b" => 2}, test_inputs.to_h)
91
+ end
92
+
93
+ it "works with variable default values" do
94
+ schema.execute("query ArgTest($arg: TestInput = {a: 1}){ argTest(d: $arg) }")
95
+
96
+ test_defaults = arg_values.last["d"]
97
+
98
+ assert_equal true, test_defaults.key?(:a)
99
+ # This is present from default val
100
+ assert_equal true, test_defaults.key?(:b)
101
+
102
+ assert_equal false, test_defaults.key?(:c)
103
+ assert_equal false, test_defaults.key?(:d)
104
+ assert_equal({"a" => 1, "b" => 2}, test_defaults.to_h)
105
+ end
106
+ end
25
107
  end
@@ -0,0 +1,39 @@
1
+ require "spec_helper"
2
+
3
+ describe GraphQL::StaticValidation::MutationRootExists do
4
+ let(:query_string) {%|
5
+ mutation addBagel {
6
+ introduceShip(input: {shipName: "Bagel"}) {
7
+ clientMutationId
8
+ shipEdge {
9
+ node { name, id }
10
+ }
11
+ }
12
+ }
13
+ |}
14
+
15
+ let(:schema) {
16
+ query_root = GraphQL::Field.define do
17
+ name "Query"
18
+ description "Query root of the system"
19
+ end
20
+
21
+ GraphQL::Schema.define do
22
+ query query_root
23
+ end
24
+ }
25
+
26
+ let(:validator) { GraphQL::StaticValidation::Validator.new(schema: schema, rules: [GraphQL::StaticValidation::MutationRootExists]) }
27
+ let(:query) { GraphQL::Query.new(schema, query_string) }
28
+ let(:errors) { validator.validate(query)[:errors] }
29
+
30
+ it "errors when a mutation is performed on a schema without a mutation root" do
31
+ assert_equal(1, errors.length)
32
+ missing_mutation_root_error = {
33
+ "message"=>"Schema is not configured for mutations",
34
+ "locations"=>[{"line"=>2, "column"=>5}],
35
+ "path"=>["mutation addBagel"],
36
+ }
37
+ assert_includes(errors, missing_mutation_root_error)
38
+ end
39
+ end
@@ -0,0 +1,34 @@
1
+ require "spec_helper"
2
+
3
+ describe GraphQL::StaticValidation::SubscriptionRootExists do
4
+ let(:query_string) {%|
5
+ subscription {
6
+ test
7
+ }
8
+ |}
9
+
10
+ let(:schema) {
11
+ query_root = GraphQL::Field.define do
12
+ name "Query"
13
+ description "Query root of the system"
14
+ end
15
+
16
+ GraphQL::Schema.define do
17
+ query query_root
18
+ end
19
+ }
20
+
21
+ let(:validator) { GraphQL::StaticValidation::Validator.new(schema: schema, rules: [GraphQL::StaticValidation::SubscriptionRootExists]) }
22
+ let(:query) { GraphQL::Query.new(schema, query_string) }
23
+ let(:errors) { validator.validate(query)[:errors] }
24
+
25
+ it "errors when a subscription is performed on a schema without a subscription root" do
26
+ assert_equal(1, errors.length)
27
+ missing_subscription_root_error = {
28
+ "message"=>"Schema is not configured for subscriptions",
29
+ "locations"=>[{"line"=>2, "column"=>5}],
30
+ "path"=>["subscription"],
31
+ }
32
+ assert_includes(errors, missing_subscription_root_error)
33
+ end
34
+ end
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: 0.18.11
4
+ version: 0.18.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-11 00:00:00.000000000 Z
11
+ date: 2016-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: codeclimate-test-reporter
@@ -246,6 +246,7 @@ files:
246
246
  - lib/graphql.rb
247
247
  - lib/graphql/analysis.rb
248
248
  - lib/graphql/analysis/analyze_query.rb
249
+ - lib/graphql/analysis/field_usage.rb
249
250
  - lib/graphql/analysis/max_query_complexity.rb
250
251
  - lib/graphql/analysis/max_query_depth.rb
251
252
  - lib/graphql/analysis/query_complexity.rb
@@ -376,7 +377,9 @@ files:
376
377
  - lib/graphql/static_validation/rules/fragments_are_named.rb
377
378
  - lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb
378
379
  - lib/graphql/static_validation/rules/fragments_are_used.rb
380
+ - lib/graphql/static_validation/rules/mutation_root_exists.rb
379
381
  - lib/graphql/static_validation/rules/required_arguments_are_present.rb
382
+ - lib/graphql/static_validation/rules/subscription_root_exists.rb
380
383
  - lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb
381
384
  - lib/graphql/static_validation/rules/variable_usages_are_allowed.rb
382
385
  - lib/graphql/static_validation/rules/variables_are_input_types.rb
@@ -390,6 +393,7 @@ files:
390
393
  - lib/graphql/version.rb
391
394
  - readme.md
392
395
  - spec/graphql/analysis/analyze_query_spec.rb
396
+ - spec/graphql/analysis/field_usage_spec.rb
393
397
  - spec/graphql/analysis/max_query_complexity_spec.rb
394
398
  - spec/graphql/analysis/max_query_depth_spec.rb
395
399
  - spec/graphql/analysis/query_complexity_spec.rb
@@ -461,7 +465,9 @@ files:
461
465
  - spec/graphql/static_validation/rules/fragments_are_named_spec.rb
462
466
  - spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb
463
467
  - spec/graphql/static_validation/rules/fragments_are_used_spec.rb
468
+ - spec/graphql/static_validation/rules/mutation_root_exists_spec.rb
464
469
  - spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb
470
+ - spec/graphql/static_validation/rules/subscription_root_exists_spec.rb
465
471
  - spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb
466
472
  - spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb
467
473
  - spec/graphql/static_validation/rules/variables_are_input_types_spec.rb
@@ -502,6 +508,7 @@ specification_version: 4
502
508
  summary: A GraphQL server implementation for Ruby
503
509
  test_files:
504
510
  - spec/graphql/analysis/analyze_query_spec.rb
511
+ - spec/graphql/analysis/field_usage_spec.rb
505
512
  - spec/graphql/analysis/max_query_complexity_spec.rb
506
513
  - spec/graphql/analysis/max_query_depth_spec.rb
507
514
  - spec/graphql/analysis/query_complexity_spec.rb
@@ -573,7 +580,9 @@ test_files:
573
580
  - spec/graphql/static_validation/rules/fragments_are_named_spec.rb
574
581
  - spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb
575
582
  - spec/graphql/static_validation/rules/fragments_are_used_spec.rb
583
+ - spec/graphql/static_validation/rules/mutation_root_exists_spec.rb
576
584
  - spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb
585
+ - spec/graphql/static_validation/rules/subscription_root_exists_spec.rb
577
586
  - spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb
578
587
  - spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb
579
588
  - spec/graphql/static_validation/rules/variables_are_input_types_spec.rb