graphql 1.8.0.pre10 → 1.8.0.pre11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +5 -5
  2. data/lib/generators/graphql/install_generator.rb +14 -8
  3. data/lib/graphql.rb +1 -24
  4. data/lib/graphql/backtrace.rb +1 -1
  5. data/lib/graphql/deprecated_dsl.rb +2 -2
  6. data/lib/graphql/execution/execute.rb +6 -0
  7. data/lib/graphql/execution/lazy/lazy_method_map.rb +1 -1
  8. data/lib/graphql/introspection/base_object.rb +1 -0
  9. data/lib/graphql/language/document_from_schema_definition.rb +4 -1
  10. data/lib/graphql/language/nodes.rb +5 -2
  11. data/lib/graphql/language/parser.rb +288 -288
  12. data/lib/graphql/language/parser.y +1 -1
  13. data/lib/graphql/language/printer.rb +12 -2
  14. data/lib/graphql/non_null_type.rb +1 -1
  15. data/lib/graphql/query.rb +1 -1
  16. data/lib/graphql/query/arguments.rb +1 -1
  17. data/lib/graphql/query/context.rb +2 -2
  18. data/lib/graphql/query/null_context.rb +1 -1
  19. data/lib/graphql/query/result.rb +1 -1
  20. data/lib/graphql/query/variables.rb +21 -3
  21. data/lib/graphql/relay.rb +1 -0
  22. data/lib/graphql/relay/mongo_relation_connection.rb +40 -0
  23. data/lib/graphql/scalar_type.rb +14 -2
  24. data/lib/graphql/schema.rb +17 -1
  25. data/lib/graphql/schema/argument.rb +39 -7
  26. data/lib/graphql/schema/enum.rb +7 -0
  27. data/lib/graphql/schema/field.rb +145 -39
  28. data/lib/graphql/schema/finder.rb +4 -4
  29. data/lib/graphql/schema/input_object.rb +13 -2
  30. data/lib/graphql/schema/interface.rb +50 -16
  31. data/lib/graphql/schema/list.rb +28 -0
  32. data/lib/graphql/schema/member.rb +8 -106
  33. data/lib/graphql/schema/member/accepts_definition.rb +58 -24
  34. data/lib/graphql/schema/member/base_dsl_methods.rb +96 -0
  35. data/lib/graphql/schema/member/build_type.rb +15 -9
  36. data/lib/graphql/schema/member/cached_graphql_definition.rb +26 -0
  37. data/lib/graphql/schema/member/graphql_type_names.rb +21 -0
  38. data/lib/graphql/schema/member/has_arguments.rb +1 -1
  39. data/lib/graphql/schema/member/has_fields.rb +91 -8
  40. data/lib/graphql/schema/member/type_system_helpers.rb +34 -0
  41. data/lib/graphql/schema/middleware_chain.rb +5 -1
  42. data/lib/graphql/schema/mutation.rb +24 -12
  43. data/lib/graphql/schema/non_null.rb +34 -0
  44. data/lib/graphql/schema/object.rb +24 -11
  45. data/lib/graphql/schema/relay_classic_mutation.rb +14 -11
  46. data/lib/graphql/schema/rescue_middleware.rb +8 -7
  47. data/lib/graphql/schema/scalar.rb +9 -2
  48. data/lib/graphql/schema/union.rb +4 -0
  49. data/lib/graphql/static_validation/definition_dependencies.rb +1 -1
  50. data/lib/graphql/static_validation/literal_validator.rb +16 -4
  51. data/lib/graphql/static_validation/validation_context.rb +1 -1
  52. data/lib/graphql/subscriptions.rb +90 -16
  53. data/lib/graphql/upgrader/member.rb +27 -89
  54. data/lib/graphql/version.rb +1 -1
  55. data/spec/dummy/app/channels/graphql_channel.rb +1 -1
  56. data/spec/dummy/log/test.log +206 -0
  57. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/-x/-xYZjAnuuzgR79fcznLTQtSdh6AARxu8FcQ_J6p7L3U.cache +0 -0
  58. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/13/13HiV12xyoQvT-1L39ZzLwMZxjyaGMiENmfw7f-QTIc.cache +0 -0
  59. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/3W/3Wtf5pCWdqq0AB-iB0Y9uUNrTkruRxIEf1XFn_BETU0.cache +1 -0
  60. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/5i/5iguGafb4hOn8262Kn8Q37ogNN9MxxQKGKNzHAzUcvI.cache +1 -0
  61. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/8m/8mj2T6yy847Mc2Z7k3Xzh8O91hhVJt3NrPe8ASNDlIA.cache +1 -0
  62. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/DT/DTQyMpr4ABZYQetsdRJ5A7S4jf1r3ie4FGOR7GZBNSs.cache +3 -0
  63. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Dq/DqJ5_yJPrP5iLlOQyTQsjAVI5FE5LCVDkED0f7GgsSo.cache +3 -0
  64. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/F8/F8MUNRzORGFgr329fNM0xLaoWCXdv3BIalT7dsvLfjs.cache +0 -0
  65. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/KB/KB07ZaKNC5uXJ7TjLi-WqnY6g7dq8wWp_8N3HNjBNxg.cache +0 -0
  66. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Rw/RwDuCV-XpnCtjNkvhpJfBuxXMk0b5AD3L9eR6M-wcy0.cache +3 -0
  67. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/UL/ULdjhhb0bRuqmaG7XSZlFYzGYCXTDnqZuJBTWRlzqgw.cache +0 -0
  68. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Up/UpPNgh0yYoUsyMDh5zWqe_U6qJIyTC6-dxMMAs1vvlM.cache +1 -0
  69. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/Wg/Wguh-szFGTI1gaL6npYwPekMXflugRei7F_mOyRucXg.cache +0 -0
  70. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/X-/X-khLYMA9mqFRPg3zAi86mREDxpKl4bdKYp3uF6WHos.cache +0 -0
  71. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/bi/BIkdhfxsezxM4q-HZ4oCNTq97WEJTigcq0tpX2cDvbY.cache +0 -0
  72. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/ff/FfxmA4CMHQZT7exx0G7NS1Wpcnny0vzp-Jhc2H36bp8.cache +1 -0
  73. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/gE/gEiiG4GZNy_djEjK2pHm_NgA-gyhLZhdQvo0Yt96GqE.cache +0 -0
  74. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/gn/gnA9ZSqpjccNL2m8pe_jBvY6SinXlCzXDWyop83Od8s.cache +1 -0
  75. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/lO/lOAan3cMwCE_Hli6gsDML88xFNfn0nxPmvrSkW7eEOw.cache +1 -0
  76. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/m1/M1pv8MJEPLXGLvS8QxVh3DSO9cI4mRt5FHFWdrvUj6o.cache +2 -0
  77. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/m7/m77qH7ZqH0_0SmwJbiKGDd-aLau1Dav847DC6ge46zY.cache +1 -0
  78. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/sj/sjRjnjRB37lH2vrgtkdJ8Cz84__IJ978IuKTM7HcztI.cache +0 -0
  79. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/um/um1JrirR4hJhK-1rE-HywlyCi5ibgxHVrReiujZBWJM.cache +1 -0
  80. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/v4/v4fwVytD7ITcE0_GDbslZEYud8a5Okm85fV1o7SDl6g.cache +0 -0
  81. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/v_/v_0PAQt0iipQjFP5zjgkkk9Stnpf4VzvnMv67d1Keuw.cache +1 -0
  82. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/wd/wdT9U4MKxe1PyqNjVuCKMpCl3dxGCIRJIlwUTfh2DQU.cache +1 -0
  83. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/xI/xIaxut_fEIhKBDqljTNwYaADK9kj3gG0ESrfHs-5_og.cache +3 -0
  84. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/y0/y0SJOqIx2fn1SKqOkAihsQow0trRJrSIyAswufVuoA8.cache +0 -0
  85. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/zg/zgpzeaX-KZErHyGJ1aBH3ZusweNXMneVZule88XsIJI.cache +1 -0
  86. data/spec/dummy/tmp/cache/assets/sprockets/v3.0/zy/zYFltDy-8VC-uKq2BVEiJJyYXNFvVzAKuMlR3ZIYZsk.cache +0 -0
  87. data/spec/dummy/tmp/screenshots/failures_test_it_handles_subscriptions.png +0 -0
  88. data/spec/fixtures/upgrader/photo.original.rb +10 -0
  89. data/spec/fixtures/upgrader/photo.transformed.rb +12 -0
  90. data/spec/fixtures/upgrader/starrable.original.rb +4 -1
  91. data/spec/fixtures/upgrader/starrable.transformed.rb +25 -22
  92. data/spec/fixtures/upgrader/subscribable.transformed.rb +32 -33
  93. data/spec/graphql/execution_error_spec.rb +18 -0
  94. data/spec/graphql/introspection/schema_type_spec.rb +1 -0
  95. data/spec/graphql/object_type_spec.rb +2 -1
  96. data/spec/graphql/query/variables_spec.rb +41 -0
  97. data/spec/graphql/relay/mongo_relation_connection_spec.rb +474 -0
  98. data/spec/graphql/schema/argument_spec.rb +65 -9
  99. data/spec/graphql/schema/build_from_definition_spec.rb +4 -2
  100. data/spec/graphql/schema/enum_spec.rb +1 -1
  101. data/spec/graphql/schema/field_spec.rb +79 -4
  102. data/spec/graphql/schema/input_object_spec.rb +56 -0
  103. data/spec/graphql/schema/instrumentation_spec.rb +4 -3
  104. data/spec/graphql/schema/interface_spec.rb +40 -25
  105. data/spec/graphql/schema/member/accepts_definition_spec.rb +38 -11
  106. data/spec/graphql/schema/member/has_fields_spec.rb +129 -0
  107. data/spec/graphql/schema/member/type_system_helpers_spec.rb +63 -0
  108. data/spec/graphql/schema/mutation_spec.rb +48 -0
  109. data/spec/graphql/schema/object_spec.rb +34 -8
  110. data/spec/graphql/schema/relay_classic_mutation_spec.rb +10 -0
  111. data/spec/graphql/schema/rescue_middleware_spec.rb +11 -0
  112. data/spec/graphql/schema/scalar_spec.rb +55 -0
  113. data/spec/graphql/subscriptions_spec.rb +31 -4
  114. data/spec/graphql/upgrader/member_spec.rb +14 -6
  115. data/spec/spec_helper.rb +7 -0
  116. data/spec/support/dummy/schema.rb +8 -0
  117. data/spec/support/jazz.rb +89 -19
  118. data/spec/support/star_trek/data.rb +109 -0
  119. data/spec/support/star_trek/schema.rb +388 -0
  120. metadata +80 -7
  121. data/lib/graphql/schema/field/dynamic_resolve.rb +0 -70
  122. data/lib/graphql/schema/field/unwrapped_resolve.rb +0 -20
  123. data/lib/graphql/schema/member/list_type_proxy.rb +0 -25
  124. data/lib/graphql/schema/member/non_null_type_proxy.rb +0 -25
@@ -276,7 +276,7 @@ rule
276
276
  | directive_definition
277
277
 
278
278
  schema_definition:
279
- SCHEMA LCURLY operation_type_definition_list RCURLY { return make_node(:SchemaDefinition, position_source: val[0], **val[2]) }
279
+ SCHEMA directives_list_opt LCURLY operation_type_definition_list RCURLY { return make_node(:SchemaDefinition, position_source: val[0], directives: val[1], **val[3]) }
280
280
 
281
281
  operation_type_definition_list:
282
282
  operation_type_definition
@@ -134,11 +134,21 @@ module GraphQL
134
134
  def print_schema_definition(schema)
135
135
  if (schema.query.nil? || schema.query == 'Query') &&
136
136
  (schema.mutation.nil? || schema.mutation == 'Mutation') &&
137
- (schema.subscription.nil? || schema.subscription == 'Subscription')
137
+ (schema.subscription.nil? || schema.subscription == 'Subscription') &&
138
+ (schema.directives.none?)
138
139
  return
139
140
  end
140
141
 
141
- out = "schema {\n".dup
142
+ out = "schema".dup
143
+ if schema.directives.any?
144
+ schema.directives.each do |dir|
145
+ out << "\n "
146
+ out << print_node(dir)
147
+ end
148
+ out << "\n{"
149
+ else
150
+ out << " {\n"
151
+ end
142
152
  out << " query: #{schema.query}\n" if schema.query
143
153
  out << " mutation: #{schema.mutation}\n" if schema.mutation
144
154
  out << " subscription: #{schema.subscription}\n" if schema.subscription
@@ -33,7 +33,7 @@ module GraphQL
33
33
  #
34
34
  class NonNullType < GraphQL::BaseType
35
35
  include GraphQL::BaseType::ModifiesAnotherType
36
- extend GraphQL::Delegate
36
+ extend Forwardable
37
37
 
38
38
  attr_reader :of_type
39
39
  def initialize(of_type:)
data/lib/graphql/query.rb CHANGED
@@ -16,7 +16,7 @@ module GraphQL
16
16
  # A combination of query string and {Schema} instance which can be reduced to a {#result}.
17
17
  class Query
18
18
  include Tracing::Traceable
19
- extend GraphQL::Delegate
19
+ extend Forwardable
20
20
 
21
21
  class OperationNameMissingError < GraphQL::ExecutionError
22
22
  def initialize(name)
@@ -5,7 +5,7 @@ module GraphQL
5
5
  #
6
6
  # {Arguments} recursively wraps the input in {Arguments} instances.
7
7
  class Arguments
8
- extend GraphQL::Delegate
8
+ extend Forwardable
9
9
 
10
10
  def self.construct_arguments_class(argument_owner)
11
11
  argument_definitions = argument_owner.arguments
@@ -99,7 +99,7 @@ module GraphQL
99
99
  end
100
100
 
101
101
  include SharedMethods
102
- extend GraphQL::Delegate
102
+ extend Forwardable
103
103
 
104
104
  attr_reader :execution_strategy
105
105
  # `strategy` is required by GraphQL::Batch
@@ -189,7 +189,7 @@ module GraphQL
189
189
  class FieldResolutionContext
190
190
  include SharedMethods
191
191
  include Tracing::Traceable
192
- extend GraphQL::Delegate
192
+ extend Forwardable
193
193
 
194
194
  attr_reader :irep_node, :field, :parent_type, :query, :schema, :parent, :key, :type
195
195
  alias :selection :irep_node
@@ -22,7 +22,7 @@ module GraphQL
22
22
  end
23
23
 
24
24
  class << self
25
- extend GraphQL::Delegate
25
+ extend Forwardable
26
26
 
27
27
  def instance
28
28
  @instance = self.new
@@ -6,7 +6,7 @@ module GraphQL
6
6
  # It provides the requested data and
7
7
  # access to the {Query} and {Query::Context}.
8
8
  class Result
9
- extend GraphQL::Delegate
9
+ extend Forwardable
10
10
 
11
11
  def initialize(query:, values:)
12
12
  @query = query
@@ -3,7 +3,7 @@ module GraphQL
3
3
  class Query
4
4
  # Read-only access to query variables, applying default values if needed.
5
5
  class Variables
6
- extend GraphQL::Delegate
6
+ extend Forwardable
7
7
 
8
8
  # @return [Array<GraphQL::Query::VariableValidationError>] Any errors encountered when parsing the provided variables and literal values
9
9
  attr_reader :errors
@@ -13,7 +13,8 @@ module GraphQL
13
13
  def initialize(ctx, ast_variables, provided_variables)
14
14
  schema = ctx.schema
15
15
  @context = ctx
16
- @provided_variables = provided_variables
16
+
17
+ @provided_variables = deep_stringify(provided_variables)
17
18
  @errors = []
18
19
  @storage = ast_variables.each_with_object({}) do |ast_variable, memo|
19
20
  # Find the right value for this variable:
@@ -27,7 +28,7 @@ module GraphQL
27
28
  variable_name = ast_variable.name
28
29
  default_value = ast_variable.default_value
29
30
  provided_value = @provided_variables[variable_name]
30
- value_was_provided = @provided_variables.key?(variable_name)
31
+ value_was_provided = @provided_variables.key?(variable_name)
31
32
 
32
33
  validation_result = variable_type.validate_input(provided_value, ctx)
33
34
  if !validation_result.valid?
@@ -45,6 +46,23 @@ module GraphQL
45
46
  end
46
47
 
47
48
  def_delegators :@storage, :length, :key?, :[], :fetch, :to_h
49
+
50
+ private
51
+
52
+ def deep_stringify(val)
53
+ case val
54
+ when Array
55
+ val.map { |v| deep_stringify(v) }
56
+ when Hash
57
+ new_val = {}
58
+ val.each do |k, v|
59
+ new_val[k.to_s] = deep_stringify(v)
60
+ end
61
+ new_val
62
+ else
63
+ val
64
+ end
65
+ end
48
66
  end
49
67
  end
50
68
  end
data/lib/graphql/relay.rb CHANGED
@@ -9,6 +9,7 @@ require 'graphql/relay/base_connection'
9
9
  require 'graphql/relay/array_connection'
10
10
  require 'graphql/relay/range_add'
11
11
  require 'graphql/relay/relation_connection'
12
+ require 'graphql/relay/mongo_relation_connection'
12
13
  require 'graphql/relay/global_id_resolve'
13
14
  require 'graphql/relay/mutation'
14
15
  require 'graphql/relay/node'
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Relay
4
+ # A connection implementation to expose MongoDB collection objects.
5
+ # It works for:
6
+ # - `Mongoid::Criteria`
7
+ class MongoRelationConnection < RelationConnection
8
+ private
9
+
10
+ def relation_offset(relation)
11
+ relation.options.skip
12
+ end
13
+
14
+ def relation_limit(relation)
15
+ relation.options.limit
16
+ end
17
+
18
+ def relation_count(relation)
19
+ # Must perform query (hence #to_a) to count results https://jira.mongodb.org/browse/MONGOID-2325
20
+ relation.to_a.count
21
+ end
22
+
23
+ def limit_nodes(sliced_nodes, limit)
24
+ if limit == 0
25
+ if sliced_nodes.respond_to?(:none) # added in Mongoid 4.0
26
+ sliced_nodes.without_options.none
27
+ else
28
+ sliced_nodes.where(id: nil) # trying to simulate #none for 3.1.7
29
+ end
30
+ else
31
+ sliced_nodes.limit(limit)
32
+ end
33
+ end
34
+ end
35
+
36
+ if defined?(Mongoid::Criteria)
37
+ BaseConnection.register_connection_implementation(Mongoid::Criteria, MongoRelationConnection)
38
+ end
39
+ end
40
+ end
@@ -56,7 +56,7 @@ module GraphQL
56
56
  # This will result in the message of the `GraphQL::CoercionError` being used in the error response:
57
57
  #
58
58
  # @example custom error response
59
- # {"message"=>"cannot coerce `"2"` to Float", "locations"=>[{"line"=>3, "column"=>9}], "fields"=>["arg"]}
59
+ # {"message"=>"cannot coerce `"2"` to Float", "locations"=>[{"line"=>3, "column"=>9}], "fields"=>["arg"]}
60
60
  #
61
61
  class ScalarType < GraphQL::BaseType
62
62
  accepts_definitions :coerce, :coerce_input, :coerce_result
@@ -109,7 +109,19 @@ module GraphQL
109
109
  end
110
110
 
111
111
  def coerce_non_null_input(value, ctx)
112
- @coerce_input_proc.call(value, ctx)
112
+ @coerce_input_proc.call(raw_coercion_input(value), ctx)
113
+ end
114
+
115
+ def raw_coercion_input(value)
116
+ if value.is_a?(GraphQL::Language::Nodes::InputObject)
117
+ value.to_h
118
+ elsif value.is_a?(Array)
119
+ value.map { |element| raw_coercion_input(element) }
120
+ elsif value.is_a?(GraphQL::Language::Nodes::Enum)
121
+ value.name
122
+ else
123
+ value
124
+ end
113
125
  end
114
126
 
115
127
  def validate_non_null_input(value, ctx)
@@ -21,6 +21,8 @@ require "graphql/schema/build_from_definition"
21
21
 
22
22
 
23
23
  require "graphql/schema/member"
24
+ require "graphql/schema/list"
25
+ require "graphql/schema/non_null"
24
26
  require "graphql/schema/argument"
25
27
  require "graphql/schema/enum_value"
26
28
  require "graphql/schema/enum"
@@ -632,7 +634,7 @@ module GraphQL
632
634
  end
633
635
 
634
636
  class << self
635
- extend GraphQL::Delegate
637
+ extend Forwardable
636
638
  # For compatibility, these methods all:
637
639
  # - Cause the Schema instance to be created, if it hasn't been created yet
638
640
  # - Delegate to that instance
@@ -656,6 +658,7 @@ module GraphQL
656
658
  # Members
657
659
  :types, :get_fields, :find,
658
660
  :root_type_for_operation,
661
+ :subscriptions,
659
662
  :union_memberships,
660
663
  :get_field, :root_types, :references_to, :type_from_ast,
661
664
  :possible_types, :get_field
@@ -692,6 +695,7 @@ module GraphQL
692
695
  schema_defn.cursor_encoder = cursor_encoder
693
696
  schema_defn.tracers.concat(defined_tracers)
694
697
  schema_defn.query_analyzers.concat(defined_query_analyzers)
698
+ schema_defn.middleware.concat(defined_middleware)
695
699
  schema_defn.multiplex_analyzers.concat(defined_multiplex_analyzers)
696
700
  defined_instrumenters.each do |step, insts|
697
701
  insts.each do |inst|
@@ -863,6 +867,14 @@ module GraphQL
863
867
  defined_query_analyzers << new_analyzer
864
868
  end
865
869
 
870
+ def middleware(new_middleware = nil)
871
+ if new_middleware
872
+ defined_middleware << new_middleware
873
+ else
874
+ graphql_definition.middleware
875
+ end
876
+ end
877
+
866
878
  def multiplex_analyzer(new_analyzer)
867
879
  defined_multiplex_analyzers << new_analyzer
868
880
  end
@@ -885,6 +897,10 @@ module GraphQL
885
897
  @defined_query_analyzers ||= []
886
898
  end
887
899
 
900
+ def defined_middleware
901
+ @defined_middleware ||= []
902
+ end
903
+
888
904
  def defined_multiplex_analyzers
889
905
  @defined_multiplex_analyzers ||= []
890
906
  end
@@ -7,26 +7,37 @@ module GraphQL
7
7
 
8
8
  NO_DEFAULT = :__no_default__
9
9
 
10
+ # @return [String] the GraphQL name for this argument, camelized unless `camelize: false` is provided
10
11
  attr_reader :name
11
12
 
12
13
  # @return [GraphQL::Schema::Field, Class] The field or input object this argument belongs to
13
14
  attr_reader :owner
14
15
 
16
+ # @return [Symbol] A method to call to transform this value before sending it to field resolution method
17
+ attr_reader :prepare
18
+
19
+ # @return [Symbol] This argument's name in Ruby keyword arguments
20
+ attr_reader :keyword
21
+
15
22
  # @param arg_name [Symbol]
16
23
  # @param type_expr
17
24
  # @param desc [String]
18
25
  # @param required [Boolean] if true, this argument is non-null; if false, this argument is nullable
19
26
  # @param description [String]
20
27
  # @param default_value [Object]
28
+ # @param as [Symbol] Override the keyword name when passed to a method
29
+ # @param prepare [Symbol] A method to call to tranform this argument's valuebefore sending it to field resolution
21
30
  # @param camelize [Boolean] if true, the name will be camelized when building the schema
22
- def initialize(arg_name, type_expr, desc = nil, required:, description: nil, default_value: NO_DEFAULT, camelize: true, owner:, &definition_block)
23
- @name = arg_name.to_s
31
+ def initialize(arg_name, type_expr, desc = nil, required:, description: nil, default_value: NO_DEFAULT, as: nil, camelize: true, prepare: nil, owner:, &definition_block)
32
+ @name = camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s
24
33
  @type_expr = type_expr
25
34
  @description = desc || description
26
35
  @null = !required
27
36
  @default_value = default_value
28
- @camelize = camelize
29
37
  @owner = owner
38
+ @as = as
39
+ @keyword = as || Schema::Member::BuildType.underscore(@name).to_sym
40
+ @prepare = prepare
30
41
 
31
42
  if definition_block
32
43
  instance_eval(&definition_block)
@@ -43,17 +54,38 @@ module GraphQL
43
54
 
44
55
  def to_graphql
45
56
  argument = GraphQL::Argument.new
46
- argument.name = @camelize ? Member::BuildType.camelize(@name) : @name
47
- argument.type = -> {
48
- Member::BuildType.parse_type(@type_expr, null: @null)
49
- }
57
+ argument.name = @name
58
+ argument.type = -> { type }
50
59
  argument.description = @description
51
60
  argument.metadata[:type_class] = self
61
+ argument.as = @as
52
62
  if NO_DEFAULT != @default_value
53
63
  argument.default_value = @default_value
54
64
  end
55
65
  argument
56
66
  end
67
+
68
+ def type
69
+ @type ||= Member::BuildType.parse_type(@type_expr, null: @null)
70
+ rescue StandardError => err
71
+ raise ArgumentError, "Couldn't build type for Argument #{@owner.name}.#{name}: #{err.class.name}: #{err.message}", err.backtrace
72
+ end
73
+
74
+ # Apply the {prepare} configuration to `value`, using methods from `obj`.
75
+ # Used by the runtime.
76
+ # @api private
77
+ def prepare_value(obj, value)
78
+ case @prepare
79
+ when nil
80
+ value
81
+ when Symbol, String
82
+ obj.public_send(@prepare, value)
83
+ when Proc
84
+ @prepare.call(value, obj.context)
85
+ else
86
+ raise "Invalid prepare for #{@owner.name}.name: #{@prepare.inspect}"
87
+ end
88
+ end
57
89
  end
58
90
  end
59
91
  end
@@ -23,6 +23,9 @@ module GraphQL
23
23
  extend GraphQL::Schema::Member::AcceptsDefinition
24
24
 
25
25
  class << self
26
+ extend Forwardable
27
+ def_delegators :graphql_definition, :coerce_isolated_input, :coerce_isolated_result
28
+
26
29
  # Define a value for this enum
27
30
  # @param graphql_name [String, Symbol] the GraphQL value for this, usually `SCREAMING_CASE`
28
31
  # @param description [String], the GraphQL description for this value, present in documentation
@@ -65,6 +68,10 @@ module GraphQL
65
68
  @enum_value_class || (superclass <= GraphQL::Schema::Enum ? superclass.enum_value_class : nil)
66
69
  end
67
70
 
71
+ def kind
72
+ GraphQL::TypeKinds::ENUM
73
+ end
74
+
68
75
  private
69
76
 
70
77
  def own_values
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  # test_via: ../object.rb
3
- require "graphql/schema/field/dynamic_resolve"
4
- require "graphql/schema/field/unwrapped_resolve"
5
3
  module GraphQL
6
4
  class Schema
7
5
  class Field
@@ -9,14 +7,17 @@ module GraphQL
9
7
  include GraphQL::Schema::Member::AcceptsDefinition
10
8
  include GraphQL::Schema::Member::HasArguments
11
9
 
12
- # @return [String]
10
+ # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided
13
11
  attr_reader :name
14
12
 
15
13
  # @return [String]
16
14
  attr_accessor :description
17
15
 
18
- # @return [Symbol]
19
- attr_reader :method
16
+ # @return [Symbol] Method or hash key to look up
17
+ attr_reader :method_sym
18
+
19
+ # @return [String] Method or hash key to look up
20
+ attr_reader :method_str
20
21
 
21
22
  # @return [Class] The type that this field belongs to
22
23
  attr_reader :owner
@@ -52,7 +53,7 @@ module GraphQL
52
53
  desc = return_type_expr
53
54
  return_type_expr = nil
54
55
  end
55
- if mutation && (return_type_expr || desc || description || function || field || null || deprecation_reason || method || resolve || introspection || hash_key)
56
+ if mutation && (return_type_expr || desc || description || function || field || !null.nil? || deprecation_reason || method || resolve || introspection || hash_key)
56
57
  raise ArgumentError, "when keyword `mutation:` is present, all arguments are ignored, please remove them"
57
58
  end
58
59
  if !(field || function || mutation)
@@ -66,7 +67,10 @@ module GraphQL
66
67
  if (field || function || resolve || resolve) && extras.any?
67
68
  raise ArgumentError, "keyword `extras:` may only be used with method-based resolve, please remove `field:`, `function:`, `resolve:`, or `mutation:`"
68
69
  end
69
- @name = name.to_s
70
+ if return_type_expr.is_a?(GraphQL::Field)
71
+ raise ArgumentError, "A GraphQL::Field was passed as the second argument, use the `field:` keyword for this instead."
72
+ end
73
+ @name = camelize ? Member::BuildType.camelize(name.to_s) : name.to_s
70
74
  if description && desc
71
75
  raise ArgumentError, "Provide description as a positional argument or `description:` keyword, but not both (#{desc.inspect}, #{description.inspect})"
72
76
  end
@@ -82,8 +86,12 @@ module GraphQL
82
86
  if method && hash_key
83
87
  raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)"
84
88
  end
85
- @method = method
86
- @hash_key = hash_key
89
+
90
+ # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work)
91
+ method_name = method || hash_key || Member::BuildType.underscore(name.to_s)
92
+
93
+ @method_str = method_name.to_s
94
+ @method_sym = method_name.to_sym
87
95
  @complexity = complexity
88
96
  @return_type_expr = return_type_expr
89
97
  @return_type_null = null
@@ -91,7 +99,6 @@ module GraphQL
91
99
  @max_page_size = max_page_size
92
100
  @introspection = introspection
93
101
  @extras = extras
94
- @camelize = camelize
95
102
  @mutation = mutation
96
103
  @mutation_class = mutation_class
97
104
  # Override the default from HasArguments
@@ -140,7 +147,6 @@ module GraphQL
140
147
  return field_inst.to_graphql
141
148
  end
142
149
 
143
- method_name = @method || @hash_key || Member::BuildType.underscore(@name)
144
150
 
145
151
  field_defn = if @field
146
152
  @field.dup
@@ -150,22 +156,21 @@ module GraphQL
150
156
  GraphQL::Field.new
151
157
  end
152
158
 
153
- field_defn.name = @camelize ? Member::BuildType.camelize(name) : name
159
+ field_defn.name = @name
154
160
  if @return_type_expr
155
- return_type_name = Member::BuildType.to_type_name(@return_type_expr)
156
- connection = @connection.nil? ? return_type_name.end_with?("Connection") : @connection
157
- field_defn.type = -> {
158
- begin
159
- Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
160
- rescue
161
- raise ArgumentError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: #{$!.message}", $!.backtrace
162
- end
163
- }
164
- elsif @connection.nil? && (@field || @function)
165
- return_type_name = Member::BuildType.to_type_name(field_defn.type)
166
- connection = return_type_name.end_with?("Connection")
167
- else
168
- connection = @connection
161
+ field_defn.type = -> { type }
162
+ end
163
+
164
+ if @connection.nil?
165
+ # Provide default based on type name
166
+ return_type_name = if @field || @function
167
+ Member::BuildType.to_type_name(field_defn.type)
168
+ elsif @return_type_expr
169
+ Member::BuildType.to_type_name(@return_type_expr)
170
+ else
171
+ raise "No connection info possible"
172
+ end
173
+ @connection = return_type_name.end_with?("Connection")
169
174
  end
170
175
 
171
176
  if @description
@@ -180,24 +185,14 @@ module GraphQL
180
185
  field_defn.mutation = @mutation_class
181
186
  end
182
187
 
183
- field_defn.resolve = if @resolve || @function || @field
184
- prev_resolve = @resolve || field_defn.resolve_proc
185
- UnwrappedResolve.new(inner_resolve: prev_resolve)
186
- else
187
- DynamicResolve.new(
188
- method_name: method_name,
189
- connection: connection,
190
- extras: @extras
191
- )
192
- end
193
-
194
- field_defn.connection = connection
188
+ field_defn.resolve = self.method(:resolve_field)
189
+ field_defn.connection = @connection
195
190
  field_defn.connection_max_page_size = @max_page_size
196
191
  field_defn.introspection = @introspection
197
192
  field_defn.complexity = @complexity
198
193
 
199
194
  # apply this first, so it can be overriden below
200
- if connection
195
+ if @connection
201
196
  # TODO: this could be a bit weird, because these fields won't be present
202
197
  # after initialization, only in the `to_graphql` response.
203
198
  # This calculation _could_ be moved up if need be.
@@ -212,8 +207,119 @@ module GraphQL
212
207
  field_defn.arguments[arg_graphql.name] = arg_graphql
213
208
  end
214
209
 
210
+ # Ok, `self` isn't a class, but this is for consistency with the classes
211
+ field_defn.metadata[:type_class] = self
212
+
215
213
  field_defn
216
214
  end
215
+
216
+ def type
217
+ @type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
218
+ rescue
219
+ raise ArgumentError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: #{$!.message}", $!.backtrace
220
+ end
221
+
222
+ # Implement {GraphQL::Field}'s resolve API.
223
+ #
224
+ # Eventually, we might hook up field instances to execution in another way. TBD.
225
+ def resolve_field(obj, args, ctx)
226
+ if @resolve || @function || @field
227
+ # Support a passed-in proc, one way or another
228
+ prev_resolve = if @resolve
229
+ @resolve
230
+ elsif @function
231
+ @function
232
+ elsif @field
233
+ @field.resolve_proc
234
+ end
235
+
236
+ # Might be nil, still want to call the func in that case
237
+ inner_obj = obj && obj.object
238
+ prev_resolve.call(inner_obj, args, ctx)
239
+ elsif @mutation_class
240
+ mutation_inst = @mutation_class.new(object: obj, arguments: args, context: ctx.query.context)
241
+ public_send_field(mutation_inst, args, ctx)
242
+ else
243
+ public_send_field(obj, args, ctx)
244
+ end
245
+ end
246
+
247
+ # Find a way to resolve this field, checking:
248
+ #
249
+ # - Hash keys, if the wrapped object is a hash;
250
+ # - A method on the wrapped object;
251
+ # - Or, raise not implemented.
252
+ #
253
+ # This can be overridden by defining a method on the object type.
254
+ # @param obj [GraphQL::Schema::Object]
255
+ # @param ruby_kwargs [Hash<Symbol => Object>]
256
+ # @param ctx [GraphQL::Query::Context]
257
+ def resolve_field_method(obj, ruby_kwargs, ctx)
258
+ if obj.object.is_a?(Hash)
259
+ inner_object = obj.object
260
+ if inner_object.key?(@method_sym)
261
+ inner_object[@method_sym]
262
+ else
263
+ inner_object[@method_str]
264
+ end
265
+ elsif obj.object.respond_to?(@method_sym)
266
+ if ruby_kwargs.any?
267
+ obj.object.public_send(@method_sym, **ruby_kwargs)
268
+ else
269
+ obj.object.public_send(@method_sym)
270
+ end
271
+ else
272
+ raise <<-ERR
273
+ Failed to implement #{@owner.graphql_name}.#{@name}, tried:
274
+
275
+ - `#{obj.class}##{@method_sym}`, which did not exist
276
+ - `#{obj.object.class}##{@method_sym}`, which did not exist
277
+ - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
278
+
279
+ To implement this field, define one of the methods above (and check for typos)
280
+ ERR
281
+ end
282
+ end
283
+
284
+ private
285
+
286
+ NO_ARGS = {}.freeze
287
+
288
+ def public_send_field(obj, graphql_args, field_ctx)
289
+ if graphql_args.any? || @extras.any?
290
+ # Splat the GraphQL::Arguments to Ruby keyword arguments
291
+ ruby_kwargs = graphql_args.to_kwargs
292
+ # Apply any `prepare` methods. Not great code organization, can this go somewhere better?
293
+ arguments.each do |name, arg_defn|
294
+ ruby_kwargs_key = arg_defn.keyword
295
+ if ruby_kwargs.key?(ruby_kwargs_key) && arg_defn.prepare
296
+ ruby_kwargs[ruby_kwargs_key] = arg_defn.prepare_value(obj, ruby_kwargs[ruby_kwargs_key])
297
+ end
298
+ end
299
+
300
+ if @connection
301
+ # Remove pagination args before passing it to a user method
302
+ ruby_kwargs.delete(:first)
303
+ ruby_kwargs.delete(:last)
304
+ ruby_kwargs.delete(:before)
305
+ ruby_kwargs.delete(:after)
306
+ end
307
+
308
+ @extras.each do |extra_arg|
309
+ # TODO: provide proper tests for `:ast_node`, `:irep_node`, `:parent`, others?
310
+ ruby_kwargs[extra_arg] = field_ctx.public_send(extra_arg)
311
+ end
312
+ else
313
+ ruby_kwargs = NO_ARGS
314
+ end
315
+
316
+
317
+ if ruby_kwargs.any?
318
+ obj.public_send(@method_sym, **ruby_kwargs)
319
+ else
320
+ obj.public_send(@method_sym)
321
+ end
322
+ end
217
323
  end
218
324
  end
219
325
  end