graphql 1.4.5 → 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/enum_generator.rb +33 -0
  3. data/lib/generators/graphql/function_generator.rb +15 -0
  4. data/lib/generators/graphql/install_generator.rb +118 -0
  5. data/lib/generators/graphql/interface_generator.rb +27 -0
  6. data/lib/generators/graphql/loader_generator.rb +17 -0
  7. data/lib/generators/graphql/mutation_generator.rb +19 -0
  8. data/lib/generators/graphql/object_generator.rb +34 -0
  9. data/lib/generators/graphql/templates/enum.erb +4 -0
  10. data/lib/generators/graphql/templates/function.erb +17 -0
  11. data/lib/generators/graphql/templates/graphql_controller.erb +32 -0
  12. data/lib/generators/graphql/templates/interface.erb +4 -0
  13. data/lib/generators/graphql/templates/loader.erb +15 -0
  14. data/lib/generators/graphql/templates/mutation.erb +12 -0
  15. data/lib/generators/graphql/templates/object.erb +5 -0
  16. data/lib/generators/graphql/templates/query_type.erb +15 -0
  17. data/lib/generators/graphql/templates/schema.erb +34 -0
  18. data/lib/generators/graphql/templates/union.erb +4 -0
  19. data/lib/generators/graphql/type_generator.rb +78 -0
  20. data/lib/generators/graphql/union_generator.rb +33 -0
  21. data/lib/graphql.rb +10 -0
  22. data/lib/graphql/analysis/analyze_query.rb +1 -1
  23. data/lib/graphql/analysis/query_complexity.rb +6 -50
  24. data/lib/graphql/analysis/query_depth.rb +1 -1
  25. data/lib/graphql/argument.rb +21 -0
  26. data/lib/graphql/compatibility/execution_specification/counter_schema.rb +3 -3
  27. data/lib/graphql/define.rb +1 -0
  28. data/lib/graphql/define/assign_argument.rb +3 -19
  29. data/lib/graphql/define/assign_mutation_function.rb +34 -0
  30. data/lib/graphql/define/assign_object_field.rb +26 -14
  31. data/lib/graphql/define/defined_object_proxy.rb +21 -0
  32. data/lib/graphql/define/instance_definable.rb +61 -11
  33. data/lib/graphql/directive.rb +6 -1
  34. data/lib/graphql/execution/directive_checks.rb +1 -0
  35. data/lib/graphql/execution/execute.rb +14 -9
  36. data/lib/graphql/execution/field_result.rb +1 -0
  37. data/lib/graphql/execution/lazy.rb +8 -17
  38. data/lib/graphql/execution/lazy/lazy_method_map.rb +2 -0
  39. data/lib/graphql/execution/lazy/resolve.rb +1 -0
  40. data/lib/graphql/execution/selection_result.rb +1 -0
  41. data/lib/graphql/execution/typecast.rb +39 -26
  42. data/lib/graphql/field.rb +15 -3
  43. data/lib/graphql/field/resolve.rb +3 -3
  44. data/lib/graphql/function.rb +134 -0
  45. data/lib/graphql/id_type.rb +1 -1
  46. data/lib/graphql/input_object_type.rb +1 -1
  47. data/lib/graphql/internal_representation.rb +1 -1
  48. data/lib/graphql/internal_representation/node.rb +35 -107
  49. data/lib/graphql/internal_representation/rewrite.rb +189 -183
  50. data/lib/graphql/internal_representation/visit.rb +38 -0
  51. data/lib/graphql/introspection/input_value_type.rb +10 -1
  52. data/lib/graphql/introspection/schema_type.rb +1 -1
  53. data/lib/graphql/language/lexer.rb +6 -3
  54. data/lib/graphql/language/lexer.rl +6 -3
  55. data/lib/graphql/object_type.rb +53 -13
  56. data/lib/graphql/query.rb +30 -14
  57. data/lib/graphql/query/arguments.rb +2 -0
  58. data/lib/graphql/query/context.rb +2 -2
  59. data/lib/graphql/query/literal_input.rb +9 -0
  60. data/lib/graphql/query/serial_execution/field_resolution.rb +2 -2
  61. data/lib/graphql/query/serial_execution/selection_resolution.rb +1 -1
  62. data/lib/graphql/relay.rb +1 -0
  63. data/lib/graphql/relay/array_connection.rb +1 -1
  64. data/lib/graphql/relay/base_connection.rb +34 -15
  65. data/lib/graphql/relay/connection_resolve.rb +7 -2
  66. data/lib/graphql/relay/mutation.rb +45 -4
  67. data/lib/graphql/relay/node.rb +18 -6
  68. data/lib/graphql/relay/range_add.rb +45 -0
  69. data/lib/graphql/relay/relation_connection.rb +17 -2
  70. data/lib/graphql/runtime_type_error.rb +1 -0
  71. data/lib/graphql/schema.rb +40 -5
  72. data/lib/graphql/schema/base_64_encoder.rb +1 -0
  73. data/lib/graphql/schema/build_from_definition.rb +56 -21
  74. data/lib/graphql/schema/default_parse_error.rb +10 -0
  75. data/lib/graphql/schema/loader.rb +8 -1
  76. data/lib/graphql/schema/null_mask.rb +1 -0
  77. data/lib/graphql/schema/validation.rb +35 -0
  78. data/lib/graphql/static_validation.rb +1 -0
  79. data/lib/graphql/static_validation/all_rules.rb +1 -0
  80. data/lib/graphql/static_validation/arguments_validator.rb +7 -4
  81. data/lib/graphql/static_validation/definition_dependencies.rb +183 -0
  82. data/lib/graphql/static_validation/rules/fields_will_merge.rb +28 -96
  83. data/lib/graphql/static_validation/rules/fragment_names_are_unique.rb +23 -0
  84. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +8 -5
  85. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +6 -31
  86. data/lib/graphql/static_validation/rules/fragments_are_used.rb +11 -41
  87. data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +2 -2
  88. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +19 -7
  89. data/lib/graphql/static_validation/validation_context.rb +22 -1
  90. data/lib/graphql/static_validation/validator.rb +4 -1
  91. data/lib/graphql/string_type.rb +5 -1
  92. data/lib/graphql/version.rb +1 -1
  93. data/readme.md +12 -3
  94. data/spec/generators/graphql/enum_generator_spec.rb +29 -0
  95. data/spec/generators/graphql/function_generator_spec.rb +33 -0
  96. data/spec/generators/graphql/install_generator_spec.rb +185 -0
  97. data/spec/generators/graphql/interface_generator_spec.rb +32 -0
  98. data/spec/generators/graphql/loader_generator_spec.rb +31 -0
  99. data/spec/generators/graphql/mutation_generator_spec.rb +28 -0
  100. data/spec/generators/graphql/object_generator_spec.rb +42 -0
  101. data/spec/generators/graphql/union_generator_spec.rb +50 -0
  102. data/spec/graphql/analysis/query_complexity_spec.rb +2 -1
  103. data/spec/graphql/define/instance_definable_spec.rb +38 -0
  104. data/spec/graphql/directive/skip_directive_spec.rb +1 -0
  105. data/spec/graphql/directive_spec.rb +18 -0
  106. data/spec/graphql/execution/typecast_spec.rb +41 -46
  107. data/spec/graphql/field_spec.rb +1 -1
  108. data/spec/graphql/function_spec.rb +128 -0
  109. data/spec/graphql/internal_representation/rewrite_spec.rb +166 -129
  110. data/spec/graphql/introspection/type_type_spec.rb +1 -1
  111. data/spec/graphql/language/lexer_spec.rb +6 -0
  112. data/spec/graphql/object_type_spec.rb +73 -2
  113. data/spec/graphql/query/arguments_spec.rb +28 -0
  114. data/spec/graphql/query/variables_spec.rb +7 -1
  115. data/spec/graphql/query_spec.rb +30 -0
  116. data/spec/graphql/relay/base_connection_spec.rb +26 -8
  117. data/spec/graphql/relay/connection_resolve_spec.rb +45 -0
  118. data/spec/graphql/relay/connection_type_spec.rb +21 -0
  119. data/spec/graphql/relay/node_spec.rb +30 -2
  120. data/spec/graphql/relay/range_add_spec.rb +113 -0
  121. data/spec/graphql/schema/build_from_definition_spec.rb +114 -0
  122. data/spec/graphql/schema/loader_spec.rb +1 -0
  123. data/spec/graphql/schema/printer_spec.rb +2 -2
  124. data/spec/graphql/schema/validation_spec.rb +80 -11
  125. data/spec/graphql/schema/warden_spec.rb +10 -10
  126. data/spec/graphql/schema_spec.rb +18 -1
  127. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +16 -0
  128. data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +50 -3
  129. data/spec/graphql/static_validation/rules/fragment_names_are_unique_spec.rb +27 -0
  130. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +57 -0
  131. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -1
  132. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +14 -0
  133. data/spec/graphql/string_type_spec.rb +7 -0
  134. data/spec/spec_helper.rb +3 -3
  135. data/spec/support/base_generator_test.rb +7 -0
  136. data/spec/support/dummy/schema.rb +32 -30
  137. data/spec/support/star_wars/schema.rb +81 -23
  138. metadata +98 -20
  139. data/lib/graphql/internal_representation/selection.rb +0 -85
@@ -23,11 +23,14 @@ module GraphQL
23
23
 
24
24
  context.visitor[GraphQL::Language::Nodes::Document].leave << ->(doc_node, parent) {
25
25
  spreads_to_validate.each do |frag_spread|
26
- fragment_child_name = context.fragments[frag_spread.node.name].type.name
27
- fragment_child = context.warden.get_type(fragment_child_name)
28
- # Might be non-existent type name
29
- if fragment_child
30
- validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path)
26
+ frag_node = context.fragments[frag_spread.node.name]
27
+ if frag_node
28
+ fragment_child_name = frag_node.type.name
29
+ fragment_child = context.warden.get_type(fragment_child_name)
30
+ # Might be non-existent type name
31
+ if fragment_child
32
+ validate_fragment_in_scope(frag_spread.parent_type, fragment_child, frag_spread.node, context, frag_spread.path)
33
+ end
31
34
  end
32
35
  end
33
36
  }
@@ -5,38 +5,13 @@ module GraphQL
5
5
  include GraphQL::StaticValidation::Message::MessageHelper
6
6
 
7
7
  def validate(context)
8
- context.visitor[GraphQL::Language::Nodes::FragmentDefinition] << ->(node, parent) {
9
- if has_nested_spread?(node, [], context)
10
- context.errors << message("Fragment #{node.name} contains an infinite loop", node, context: context)
8
+ context.visitor[GraphQL::Language::Nodes::Document].leave << ->(_n, _p) do
9
+ dependency_map = context.dependencies
10
+ dependency_map.cyclical_definitions.each do |defn|
11
+ if defn.node.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
12
+ context.errors << message("Fragment #{defn.name} contains an infinite loop", defn.node, path: defn.path)
13
+ end
11
14
  end
12
- }
13
- end
14
-
15
- private
16
-
17
- def has_nested_spread?(fragment_def, parent_fragment_names, context)
18
- nested_spreads = get_spreads(fragment_def.selections)
19
-
20
- nested_spreads.any? do |spread|
21
- nested_def = context.fragments[spread.name]
22
- if nested_def.nil?
23
- # this is another kind of error
24
- false
25
- else
26
- parent_fragment_names.include?(spread.name) || has_nested_spread?(nested_def, parent_fragment_names + [fragment_def.name], context)
27
- end
28
- end
29
- end
30
-
31
- # Find spreads contained in this selection & return them in a flat array
32
- def get_spreads(selection)
33
- case selection
34
- when GraphQL::Language::Nodes::FragmentSpread
35
- [selection]
36
- when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InlineFragment
37
- get_spreads(selection.selections)
38
- when Array
39
- selection.map { |s| get_spreads(s) }.flatten
40
15
  end
41
16
  end
42
17
  end
@@ -5,49 +5,19 @@ module GraphQL
5
5
  include GraphQL::StaticValidation::Message::MessageHelper
6
6
 
7
7
  def validate(context)
8
- v = context.visitor
9
- used_fragments = []
10
- defined_fragments = []
11
-
12
- v[GraphQL::Language::Nodes::Document] << ->(node, parent) {
13
- defined_fragments = node.definitions
14
- .select { |defn| defn.is_a?(GraphQL::Language::Nodes::FragmentDefinition) }
15
- .map { |node| FragmentInstance.new(node: node, path: context.path) }
16
- }
17
-
18
- v[GraphQL::Language::Nodes::FragmentSpread] << ->(node, parent) {
19
- used_fragments << FragmentInstance.new(node: node, path: context.path)
20
- if defined_fragments.none? { |defn| defn.name == node.name }
21
- GraphQL::Language::Visitor::SKIP
8
+ context.visitor[GraphQL::Language::Nodes::Document].leave << ->(_n, _p) do
9
+ dependency_map = context.dependencies
10
+ dependency_map.unmet_dependencies.each do |op_defn, spreads|
11
+ spreads.each do |fragment_spread|
12
+ context.errors << message("Fragment #{fragment_spread.name} was used, but not defined", fragment_spread.node, path: fragment_spread.path)
13
+ end
22
14
  end
23
- }
24
- v[GraphQL::Language::Nodes::Document].leave << ->(node, parent) { add_errors(context, used_fragments, defined_fragments) }
25
- end
26
-
27
- private
28
-
29
- def add_errors(context, used_fragments, defined_fragments)
30
- undefined_fragments = find_difference(used_fragments, defined_fragments.map(&:name))
31
- undefined_fragments.each do |fragment|
32
- context.errors << message("Fragment #{fragment.name} was used, but not defined", fragment.node, path: fragment.path)
33
- end
34
15
 
35
- unused_fragments = find_difference(defined_fragments, used_fragments.map(&:name))
36
- unused_fragments.each do |fragment|
37
- context.errors << message("Fragment #{fragment.name} was defined, but not used", fragment.node, path: fragment.path)
38
- end
39
- end
40
-
41
- def find_difference(fragments, allowed_fragment_names)
42
- fragments.select {|f| f.name && !allowed_fragment_names.include?(f.name) }
43
- end
44
-
45
- class FragmentInstance
46
- attr_reader :name, :node, :path
47
- def initialize(node:, path:)
48
- @node = node
49
- @name = node.name
50
- @path = path
16
+ dependency_map.unused_dependencies.each do |fragment|
17
+ if !fragment.name.nil?
18
+ context.errors << message("Fragment #{fragment.name} was defined, but not used", fragment.node, path: fragment.path)
19
+ end
20
+ end
51
21
  end
52
22
  end
53
23
  end
@@ -7,11 +7,11 @@ module GraphQL
7
7
  def validate(context)
8
8
  op_names = Hash.new { |h, k| h[k] = [] }
9
9
 
10
- context.visitor[GraphQL::Language::Nodes::OperationDefinition].enter << -> (node, _parent) {
10
+ context.visitor[GraphQL::Language::Nodes::OperationDefinition].enter << ->(node, _parent) {
11
11
  op_names[node.name] << node
12
12
  }
13
13
 
14
- context.visitor[GraphQL::Language::Nodes::Document].leave << -> (node, _parent) {
14
+ context.visitor[GraphQL::Language::Nodes::Document].leave << ->(node, _parent) {
15
15
  op_count = op_names.values.inject(0) { |m, v| m += v.size }
16
16
 
17
17
  op_names.each do |name, nodes|
@@ -33,7 +33,10 @@ module GraphQL
33
33
  private
34
34
 
35
35
  def validate_usage(arguments, arg_node, ast_var, context)
36
- var_type = to_query_type(ast_var.type, context.schema.types)
36
+ var_type = to_query_type(ast_var.type, context.query.warden)
37
+ if var_type.nil?
38
+ return
39
+ end
37
40
  if !ast_var.default_value.nil?
38
41
  var_type = GraphQL::NonNullType.new(of_type: var_type)
39
42
  end
@@ -53,13 +56,22 @@ module GraphQL
53
56
  end
54
57
  end
55
58
 
56
- def to_query_type(ast_type, types)
57
- if ast_type.is_a?(GraphQL::Language::Nodes::NonNullType)
58
- GraphQL::NonNullType.new(of_type: to_query_type(ast_type.of_type, types))
59
- elsif ast_type.is_a?(GraphQL::Language::Nodes::ListType)
60
- GraphQL::ListType.new(of_type: to_query_type(ast_type.of_type, types))
59
+ def to_query_type(ast_type, warden)
60
+ case ast_type
61
+ when GraphQL::Language::Nodes::NonNullType
62
+ wrap_query_type(to_query_type(ast_type.of_type, warden), GraphQL::NonNullType)
63
+ when GraphQL::Language::Nodes::ListType
64
+ wrap_query_type(to_query_type(ast_type.of_type, warden), GraphQL::ListType)
65
+ else
66
+ warden.get_type(ast_type.name)
67
+ end
68
+ end
69
+
70
+ def wrap_query_type(type, wrapper)
71
+ if type.nil?
72
+ nil
61
73
  else
62
- types[ast_type.name]
74
+ wrapper.new(of_type: type)
63
75
  end
64
76
  end
65
77
 
@@ -12,7 +12,11 @@ module GraphQL
12
12
  # It also provides limited access to the {TypeStack} instance,
13
13
  # which tracks state as you climb in and out of different fields.
14
14
  class ValidationContext
15
- attr_reader :query, :schema, :document, :errors, :visitor, :fragments, :operations, :warden
15
+ attr_reader :query, :schema,
16
+ :document, :errors, :visitor,
17
+ :fragments, :operations, :warden,
18
+ :dependencies, :each_irep_node_handlers
19
+
16
20
  def initialize(query)
17
21
  @query = query
18
22
  @schema = query.schema
@@ -33,12 +37,29 @@ module GraphQL
33
37
  @errors = []
34
38
  @visitor = GraphQL::Language::Visitor.new(document)
35
39
  @type_stack = GraphQL::StaticValidation::TypeStack.new(schema, visitor)
40
+ definition_dependencies = DefinitionDependencies.mount(self)
41
+ @on_dependency_resolve_handlers = []
42
+ @each_irep_node_handlers = []
43
+ visitor[GraphQL::Language::Nodes::Document].leave << ->(_n, _p) {
44
+ @dependencies = definition_dependencies.dependency_map { |defn, spreads, frag|
45
+ @on_dependency_resolve_handlers.each { |h| h.call(defn, spreads, frag) }
46
+ }
47
+ }
48
+ end
49
+
50
+
51
+ def on_dependency_resolve(&handler)
52
+ @on_dependency_resolve_handlers << handler
36
53
  end
37
54
 
38
55
  def object_types
39
56
  @type_stack.object_types
40
57
  end
41
58
 
59
+ def each_irep_node(&handler)
60
+ @each_irep_node_handlers << handler
61
+ end
62
+
42
63
  # @return [GraphQL::BaseType] The current object type
43
64
  def type_definition
44
65
  object_types.last
@@ -32,11 +32,14 @@ module GraphQL
32
32
  end
33
33
 
34
34
  context.visitor.visit
35
+ # Post-validation: allow validators to register handlers on rewritten query nodes
36
+ rewrite_result = rewrite.operations
37
+ GraphQL::InternalRepresentation::Visit.visit_each_node(rewrite_result, context.each_irep_node_handlers)
35
38
 
36
39
  {
37
40
  errors: context.errors,
38
41
  # If there were errors, the irep is garbage
39
- irep: context.errors.none? ? rewrite.operations : nil,
42
+ irep: context.errors.any? ? nil : rewrite_result,
40
43
  }
41
44
  end
42
45
  end
@@ -3,7 +3,11 @@ GraphQL::STRING_TYPE = GraphQL::ScalarType.define do
3
3
  name "String"
4
4
  description "Represents textual data as UTF-8 character sequences. This type is most often used by GraphQL to represent free-form human-readable text."
5
5
 
6
- coerce_result ->(value) { value.to_s }
6
+ coerce_result ->(value) {
7
+ str = value.to_s
8
+ str.encoding == Encoding::US_ASCII || str.encoding == Encoding::UTF_8 ? str : nil
9
+ }
10
+
7
11
  coerce_input ->(value) { value.is_a?(String) ? value : nil }
8
12
  default_scalar true
9
13
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.4.5"
3
+ VERSION = "1.5.3"
4
4
  end
data/readme.md CHANGED
@@ -8,8 +8,9 @@
8
8
 
9
9
  A Ruby implementation of [GraphQL](http://graphql.org/).
10
10
 
11
- - [Website](https://rmosolgo.github.io/graphql-ruby)
12
- - [API Documentation](http://www.rubydoc.info/github/rmosolgo/graphql-ruby)
11
+ - [Website](https://rmosolgo.github.io/graphql-ruby)
12
+ - [API Documentation](http://www.rubydoc.info/github/rmosolgo/graphql-ruby)
13
+ - [Newsletter](https://tinyletter.com/graphql-ruby)
13
14
 
14
15
  ## Installation
15
16
 
@@ -26,7 +27,15 @@ $ bundle install
26
27
 
27
28
  ## Getting Started
28
29
 
29
- See "Getting Started" on the [website](https://rmosolgo.github.io/graphql-ruby/) or on [GitHub](https://github.com/rmosolgo/graphql-ruby/blob/master/guides/index.md)
30
+ ```
31
+ $ rails generate graphql:install
32
+ ```
33
+
34
+ Or, see ["Getting Started"](https://rmosolgo.github.io/graphql-ruby/).
35
+
36
+ ## Upgrade
37
+
38
+ I also sell [GraphQL::Pro](http://graphql.pro) which provides several features on top of the GraphQL runtime, including [authorization](http://rmosolgo.github.io/graphql-ruby/pro/authorization), [monitoring](http://rmosolgo.github.io/graphql-ruby/pro/monitoring) plugins and [static queries](http://rmosolgo.github.io/graphql-ruby/pro/persisted_queries). Besides that, Pro customers get email support and an opportunity to support graphql-ruby's development!
30
39
 
31
40
  ## Goals
32
41
 
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+ require "generators/graphql/enum_generator"
4
+
5
+ class GraphQLGeneratorsEnumGeneratorTest < BaseGeneratorTest
6
+ tests Graphql::Generators::EnumGenerator
7
+
8
+ test "it generate enums with values" do
9
+ expected_content = <<-RUBY
10
+ Types::FamilyType = GraphQL::EnumType.define do
11
+ name "Family"
12
+ value "NIGHTSHADE"
13
+ value "BRASSICA", value: Family::COLE
14
+ value "UMBELLIFER", value: :umbellifer
15
+ value "LEGUME", value: "bean & friends"
16
+ value "CURCURBITS", value: 5
17
+ end
18
+ RUBY
19
+
20
+ run_generator(["Family",
21
+ "NIGHTSHADE",
22
+ "BRASSICA:Family::COLE",
23
+ "UMBELLIFER::umbellifer",
24
+ 'LEGUME:"bean & friends"',
25
+ "CURCURBITS:5"
26
+ ])
27
+ assert_file "app/graphql/types/family_type.rb", expected_content
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+ require "generators/graphql/function_generator"
4
+
5
+ class GraphQLGeneratorsFunctionGeneratorTest < BaseGeneratorTest
6
+ tests Graphql::Generators::FunctionGenerator
7
+
8
+ test "it generates an empty function by name" do
9
+ run_generator(["FindRecord"])
10
+
11
+ expected_content = <<-RUBY
12
+ class Functions::FindRecord < GraphQL::Function
13
+ # Define `initialize` to store field-level options, eg
14
+ #
15
+ # field :myField, function: Functions::FindRecord.new(type: MyType)
16
+ #
17
+ # attr_reader :type
18
+ # def initialize(type:)
19
+ # @type = type
20
+ # end
21
+
22
+ # add arguments by type:
23
+ # argument :id, !GraphQL::ID_TYPE
24
+
25
+ # Resolve function:
26
+ def call(obj, args, ctx)
27
+ end
28
+ end
29
+ RUBY
30
+
31
+ assert_file "app/graphql/functions/find_record.rb", expected_content
32
+ end
33
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+ require "generators/graphql/install_generator"
4
+
5
+ class GraphQLGeneratorsInstallGeneratorTest < Rails::Generators::TestCase
6
+
7
+ tests Graphql::Generators::InstallGenerator
8
+ destination File.expand_path("../../../tmp/dummy", File.dirname(__FILE__))
9
+
10
+ setup do
11
+ prepare_destination
12
+ FileUtils.cd(File.expand_path("../../../tmp", File.dirname(__FILE__))) do
13
+ `rm -rf dummy`
14
+ `rails new dummy --skip-active-record --skip-test-unit --skip-spring --skip-bundle`
15
+ end
16
+ end
17
+
18
+ test "it generates a folder structure" do
19
+ run_generator([])
20
+
21
+ assert_file "app/graphql/types/.keep"
22
+ assert_file "app/graphql/mutations/.keep"
23
+ expected_query_route = %|post "/graphql", to: "graphql#execute"|
24
+ expected_graphiql_route = %|
25
+ if Rails.env.development?
26
+ mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
27
+ end
28
+ |
29
+
30
+ assert_file "config/routes.rb" do |contents|
31
+ assert_includes contents, expected_query_route
32
+ assert_includes contents, expected_graphiql_route
33
+ end
34
+
35
+ assert_file "Gemfile" do |contents|
36
+ assert_match %r{gem ('|")graphiql-rails('|"), :?group(:| =>) :development}, contents
37
+ end
38
+
39
+ expected_schema = <<-RUBY
40
+ DummySchema = GraphQL::Schema.define do
41
+ query(Types::QueryType)
42
+ end
43
+ RUBY
44
+ assert_file "app/graphql/dummy_schema.rb", expected_schema
45
+
46
+
47
+ expected_query_type = <<-RUBY
48
+ Types::QueryType = GraphQL::ObjectType.define do
49
+ name "Query"
50
+ # Add root-level fields here.
51
+ # They will be entry points for queries on your schema.
52
+
53
+ # TODO: remove me
54
+ field :testField, types.String do
55
+ description \"An example field added by the generator\"
56
+ resolve ->(obj, args, ctx) {
57
+ \"Hello World!\"
58
+ }
59
+ end
60
+ end
61
+ RUBY
62
+
63
+ assert_file "app/graphql/types/query_type.rb", expected_query_type
64
+ assert_file "app/controllers/graphql_controller.rb", EXPECTED_GRAPHQLS_CONTROLLER
65
+ end
66
+
67
+ test "it generates graphql-batch and relay boilerplate" do
68
+ run_generator(["--batch", "--relay"])
69
+ assert_file "app/graphql/loaders/.keep"
70
+ assert_file "Gemfile" do |contents|
71
+ assert_match %r{gem ('|")graphql-batch('|")}, contents
72
+ end
73
+
74
+ expected_query_type = <<-RUBY
75
+ Types::QueryType = GraphQL::ObjectType.define do
76
+ name "Query"
77
+ # Add root-level fields here.
78
+ # They will be entry points for queries on your schema.
79
+
80
+ # TODO: remove me
81
+ field :testField, types.String do
82
+ description \"An example field added by the generator\"
83
+ resolve ->(obj, args, ctx) {
84
+ \"Hello World!\"
85
+ }
86
+ end
87
+
88
+ field :node, GraphQL::Relay::Node.field
89
+ end
90
+ RUBY
91
+
92
+ assert_file "app/graphql/types/query_type.rb", expected_query_type
93
+ assert_file "app/graphql/dummy_schema.rb", EXPECTED_RELAY_BATCH_SCHEMA
94
+ end
95
+
96
+ test "it can skip keeps, skip graphiql and customize schema name" do
97
+ run_generator(["--skip-keeps", "--skip-graphiql", "--schema=CustomSchema"])
98
+ assert_no_file "app/graphql/types/.keep"
99
+ assert_no_file "app/graphql/mutations/.keep"
100
+ assert_file "app/graphql/types"
101
+ assert_file "app/graphql/mutations"
102
+ assert_file "Gemfile" do |contents|
103
+ refute_includes contents, "graphiql-rails"
104
+ end
105
+
106
+ assert_file "config/routes.rb" do |contents|
107
+ refute_includes contents, "GraphiQL::Rails"
108
+ end
109
+
110
+ assert_file "app/graphql/custom_schema.rb", /CustomSchema = GraphQL::Schema\.define/
111
+ assert_file "app/controllers/graphql_controller.rb", /CustomSchema\.execute/
112
+ end
113
+
114
+ EXPECTED_GRAPHQLS_CONTROLLER = <<-RUBY
115
+ class GraphqlController < ApplicationController
116
+ def execute
117
+ variables = ensure_hash(params[:variables])
118
+ query = params[:query]
119
+ context = {
120
+ # Query context goes here, for example:
121
+ # current_user: current_user,
122
+ }
123
+ result = DummySchema.execute(query, variables: variables, context: context)
124
+ render json: result
125
+ end
126
+
127
+ private
128
+
129
+ # Handle form data, JSON body, or a blank value
130
+ def ensure_hash(ambiguous_param)
131
+ case ambiguous_param
132
+ when String
133
+ if ambiguous_param.present?
134
+ ensure_hash(JSON.parse(ambiguous_param))
135
+ else
136
+ {}
137
+ end
138
+ when Hash, ActionController::Parameters
139
+ ambiguous_param
140
+ when nil
141
+ {}
142
+ else
143
+ raise ArgumentError, "Unexpected parameter: \#{ambiguous_param}"
144
+ end
145
+ end
146
+ end
147
+ RUBY
148
+
149
+ EXPECTED_RELAY_BATCH_SCHEMA = <<-RUBY
150
+ DummySchema = GraphQL::Schema.define do
151
+ query(Types::QueryType)
152
+
153
+ # Relay Object Identification:
154
+
155
+ # Return a string UUID for `object`
156
+ id_from_object ->(object, type_definition, query_ctx) {
157
+ # Here's a simple implementation which:
158
+ # - joins the type name & object.id
159
+ # - encodes it with base64:
160
+ # GraphQL::Schema::UniqueWithinType.encode(type_definition.name, object.id)
161
+ }
162
+
163
+ # Given a string UUID, find the object
164
+ object_from_id ->(id, query_ctx) {
165
+ # For example, to decode the UUIDs generated above:
166
+ # type_name, item_id = GraphQL::Schema::UniqueWithinType.decode(id)
167
+ #
168
+ # Then, based on `type_name` and `id`
169
+ # find an object in your application
170
+ # ...
171
+ }
172
+
173
+ # Object Resolution
174
+ resolve_type -> (obj, ctx) {
175
+ # TODO: Implement this function
176
+ # to return the correct type for `obj`
177
+ raise(NotImplementedError)
178
+ }
179
+
180
+ # GraphQL::Batch setup:
181
+ lazy_resolve(Promise, :sync)
182
+ instrument(:query, GraphQL::Batch::Setup)
183
+ end
184
+ RUBY
185
+ end