graphql 1.8.1 → 1.8.2

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