graphql 0.18.11 → 0.18.12

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.
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