graphql 0.7.1 → 0.8.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +2 -0
  3. data/lib/graphql/base_type.rb +79 -0
  4. data/lib/graphql/enum_type.rb +1 -3
  5. data/lib/graphql/input_object_type.rb +2 -2
  6. data/lib/graphql/interface_type.rb +3 -22
  7. data/lib/graphql/list_type.rb +5 -3
  8. data/lib/graphql/non_null_type.rb +4 -2
  9. data/lib/graphql/object_type.rb +1 -34
  10. data/lib/graphql/query.rb +10 -5
  11. data/lib/graphql/query/arguments.rb +1 -1
  12. data/lib/graphql/query/base_execution.rb +21 -2
  13. data/lib/graphql/query/base_execution/value_resolution.rb +82 -0
  14. data/lib/graphql/query/executor.rb +7 -5
  15. data/lib/graphql/query/parallel_execution.rb +101 -0
  16. data/lib/graphql/query/serial_execution/field_resolution.rb +18 -9
  17. data/lib/graphql/scalar_type.rb +3 -3
  18. data/lib/graphql/schema.rb +4 -0
  19. data/lib/graphql/schema/type_map.rb +34 -0
  20. data/lib/graphql/schema/type_reducer.rb +14 -16
  21. data/lib/graphql/static_validation/arguments_validator.rb +8 -8
  22. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +6 -8
  23. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +7 -11
  24. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
  25. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +1 -1
  26. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  27. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +25 -4
  28. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  29. data/lib/graphql/static_validation/type_stack.rb +3 -3
  30. data/lib/graphql/type_kinds.rb +0 -19
  31. data/lib/graphql/union_type.rb +3 -13
  32. data/lib/graphql/version.rb +1 -1
  33. data/readme.md +0 -11
  34. data/spec/graphql/base_type_spec.rb +24 -0
  35. data/spec/graphql/object_type_spec.rb +0 -20
  36. data/spec/graphql/query/parallel_execution_spec.rb +29 -0
  37. data/spec/graphql/query_spec.rb +23 -6
  38. data/spec/graphql/schema/type_reducer_spec.rb +25 -0
  39. data/spec/graphql/static_validation/validator_spec.rb +4 -3
  40. data/spec/spec_helper.rb +1 -1
  41. data/spec/support/dairy_app.rb +6 -2
  42. data/spec/support/parallel_schema.rb +31 -0
  43. metadata +26 -3
  44. data/lib/graphql/query/value_resolution.rb +0 -76
@@ -12,13 +12,28 @@ module GraphQL
12
12
 
13
13
  def result
14
14
  result_name = ast_node.alias || ast_node.name
15
- { result_name => result_value}
15
+ result_value = get_finished_value(get_raw_value)
16
+ { result_name => result_value }
16
17
  end
17
18
 
18
19
  private
19
20
 
20
- def result_value
21
+ def get_finished_value(raw_value)
22
+ if raw_value.nil?
23
+ nil
24
+ else
25
+ resolved_type = field.type.resolve_type(raw_value)
26
+ strategy_class = GraphQL::Query::BaseExecution::ValueResolution.get_strategy_for_kind(resolved_type.kind)
27
+ result_strategy = strategy_class.new(raw_value, resolved_type, target, parent_type, ast_node, query, execution_strategy)
28
+ result_strategy.result
29
+ end
30
+ end
31
+
32
+
33
+ def get_raw_value
34
+ query.context.ast_node = ast_node
21
35
  value = field.resolve(target, arguments, query.context)
36
+ query.context.ast_node = nil
22
37
 
23
38
  if value == GraphQL::Query::DEFAULT_RESOLVE
24
39
  begin
@@ -28,13 +43,7 @@ module GraphQL
28
43
  end
29
44
  end
30
45
 
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
46
+ value
38
47
  end
39
48
  end
40
49
  end
@@ -1,9 +1,9 @@
1
1
  module GraphQL
2
2
  # The parent type for scalars, eg {GraphQL::STRING_TYPE}, {GraphQL::INT_TYPE}
3
3
  #
4
- class ScalarType < GraphQL::ObjectType
5
- defined_by_config :name, :coerce
6
- attr_accessor :name
4
+ class ScalarType < GraphQL::BaseType
5
+ defined_by_config :name, :coerce, :description
6
+ attr_accessor :name, :description
7
7
 
8
8
  def coerce(value)
9
9
  @coerce_proc.call(value)
@@ -4,6 +4,9 @@ class GraphQL::Schema
4
4
  DYNAMIC_FIELDS = ["__type", "__typename", "__schema"]
5
5
 
6
6
  attr_reader :query, :mutation, :directives, :static_validator
7
+ # Override these if you don't want the default executors:
8
+ attr_accessor :query_execution_strategy, :mutation_execution_strategy
9
+
7
10
 
8
11
  # @param query [GraphQL::ObjectType] the query root for the schema
9
12
  # @param mutation [GraphQL::ObjectType, nil] the mutation root for the schema
@@ -48,4 +51,5 @@ require 'graphql/schema/each_item_validator'
48
51
  require 'graphql/schema/field_validator'
49
52
  require 'graphql/schema/implementation_validator'
50
53
  require 'graphql/schema/type_reducer'
54
+ require 'graphql/schema/type_map'
51
55
  require 'graphql/schema/type_validator'
@@ -0,0 +1,34 @@
1
+ module GraphQL
2
+ class Schema
3
+ # Stores `{ name => type }` pairs for a given schema.
4
+ # It behaves like a hash except for a couple things:
5
+ # - if you use `[key]` and that key isn't defined, 💥!
6
+ # - if you try to define the same key twice, 💥!
7
+ #
8
+ # If you want a type, but want to handle the undefined case, use {#fetch}.
9
+ class TypeMap
10
+ extend Forwardable
11
+ def_delegators :@storage, :key?, :keys, :values
12
+
13
+ def initialize
14
+ @storage = {}
15
+ end
16
+
17
+ def [](key)
18
+ @storage[key] || raise("No type found for '#{key}'")
19
+ end
20
+
21
+ def []=(key, value)
22
+ if @storage.key?(key)
23
+ raise("Duplicate type definition found for name '#{key}'")
24
+ else
25
+ @storage[key] = value
26
+ end
27
+ end
28
+
29
+ def fetch(key, fallback_value)
30
+ @storage.key?(key) ? @storage[key] : fallback_value
31
+ end
32
+ end
33
+ end
34
+ end
@@ -4,25 +4,24 @@ class GraphQL::Schema::TypeReducer
4
4
  attr_reader :type, :existing_type_hash
5
5
 
6
6
  def initialize(type, existing_type_hash)
7
- @type = type
7
+ type = type.nil? ? nil : type.unwrap
8
+ validate_type(type)
9
+ if type.respond_to?(:name) && existing_type_hash.fetch(type.name, nil).equal?(type)
10
+ @result = existing_type_hash
11
+ else
12
+ @type = type
13
+ end
8
14
  @existing_type_hash = existing_type_hash
9
15
  end
10
16
 
11
17
  def result
12
- @result ||= if type.respond_to?(:kind) && type.kind.wraps?
13
- reduce_type(type.of_type, existing_type_hash)
14
- elsif type.respond_to?(:name) && existing_type_hash.has_key?(type.name)
15
- # been here, done that
16
- existing_type_hash
17
- else
18
- validate_type(type)
19
- find_types(type, existing_type_hash.dup)
20
- end
18
+ @result ||= find_types(type, existing_type_hash)
21
19
  end
22
20
 
23
21
  # Reduce all of `types` and return the combined result
24
22
  def self.find_all(types)
25
- types.reduce({}) do |memo, type|
23
+ type_map = GraphQL::Schema::TypeMap.new
24
+ types.reduce(type_map) do |memo, type|
26
25
  self.new(type, memo).result
27
26
  end
28
27
  end
@@ -33,21 +32,20 @@ class GraphQL::Schema::TypeReducer
33
32
  type_hash[type.name] = type
34
33
  if type.kind.fields?
35
34
  type.fields.each do |name, field|
36
-
37
- type_hash.merge!(reduce_type(field.type, type_hash))
35
+ reduce_type(field.type, type_hash)
38
36
  field.arguments.each do |name, argument|
39
- type_hash.merge!(reduce_type(argument.type, type_hash))
37
+ reduce_type(argument.type, type_hash)
40
38
  end
41
39
  end
42
40
  end
43
41
  if type.kind.object?
44
42
  type.interfaces.each do |interface|
45
- type_hash.merge!(reduce_type(interface, type_hash))
43
+ reduce_type(interface, type_hash)
46
44
  end
47
45
  end
48
46
  if type.kind.resolves?
49
47
  type.possible_types.each do |possible_type|
50
- type_hash.merge!(reduce_type(possible_type, type_hash))
48
+ reduce_type(possible_type, type_hash)
51
49
  end
52
50
  end
53
51
  type_hash
@@ -4,14 +4,14 @@ class GraphQL::StaticValidation::ArgumentsValidator
4
4
 
5
5
  def validate(context)
6
6
  visitor = context.visitor
7
- visitor[GraphQL::Language::Nodes::Field] << -> (node, parent) {
8
- return if context.skip_field?(node.name)
9
- field_defn = context.field_definition
10
- validate_node(node, field_defn, context)
11
- }
12
- visitor[GraphQL::Language::Nodes::Directive] << -> (node, parent) {
13
- directive_defn = context.schema.directives[node.name]
14
- validate_node(node, directive_defn, context)
7
+ visitor[GraphQL::Language::Nodes::Argument] << -> (node, parent) {
8
+ return if parent.is_a?(GraphQL::Language::Nodes::InputObject) || context.skip_field?(parent.name)
9
+ if parent.is_a?(GraphQL::Language::Nodes::Directive)
10
+ parent_defn = context.schema.directives[parent.name]
11
+ else
12
+ parent_defn = context.field_definition
13
+ end
14
+ validate_node(parent, node, parent_defn, context)
15
15
  }
16
16
  end
17
17
  end
@@ -1,13 +1,11 @@
1
1
  class GraphQL::StaticValidation::ArgumentLiteralsAreCompatible < GraphQL::StaticValidation::ArgumentsValidator
2
- def validate_node(node, defn, context)
3
- args_with_literals = node.arguments.select {|a| !a.value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)}
2
+ def validate_node(parent, node, defn, context)
3
+ return if node.value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
4
4
  validator = GraphQL::StaticValidation::LiteralValidator.new
5
- args_with_literals.each do |arg|
6
- arg_defn = defn.arguments[arg.name]
7
- valid = validator.validate(arg.value, arg_defn.type)
8
- if !valid
9
- context.errors << message("Argument #{arg.name} on #{node.class.name.split("::").last} '#{node.name}' has an invalid value", node)
10
- end
5
+ arg_defn = defn.arguments[node.name]
6
+ valid = validator.validate(node.value, arg_defn.type)
7
+ if !valid
8
+ context.errors << message("Argument #{node.name} on #{parent.class.name.split("::").last} '#{parent.name}' has an invalid value", parent)
11
9
  end
12
10
  end
13
11
  end
@@ -1,15 +1,11 @@
1
1
  class GraphQL::StaticValidation::ArgumentsAreDefined < GraphQL::StaticValidation::ArgumentsValidator
2
- def validate_node(node, defn, context)
3
- skip = nil
4
-
5
- node.arguments.each do |argument|
6
- argument_defn = defn.arguments[argument.name]
7
- if argument_defn.nil?
8
- context.errors << message("#{node.class.name.split("::").last} '#{node.name}' doesn't accept argument #{argument.name}", node)
9
- skip = GraphQL::Language::Visitor::SKIP
10
- end
2
+ def validate_node(parent, node, defn, context)
3
+ argument_defn = defn.arguments[node.name]
4
+ if argument_defn.nil?
5
+ context.errors << message("#{parent.class.name.split("::").last} '#{parent.name}' doesn't accept argument #{node.name}", parent)
6
+ GraphQL::Language::Visitor::SKIP
7
+ else
8
+ nil
11
9
  end
12
-
13
- skip
14
10
  end
15
11
  end
@@ -8,7 +8,7 @@ class GraphQL::StaticValidation::FieldsAreDefinedOnType
8
8
  visitor[GraphQL::Language::Nodes::Field] << -> (node, parent) {
9
9
  return if context.skip_field?(node.name)
10
10
  parent_type = context.object_types[-2]
11
- parent_type = parent_type.kind.unwrap(parent_type)
11
+ parent_type = parent_type.unwrap
12
12
  validate_field(context.errors, node, parent_type, parent)
13
13
  }
14
14
  end
@@ -14,7 +14,7 @@ class GraphQL::StaticValidation::FieldsHaveAppropriateSelections
14
14
  private
15
15
 
16
16
  def validate_field_selections(ast_field, field_defn, errors)
17
- resolved_type = field_defn.type.kind.unwrap(field_defn.type)
17
+ resolved_type = field_defn.type.unwrap
18
18
 
19
19
  if resolved_type.kind.scalar? && ast_field.selections.any?
20
20
  error = message("Selections can't be made on scalars (field '#{ast_field.name}' returns #{resolved_type.name} but has selections [#{ast_field.selections.map(&:name).join(", ")}])", ast_field)
@@ -15,7 +15,7 @@ class GraphQL::StaticValidation::FragmentTypesExist
15
15
  private
16
16
 
17
17
  def validate_type_exists(node, context)
18
- type = context.schema.types[node.type]
18
+ type = context.schema.types.fetch(node.type, nil)
19
19
  if type.nil?
20
20
  context.errors << message("No such type #{node.type}, so it can't be a fragment condition", node)
21
21
  GraphQL::Language::Visitor::SKIP
@@ -1,13 +1,34 @@
1
- class GraphQL::StaticValidation::RequiredArgumentsArePresent < GraphQL::StaticValidation::ArgumentsValidator
2
- def validate_node(node, defn, context)
3
- present_argument_names = node.arguments.map(&:name)
1
+ class GraphQL::StaticValidation::RequiredArgumentsArePresent
2
+ include GraphQL::StaticValidation::Message::MessageHelper
3
+
4
+ def validate(context)
5
+ v = context.visitor
6
+ v[GraphQL::Language::Nodes::Field] << -> (node, parent) { validate_field(node, context) }
7
+ v[GraphQL::Language::Nodes::Directive] << -> (node, parent) { validate_directive(node, context) }
8
+ end
9
+
10
+ private
11
+
12
+ def validate_directive(ast_directive, context)
13
+ directive_defn = context.schema.directives[ast_directive.name]
14
+ assert_required_args(ast_directive, directive_defn, context)
15
+ end
16
+
17
+ def validate_field(ast_field, context)
18
+ return if context.skip_field?(ast_field.name)
19
+ defn = context.field_definition
20
+ assert_required_args(ast_field, defn, context)
21
+ end
22
+
23
+ def assert_required_args(ast_node, defn, context)
24
+ present_argument_names = ast_node.arguments.map(&:name)
4
25
  required_argument_names = defn.arguments.values
5
26
  .select { |a| a.type.kind.non_null? }
6
27
  .map(&:name)
7
28
 
8
29
  missing_names = required_argument_names - present_argument_names
9
30
  if missing_names.any?
10
- context.errors << message("#{node.class.name.split("::").last} '#{node.name}' is missing required arguments: #{missing_names.join(", ")}", node)
31
+ context.errors << message("#{ast_node.class.name.split("::").last} '#{ast_node.name}' is missing required arguments: #{missing_names.join(", ")}", ast_node)
11
32
  end
12
33
  end
13
34
  end
@@ -32,8 +32,8 @@ class GraphQL::StaticValidation::VariableUsagesAreAllowed
32
32
  arg_defn = arguments[arg_node.name]
33
33
  arg_defn_type = arg_defn.type
34
34
 
35
- var_inner_type = var_type.kind.unwrap(var_type)
36
- arg_inner_type = arg_defn_type.kind.unwrap(arg_defn_type)
35
+ var_inner_type = var_type.unwrap
36
+ arg_inner_type = arg_defn_type.unwrap
37
37
 
38
38
  if var_inner_type != arg_inner_type
39
39
  context.errors << create_error("Type mismatch", var_type, ast_var, arg_defn, arg_node)
@@ -48,9 +48,9 @@ class GraphQL::StaticValidation::TypeStack
48
48
 
49
49
  class FragmentWithTypeStrategy
50
50
  def push(stack, node)
51
- object_type = stack.schema.types[node.type]
51
+ object_type = stack.schema.types.fetch(node.type, nil)
52
52
  if !object_type.nil?
53
- object_type = object_type.kind.unwrap(object_type)
53
+ object_type = object_type.unwrap
54
54
  end
55
55
  stack.object_types.push(object_type)
56
56
  end
@@ -77,7 +77,7 @@ class GraphQL::StaticValidation::TypeStack
77
77
  class FieldStrategy
78
78
  def push(stack, node)
79
79
  parent_type = stack.object_types.last
80
- parent_type = parent_type.kind.unwrap(parent_type)
80
+ parent_type = parent_type.unwrap
81
81
  if parent_type.kind.fields?
82
82
  field_class = stack.schema.get_field(parent_type, node.name)
83
83
  stack.field_definitions.push(field_class)
@@ -23,25 +23,6 @@ module GraphQL::TypeKinds
23
23
  def to_s; @name; end
24
24
  # Is this TypeKind composed of many values?
25
25
  def composite?; @composite; end
26
-
27
- # Get the implementing type for `value` from `type` (no-op for TypeKinds which don't `resolves?`)
28
- def resolve(type, value)
29
- if resolves?
30
- type.resolve_type(value)
31
- else
32
- type
33
- end
34
- end
35
-
36
- # Get the modified type for `type` (no-op for TypeKinds which don't `wraps?`)
37
- def unwrap(type)
38
- if wraps?
39
- wrapped_type = type.of_type
40
- wrapped_type.kind.unwrap(wrapped_type)
41
- else
42
- type
43
- end
44
- end
45
26
  end
46
27
 
47
28
  TYPE_KINDS = [
@@ -8,22 +8,12 @@
8
8
  # possible_types [DogType, CatType, FishType]
9
9
  # end
10
10
  #
11
- class GraphQL::UnionType
12
- include GraphQL::DefinitionHelpers::NonNullWithBang
13
- include GraphQL::DefinitionHelpers::DefinedByConfig
14
- attr_accessor :name, :description, :possible_types, :resolve_type
11
+ class GraphQL::UnionType < GraphQL::BaseType
12
+ include GraphQL::BaseType::HasPossibleTypes
13
+ attr_accessor :name, :description, :possible_types
15
14
  defined_by_config :name, :description, :possible_types, :resolve_type
16
15
 
17
16
  def kind
18
17
  GraphQL::TypeKinds::UNION
19
18
  end
20
-
21
- # @see {InterfaceType#resolve_type}
22
- def resolve_type(object)
23
- instance_exec(object, &@resolve_type_proc)
24
- end
25
-
26
- def resolve_type=(new_proc)
27
- @resolve_type_proc = new_proc || GraphQL::InterfaceType::DEFAULT_RESOLVE_TYPE
28
- end
29
19
  end
@@ -1,3 +1,3 @@
1
1
  module GraphQL
2
- VERSION = "0.7.1"
2
+ VERSION = "0.8.0"
3
3
  end
data/readme.md CHANGED
@@ -95,20 +95,9 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
95
95
  - if you were to request a field, then request it in a fragment, it would get looked up twice
96
96
  - https://github.com/graphql/graphql-js/issues/19#issuecomment-118515077
97
97
  - Code clean-up
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
101
98
  - Raise if you try to configure an attribute which doesn't suit the type
102
99
  - 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
100
  - 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
101
  - Write Ruby bindings for [libgraphqlparser](https://github.com/graphql/libgraphqlparser) and use that instead of Parslet
113
102
  - Add instrumentation
114
103
  - Some way to expose what queries are run, what types & fields are accessed, how long things are taking, etc
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe GraphQL::BaseType do
4
+ it 'becomes non-null with !' do
5
+ type = GraphQL::EnumType.new
6
+ non_null_type = !type
7
+ assert_equal(GraphQL::TypeKinds::NON_NULL, non_null_type.kind)
8
+ assert_equal(type, non_null_type.of_type)
9
+ assert_equal(GraphQL::TypeKinds::NON_NULL, (!GraphQL::STRING_TYPE).kind)
10
+ end
11
+
12
+ it 'can be compared' do
13
+ assert_equal(!GraphQL::INT_TYPE, !GraphQL::INT_TYPE)
14
+ refute_equal(!GraphQL::FLOAT_TYPE, GraphQL::FLOAT_TYPE)
15
+ assert_equal(
16
+ GraphQL::ListType.new(of_type: MilkType),
17
+ GraphQL::ListType.new(of_type: MilkType)
18
+ )
19
+ refute_equal(
20
+ GraphQL::ListType.new(of_type: MilkType),
21
+ GraphQL::ListType.new(of_type: !MilkType)
22
+ )
23
+ end
24
+ end