graphql 1.8.0.pre9 → 1.8.0.pre10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/argument.rb +1 -0
  3. data/lib/graphql/base_type.rb +2 -0
  4. data/lib/graphql/compatibility/query_parser_specification.rb +110 -0
  5. data/lib/graphql/deprecated_dsl.rb +15 -3
  6. data/lib/graphql/directive.rb +1 -0
  7. data/lib/graphql/enum_type.rb +2 -0
  8. data/lib/graphql/execution/multiplex.rb +1 -1
  9. data/lib/graphql/field.rb +2 -0
  10. data/lib/graphql/introspection/entry_points.rb +2 -2
  11. data/lib/graphql/introspection/schema_field.rb +1 -1
  12. data/lib/graphql/introspection/type_by_name_field.rb +1 -1
  13. data/lib/graphql/language/parser.rb +25 -25
  14. data/lib/graphql/language/parser.y +7 -7
  15. data/lib/graphql/query/arguments.rb +12 -0
  16. data/lib/graphql/relay/mutation/instrumentation.rb +1 -1
  17. data/lib/graphql/relay/mutation/resolve.rb +5 -1
  18. data/lib/graphql/schema.rb +5 -1
  19. data/lib/graphql/schema/argument.rb +1 -0
  20. data/lib/graphql/schema/build_from_definition.rb +60 -18
  21. data/lib/graphql/schema/enum.rb +1 -0
  22. data/lib/graphql/schema/enum_value.rb +1 -0
  23. data/lib/graphql/schema/field.rb +44 -31
  24. data/lib/graphql/schema/field/dynamic_resolve.rb +4 -8
  25. data/lib/graphql/schema/input_object.rb +30 -19
  26. data/lib/graphql/schema/interface.rb +12 -5
  27. data/lib/graphql/schema/member.rb +10 -0
  28. data/lib/graphql/schema/member/build_type.rb +3 -1
  29. data/lib/graphql/schema/member/has_arguments.rb +50 -0
  30. data/lib/graphql/schema/member/has_fields.rb +1 -1
  31. data/lib/graphql/schema/member/instrumentation.rb +4 -4
  32. data/lib/graphql/schema/mutation.rb +195 -0
  33. data/lib/graphql/schema/object.rb +4 -5
  34. data/lib/graphql/schema/relay_classic_mutation.rb +85 -0
  35. data/lib/graphql/schema/scalar.rb +1 -0
  36. data/lib/graphql/schema/traversal.rb +1 -1
  37. data/lib/graphql/schema/union.rb +1 -0
  38. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  39. data/lib/graphql/unresolved_type_error.rb +3 -2
  40. data/lib/graphql/upgrader/member.rb +194 -19
  41. data/lib/graphql/version.rb +1 -1
  42. data/spec/fixtures/upgrader/delete_project.original.rb +28 -0
  43. data/spec/fixtures/upgrader/delete_project.transformed.rb +27 -0
  44. data/spec/fixtures/upgrader/increment_count.original.rb +59 -0
  45. data/spec/fixtures/upgrader/increment_count.transformed.rb +50 -0
  46. data/spec/graphql/execution/multiplex_spec.rb +1 -1
  47. data/spec/graphql/language/parser_spec.rb +0 -74
  48. data/spec/graphql/query/serial_execution/value_resolution_spec.rb +2 -8
  49. data/spec/graphql/query_spec.rb +26 -0
  50. data/spec/graphql/relay/mutation_spec.rb +2 -2
  51. data/spec/graphql/schema/build_from_definition_spec.rb +59 -0
  52. data/spec/graphql/schema/field_spec.rb +24 -0
  53. data/spec/graphql/schema/input_object_spec.rb +1 -0
  54. data/spec/graphql/schema/interface_spec.rb +4 -1
  55. data/spec/graphql/schema/mutation_spec.rb +99 -0
  56. data/spec/graphql/schema/relay_classic_mutation_spec.rb +28 -0
  57. data/spec/support/jazz.rb +25 -1
  58. data/spec/support/star_wars/schema.rb +17 -27
  59. metadata +17 -2
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.8.0.pre9"
3
+ VERSION = "1.8.0.pre10"
4
4
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Platform
4
+ module Mutations
5
+ DeleteProject = GraphQL::Relay::Mutation.define do
6
+ name "DeleteProject"
7
+ description "Deletes a project."
8
+
9
+ minimum_accepted_scopes ["public_repo"]
10
+
11
+ input_field :projectId, !types.ID, "The Project ID to update."
12
+ return_field :owner, !Interfaces::ProjectOwner, "The repository or organization the project was removed from."
13
+
14
+ resolve ->(root_obj, inputs, context) do
15
+ project = Platform::Helpers::NodeIdentification.typed_object_from_id(
16
+ [Objects::Project], inputs[:projectId], context
17
+ )
18
+
19
+ context[:permission].can_modify?("DeleteProject", project).sync
20
+ context[:abilities].authorize_content(:project, :destroy, owner: project.owner)
21
+
22
+ project.enqueue_delete(actor: context[:viewer])
23
+
24
+ { owner: project.owner }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Platform
4
+ module Mutations
5
+ class DeleteProject < Mutations::BaseMutation
6
+ description "Deletes a project."
7
+
8
+ minimum_accepted_scopes ["public_repo"]
9
+
10
+ argument :project_id, ID, "The Project ID to update.", required: true
11
+ field :owner, Interfaces::ProjectOwner, "The repository or organization the project was removed from.", null: false
12
+
13
+ def resolve(**inputs)
14
+ project = Platform::Helpers::NodeIdentification.typed_object_from_id(
15
+ [Objects::Project], inputs[:project_id], @context
16
+ )
17
+
18
+ @context[:permission].can_modify?("DeleteProject", project).sync
19
+ @context[:abilities].authorize_content(:project, :destroy, owner: project.owner)
20
+
21
+ project.enqueue_delete(actor: @context[:viewer])
22
+
23
+ { owner: project.owner }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Platform
4
+ module Mutations
5
+ IncrementThing = GraphQL::Relay::Mutation.define do
6
+ name "IncrementThing"
7
+ description "increments the thing by 1."
8
+ visibility :internal
9
+ minimum_accepted_scopes ["repo"]
10
+
11
+ input_field(:thingId,
12
+ !types.ID,
13
+ "Thing ID to log.",
14
+ option: :setting)
15
+
16
+ return_field(
17
+ :thingId,
18
+ !types.ID,
19
+ "Thing ID to log."
20
+ )
21
+
22
+ resolve ->(root_obj, inputs, context) do
23
+ if some_early_check
24
+ return { thingId: "000" }
25
+ end
26
+
27
+ # These shouldn't be modified:
28
+ { abcDef: 1 }
29
+ some_method do { xyzAbc: 1 } end
30
+
31
+ thing = Platform::Helpers::NodeIdentification.typed_object_from_id(Objects::Thing, inputs[:thingId], context)
32
+ raise Errors::Validation.new("Thing not found.") unless thing
33
+
34
+ ThingActivity.track(thing.id, Time.now.change(min: 0, sec: 0))
35
+
36
+
37
+ if random_condition
38
+ { thingId: thing.global_relay_id }
39
+ elsif other_random_thing
40
+ { :thingId => "abc" }
41
+ elsif something_else
42
+ method_with_block {
43
+ { thingId: "pqr" }
44
+ }
45
+ elsif yet_another_thing
46
+ begin
47
+ { thingId: "987" }
48
+ rescue
49
+ { thingId: "789" }
50
+ end
51
+ else
52
+ return {
53
+ thingId: "xyz"
54
+ }
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Platform
4
+ module Mutations
5
+ class IncrementThing < Mutations::BaseMutation
6
+ description "increments the thing by 1."
7
+ visibility :internal
8
+ minimum_accepted_scopes ["repo"]
9
+
10
+ argument :thing_id, ID, "Thing ID to log.", option: :setting, required: true
11
+
12
+ field :thing_id, ID, "Thing ID to log.", null: false
13
+
14
+ def resolve(**inputs)
15
+ if some_early_check
16
+ return { thing_id: "000" }
17
+ end
18
+
19
+ # These shouldn't be modified:
20
+ { abcDef: 1 }
21
+ some_method do { xyzAbc: 1 } end
22
+
23
+ thing = Platform::Helpers::NodeIdentification.typed_object_from_id(Objects::Thing, inputs[:thing_id], @context)
24
+ raise Errors::Validation.new("Thing not found.") unless thing
25
+
26
+ ThingActivity.track(thing.id, Time.now.change(min: 0, sec: 0))
27
+
28
+ if random_condition
29
+ { thing_id: thing.global_relay_id }
30
+ elsif other_random_thing
31
+ { :thing_id => "abc" }
32
+ elsif something_else
33
+ method_with_block {
34
+ { thing_id: "pqr" }
35
+ }
36
+ elsif yet_another_thing
37
+ begin
38
+ { thing_id: "987" }
39
+ rescue
40
+ { thing_id: "789" }
41
+ end
42
+ else
43
+ return {
44
+ thing_id: "xyz"
45
+ }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -176,7 +176,7 @@ describe GraphQL::Execution::Multiplex do
176
176
  assert_raises(GraphQL::Error) do
177
177
  InspectSchema.execute("{ raiseError }")
178
178
  end
179
- unhandled_err_json = 'null'
179
+ unhandled_err_json = '{}'
180
180
  assert_equal unhandled_err_json, InspectQueryInstrumentation.last_json
181
181
  end
182
182
  end
@@ -44,80 +44,6 @@ describe GraphQL::Language::Parser do
44
44
  assert_equal schema_string, document.to_query_string
45
45
  end
46
46
 
47
- describe "implements" do
48
- it "parses when there are no interfaces" do
49
- schema = "
50
- type A {
51
- a: String
52
- }
53
- "
54
-
55
- document = subject.parse(schema)
56
-
57
- assert_equal [], document.definitions[0].interfaces.map(&:name)
58
- end
59
-
60
- it "parses with leading ampersand" do
61
- schema = "
62
- type A implements & B {
63
- a: String
64
- }
65
- "
66
-
67
- document = subject.parse(schema)
68
-
69
- assert_equal ["B"], document.definitions[0].interfaces.map(&:name)
70
- end
71
-
72
- it "parses with leading ampersand and multiple interfaces" do
73
- schema = "
74
- type A implements & B & C {
75
- a: String
76
- }
77
- "
78
-
79
- document = subject.parse(schema)
80
-
81
- assert_equal ["B", "C"], document.definitions[0].interfaces.map(&:name)
82
- end
83
-
84
- it "parses without leading ampersand" do
85
- schema = "
86
- type A implements B {
87
- a: String
88
- }
89
- "
90
-
91
- document = subject.parse(schema)
92
-
93
- assert_equal ["B"], document.definitions[0].interfaces.map(&:name)
94
- end
95
-
96
- it "parses without leading ampersand and multiple interfaces" do
97
- schema = "
98
- type A implements B & C {
99
- a: String
100
- }
101
- "
102
-
103
- document = subject.parse(schema)
104
-
105
- assert_equal ["B", "C"], document.definitions[0].interfaces.map(&:name)
106
- end
107
-
108
- it "supports the old way of parsing multiple interfaces for backwards compatibility" do
109
- schema = "
110
- type A implements B, C {
111
- a: String
112
- }
113
- "
114
-
115
- document = subject.parse(schema)
116
-
117
- assert_equal ["B", "C"], document.definitions[0].interfaces.map(&:name)
118
- end
119
- end
120
-
121
47
  describe ".parse_file" do
122
48
  it "assigns filename to all nodes" do
123
49
  example_filename = "spec/support/parser/filename_example.graphql"
@@ -86,10 +86,7 @@ describe GraphQL::Query::SerialExecution::ValueResolution do
86
86
 
87
87
  it "raises an error" do
88
88
  err = assert_raises(GraphQL::UnresolvedTypeError) { result }
89
- expected_message = "The value from \"resolvesToNilInterface\" on \"Query\" could not be resolved to \"SomeInterface\". " \
90
- "(Received: `nil`, Expected: [SomeObject]) " \
91
- "Make sure you have defined a `type_from_object` proc on your schema and that value `1337` " \
92
- "gets resolved to a valid type."
89
+ expected_message = "The value from \"resolvesToNilInterface\" on \"Query\" could not be resolved to \"SomeInterface\". (Received: `nil`, Expected: [SomeObject]) Make sure you have defined a `resolve_type` proc on your schema and that value `1337` gets resolved to a valid type. You may need to add your type to `orphan_types` if it implements an interface but isn't a return type of any other field."
93
90
  assert_equal expected_message, err.message
94
91
  end
95
92
  end
@@ -103,10 +100,7 @@ describe GraphQL::Query::SerialExecution::ValueResolution do
103
100
 
104
101
  it "raises an error" do
105
102
  err = assert_raises(GraphQL::UnresolvedTypeError) { result }
106
- expected_message = "The value from \"resolvesToWrongTypeInterface\" on \"Query\" could not be resolved to \"SomeInterface\". " \
107
- "(Received: `OtherObject`, Expected: [SomeObject]) " \
108
- "Make sure you have defined a `type_from_object` proc on your schema and that value `:something` " \
109
- "gets resolved to a valid type."
103
+ expected_message = "The value from \"resolvesToWrongTypeInterface\" on \"Query\" could not be resolved to \"SomeInterface\". (Received: `OtherObject`, Expected: [SomeObject]) Make sure you have defined a `resolve_type` proc on your schema and that value `:something` gets resolved to a valid type. You may need to add your type to `orphan_types` if it implements an interface but isn't a return type of any other field."
110
104
  assert_equal expected_message, err.message
111
105
  end
112
106
  end
@@ -253,6 +253,32 @@ describe GraphQL::Query do
253
253
  assert_equal [nil], Instrumenter::ERROR_LOG
254
254
  end
255
255
  end
256
+
257
+ describe "when an error propagated through execution" do
258
+ module ExtensionsInstrumenter
259
+ LOG = []
260
+ def self.before_query(q); end;
261
+
262
+ def self.after_query(q)
263
+ q.result["extensions"] = { "a" => 1 }
264
+ LOG << :ok
265
+ end
266
+ end
267
+
268
+ let(:schema) {
269
+ Dummy::Schema.redefine {
270
+ instrument(:query, ExtensionsInstrumenter)
271
+ }
272
+ }
273
+
274
+ it "can add to extensions" do
275
+ ExtensionsInstrumenter::LOG.clear
276
+ assert_raises(RuntimeError) do
277
+ schema.execute "{ error }"
278
+ end
279
+ assert_equal [:ok], ExtensionsInstrumenter::LOG
280
+ end
281
+ end
256
282
  end
257
283
 
258
284
  it "uses root_value as the object for the root type" do
@@ -108,9 +108,9 @@ describe GraphQL::Relay::Mutation do
108
108
 
109
109
  it "inserts itself into the derived objects' metadata" do
110
110
  assert_equal StarWars::IntroduceShipMutation, StarWars::IntroduceShipMutation.field.mutation
111
- assert_equal StarWars::IntroduceShipMutation, StarWars::IntroduceShipMutation.return_type.mutation
111
+ assert_equal StarWars::IntroduceShipMutation, StarWars::IntroduceShipMutation.graphql_field.mutation
112
+ assert_equal StarWars::IntroduceShipMutation, StarWars::IntroduceShipMutation.payload_type.mutation
112
113
  assert_equal StarWars::IntroduceShipMutation, StarWars::IntroduceShipMutation.input_type.mutation
113
- assert_equal StarWars::IntroduceShipMutation, StarWars::IntroduceShipMutation.result_class.mutation
114
114
  end
115
115
 
116
116
  describe "return_field ... property:" do
@@ -612,6 +612,65 @@ type Query {
612
612
 
613
613
  build_schema_and_compare_output(schema.chop)
614
614
  end
615
+
616
+ it "tracks original AST node" do
617
+ schema_definition = <<-GRAPHQL
618
+ schema {
619
+ query: Query
620
+ }
621
+
622
+ enum Enum {
623
+ VALUE
624
+ }
625
+
626
+ type Query {
627
+ field(argument: String): String
628
+ deprecatedField(argument: String): String @deprecated(reason: "Test")
629
+ }
630
+
631
+ interface Interface {
632
+ field(argument: String): String
633
+ }
634
+
635
+ union Union = Query
636
+
637
+ scalar Scalar
638
+
639
+ input Input {
640
+ argument: String
641
+ }
642
+
643
+ directive @Directive (
644
+ # Argument
645
+ argument: String
646
+ ) on SCHEMA
647
+
648
+ type Type implements Interface {
649
+ field(argument: String): String
650
+ }
651
+ GRAPHQL
652
+
653
+ schema = GraphQL::Schema.from_definition(schema_definition)
654
+
655
+ assert_equal [1, 1], schema.ast_node.position
656
+ assert_equal [5, 1], schema.types["Enum"].ast_node.position
657
+ assert_equal [6, 3], schema.types["Enum"].values["VALUE"].ast_node.position
658
+ assert_equal [9, 1], schema.types["Query"].ast_node.position
659
+ assert_equal [10, 3], schema.types["Query"].fields["field"].ast_node.position
660
+ assert_equal [10, 9], schema.types["Query"].fields["field"].arguments["argument"].ast_node.position
661
+ assert_equal [11, 45], schema.types["Query"].fields["deprecatedField"].ast_node.directives[0].position
662
+ assert_equal [11, 57], schema.types["Query"].fields["deprecatedField"].ast_node.directives[0].arguments[0].position
663
+ assert_equal [14, 1], schema.types["Interface"].ast_node.position
664
+ assert_equal [15, 3], schema.types["Interface"].fields["field"].ast_node.position
665
+ assert_equal [15, 9], schema.types["Interface"].fields["field"].arguments["argument"].ast_node.position
666
+ assert_equal [18, 1], schema.types["Union"].ast_node.position
667
+ assert_equal [20, 1], schema.types["Scalar"].ast_node.position
668
+ assert_equal [22, 1], schema.types["Input"].ast_node.position
669
+ assert_equal [22, 1], schema.types["Input"].arguments["argument"].ast_node.position
670
+ assert_equal [26, 1], schema.directives["Directive"].ast_node.position
671
+ assert_equal [28, 3], schema.directives["Directive"].arguments["argument"].ast_node.position
672
+ assert_equal [31, 22], schema.types["Type"].ast_node.interfaces[0].position
673
+ end
615
674
  end
616
675
 
617
676
  describe 'Failures' do
@@ -46,6 +46,14 @@ describe GraphQL::Schema::Field do
46
46
  assert_equal "A Description.", object.fields["test"].description
47
47
  end
48
48
 
49
+ it "accepts anonymous classes as type" do
50
+ type = Class.new(GraphQL::Schema::Object) do
51
+ graphql_name 'MyType'
52
+ end
53
+ field = GraphQL::Schema::Field.new(:my_field, type, owner: nil, null: true)
54
+ assert_equal type.to_graphql, field.to_graphql.type
55
+ end
56
+
49
57
  describe "extras" do
50
58
  it "can get errors, which adds path" do
51
59
  query_str = <<-GRAPHQL
@@ -140,4 +148,20 @@ describe GraphQL::Schema::Field do
140
148
  end
141
149
  end
142
150
  end
151
+
152
+ describe "build type errors" do
153
+ it "includes the full name" do
154
+ thing = Class.new(GraphQL::Schema::Object) do
155
+ graphql_name "Thing"
156
+ # `Set` is a class but not a GraphQL type
157
+ field :stuff, Set, null: false
158
+ end
159
+
160
+ err = assert_raises ArgumentError do
161
+ thing.fields["stuff"].to_graphql.type
162
+ end
163
+
164
+ assert_includes err.message, "Thing.stuff"
165
+ end
166
+ end
143
167
  end
@@ -46,6 +46,7 @@ describe GraphQL::Schema::InputObject do
46
46
  "hi, ABC, 4, (hi, xyz, -, (-))",
47
47
  "ABC",
48
48
  "ABC",
49
+ "true",
49
50
  "ABC",
50
51
  ]
51
52
  assert_equal expected_info, res["data"]["inspectInput"]