graphql 0.6.2 → 0.7.0

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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/query.rb +7 -44
  3. data/lib/graphql/query/base_execution.rb +34 -0
  4. data/lib/graphql/query/base_execution/selected_object_resolution.rb +16 -0
  5. data/lib/graphql/query/directive_chain.rb +15 -4
  6. data/lib/graphql/query/executor.rb +53 -0
  7. data/lib/graphql/query/serial_execution.rb +12 -0
  8. data/lib/graphql/query/serial_execution/field_resolution.rb +42 -0
  9. data/lib/graphql/query/serial_execution/fragment_spread_resolution.rb +22 -0
  10. data/lib/graphql/query/serial_execution/inline_fragment_resolution.rb +21 -0
  11. data/lib/graphql/query/serial_execution/operation_resolution.rb +22 -0
  12. data/lib/graphql/query/serial_execution/selection_resolution.rb +42 -0
  13. data/lib/graphql/query/value_resolution.rb +76 -0
  14. data/lib/graphql/schema/type_validator.rb +2 -2
  15. data/lib/graphql/version.rb +1 -1
  16. data/readme.md +18 -12
  17. data/spec/graphql/directive_spec.rb +3 -0
  18. data/spec/graphql/introspection/schema_type_spec.rb +1 -0
  19. data/spec/graphql/query/executor_spec.rb +90 -0
  20. data/spec/graphql/query_spec.rb +65 -118
  21. data/spec/graphql/schema/type_validator_spec.rb +20 -7
  22. data/spec/support/dairy_app.rb +11 -0
  23. metadata +15 -10
  24. data/lib/graphql/query/field_resolution_strategy.rb +0 -87
  25. data/lib/graphql/query/fragment_spread_resolution_strategy.rb +0 -16
  26. data/lib/graphql/query/inline_fragment_resolution_strategy.rb +0 -14
  27. data/lib/graphql/query/operation_resolver.rb +0 -26
  28. data/lib/graphql/query/selection_resolver.rb +0 -20
  29. data/spec/graphql/query/operation_resolver_spec.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 08f966586efa49fab35baf8a07f91bf1a8f1b90d
4
- data.tar.gz: 1906d5e21b82db60e65acea99164cf8bb2f8e946
3
+ metadata.gz: 597ceac7f7f1c006aaaf98ff69fb2320e80c08fb
4
+ data.tar.gz: 8145bb6df94810a05738c2dbde3753c7c8e8e26d
5
5
  SHA512:
6
- metadata.gz: c191f4862f354f98264c330040e001d7b5eeea2e5c97008bf13d19b196213d6018e4f9e4f48b0e11cb066b0851857791bc445af114caef4560213dbfe6f71436
7
- data.tar.gz: e1ee28e2a7450bd018aa32816f7e8461791c8e236a5afa443955a594430d9ae7a12faa567b4056261d2903936e9404cbd1367bf3a13b5562d7ac3b68e2d95ad0
6
+ metadata.gz: 1127f6886b67be9890dfef2db3708210a319f292fafb35d49dd4f0092330717c783e886710f7573353b7582e8a3fa743367c70ca69e0740afbc11f5a87b77920
7
+ data.tar.gz: 2ff711f09df54c8e91c97045c72780d6921fcfefce04ebadeb8368b56452e4300680c64689f48b2acadd38686ec3e9b50c72dde5bfe6e780ff891d687367afe1
data/lib/graphql/query.rb CHANGED
@@ -3,7 +3,7 @@ class GraphQL::Query
3
3
  # The executor will send the field's name to the target object
4
4
  # and use the result.
5
5
  DEFAULT_RESOLVE = :__default_resolve
6
- attr_reader :schema, :document, :context, :fragments, :variables
6
+ attr_reader :schema, :document, :context, :fragments, :variables, :operations, :debug
7
7
 
8
8
  # Prepare query `query_string` on `schema`
9
9
  # @param schema [GraphQL::Schema]
@@ -13,17 +13,12 @@ class GraphQL::Query
13
13
  # @param debug [Boolean] if true, errors are raised, if false, errors are put in the `errors` key
14
14
  # @param validate [Boolean] if true, `query_string` will be validated with {StaticValidation::Validator}
15
15
  # @param operation_name [String] if the query string contains many operations, this is the one which should be executed
16
- def initialize(schema, query_string, context: nil, params: nil, variables: {}, debug: true, validate: true, operation_name: nil)
16
+ def initialize(schema, query_string, context: nil, variables: {}, debug: true, validate: true, operation_name: nil)
17
17
  @schema = schema
18
18
  @debug = debug
19
19
  @context = Context.new(context)
20
20
 
21
21
  @variables = variables
22
- if params
23
- warn("[GraphQL] params option is deprecated for GraphQL::Query#new, use variables instead")
24
- @variables = params
25
- end
26
-
27
22
  @validate = validate
28
23
  @operation_name = operation_name
29
24
  @fragments = {}
@@ -45,39 +40,15 @@ class GraphQL::Query
45
40
  return { "errors" => validation_errors }
46
41
  end
47
42
 
48
- @result ||= { "data" => execute }
49
-
50
- rescue OperationNameMissingError => err
51
- {"errors" => [{"message" => err.message}]}
52
- rescue StandardError => err
53
- @debug && raise(err)
54
- message = "Something went wrong during query execution: #{err}" # \n #{err.backtrace.join("\n ")}"
55
- {"errors" => [{"message" => message}]}
43
+ @result ||= Executor.new(self, @operation_name).result
56
44
  end
57
45
 
58
46
  private
59
47
 
60
- def execute
61
- return {} if @operations.none?
62
- operation = find_operation(@operation_name, @operations)
63
- resolver = OperationResolver.new(operation, self)
64
- resolver.result
65
- end
66
-
67
48
  def validation_errors
68
49
  @validation_errors ||= @schema.static_validator.validate(@document)
69
50
  end
70
51
 
71
- def find_operation(operation_name, operations)
72
- if operations.length == 1
73
- operations.values.first
74
- elsif !operations.key?(operation_name)
75
- raise OperationNameMissingError, operations.keys
76
- else
77
- operations[operation_name]
78
- end
79
- end
80
-
81
52
  # Expose some query-specific info to field resolve functions.
82
53
  # It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`.
83
54
  class Context
@@ -89,20 +60,12 @@ class GraphQL::Query
89
60
  @arbitrary_hash[key]
90
61
  end
91
62
  end
92
-
93
- class OperationNameMissingError < StandardError
94
- def initialize(names)
95
- msg = "You must provide an operation name from: #{names.join(", ")}"
96
- super(msg)
97
- end
98
- end
99
63
  end
100
64
 
101
65
  require 'graphql/query/arguments'
102
- require 'graphql/query/field_resolution_strategy'
103
- require 'graphql/query/fragment_spread_resolution_strategy'
104
- require 'graphql/query/inline_fragment_resolution_strategy'
105
- require 'graphql/query/operation_resolver'
106
- require 'graphql/query/selection_resolver'
66
+ require 'graphql/query/base_execution'
67
+ require 'graphql/query/serial_execution'
68
+ require 'graphql/query/value_resolution'
107
69
  require 'graphql/query/type_resolver'
108
70
  require 'graphql/query/directive_chain'
71
+ require 'graphql/query/executor'
@@ -0,0 +1,34 @@
1
+ module GraphQL
2
+ class Query
3
+ class BaseExecution
4
+
5
+ def field_resolution
6
+ get_class :FieldResolution
7
+ end
8
+
9
+ def fragment_spread_resolution
10
+ get_class :FragmentSpreadResolution
11
+ end
12
+
13
+ def inline_fragment_resolution
14
+ get_class :InlineFragmentResolution
15
+ end
16
+
17
+ def operation_resolution
18
+ get_class :OperationResolution
19
+ end
20
+
21
+ def selection_resolution
22
+ get_class :SelectionResolution
23
+ end
24
+
25
+ private
26
+
27
+ def get_class(class_name)
28
+ self.class.const_get(class_name)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ require 'graphql/query/base_execution/selected_object_resolution'
@@ -0,0 +1,16 @@
1
+ module GraphQL
2
+ class Query
3
+ class BaseExecution
4
+ class SelectedObjectResolution
5
+ attr_reader :ast_node, :parent_type, :target, :query, :execution_strategy
6
+ def initialize(ast_node, parent_type, target, query, execution_strategy)
7
+ @ast_node = ast_node
8
+ @parent_type = parent_type
9
+ @target = target
10
+ @query = query
11
+ @execution_strategy = execution_strategy
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,4 @@
1
+ # TODO: directives on fragments: http://facebook.github.io/graphql/#sec-Fragment-Directives
1
2
  class GraphQL::Query::DirectiveChain
2
3
  DIRECTIVE_ON = {
3
4
  GraphQL::Language::Nodes::Field => GraphQL::Directive::ON_FIELD,
@@ -12,10 +13,15 @@ class GraphQL::Query::DirectiveChain
12
13
  }
13
14
 
14
15
  attr_reader :result
15
- def initialize(ast_node, operation_resolver, &block)
16
- directives = operation_resolver.query.schema.directives
16
+ def initialize(ast_node, query, &block)
17
+ directives = query.schema.directives
17
18
  on_what = DIRECTIVE_ON[ast_node.class]
18
- ast_directives = GET_DIRECTIVES[ast_node.class].call(ast_node, operation_resolver.query.fragments)
19
+ ast_directives = GET_DIRECTIVES[ast_node.class].call(ast_node, query.fragments)
20
+
21
+ if contains_skip?(ast_directives)
22
+ ast_directives = ast_directives.reject { |ast_directive| ast_directive.name == 'include' }
23
+ end
24
+
19
25
  applicable_directives = ast_directives
20
26
  .map { |ast_directive| [ast_directive, directives[ast_directive.name]] }
21
27
  .select { |directive_pair| directive_pair.last.on.include?(on_what) }
@@ -24,10 +30,15 @@ class GraphQL::Query::DirectiveChain
24
30
  @result = block.call
25
31
  else
26
32
  applicable_directives.map do |(ast_directive, directive)|
27
- args = GraphQL::Query::Arguments.new(ast_directive.arguments, directive.arguments, operation_resolver.variables)
33
+ args = GraphQL::Query::Arguments.new(ast_directive.arguments, directive.arguments, query.variables)
28
34
  @result = directive.resolve(args, block)
29
35
  end
30
36
  @result ||= {}
31
37
  end
32
38
  end
39
+
40
+ private
41
+ def contains_skip?(directives)
42
+ directives.any? { |directive| directive.name == 'skip' }
43
+ end
33
44
  end
@@ -0,0 +1,53 @@
1
+ module GraphQL
2
+ class Query
3
+ class Executor
4
+ class OperationNameMissingError < StandardError
5
+ def initialize(names)
6
+ msg = "You must provide an operation name from: #{names.join(", ")}"
7
+ super(msg)
8
+ end
9
+ end
10
+
11
+ attr_reader :query, :operation_name
12
+ def initialize(query, operation_name)
13
+ @query = query
14
+ @operation_name = operation_name
15
+ end
16
+
17
+ def result
18
+ {"data" => execute }
19
+ rescue OperationNameMissingError => err
20
+ {"errors" => [{"message" => err.message}]}
21
+ rescue StandardError => err
22
+ query.debug && raise(err)
23
+ message = "Something went wrong during query execution: #{err}" # \n #{err.backtrace.join("\n ")}"
24
+ {"errors" => [{"message" => message}]}
25
+ end
26
+
27
+ private
28
+
29
+ def execute
30
+ return {} if query.operations.none?
31
+ operation = find_operation(operation_name, query.operations)
32
+ if operation.operation_type == "query"
33
+ root = query.schema.query
34
+ elsif operation.operation_type == "mutation"
35
+ root = query.schema.mutation
36
+ end
37
+ execution_strategy = GraphQL::Query::SerialExecution.new
38
+ resolver = execution_strategy.operation_resolution.new(operation, root, query, execution_strategy)
39
+ resolver.result
40
+ end
41
+
42
+ def find_operation(operation_name, operations)
43
+ if operations.length == 1
44
+ operations.values.first
45
+ elsif !operations.key?(operation_name)
46
+ raise OperationNameMissingError, operations.keys
47
+ else
48
+ operations[operation_name]
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,12 @@
1
+ module GraphQL
2
+ class Query
3
+ class SerialExecution < GraphQL::Query::BaseExecution
4
+ end
5
+ end
6
+ end
7
+
8
+ require 'graphql/query/serial_execution/field_resolution'
9
+ require 'graphql/query/serial_execution/fragment_spread_resolution'
10
+ require 'graphql/query/serial_execution/inline_fragment_resolution'
11
+ require 'graphql/query/serial_execution/operation_resolution'
12
+ require 'graphql/query/serial_execution/selection_resolution'
@@ -0,0 +1,42 @@
1
+ module GraphQL
2
+ class Query
3
+ class SerialExecution
4
+ class FieldResolution < GraphQL::Query::BaseExecution::SelectedObjectResolution
5
+ attr_reader :field, :arguments
6
+
7
+ def initialize(ast_node, parent_type, target, query, execution_strategy)
8
+ super
9
+ @field = query.schema.get_field(parent_type, ast_node.name) || raise("No field found on #{parent_type.name} '#{parent_type}' for '#{ast_node.name}'")
10
+ @arguments = GraphQL::Query::Arguments.new(ast_node.arguments, field.arguments, query.variables)
11
+ end
12
+
13
+ def result
14
+ result_name = ast_node.alias || ast_node.name
15
+ { result_name => result_value}
16
+ end
17
+
18
+ private
19
+
20
+ def result_value
21
+ value = field.resolve(target, arguments, query.context)
22
+
23
+ if value == GraphQL::Query::DEFAULT_RESOLVE
24
+ begin
25
+ value = target.public_send(ast_node.name)
26
+ rescue NoMethodError => err
27
+ raise("Couldn't resolve field '#{ast_node.name}' to #{target.class} '#{target}' (resulted in #{err})")
28
+ end
29
+ end
30
+
31
+ return nil if value.nil?
32
+
33
+
34
+ resolved_type = field.type.kind.resolve(field.type, value)
35
+ strategy_class = GraphQL::Query::ValueResolution.get_strategy_for_kind(resolved_type.kind)
36
+ result_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_node, query, execution_strategy)
37
+ result_strategy.result
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,22 @@
1
+ module GraphQL
2
+ class Query
3
+ class SerialExecution
4
+ class FragmentSpreadResolution < GraphQL::Query::BaseExecution::SelectedObjectResolution
5
+ attr_reader :ast_fragment, :resolved_type
6
+ def initialize(ast_node, type, target, query, execution_strategy)
7
+ super
8
+ @ast_fragment = query.fragments[ast_node.name]
9
+ child_type = query.schema.types[ast_fragment.type]
10
+ @resolved_type = GraphQL::Query::TypeResolver.new(target, child_type, type).type
11
+ end
12
+
13
+ def result
14
+ return {} if resolved_type.nil?
15
+ selections = ast_fragment.selections
16
+ resolver = execution_strategy.selection_resolution.new(target, resolved_type, selections, query, execution_strategy)
17
+ resolver.result
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ module GraphQL
2
+ class Query
3
+ class SerialExecution
4
+ class InlineFragmentResolution < GraphQL::Query::BaseExecution::SelectedObjectResolution
5
+ attr_reader :resolved_type
6
+ def initialize(ast_node, type, target, query, execution_strategy)
7
+ super
8
+ child_type = query.schema.types[ast_node.type]
9
+ @resolved_type = GraphQL::Query::TypeResolver.new(target, child_type, type).type
10
+ end
11
+
12
+ def result
13
+ return {} if resolved_type.nil?
14
+ selections = ast_node.selections
15
+ resolver = execution_strategy.selection_resolution.new(target, resolved_type, selections, query, execution_strategy)
16
+ resolver.result
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ module GraphQL
2
+ class Query
3
+ class SerialExecution
4
+ class OperationResolution
5
+ attr_reader :query, :target, :ast_operation_definition, :execution_strategy
6
+
7
+ def initialize(ast_operation_definition, target, query, execution_strategy)
8
+ @ast_operation_definition = ast_operation_definition
9
+ @query = query
10
+ @target = target
11
+ @execution_strategy = execution_strategy
12
+ end
13
+
14
+ def result
15
+ selections = ast_operation_definition.selections
16
+ resolver = execution_strategy.selection_resolution.new(nil, target, selections, query, execution_strategy)
17
+ resolver.result
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,42 @@
1
+ module GraphQL
2
+ class Query
3
+ class SerialExecution
4
+ class SelectionResolution
5
+ attr_reader :target, :type, :selections, :query, :execution_strategy
6
+
7
+ RESOLUTION_STRATEGIES = {
8
+ GraphQL::Language::Nodes::Field => :field_resolution,
9
+ GraphQL::Language::Nodes::FragmentSpread => :fragment_spread_resolution,
10
+ GraphQL::Language::Nodes::InlineFragment => :inline_fragment_resolution,
11
+ }
12
+
13
+ def initialize(target, type, selections, query, execution_strategy)
14
+ @target = target
15
+ @type = type
16
+ @selections = selections
17
+ @query = query
18
+ @execution_strategy = execution_strategy
19
+ end
20
+
21
+ def result
22
+ selections.reduce({}) do |memo, ast_field|
23
+ field_value = resolve_field(ast_field)
24
+ memo.merge(field_value)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def resolve_field(ast_field)
31
+ chain = GraphQL::Query::DirectiveChain.new(ast_field, query) {
32
+ strategy_name = RESOLUTION_STRATEGIES[ast_field.class]
33
+ strategy_class = execution_strategy.public_send(strategy_name)
34
+ strategy = strategy_class.new(ast_field, type, target, query, execution_strategy)
35
+ strategy.result
36
+ }
37
+ chain.result
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,76 @@
1
+ module GraphQL
2
+ class Query
3
+ module ValueResolution
4
+ def self.get_strategy_for_kind(kind)
5
+ TYPE_KIND_STRATEGIES[kind] || raise("No value resolution strategy for #{kind}!")
6
+ end
7
+
8
+ class BaseResolution
9
+ attr_reader :value, :field_type, :target, :parent_type,
10
+ :ast_field, :query, :execution_strategy
11
+ def initialize(value, field_type, target, parent_type, ast_field, query, execution_strategy)
12
+ @value = value
13
+ @field_type = field_type
14
+ @target = target
15
+ @parent_type = parent_type
16
+ @ast_field = ast_field
17
+ @query = query
18
+ @execution_strategy = execution_strategy
19
+ end
20
+
21
+ def result
22
+ raise NotImplementedError, "Should return a value based on initialization params"
23
+ end
24
+ end
25
+
26
+ class ScalarResolution < BaseResolution
27
+ def result
28
+ field_type.coerce(value)
29
+ end
30
+ end
31
+
32
+ class ListResolution < BaseResolution
33
+ def result
34
+ wrapped_type = field_type.of_type
35
+ value.map do |item|
36
+ resolved_type = wrapped_type.kind.resolve(wrapped_type, item)
37
+ strategy_class = GraphQL::Query::ValueResolution.get_strategy_for_kind(resolved_type.kind)
38
+ inner_strategy = strategy_class.new(item, resolved_type, target, parent_type, ast_field, query, execution_strategy)
39
+ inner_strategy.result
40
+ end
41
+ end
42
+ end
43
+
44
+ class ObjectResolution < BaseResolution
45
+ def result
46
+ resolver = execution_strategy.selection_resolution.new(value, field_type, ast_field.selections, query, execution_strategy)
47
+ resolver.result
48
+ end
49
+ end
50
+
51
+ class EnumResolution < BaseResolution
52
+ def result
53
+ value.to_s
54
+ end
55
+ end
56
+
57
+ class NonNullResolution < BaseResolution
58
+ def result
59
+ wrapped_type = field_type.of_type
60
+ resolved_type = wrapped_type.kind.resolve(wrapped_type, value)
61
+ strategy_class = GraphQL::Query::ValueResolution.get_strategy_for_kind(resolved_type.kind)
62
+ inner_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_field, query, execution_strategy)
63
+ inner_strategy.result
64
+ end
65
+ end
66
+
67
+ TYPE_KIND_STRATEGIES = {
68
+ GraphQL::TypeKinds::SCALAR => ScalarResolution,
69
+ GraphQL::TypeKinds::LIST => ListResolution,
70
+ GraphQL::TypeKinds::OBJECT => ObjectResolution,
71
+ GraphQL::TypeKinds::ENUM => EnumResolution,
72
+ GraphQL::TypeKinds::NON_NULL => NonNullResolution,
73
+ }
74
+ end
75
+ end
76
+ end
@@ -45,8 +45,8 @@ class GraphQL::Schema::TypeValidator
45
45
 
46
46
  if type.kind.union?
47
47
  union_types = type.possible_types
48
- if union_types.length < 2
49
- own_errors << "Union #{type_name} must be defined with 2 or more types, not #{union_types.length}"
48
+ if union_types.none?
49
+ own_errors << "Union #{type_name} must be defined with 1 or more types, not 0!"
50
50
  end
51
51
  end
52
52
  errors.push(*own_errors)
@@ -1,3 +1,3 @@
1
1
  module GraphQL
2
- VERSION = "0.6.2"
2
+ VERSION = "0.7.0"
3
3
  end
data/readme.md CHANGED
@@ -91,28 +91,34 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
91
91
 
92
92
  ## To Do
93
93
 
94
- - To match spec:
95
- - Directives:
96
- - `@skip` has precedence over `@include`
97
- - directives on fragments: http://facebook.github.io/graphql/#sec-Fragment-Directives
98
- - Union
99
- - only require one type https://github.com/facebook/graphql/commit/972fd5aae01195b018d305c82e43f714f9a41679
100
94
  - Field merging
101
95
  - if you were to request a field, then request it in a fragment, it would get looked up twice
102
96
  - https://github.com/graphql/graphql-js/issues/19#issuecomment-118515077
103
97
  - Code clean-up
104
- - Accept native Ruby types in definitions, then convert them to GraphQL types
105
- - Remove deprecated `params:` keyword
98
+ - Easier built-in type definition
99
+ - Make an object that accepts type objects, symbols, or corresponding Ruby classes and convertes them to GraphQL types
100
+ - Hook up that object to `DefinitionConfig`, so it can map from incoming values to GraphQL types
106
101
  - Raise if you try to configure an attribute which doesn't suit the type
107
- - Cook up some path other than "n+1s everywhere"
108
- - See Sangria's `project` approach ([in progress](https://github.com/rmosolgo/graphql-ruby/pull/15))
109
- - Try debounced approach?
102
+ - ie, if you try to define `resolve` on an ObjectType, it should somehow raise
103
+ - Make better inheritance between types
104
+ - Implement a BaseType (?) and make all type classes extend that
105
+ - No more extending ObjectType!
106
+ - Move `TypeKind#unwrap` to BaseType & update all code
107
+ - Also move `TypeKind#resolve` ?
108
+ - Big ideas:
109
+ - Cook up some path other than "n+1s everywhere"
110
+ - See Sangria's `project` approach ([in progress](https://github.com/rmosolgo/graphql-ruby/pull/15))
111
+ - Try debounced approach?
112
+ - Write Ruby bindings for [libgraphqlparser](https://github.com/graphql/libgraphqlparser) and use that instead of Parslet
113
+ - Add instrumentation
114
+ - Some way to expose what queries are run, what types & fields are accessed, how long things are taking, etc
115
+
110
116
 
111
117
  ## Goals
112
118
 
113
119
  - Implement the GraphQL spec & support a Relay front end
114
120
  - Provide idiomatic, plain-Ruby API with similarities to reference implementation where possible
115
- - Support `graphql-rails`
121
+ - Support Ruby on Rails and Relay
116
122
 
117
123
  ## Getting Involved
118
124
 
@@ -8,6 +8,8 @@ describe GraphQL::Directive do
8
8
  # plain fields:
9
9
  skipFlavor: flavor @skip(if: true)
10
10
  dontSkipFlavor: flavor @skip(if: false)
11
+ dontSkipDontIncludeFlavor: flavor @skip(if: false), @include(if: false)
12
+ skipAndInclude: flavor @skip(if: true), @include(if: true)
11
13
  includeFlavor: flavor @include(if: $t)
12
14
  dontIncludeFlavor: flavor @include(if: $f)
13
15
  # fields in fragments
@@ -25,6 +27,7 @@ describe GraphQL::Directive do
25
27
  it 'intercepts fields' do
26
28
  expected = { "data" =>{
27
29
  "cheese" => {
30
+ "dontSkipDontIncludeFlavor" => "Brie", #skip has precedence over include
28
31
  "dontSkipFlavor" => "Brie",
29
32
  "includeFlavor" => "Brie",
30
33
  "includeId" => 1,
@@ -23,6 +23,7 @@ describe GraphQL::Introspection::SchemaType do
23
23
  {"name"=>"favoriteEdible"},
24
24
  {"name"=>"searchDairy"},
25
25
  {"name"=>"error"},
26
+ {"name"=>"maybeNull"},
26
27
  ]
27
28
  },
28
29
  "mutationType"=> {
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ describe GraphQL::Query::Executor do
4
+ let(:debug) { false }
5
+ let(:operation_name) { nil }
6
+ let(:query) { GraphQL::Query.new(
7
+ DummySchema,
8
+ query_string,
9
+ variables: {"cheeseId" => 2},
10
+ debug: debug,
11
+ operation_name: operation_name,
12
+ )}
13
+ let(:result) { query.result }
14
+
15
+ describe "multiple operations" do
16
+ let(:query_string) { %|
17
+ query getCheese1 { cheese(id: 1) { flavor } }
18
+ query getCheese2 { cheese(id: 2) { flavor } }
19
+ |}
20
+
21
+ describe "when an operation is named" do
22
+ let(:operation_name) { "getCheese2" }
23
+
24
+ it "runs the named one" do
25
+ expected = {
26
+ "data" => {
27
+ "cheese" => {
28
+ "flavor" => "Gouda"
29
+ }
30
+ }
31
+ }
32
+ assert_equal(expected, result)
33
+ end
34
+ end
35
+
36
+ describe "when one is NOT named" do
37
+ it "returns an error" do
38
+ expected = {
39
+ "errors" => [
40
+ {"message" => "You must provide an operation name from: getCheese1, getCheese2"}
41
+ ]
42
+ }
43
+ assert_equal(expected, result)
44
+ end
45
+ end
46
+ end
47
+
48
+
49
+ describe 'execution order' do
50
+ let(:query_string) {%|
51
+ mutation setInOrder {
52
+ first: pushValue(value: 1)
53
+ second: pushValue(value: 5)
54
+ third: pushValue(value: 2)
55
+ fourth: replaceValues(input: {values: [6,5,4]})
56
+ }
57
+ |}
58
+
59
+ it 'executes mutations in order' do
60
+ expected = {"data"=>{
61
+ "first"=> [1],
62
+ "second"=>[1, 5],
63
+ "third"=> [1, 5, 2],
64
+ "fourth"=> [6, 5 ,4],
65
+ }}
66
+ assert_equal(expected, result)
67
+ end
68
+ end
69
+
70
+
71
+ describe 'runtime errors' do
72
+ let(:query_string) {%| query noMilk { error }|}
73
+ describe 'if debug: false' do
74
+ let(:debug) { false }
75
+ it 'turns into error messages' do
76
+ expected = {"errors"=>[
77
+ {"message"=>"Something went wrong during query execution: This error was raised on purpose"}
78
+ ]}
79
+ assert_equal(expected, result)
80
+ end
81
+ end
82
+
83
+ describe 'if debug: true' do
84
+ let(:debug) { true }
85
+ it 'raises error' do
86
+ assert_raises(RuntimeError) { result }
87
+ end
88
+ end
89
+ end
90
+ end
@@ -1,45 +1,44 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe GraphQL::Query do
4
- describe '#execute' do
5
- let(:query_string) { %|
6
- query getFlavor($cheeseId: Int!) {
7
- brie: cheese(id: 1) { ...cheeseFields, taste: flavor },
8
- cheese(id: $cheeseId) {
9
- __typename,
10
- id,
11
- ...cheeseFields,
12
- ... edibleFields,
13
- ... on Cheese { cheeseKind: flavor },
14
- }
15
- fromSource(source: COW) { id }
16
- fromSheep: fromSource(source: SHEEP) { id }
17
- firstSheep: searchDairy(product: {source: SHEEP}) {
18
- __typename,
19
- ... dairyFields,
20
- ... milkFields
21
- }
22
- favoriteEdible { __typename, fatContent }
4
+ let(:query_string) { %|
5
+ query getFlavor($cheeseId: Int!) {
6
+ brie: cheese(id: 1) { ...cheeseFields, taste: flavor },
7
+ cheese(id: $cheeseId) {
8
+ __typename,
9
+ id,
10
+ ...cheeseFields,
11
+ ... edibleFields,
12
+ ... on Cheese { cheeseKind: flavor },
23
13
  }
24
- fragment cheeseFields on Cheese { flavor }
25
- fragment edibleFields on Edible { fatContent }
26
- fragment milkFields on Milk { source }
27
- fragment dairyFields on AnimalProduct {
28
- ... on Cheese { flavor }
29
- ... on Milk { source }
14
+ fromSource(source: COW) { id }
15
+ fromSheep: fromSource(source: SHEEP) { id }
16
+ firstSheep: searchDairy(product: {source: SHEEP}) {
17
+ __typename,
18
+ ... dairyFields,
19
+ ... milkFields
30
20
  }
31
- |}
32
- let(:debug) { false }
33
- let(:operation_name) { nil }
34
- let(:query) { GraphQL::Query.new(
35
- DummySchema,
36
- query_string,
37
- variables: {"cheeseId" => 2},
38
- debug: debug,
39
- operation_name: operation_name,
40
- )}
41
- let(:result) { query.result }
42
-
21
+ favoriteEdible { __typename, fatContent }
22
+ }
23
+ fragment cheeseFields on Cheese { flavor }
24
+ fragment edibleFields on Edible { fatContent }
25
+ fragment milkFields on Milk { source }
26
+ fragment dairyFields on AnimalProduct {
27
+ ... on Cheese { flavor }
28
+ ... on Milk { source }
29
+ }
30
+ |}
31
+ let(:debug) { false }
32
+ let(:operation_name) { nil }
33
+ let(:query) { GraphQL::Query.new(
34
+ DummySchema,
35
+ query_string,
36
+ variables: {"cheeseId" => 2},
37
+ debug: debug,
38
+ operation_name: operation_name,
39
+ )}
40
+ let(:result) { query.result }
41
+ describe '#result' do
43
42
  it 'returns fields on objects' do
44
43
  expected = {"data"=> {
45
44
  "brie" => { "flavor" => "Brie", "taste" => "Brie" },
@@ -58,96 +57,44 @@ describe GraphQL::Query do
58
57
  assert_equal(expected, result)
59
58
  end
60
59
 
61
- it 'exposes fragments' do
62
- assert_equal(GraphQL::Language::Nodes::FragmentDefinition, query.fragments['cheeseFields'].class)
63
- end
64
-
65
- describe 'runtime errors' do
66
- let(:query_string) {%| query noMilk { error }|}
67
- describe 'if debug: false' do
68
- let(:debug) { false }
69
- it 'turns into error messages' do
70
- expected = {"errors"=>[
71
- {"message"=>"Something went wrong during query execution: This error was raised on purpose"}
72
- ]}
73
- assert_equal(expected, result)
74
- end
75
- end
60
+ describe "when it hits null objects" do
61
+ let(:query_string) {%|
62
+ {
63
+ maybeNull {
64
+ cheese {
65
+ flavor,
66
+ similarCheeses(source: [SHEEP]) { flavor }
67
+ }
68
+ }
69
+ }
70
+ |}
76
71
 
77
- describe 'if debug: true' do
78
- let(:debug) { true }
79
- it 'raises error' do
80
- assert_raises(RuntimeError) { result }
81
- end
72
+ it "skips null objects" do
73
+ expected = {"data"=> {
74
+ "maybeNull" => { "cheese" => nil }
75
+ }}
76
+ assert_equal(expected, result)
82
77
  end
83
78
  end
79
+ end
84
80
 
85
- describe "malformed queries" do
86
- describe "whitespace-only" do
87
- let(:query_string) { " " }
88
- it "doesn't blow up" do
89
- assert_equal({"data"=> {}}, result)
90
- end
91
- end
92
-
93
- describe "empty string" do
94
- let(:query_string) { "" }
95
- it "doesn't blow up" do
96
- assert_equal({"data"=> {}}, result)
97
- end
98
- end
99
- end
81
+ it 'exposes fragments' do
82
+ assert_equal(GraphQL::Language::Nodes::FragmentDefinition, query.fragments['cheeseFields'].class)
83
+ end
100
84
 
101
- describe "multiple operations" do
102
- let(:query_string) { %|
103
- query getCheese1 { cheese(id: 1) { flavor } }
104
- query getCheese2 { cheese(id: 2) { flavor } }
105
- |}
106
- describe "when an operation is named" do
107
- let(:operation_name) { "getCheese2" }
108
- it "runs the named one" do
109
- expected = {
110
- "data" => {
111
- "cheese" => {
112
- "flavor" => "Gouda"
113
- }
114
- }
115
- }
116
- assert_equal(expected, result)
117
- end
118
- end
119
85
 
120
- describe "when one is NOT named" do
121
- it "returns an error" do
122
- expected = {
123
- "errors" => [
124
- {"message" => "You must provide an operation name from: getCheese1, getCheese2"}
125
- ]
126
- }
127
- assert_equal(expected, result)
128
- end
86
+ describe "malformed queries" do
87
+ describe "whitespace-only" do
88
+ let(:query_string) { " " }
89
+ it "doesn't blow up" do
90
+ assert_equal({"data"=> {}}, result)
129
91
  end
130
92
  end
131
93
 
132
-
133
- describe 'execution order' do
134
- let(:query_string) {%|
135
- mutation setInOrder {
136
- first: pushValue(value: 1)
137
- second: pushValue(value: 5)
138
- third: pushValue(value: 2)
139
- fourth: replaceValues(input: {values: [6,5,4]})
140
- }
141
- |}
142
-
143
- it 'executes mutations in order' do
144
- expected = {"data"=>{
145
- "first"=> [1],
146
- "second"=>[1, 5],
147
- "third"=> [1, 5, 2],
148
- "fourth"=> [6, 5 ,4],
149
- }}
150
- assert_equal(expected, result)
94
+ describe "empty string" do
95
+ let(:query_string) { "" }
96
+ it "doesn't blow up" do
97
+ assert_equal({"data"=> {}}, result)
151
98
  end
152
99
  end
153
100
  end
@@ -50,19 +50,32 @@ describe GraphQL::Schema::TypeValidator do
50
50
 
51
51
  describe "when a Union isnt valid" do
52
52
  let(:object) {
53
+ union_types = types
53
54
  GraphQL::UnionType.define do
54
55
  name "Something"
55
56
  description "some union"
56
- possible_types [DairyProductInputType]
57
+ possible_types union_types
57
58
  end
58
59
  }
59
60
  let(:errors) { e = []; GraphQL::Schema::TypeValidator.new.validate(object, e); e;}
60
- it 'must be 2+ types, must be only object types' do
61
- expected = [
62
- "Something.possible_types must be objects, but some aren't: DairyProductInput",
63
- "Union Something must be defined with 2 or more types, not 1",
64
- ]
65
- assert_equal(expected, errors)
61
+
62
+ describe 'when non-object types' do
63
+ let(:types) { [DairyProductInputType] }
64
+ it 'must be must be only object types' do
65
+ expected = [
66
+ "Something.possible_types must be objects, but some aren't: DairyProductInput"
67
+ ]
68
+ assert_equal(expected, errors)
69
+ end
70
+ end
71
+ describe 'when no types' do
72
+ let(:types) { [] }
73
+ it 'must have a type' do
74
+ expected = [
75
+ "Union Something must be defined with 1 or more types, not 0!"
76
+ ]
77
+ assert_equal(expected, errors)
78
+ end
66
79
  end
67
80
  end
68
81
  end
@@ -62,6 +62,12 @@ MilkType = GraphQL::ObjectType.define do
62
62
  end
63
63
  end
64
64
 
65
+ MaybeNullType = GraphQL::ObjectType.define do
66
+ name "MaybeNull"
67
+ description "An object whose fields return nil"
68
+ field :cheese, CheeseType
69
+ end
70
+
65
71
  DairyProductUnion = GraphQL::UnionType.define do
66
72
  name "DairyProduct"
67
73
  description "Kinds of food made from milk"
@@ -131,6 +137,11 @@ QueryType = GraphQL::ObjectType.define do
131
137
  type GraphQL::STRING_TYPE
132
138
  resolve -> (t, a, c) { raise("This error was raised on purpose") }
133
139
  end
140
+
141
+ # To test possibly-null fields
142
+ field :maybeNull, MaybeNullType do
143
+ resolve -> (t, a, c) { OpenStruct.new(cheese: nil) }
144
+ end
134
145
  end
135
146
 
136
147
  GLOBAL_VALUES = []
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-20 00:00:00.000000000 Z
11
+ date: 2015-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parslet
@@ -206,13 +206,18 @@ files:
206
206
  - lib/graphql/object_type.rb
207
207
  - lib/graphql/query.rb
208
208
  - lib/graphql/query/arguments.rb
209
+ - lib/graphql/query/base_execution.rb
210
+ - lib/graphql/query/base_execution/selected_object_resolution.rb
209
211
  - lib/graphql/query/directive_chain.rb
210
- - lib/graphql/query/field_resolution_strategy.rb
211
- - lib/graphql/query/fragment_spread_resolution_strategy.rb
212
- - lib/graphql/query/inline_fragment_resolution_strategy.rb
213
- - lib/graphql/query/operation_resolver.rb
214
- - lib/graphql/query/selection_resolver.rb
212
+ - lib/graphql/query/executor.rb
213
+ - lib/graphql/query/serial_execution.rb
214
+ - lib/graphql/query/serial_execution/field_resolution.rb
215
+ - lib/graphql/query/serial_execution/fragment_spread_resolution.rb
216
+ - lib/graphql/query/serial_execution/inline_fragment_resolution.rb
217
+ - lib/graphql/query/serial_execution/operation_resolution.rb
218
+ - lib/graphql/query/serial_execution/selection_resolution.rb
215
219
  - lib/graphql/query/type_resolver.rb
220
+ - lib/graphql/query/value_resolution.rb
216
221
  - lib/graphql/repl.rb
217
222
  - lib/graphql/scalar_type.rb
218
223
  - lib/graphql/schema.rb
@@ -263,7 +268,7 @@ files:
263
268
  - spec/graphql/language/transform_spec.rb
264
269
  - spec/graphql/language/visitor_spec.rb
265
270
  - spec/graphql/object_type_spec.rb
266
- - spec/graphql/query/operation_resolver_spec.rb
271
+ - spec/graphql/query/executor_spec.rb
267
272
  - spec/graphql/query_spec.rb
268
273
  - spec/graphql/schema/field_validator_spec.rb
269
274
  - spec/graphql/schema/type_reducer_spec.rb
@@ -312,7 +317,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
312
317
  version: '0'
313
318
  requirements: []
314
319
  rubyforge_project:
315
- rubygems_version: 2.4.5
320
+ rubygems_version: 2.2.0
316
321
  signing_key:
317
322
  specification_version: 4
318
323
  summary: A GraphQL implementation for Ruby
@@ -331,7 +336,7 @@ test_files:
331
336
  - spec/graphql/language/transform_spec.rb
332
337
  - spec/graphql/language/visitor_spec.rb
333
338
  - spec/graphql/object_type_spec.rb
334
- - spec/graphql/query/operation_resolver_spec.rb
339
+ - spec/graphql/query/executor_spec.rb
335
340
  - spec/graphql/query_spec.rb
336
341
  - spec/graphql/schema/field_validator_spec.rb
337
342
  - spec/graphql/schema/type_reducer_spec.rb
@@ -1,87 +0,0 @@
1
- class GraphQL::Query::FieldResolutionStrategy
2
- attr_reader :result, :result_value
3
-
4
- def initialize(ast_field, parent_type, target, operation_resolver)
5
- field_name = ast_field.name
6
- field = operation_resolver.query.schema.get_field(parent_type, field_name) || raise("No field found on #{parent_type.name} '#{parent_type}' for '#{field_name}'")
7
- arguments = GraphQL::Query::Arguments.new(ast_field.arguments, field.arguments, operation_resolver.variables)
8
- value = field.resolve(target, arguments, operation_resolver.context)
9
- if value.nil?
10
- @result_value = value
11
- else
12
- if value == GraphQL::Query::DEFAULT_RESOLVE
13
- begin
14
- value = target.send(field_name)
15
- rescue NoMethodError => err
16
- raise("Couldn't resolve field '#{field_name}' to #{target.class} '#{target}' (resulted in #{err})")
17
- end
18
- end
19
- resolved_type = field.type.kind.resolve(field.type, value)
20
- strategy_class = self.class.get_strategy_for_kind(resolved_type.kind)
21
- result_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_field, operation_resolver)
22
- @result_value = result_strategy.result
23
- end
24
- result_name = ast_field.alias || ast_field.name
25
- @result = { result_name => @result_value}
26
- end
27
-
28
- def self.get_strategy_for_kind(kind)
29
- FIELD_TYPE_KIND_STRATEGIES[kind] || raise("No strategy for #{kind}")
30
- end
31
-
32
- class ScalarResolutionStrategy
33
- attr_reader :result
34
- def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
35
- @result = field_type.coerce(value)
36
- end
37
- end
38
-
39
- class ListResolutionStrategy
40
- attr_reader :result
41
- def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
42
- wrapped_type = field_type.of_type
43
- @result = value.map do |item|
44
- resolved_type = wrapped_type.kind.resolve(wrapped_type, item)
45
- strategy_class = GraphQL::Query::FieldResolutionStrategy.get_strategy_for_kind(resolved_type.kind)
46
- inner_strategy = strategy_class.new(item, resolved_type, target, parent_type, ast_field, operation_resolver)
47
- inner_strategy.result
48
- end
49
- end
50
- end
51
-
52
- class ObjectResolutionStrategy
53
- attr_reader :result
54
- def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
55
- resolver = GraphQL::Query::SelectionResolver.new(value, field_type, ast_field.selections, operation_resolver)
56
- @result = resolver.result
57
- end
58
- end
59
-
60
- class EnumResolutionStrategy
61
- attr_reader :result
62
- def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
63
- @result = value.to_s
64
- end
65
- end
66
-
67
- class NonNullResolutionStrategy
68
- attr_reader :result
69
- def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
70
- wrapped_type = field_type.of_type
71
- resolved_type = wrapped_type.kind.resolve(wrapped_type, value)
72
- strategy_class = GraphQL::Query::FieldResolutionStrategy.get_strategy_for_kind(resolved_type.kind)
73
- inner_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_field, operation_resolver)
74
- @result = inner_strategy.result
75
- end
76
- end
77
-
78
- private
79
-
80
- FIELD_TYPE_KIND_STRATEGIES = {
81
- GraphQL::TypeKinds::SCALAR => ScalarResolutionStrategy,
82
- GraphQL::TypeKinds::LIST => ListResolutionStrategy,
83
- GraphQL::TypeKinds::OBJECT => ObjectResolutionStrategy,
84
- GraphQL::TypeKinds::ENUM => EnumResolutionStrategy,
85
- GraphQL::TypeKinds::NON_NULL => NonNullResolutionStrategy,
86
- }
87
- end
@@ -1,16 +0,0 @@
1
- class GraphQL::Query::FragmentSpreadResolutionStrategy
2
- attr_reader :result
3
- def initialize(ast_fragment_spread, type, target, operation_resolver)
4
- fragments = operation_resolver.query.fragments
5
- fragment_def = fragments[ast_fragment_spread.name]
6
- child_type = operation_resolver.query.schema.types[fragment_def.type]
7
- resolved_type = GraphQL::Query::TypeResolver.new(target, child_type, type).type
8
- if resolved_type.nil?
9
- @result = {}
10
- else
11
- selections = fragment_def.selections
12
- resolver = GraphQL::Query::SelectionResolver.new(target, resolved_type, selections, operation_resolver)
13
- @result = resolver.result
14
- end
15
- end
16
- end
@@ -1,14 +0,0 @@
1
- class GraphQL::Query::InlineFragmentResolutionStrategy
2
- attr_reader :result
3
- def initialize(ast_inline_fragment, type, target, operation_resolver)
4
- child_type = operation_resolver.query.schema.types[ast_inline_fragment.type]
5
- resolved_type = GraphQL::Query::TypeResolver.new(target, child_type, type).type
6
- if resolved_type.nil?
7
- @result = {}
8
- else
9
- selections = ast_inline_fragment.selections
10
- resolver = GraphQL::Query::SelectionResolver.new(target, resolved_type, selections, operation_resolver)
11
- @result = resolver.result
12
- end
13
- end
14
- end
@@ -1,26 +0,0 @@
1
- class GraphQL::Query::OperationResolver
2
- attr_reader :variables, :query, :context
3
-
4
- def initialize(operation_definition, query)
5
- @operation_definition = operation_definition
6
- @variables = query.variables
7
- @query = query
8
- @context = query.context
9
- end
10
-
11
- def result
12
- @result ||= execute(@operation_definition, query)
13
- end
14
-
15
- private
16
-
17
- def execute(op_def, query)
18
- root = if op_def.operation_type == "query"
19
- query.schema.query
20
- elsif op_def.operation_type == "mutation"
21
- query.schema.mutation
22
- end
23
- resolver = GraphQL::Query::SelectionResolver.new(nil, root, op_def.selections, self)
24
- resolver.result
25
- end
26
- end
@@ -1,20 +0,0 @@
1
- class GraphQL::Query::SelectionResolver
2
- attr_reader :result
3
-
4
- RESOLUTION_STRATEGIES = {
5
- GraphQL::Language::Nodes::Field => GraphQL::Query::FieldResolutionStrategy,
6
- GraphQL::Language::Nodes::FragmentSpread => GraphQL::Query::FragmentSpreadResolutionStrategy,
7
- GraphQL::Language::Nodes::InlineFragment => GraphQL::Query::InlineFragmentResolutionStrategy,
8
- }
9
-
10
- def initialize(target, type, selections, operation_resolver)
11
- @result = selections.reduce({}) do |memo, ast_field|
12
- chain = GraphQL::Query::DirectiveChain.new(ast_field, operation_resolver) {
13
- strategy_class = RESOLUTION_STRATEGIES[ast_field.class]
14
- strategy = strategy_class.new(ast_field, type, target, operation_resolver)
15
- strategy.result
16
- }
17
- memo.merge(chain.result)
18
- end
19
- end
20
- end
@@ -1,14 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe GraphQL::Query::OperationResolver do
4
- let(:operation) { GraphQL.parse("query getCheese($cheeseId: Int!) { cheese(id: $cheeseId) { name }}", as: :operation_definition) }
5
- let(:variables) { {"cheeseId" => 1}}
6
- let(:query) { OpenStruct.new(variables: variables, context: nil) }
7
- let(:resolver) { GraphQL::Query::OperationResolver.new(operation, query)}
8
-
9
- describe "variables" do
10
- it 'returns variables by name' do
11
- assert_equal(1, resolver.variables["cheeseId"])
12
- end
13
- end
14
- end