graphql 1.8.1 → 1.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +1 -1
  3. data/lib/generators/graphql/enum_generator.rb +3 -3
  4. data/lib/generators/graphql/install_generator.rb +12 -1
  5. data/lib/generators/graphql/mutation_generator.rb +4 -4
  6. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  7. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  8. data/lib/generators/graphql/templates/base_interface.erb +3 -0
  9. data/lib/generators/graphql/templates/base_object.erb +2 -0
  10. data/lib/generators/graphql/templates/base_union.erb +2 -0
  11. data/lib/generators/graphql/templates/enum.erb +1 -2
  12. data/lib/generators/graphql/templates/interface.erb +2 -3
  13. data/lib/generators/graphql/templates/mutation.erb +4 -5
  14. data/lib/generators/graphql/templates/mutation_type.erb +6 -9
  15. data/lib/generators/graphql/templates/object.erb +2 -3
  16. data/lib/generators/graphql/templates/query_type.erb +6 -8
  17. data/lib/generators/graphql/templates/schema.erb +7 -7
  18. data/lib/generators/graphql/templates/union.erb +1 -2
  19. data/lib/generators/graphql/type_generator.rb +33 -18
  20. data/lib/generators/graphql/union_generator.rb +1 -1
  21. data/lib/graphql/execution/execute.rb +3 -0
  22. data/lib/graphql/schema.rb +1 -0
  23. data/lib/graphql/schema/input_object.rb +22 -1
  24. data/lib/graphql/schema/relay_classic_mutation.rb +6 -2
  25. data/lib/graphql/static_validation/all_rules.rb +1 -0
  26. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +31 -0
  27. data/lib/graphql/subscriptions/instrumentation.rb +3 -0
  28. data/lib/graphql/version.rb +1 -1
  29. data/spec/generators/graphql/enum_generator_spec.rb +1 -2
  30. data/spec/generators/graphql/install_generator_spec.rb +23 -24
  31. data/spec/generators/graphql/interface_generator_spec.rb +5 -6
  32. data/spec/generators/graphql/mutation_generator_spec.rb +10 -27
  33. data/spec/generators/graphql/object_generator_spec.rb +7 -10
  34. data/spec/generators/graphql/union_generator_spec.rb +3 -6
  35. data/spec/graphql/execution/execute_spec.rb +97 -0
  36. data/spec/graphql/schema/input_object_spec.rb +32 -0
  37. data/spec/graphql/schema/relay_classic_mutation_spec.rb +16 -0
  38. data/spec/graphql/static_validation/rules/argument_names_are_unique_spec.rb +44 -0
  39. data/spec/graphql/subscriptions_spec.rb +21 -1
  40. data/spec/support/jazz.rb +15 -2
  41. metadata +10 -6
  42. data/lib/generators/graphql/function_generator.rb +0 -18
  43. data/lib/generators/graphql/templates/function.erb +0 -17
  44. data/spec/generators/graphql/function_generator_spec.rb +0 -59
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6d3ae1b02473b2855990db63798b1c5d2fea46de
4
- data.tar.gz: cfdc979146afea3eb44a969b66ac3a0763956db7
3
+ metadata.gz: 7cbf4bc1aa62d7f5ea7cec15027db39ca2162fe1
4
+ data.tar.gz: f622a8c446b557fb4fcdfb50e8868a808cb0b94b
5
5
  SHA512:
6
- metadata.gz: 762efe82d940d001685c0cf242e1b6fc0c7ee9a5b77de66c4917268b6d2ae0340346daf707d74d41c9b1dd77b71c2d2d1f887e74e2eae2ab9c7869a787a5a74b
7
- data.tar.gz: 4bcd06d7c866bb65154310cb8d11965902372860747aaaa99ebf44a73ebac3f6b1743e459e47730ba44e7fab57588031352bd1ed63925a42ec89550d32bf7906
6
+ metadata.gz: '0948dbb7c4dca8399a2c20a5e59bd823223c0662fb687e6062cdeb76709f0f5ad097bd6d0e77425d19125ddbd0ed4a1ba3ee88a3f5da61f2eae1a4dd34d8e7d7'
7
+ data.tar.gz: 409291f67651736ac996e6971b53fea7aa854983a69ad89887e6d71b68cef3550022317f83f268e23f83242c8e13ad71e65e5658da99aefa2e09c0c485ebda70
@@ -16,7 +16,7 @@ module Graphql
16
16
 
17
17
  def insert_root_type(type, name)
18
18
  log :add_root_type, type
19
- sentinel = /GraphQL\:\:Schema\.define do\s*\n/m
19
+ sentinel = /< GraphQL::Schema\s*\n/m
20
20
 
21
21
  in_root do
22
22
  inject_into_file schema_file_path, " #{type}(Types::#{name})\n", after: sentinel, verbose: false, force: false
@@ -3,11 +3,11 @@ require 'generators/graphql/type_generator'
3
3
 
4
4
  module Graphql
5
5
  module Generators
6
- # Generate an interface type by name,
7
- # with the specified fields.
6
+ # Generate an enum type by name, with the given values.
7
+ # To add a `value:` option, add another value after a `:`.
8
8
  #
9
9
  # ```
10
- # rails g graphql:interface NamedEntityType name:String!
10
+ # rails g graphql:enum ProgrammingLanguage RUBY PYTHON PERL PERL6:"PERL"
11
11
  # ```
12
12
  class EnumGenerator < TypeGeneratorBase
13
13
  desc "Create a GraphQL::EnumType with the given name and values"
@@ -13,6 +13,11 @@ module Graphql
13
13
  # - graphql/
14
14
  # - resolvers/
15
15
  # - types/
16
+ # - base_enum.rb
17
+ # - base_input_object.rb
18
+ # - base_interface.rb
19
+ # - base_object.rb
20
+ # - base_union.rb
16
21
  # - query_type.rb
17
22
  # - loaders/
18
23
  # - mutations/
@@ -40,6 +45,8 @@ module Graphql
40
45
  # Accept a `--batch` option which adds `GraphQL::Batch` setup.
41
46
  #
42
47
  # Use `--no-graphiql` to skip `graphiql-rails` installation.
48
+ #
49
+ # TODO: also add base classes
43
50
  class InstallGenerator < Rails::Generators::Base
44
51
  include Core
45
52
 
@@ -85,7 +92,11 @@ module Graphql
85
92
  create_dir("#{options[:directory]}/types")
86
93
  template("schema.erb", schema_file_path)
87
94
 
88
- # Note: Yuo can't have a schema without the query type, otherwise introspection breaks
95
+ ["base_object", "base_enum", "base_input_object", "base_interface", "base_union"].each do |base_type|
96
+ template("#{base_type}.erb", "#{options[:directory]}/types/#{base_type}.rb")
97
+ end
98
+
99
+ # Note: You can't have a schema without the query type, otherwise introspection breaks
89
100
  template("query_type.erb", "#{options[:directory]}/types/query_type.rb")
90
101
  insert_root_type('query', 'QueryType')
91
102
 
@@ -6,12 +6,12 @@ module Graphql
6
6
  module Generators
7
7
  # TODO: What other options should be supported?
8
8
  #
9
- # @example Generate a `Relay::Mutation` by name
9
+ # @example Generate a `GraphQL::Schema::RelayClassicMutation` by name
10
10
  # rails g graphql:mutation CreatePostMutation
11
11
  class MutationGenerator < Rails::Generators::Base
12
12
  include Core
13
13
 
14
- desc "Create a Relay mutation by name"
14
+ desc "Create a Relay Classic mutation by name"
15
15
  source_root File.expand_path('../templates', __FILE__)
16
16
 
17
17
  argument :name, type: :string
@@ -32,10 +32,10 @@ module Graphql
32
32
  else
33
33
  log :gsub, "#{options[:directory]}/types/mutation_type.rb"
34
34
  end
35
-
35
+
36
36
  template "mutation.erb", "#{options[:directory]}/mutations/#{file_name}.rb"
37
37
 
38
- sentinel = /name "Mutation"\s*\n/m
38
+ sentinel = /class .*MutationType/m
39
39
  in_root do
40
40
  gsub_file "#{options[:directory]}/types/mutation_type.rb", / \# TODO\: Add Mutations as fields\s*\n/m, ""
41
41
  inject_into_file "#{options[:directory]}/types/mutation_type.rb", " field :#{field_name}, Mutations::#{mutation_name}.field\n", after: sentinel, verbose: false, force: false
@@ -0,0 +1,2 @@
1
+ class Types::BaseEnum < GraphQL::Schema::Enum
2
+ end
@@ -0,0 +1,2 @@
1
+ class Types::BaseInputObject < GraphQL::Schema::InputObject
2
+ end
@@ -0,0 +1,3 @@
1
+ module Types::BaseInterface
2
+ include GraphQL::Schema::Interface
3
+ end
@@ -0,0 +1,2 @@
1
+ class Types::BaseObject < GraphQL::Schema::Object
2
+ end
@@ -0,0 +1,2 @@
1
+ class Types::BaseUnion < GraphQL::Schema::Union
2
+ end
@@ -1,4 +1,3 @@
1
- <%= type_ruby_name %> = GraphQL::EnumType.define do
2
- name "<%= type_graphql_name %>"
1
+ class <%= type_ruby_name %> < Types::BaseEnum
3
2
  <% prepared_values.each do |v| %> value "<%= v[0] %>"<%= v.length > 1 ? ", value: #{v[1]}" : "" %>
4
3
  <% end %>end
@@ -1,4 +1,3 @@
1
- <%= type_ruby_name %> = GraphQL::InterfaceType.define do
2
- name "<%= type_graphql_name %>"
3
- <% normalized_fields.each do |f| %> field :<%= f[0] %>, <%= f[1] %>
1
+ class <%= type_ruby_name %> < Types::BaseInterface
2
+ <% normalized_fields.each do |f| %> <%= f.to_ruby %>
4
3
  <% end %>end
@@ -1,12 +1,11 @@
1
- Mutations::<%= mutation_name %> = GraphQL::Relay::Mutation.define do
2
- name "<%= mutation_name %>"
1
+ class Mutations::<%= mutation_name %> < GraphQL::Schema::RelayClassicMutation
3
2
  # TODO: define return fields
4
3
  # return_field :post, Types::PostType
5
4
 
6
5
  # TODO: define arguments
7
6
  # input_field :name, !types.String
8
7
 
9
- resolve ->(obj, args, ctx) {
10
- # TODO: define resolve function
11
- }
8
+ def resolve(**inputs)
9
+ # TODO: define resolve method
10
+ end
12
11
  end
@@ -1,11 +1,8 @@
1
- Types::MutationType = GraphQL::ObjectType.define do
2
- name "Mutation"
3
-
4
- # TODO: Remove me
5
- field :testField, types.String do
6
- description "An example field added by the generator"
7
- resolve ->(obj, args, ctx) {
8
- "Hello World!"
9
- }
1
+ class Types::MutationType < Types::BaseObject
2
+ # TODO: remove me
3
+ field :test_field, String, null: false,
4
+ description: "An example field added by the generator"
5
+ def test_field
6
+ "Hello World"
10
7
  end
11
8
  end
@@ -1,5 +1,4 @@
1
- <%= type_ruby_name %> = GraphQL::ObjectType.define do
2
- name "<%= type_graphql_name %>"
1
+ class <%= type_ruby_name %> < Types::BaseObject
3
2
  <% if options.node %> implements GraphQL::Relay::Node.interface
4
- <% end %><% normalized_fields.each do |f| %> field :<%= f[0] %>, <%= f[1] %>
3
+ <% end %><% normalized_fields.each do |f| %> <%= f.to_ruby %>
5
4
  <% end %>end
@@ -1,15 +1,13 @@
1
- Types::QueryType = GraphQL::ObjectType.define do
2
- name "Query"
1
+ class Types::QueryType < Types::BaseObject
3
2
  # Add root-level fields here.
4
3
  # They will be entry points for queries on your schema.
5
4
 
6
5
  # TODO: remove me
7
- field :testField, types.String do
8
- description "An example field added by the generator"
9
- resolve ->(obj, args, ctx) {
10
- "Hello World!"
11
- }
6
+ field :test_field, String, null: false,
7
+ description: "An example field added by the generator"
8
+ def test_field
9
+ "Hello World!"
12
10
  end
13
11
  <% if options[:relay] %>
14
- field :node, GraphQL::Relay::Node.field
12
+ field :node, field: GraphQL::Relay::Node.field
15
13
  <% end %>end
@@ -1,31 +1,31 @@
1
- <%= schema_name %> = GraphQL::Schema.define do
1
+ class <%= schema_name %> < GraphQL::Schema
2
2
  <% if options[:relay] %>
3
3
  # Relay Object Identification:
4
4
 
5
5
  # Return a string UUID for `object`
6
- id_from_object ->(object, type_definition, query_ctx) {
6
+ def self.id_from_object(object, type_definition, query_ctx)
7
7
  # Here's a simple implementation which:
8
8
  # - joins the type name & object.id
9
9
  # - encodes it with base64:
10
10
  # GraphQL::Schema::UniqueWithinType.encode(type_definition.name, object.id)
11
- }
11
+ end
12
12
 
13
13
  # Given a string UUID, find the object
14
- object_from_id ->(id, query_ctx) {
14
+ def self.object_from_id(id, query_ctx)
15
15
  # For example, to decode the UUIDs generated above:
16
16
  # type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id)
17
17
  #
18
18
  # Then, based on `type_name` and `id`
19
19
  # find an object in your application
20
20
  # ...
21
- }
21
+ end
22
22
 
23
23
  # Object Resolution
24
- resolve_type -> (type, obj, ctx) {
24
+ def self.resolve_type(type, obj, ctx)
25
25
  # TODO: Implement this function
26
26
  # to return the correct type for `obj`
27
27
  raise(NotImplementedError)
28
- }
28
+ end
29
29
  <% end %><% if options[:batch] %>
30
30
  # GraphQL::Batch setup:
31
31
  use GraphQL::Batch
@@ -1,4 +1,3 @@
1
- <%= type_ruby_name %> = GraphQL::UnionType.define do
2
- name "<%= type_graphql_name %>"
1
+ class <%= type_ruby_name %> < Types::BaseUnion
3
2
  <% if possible_types.any? %> possible_types [<%= normalized_possible_types.join(", ") %>]
4
3
  <% end %>end
@@ -21,33 +21,35 @@ module Graphql
21
21
  # TODO: nullability / list with `mode: :graphql` doesn't work
22
22
  # @param type_expresson [String]
23
23
  # @param mode [Symbol]
24
- # @return [String]
25
- def self.normalize_type_expression(type_expression, mode:)
24
+ # @param null [Boolean]
25
+ # @return [(String, Boolean)] The type expression, followed by `null:` value
26
+ def self.normalize_type_expression(type_expression, mode:, null: true)
26
27
  if type_expression.start_with?("!")
27
- "!#{normalize_type_expression(type_expression[1..-1], mode: mode)}"
28
+ normalize_type_expression(type_expression[1..-1], mode: mode, null: false)
28
29
  elsif type_expression.end_with?("!")
29
- "!#{normalize_type_expression(type_expression[0..-2], mode: mode)}"
30
+ normalize_type_expression(type_expression[0..-2], mode: mode, null: false)
30
31
  elsif type_expression.start_with?("[") && type_expression.end_with?("]")
31
- "types[#{normalize_type_expression(type_expression[1..-2], mode: mode)}]"
32
- elsif type_expression.start_with?("types[") && type_expression.end_with?("]")
33
- "types[#{normalize_type_expression(type_expression[6..-2], mode: mode)}]"
32
+ name, is_null = normalize_type_expression(type_expression[1..-2], mode: mode, null: null)
33
+ ["[#{name}]", is_null]
34
34
  elsif type_expression.end_with?("Type")
35
- normalize_type_expression(type_expression[0..-5], mode: mode)
35
+ normalize_type_expression(type_expression[0..-5], mode: mode, null: null)
36
36
  elsif type_expression.start_with?("Types::")
37
- normalize_type_expression(type_expression[7..-1], mode: mode)
37
+ normalize_type_expression(type_expression[7..-1], mode: mode, null: null)
38
38
  elsif type_expression.start_with?("types.")
39
- normalize_type_expression(type_expression[6..-1], mode: mode)
39
+ normalize_type_expression(type_expression[6..-1], mode: mode, null: null)
40
40
  else
41
41
  case mode
42
42
  when :ruby
43
43
  case type_expression
44
- when "Int", "Float", "Boolean", "String", "ID"
45
- "types.#{type_expression}"
44
+ when "Int"
45
+ ["Integer", null]
46
+ when "Integer", "Float", "Boolean", "String", "ID"
47
+ [type_expression, null]
46
48
  else
47
- "Types::#{type_expression.camelize}Type"
49
+ ["Types::#{type_expression.camelize}Type", null]
48
50
  end
49
51
  when :graphql
50
- type_expression.camelize
52
+ [type_expression.camelize, null]
51
53
  else
52
54
  raise "Unexpected normalize mode: #{mode}"
53
55
  end
@@ -58,12 +60,12 @@ module Graphql
58
60
 
59
61
  # @return [String] The user-provided type name, normalized to Ruby code
60
62
  def type_ruby_name
61
- @type_ruby_name ||= self.class.normalize_type_expression(type_name, mode: :ruby)
63
+ @type_ruby_name ||= self.class.normalize_type_expression(type_name, mode: :ruby)[0]
62
64
  end
63
65
 
64
66
  # @return [String] The user-provided type name, as a GraphQL name
65
67
  def type_graphql_name
66
- @type_graphql_name ||= self.class.normalize_type_expression(type_name, mode: :graphql)
68
+ @type_graphql_name ||= self.class.normalize_type_expression(type_name, mode: :graphql)[0]
67
69
  end
68
70
 
69
71
  # @return [String] The user-provided type name, as a file name (without extension)
@@ -71,13 +73,26 @@ module Graphql
71
73
  @type_file_name ||= "#{type_graphql_name}Type".underscore
72
74
  end
73
75
 
74
- # @return [Array<Array(String, String)>>] User-provided fields, in `(name, Ruby type name)` pairs
76
+ # @return [Array<NormalizedField>] User-provided fields, in `(name, Ruby type name)` pairs
75
77
  def normalized_fields
76
78
  @normalized_fields ||= fields.map { |f|
77
79
  name, raw_type = f.split(":", 2)
78
- [name, self.class.normalize_type_expression(raw_type, mode: :ruby)]
80
+ type_expr, null = self.class.normalize_type_expression(raw_type, mode: :ruby)
81
+ NormalizedField.new(name, type_expr, null)
79
82
  }
80
83
  end
84
+
85
+ class NormalizedField
86
+ def initialize(name, type_expr, null)
87
+ @name = name
88
+ @type_expr = type_expr
89
+ @null = null
90
+ end
91
+
92
+ def to_ruby
93
+ "field :#{@name}, #{@type_expr}, null: #{@null}"
94
+ end
95
+ end
81
96
  end
82
97
  end
83
98
  end
@@ -26,7 +26,7 @@ module Graphql
26
26
  private
27
27
 
28
28
  def normalized_possible_types
29
- possible_types.map { |t| self.class.normalize_type_expression(t, mode: :ruby) }
29
+ possible_types.map { |t| self.class.normalize_type_expression(t, mode: :ruby)[0] }
30
30
  end
31
31
  end
32
32
  end
@@ -211,10 +211,13 @@ module GraphQL
211
211
  inner_ctx,
212
212
  )
213
213
 
214
+ return PROPAGATE_NULL if inner_result == PROPAGATE_NULL
215
+
214
216
  inner_ctx.value = inner_result
215
217
  result << inner_ctx
216
218
  i += 1
217
219
  end
220
+
218
221
  result
219
222
  when GraphQL::TypeKinds::NON_NULL
220
223
  inner_type = field_type.of_type
@@ -652,6 +652,7 @@ module GraphQL
652
652
  # Configuration
653
653
  :max_complexity=, :max_depth=,
654
654
  :metadata,
655
+ :default_mask,
655
656
  :default_filter, :redefine,
656
657
  :id_from_object_proc, :object_from_id_proc,
657
658
  :id_from_object=, :object_from_id=, :type_error,
@@ -27,7 +27,28 @@ module GraphQL
27
27
  attr_reader :arguments
28
28
 
29
29
  # Ruby-like hash behaviors, read-only
30
- def_delegators :@ruby_style_hash, :to_h, :keys, :values, :each, :any?
30
+ def_delegators :@ruby_style_hash, :keys, :values, :each, :any?
31
+
32
+ def to_h
33
+ @ruby_style_hash.inject({}) do |h, (key, value)|
34
+ h.merge(key => unwrap_value(value))
35
+ end
36
+ end
37
+
38
+ def unwrap_value(value)
39
+ case value
40
+ when Array
41
+ value.map { |item| unwrap_value(item) }
42
+ when Hash
43
+ value.inject({}) do |h, (key, value)|
44
+ h.merge(key => unwrap_value(value))
45
+ end
46
+ when InputObject
47
+ value.to_h
48
+ else
49
+ value
50
+ end
51
+ end
31
52
 
32
53
  # Lookup a key on this object, it accepts new-style underscored symbols
33
54
  # Or old-style camelized identifiers.
@@ -27,10 +27,14 @@ module GraphQL
27
27
 
28
28
  # Override {GraphQL::Schema::Mutation#resolve_mutation} to
29
29
  # delete `client_mutation_id` from the kwargs.
30
- def resolve_mutation(kwargs)
30
+ def resolve_mutation(**kwargs)
31
31
  # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
32
32
  kwargs.delete(:client_mutation_id)
33
- resolve(**kwargs)
33
+ if kwargs.any?
34
+ resolve(**kwargs)
35
+ else
36
+ resolve
37
+ end
34
38
  end
35
39
 
36
40
  resolve_method(:resolve_mutation)
@@ -24,6 +24,7 @@ module GraphQL
24
24
  GraphQL::StaticValidation::ArgumentsAreDefined,
25
25
  GraphQL::StaticValidation::ArgumentLiteralsAreCompatible,
26
26
  GraphQL::StaticValidation::RequiredArgumentsArePresent,
27
+ GraphQL::StaticValidation::ArgumentNamesAreUnique,
27
28
  GraphQL::StaticValidation::VariableNamesAreUnique,
28
29
  GraphQL::StaticValidation::VariablesAreInputTypes,
29
30
  GraphQL::StaticValidation::VariableDefaultValuesAreCorrectlyTyped,
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class ArgumentNamesAreUnique
5
+ include GraphQL::StaticValidation::Message::MessageHelper
6
+
7
+ def validate(context)
8
+ context.visitor[GraphQL::Language::Nodes::Field] << ->(node, parent) {
9
+ validate_arguments(node, context)
10
+ }
11
+
12
+ context.visitor[GraphQL::Language::Nodes::Directive] << ->(node, parent) {
13
+ validate_arguments(node, context)
14
+ }
15
+ end
16
+
17
+ def validate_arguments(node, context)
18
+ argument_defns = node.arguments
19
+ if argument_defns.any?
20
+ args_by_name = Hash.new { |h, k| h[k] = [] }
21
+ argument_defns.each { |a| args_by_name[a.name] << a }
22
+ args_by_name.each do |name, defns|
23
+ if defns.size > 1
24
+ context.errors << message("There can be only one argument named \"#{name}\"", defns, context: context)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end