graphql 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graph_ql/directive.rb +9 -5
  3. data/lib/graph_ql/directives/include_directive.rb +2 -2
  4. data/lib/graph_ql/directives/skip_directive.rb +2 -2
  5. data/lib/graph_ql/enum.rb +5 -8
  6. data/lib/graph_ql/field.rb +50 -0
  7. data/lib/graph_ql/interface.rb +4 -0
  8. data/lib/graph_ql/introspection/arguments_field.rb +2 -2
  9. data/lib/graph_ql/introspection/directive_type.rb +10 -10
  10. data/lib/graph_ql/introspection/enum_value_type.rb +11 -8
  11. data/lib/graph_ql/introspection/enum_values_field.rb +5 -5
  12. data/lib/graph_ql/introspection/field_type.rb +14 -10
  13. data/lib/graph_ql/introspection/fields_field.rb +4 -4
  14. data/lib/graph_ql/introspection/input_fields_field.rb +3 -3
  15. data/lib/graph_ql/introspection/input_value_type.rb +8 -8
  16. data/lib/graph_ql/introspection/interfaces_field.rb +5 -0
  17. data/lib/graph_ql/introspection/of_type_field.rb +3 -9
  18. data/lib/graph_ql/introspection/possible_types_field.rb +3 -10
  19. data/lib/graph_ql/introspection/schema_type.rb +8 -14
  20. data/lib/graph_ql/introspection/type_kind_enum.rb +1 -1
  21. data/lib/graph_ql/introspection/type_type.rb +17 -19
  22. data/lib/graph_ql/introspection/typename_field.rb +15 -0
  23. data/lib/graph_ql/parser.rb +9 -0
  24. data/lib/graph_ql/parser/nodes.rb +17 -7
  25. data/lib/graph_ql/parser/parser.rb +7 -7
  26. data/lib/graph_ql/parser/transform.rb +23 -20
  27. data/lib/graph_ql/parser/visitor.rb +29 -13
  28. data/lib/graph_ql/query.rb +39 -16
  29. data/lib/graph_ql/query/field_resolution_strategy.rb +15 -11
  30. data/lib/graph_ql/query/type_resolver.rb +4 -2
  31. data/lib/graph_ql/repl.rb +1 -1
  32. data/lib/graph_ql/schema.rb +19 -7
  33. data/lib/graph_ql/schema/each_item_validator.rb +12 -0
  34. data/lib/graph_ql/schema/field_validator.rb +13 -0
  35. data/lib/graph_ql/schema/implementation_validator.rb +21 -0
  36. data/lib/graph_ql/schema/schema_validator.rb +10 -0
  37. data/lib/graph_ql/schema/type_reducer.rb +6 -6
  38. data/lib/graph_ql/schema/type_validator.rb +47 -0
  39. data/lib/graph_ql/static_validation.rb +18 -0
  40. data/lib/graph_ql/static_validation/argument_literals_are_compatible.rb +13 -0
  41. data/lib/graph_ql/static_validation/arguments_are_defined.rb +10 -0
  42. data/lib/graph_ql/static_validation/arguments_validator.rb +16 -0
  43. data/lib/graph_ql/static_validation/directives_are_defined.rb +18 -0
  44. data/lib/graph_ql/static_validation/fields_are_defined_on_type.rb +29 -0
  45. data/lib/graph_ql/static_validation/fields_have_appropriate_selections.rb +31 -0
  46. data/lib/graph_ql/static_validation/fields_will_merge.rb +93 -0
  47. data/lib/graph_ql/static_validation/fragment_types_exist.rb +24 -0
  48. data/lib/graph_ql/static_validation/fragments_are_used.rb +30 -0
  49. data/lib/graph_ql/static_validation/literal_validator.rb +27 -0
  50. data/lib/graph_ql/static_validation/message.rb +29 -0
  51. data/lib/graph_ql/static_validation/required_arguments_are_present.rb +13 -0
  52. data/lib/graph_ql/static_validation/type_stack.rb +87 -0
  53. data/lib/graph_ql/static_validation/validator.rb +48 -0
  54. data/lib/graph_ql/type_kinds.rb +50 -12
  55. data/lib/graph_ql/types/argument_definer.rb +7 -0
  56. data/lib/graph_ql/types/boolean_type.rb +3 -3
  57. data/lib/graph_ql/types/field_definer.rb +19 -0
  58. data/lib/graph_ql/types/float_type.rb +3 -3
  59. data/lib/graph_ql/types/input_object_type.rb +4 -0
  60. data/lib/graph_ql/types/input_value.rb +1 -1
  61. data/lib/graph_ql/types/int_type.rb +4 -4
  62. data/lib/graph_ql/types/list_type.rb +5 -1
  63. data/lib/graph_ql/types/non_null_type.rb +4 -0
  64. data/lib/graph_ql/types/object_type.rb +9 -20
  65. data/lib/graph_ql/types/scalar_type.rb +4 -0
  66. data/lib/graph_ql/types/string_type.rb +3 -3
  67. data/lib/graph_ql/types/type_definer.rb +5 -9
  68. data/lib/graph_ql/union.rb +6 -17
  69. data/lib/graph_ql/version.rb +1 -1
  70. data/lib/graphql.rb +58 -78
  71. data/readme.md +80 -7
  72. data/spec/graph_ql/interface_spec.rb +15 -1
  73. data/spec/graph_ql/introspection/directive_type_spec.rb +2 -2
  74. data/spec/graph_ql/introspection/schema_type_spec.rb +2 -1
  75. data/spec/graph_ql/introspection/type_type_spec.rb +16 -1
  76. data/spec/graph_ql/parser/parser_spec.rb +3 -1
  77. data/spec/graph_ql/parser/transform_spec.rb +12 -2
  78. data/spec/graph_ql/parser/visitor_spec.rb +13 -5
  79. data/spec/graph_ql/query_spec.rb +25 -13
  80. data/spec/graph_ql/schema/field_validator_spec.rb +21 -0
  81. data/spec/graph_ql/schema/type_reducer_spec.rb +2 -2
  82. data/spec/graph_ql/schema/type_validator_spec.rb +54 -0
  83. data/spec/graph_ql/static_validation/argument_literals_are_compatible_spec.rb +41 -0
  84. data/spec/graph_ql/static_validation/arguments_are_defined_spec.rb +40 -0
  85. data/spec/graph_ql/static_validation/directives_are_defined_spec.rb +33 -0
  86. data/spec/graph_ql/static_validation/fields_are_defined_on_type_spec.rb +59 -0
  87. data/spec/graph_ql/static_validation/fields_have_appropriate_selections_spec.rb +30 -0
  88. data/spec/graph_ql/{validations → static_validation}/fields_will_merge_spec.rb +24 -17
  89. data/spec/graph_ql/static_validation/fragment_types_exist_spec.rb +38 -0
  90. data/spec/graph_ql/static_validation/fragments_are_used_spec.rb +24 -0
  91. data/spec/graph_ql/static_validation/required_arguments_are_present_spec.rb +41 -0
  92. data/spec/graph_ql/static_validation/type_stack_spec.rb +35 -0
  93. data/spec/graph_ql/static_validation/validator_spec.rb +28 -0
  94. data/spec/graph_ql/types/object_type_spec.rb +1 -1
  95. data/spec/graph_ql/union_spec.rb +1 -14
  96. data/spec/support/dummy_app.rb +75 -53
  97. metadata +53 -31
  98. data/lib/graph_ql/fields/abstract_field.rb +0 -37
  99. data/lib/graph_ql/fields/access_field.rb +0 -24
  100. data/lib/graph_ql/fields/field.rb +0 -34
  101. data/lib/graph_ql/types/abstract_type.rb +0 -14
  102. data/lib/graph_ql/validations/fields_are_defined_on_type.rb +0 -44
  103. data/lib/graph_ql/validations/fields_will_merge.rb +0 -80
  104. data/lib/graph_ql/validations/fragments_are_used.rb +0 -24
  105. data/lib/graph_ql/validator.rb +0 -29
  106. data/spec/graph_ql/validations/fields_are_defined_on_type_spec.rb +0 -28
  107. data/spec/graph_ql/validations/fragments_are_used_spec.rb +0 -28
  108. data/spec/graph_ql/validator_spec.rb +0 -24
@@ -0,0 +1,15 @@
1
+ # A wrapper to create `__typename`.
2
+ # Uses `.create` because I couldn't figure out how to
3
+ # pass `DEFINITION` via `super` (then I could extend GraphQL::Field)
4
+ class GraphQL::Introspection::TypenameField
5
+ DEFINITION = Proc.new { |f, wrapped_type|
6
+ f.name "__typename"
7
+ f.description "The name of this type"
8
+ f.type -> { !GraphQL::STRING_TYPE }
9
+ f.resolve -> (obj, a, c) { wrapped_type.name }
10
+ }
11
+
12
+ def self.create(wrapped_type)
13
+ GraphQL::Field.new { |f| DEFINITION.call(f, wrapped_type) }
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ require 'graph_ql/parser/nodes'
2
+ require 'graph_ql/parser/parser'
3
+ require 'graph_ql/parser/transform'
4
+ require 'graph_ql/parser/visitor'
5
+
6
+ module GraphQL
7
+ PARSER = GraphQL::Parser.new
8
+ TRANSFORM = GraphQL::Transform.new
9
+ end
@@ -3,20 +3,30 @@ module GraphQL::Nodes
3
3
  # - require their keyword arguments, throw ArgumentError if they don't match
4
4
  # - expose accessors for keyword arguments
5
5
  class AbstractNode
6
+ attr_accessor :line, :col
6
7
  def initialize(options)
7
8
  required_keys = self.class.required_attrs
9
+ allowed_keys = required_keys + [:line, :col]
10
+ position_source = options.delete(:position_source)
11
+ if !position_source.nil?
12
+ options[:line], options[:col] = position_source.line_and_column
13
+ end
8
14
 
9
- extra_keys = options.keys - required_keys
15
+ present_keys = options.keys
16
+ extra_keys = present_keys - allowed_keys
10
17
  if extra_keys.any?
11
18
  raise ArgumentError, "#{self.class.name} Extra arguments: #{extra_keys}"
12
19
  end
13
20
 
14
- required_keys.each do |attr|
15
- if !options.has_key?(attr)
16
- raise ArgumentError, "#{self.class.name} Missing argument: #{attr}"
17
- else
18
- value = options[attr]
19
- self.send("#{attr}=", value)
21
+ missing_keys = required_keys - present_keys
22
+ if missing_keys.any?
23
+ raise ArgumentError, "#{self.class.name} Missing arguments: #{missing_keys}"
24
+ end
25
+
26
+ allowed_keys.each do |key|
27
+ if options.has_key?(key)
28
+ value = options[key]
29
+ self.send("#{key}=", value)
20
30
  end
21
31
  end
22
32
  end
@@ -12,7 +12,7 @@ class GraphQL::Parser < Parslet::Parser
12
12
 
13
13
  # TODO: whitespace sensitive regarding `on`, eg `onFood`, see lookahead note in spec
14
14
  rule(:fragment_definition) {
15
- str("fragment") >>
15
+ str("fragment").as(:fragment_keyword) >>
16
16
  space? >> name.as(:fragment_name) >>
17
17
  space? >> str("on") >> space? >> name.as(:type_condition) >>
18
18
  space? >> directives.maybe.as(:optional_directives).as(:directives) >>
@@ -20,14 +20,14 @@ class GraphQL::Parser < Parslet::Parser
20
20
  }
21
21
 
22
22
  rule(:fragment_spread) {
23
- str("...") >> space? >>
23
+ spread.as(:fragment_spread_keyword) >> space? >>
24
24
  name.as(:fragment_spread_name) >> space? >>
25
25
  directives.maybe.as(:optional_directives).as(:directives)
26
26
  }
27
-
27
+ rule(:spread) { str("...") }
28
28
  # TODO: `on` bug, see spec
29
29
  rule(:inline_fragment) {
30
- str("...") >> space? >>
30
+ spread.as(:fragment_spread_keyword) >> space? >>
31
31
  str("on ") >> name.as(:inline_fragment_type) >> space? >>
32
32
  directives.maybe.as(:optional_directives).as(:directives) >> space? >>
33
33
  selections.as(:selections)
@@ -37,7 +37,7 @@ class GraphQL::Parser < Parslet::Parser
37
37
  rule(:unnamed_selections) { selections.as(:unnamed_selections)}
38
38
  rule(:named_operation_definition) {
39
39
  operation_type.as(:operation_type) >> space? >>
40
- name.as(:name) >>
40
+ name.as(:name) >> space? >>
41
41
  operation_variable_definitions.maybe.as(:optional_variables).as(:variables) >> space? >>
42
42
  directives.maybe.as(:optional_directives).as(:directives) >> space? >>
43
43
  selections.as(:selections)
@@ -88,10 +88,10 @@ class GraphQL::Parser < Parslet::Parser
88
88
  value_variable |
89
89
  value_enum
90
90
  )}
91
- rule(:value_sign?) { str("-").maybe }
91
+ rule(:value_sign?) { match('[\-\+]').maybe }
92
92
  rule(:value_array) { (str("[") >> (value >> separator?).repeat(0) >> str("]")).as(:array) }
93
93
  rule(:value_boolean) { (str("true") | str("false")).as(:boolean) }
94
- rule(:value_float) { (value_sign? >> match('\d').repeat(1) >> str(".") >> match('\d').repeat(1) >> (str("e") >> value_sign? >> match('\d').repeat(1)).maybe).as(:float) }
94
+ rule(:value_float) { (value_sign? >> match('\d').repeat(1) >> str(".") >> match('\d').repeat(1) >> (match("[eE]") >> value_sign? >> match('\d').repeat(1)).maybe).as(:float) }
95
95
  rule(:value_input_object) { str("{") >> value_input_object_pair.repeat(1).as(:input_object) >> str("}") }
96
96
  rule(:value_input_object_pair) { space? >> name.as(:input_object_name) >> space? >> str(":") >> space? >> value.as(:input_object_value) >> separator? }
97
97
  rule(:value_int) { (value_sign? >> match('\d').repeat(1)).as(:int) }
@@ -11,26 +11,29 @@ class GraphQL::Transform < Parslet::Transform
11
11
  end
12
12
 
13
13
  # Document
14
- rule(document_parts: sequence(:p)) { Document.new(parts: p)}
14
+ rule(document_parts: sequence(:p)) { Document.new(parts: p, line: p.first.line, col: p.first.col)}
15
15
 
16
16
  # Fragment Definition
17
17
  rule(
18
+ fragment_keyword: simple(:kw),
18
19
  fragment_name: simple(:name),
19
20
  type_condition: simple(:type),
20
21
  directives: sequence(:directives),
21
22
  selections: sequence(:selections)
22
- ) {FragmentDefinition.new(name: name.to_s, type: type.to_s, directives: directives, selections: selections)}
23
+ ) {FragmentDefinition.new(name: name.to_s, type: type.to_s, directives: directives, selections: selections, position_source: kw)}
23
24
 
24
25
  rule(
26
+ fragment_spread_keyword: simple(:kw),
25
27
  fragment_spread_name: simple(:n),
26
28
  directives: sequence(:d)
27
- ) { FragmentSpread.new(name: n.to_s, directives: d)}
29
+ ) { FragmentSpread.new(name: n.to_s, directives: d, position_source: kw)}
28
30
 
29
31
  rule(
32
+ fragment_spread_keyword: simple(:kw),
30
33
  inline_fragment_type: simple(:n),
31
34
  directives: sequence(:d),
32
35
  selections: sequence(:s),
33
- ) { InlineFragment.new(type: n.to_s, directives: d, selections: s)}
36
+ ) { InlineFragment.new(type: n.to_s, directives: d, selections: s, position_source: kw)}
34
37
 
35
38
  # Operation Definition
36
39
  rule(
@@ -39,14 +42,14 @@ class GraphQL::Transform < Parslet::Transform
39
42
  variables: sequence(:v),
40
43
  directives: sequence(:d),
41
44
  selections: sequence(:s),
42
- ) { OperationDefinition.new(operation_type: ot.to_s, name: n.to_s, variables: v, directives: d, selections: s) }
45
+ ) { OperationDefinition.new(operation_type: ot.to_s, name: n.to_s, variables: v, directives: d, selections: s, position_source: ot) }
43
46
  optional_sequence(:optional_variables)
44
- rule(variable_name: simple(:n), variable_type: simple(:t), variable_optional_default_value: simple(:v)) { Variable.new(name: n.name, type: t, default_value: v)}
45
- rule(variable_name: simple(:n), variable_type: simple(:t), variable_optional_default_value: sequence(:v)) { Variable.new(name: n.name, type: t, default_value: v)}
47
+ rule(variable_name: simple(:n), variable_type: simple(:t), variable_optional_default_value: simple(:v)) { Variable.new(name: n.name, type: t, default_value: v, line: n.line, col: n.col)}
48
+ rule(variable_name: simple(:n), variable_type: simple(:t), variable_optional_default_value: sequence(:v)) { Variable.new(name: n.name, type: t, default_value: v, line: n.line, col: n.col)}
46
49
  rule(variable_default_value: simple(:v) ) { v }
47
50
  rule(variable_default_value: sequence(:v) ) { v }
48
51
  # Query short-hand
49
- rule(unnamed_selections: sequence(:s)) { OperationDefinition.new(selections: s, operation_type: "query", name: nil, variables: [], directives: [])}
52
+ rule(unnamed_selections: sequence(:s)) { OperationDefinition.new(selections: s, operation_type: "query", name: nil, variables: [], directives: [], line: s.first.line, col: s.first.col)}
50
53
 
51
54
  # Field
52
55
  rule(
@@ -55,32 +58,32 @@ class GraphQL::Transform < Parslet::Transform
55
58
  field_arguments: sequence(:args),
56
59
  directives: sequence(:dir),
57
60
  selections: sequence(:sel)
58
- ) { Field.new(alias: a, name: name.to_s, arguments: args, directives: dir, selections: sel) }
61
+ ) { Field.new(alias: a && a.to_s, name: name.to_s, arguments: args, directives: dir, selections: sel, position_source: [a, name].find { |part| !part.nil? }) }
59
62
 
60
- rule(alias_name: simple(:a)) { a.to_s }
63
+ rule(alias_name: simple(:a)) { a }
61
64
  optional_sequence(:optional_field_arguments)
62
- rule(field_argument_name: simple(:n), field_argument_value: simple(:v)) { Argument.new(name: n.to_s, value: v)}
65
+ rule(field_argument_name: simple(:n), field_argument_value: simple(:v)) { Argument.new(name: n.to_s, value: v, position_source: n)}
63
66
  optional_sequence(:optional_selections)
64
67
  optional_sequence(:optional_directives)
65
68
 
66
69
  # Directive
67
- rule(directive_name: simple(:name), directive_arguments: sequence(:args)) { Directive.new(name: name.to_s, arguments: args) }
68
- rule(directive_argument_name: simple(:n), directive_argument_value: simple(:v)) { Argument.new(name: n.to_s, value: v)}
70
+ rule(directive_name: simple(:name), directive_arguments: sequence(:args)) { Directive.new(name: name.to_s, arguments: args, position_source: name ) }
71
+ rule(directive_argument_name: simple(:n), directive_argument_value: simple(:v)) { Argument.new(name: n.to_s, value: v, position_source: n)}
69
72
  optional_sequence(:optional_directive_arguments)
70
73
 
71
74
  # Type Defs
72
- rule(type_name: simple(:n)) { TypeName.new(name: n.to_s) }
73
- rule(list_type: simple(:t)) { ListType.new(of_type: t)}
74
- rule(non_null_type: simple(:t)) { NonNullType.new(of_type: t)}
75
+ rule(type_name: simple(:n)) { TypeName.new(name: n.to_s, position_source: n) }
76
+ rule(list_type: simple(:t)) { ListType.new(of_type: t, line: t.line, col: t.col)}
77
+ rule(non_null_type: simple(:t)) { NonNullType.new(of_type: t, line: t.line, col: t.col)}
75
78
 
76
79
  # Values
77
80
  rule(array: sequence(:v)) { v }
78
81
  rule(boolean: simple(:v)) { v == "true" ? true : false }
79
- rule(input_object: sequence(:v)) { InputObject.new(pairs: v) }
80
- rule(input_object_name: simple(:n), input_object_value: simple(:v)) { Argument.new(name: n.to_s, value: v)}
82
+ rule(input_object: sequence(:v)) { InputObject.new(pairs: v, line: v.first.line, col: v.first.col) }
83
+ rule(input_object_name: simple(:n), input_object_value: simple(:v)) { Argument.new(name: n.to_s, value: v, position_source: n)}
81
84
  rule(int: simple(:v)) { v.to_i }
82
85
  rule(float: simple(:v)) { v.to_f }
83
86
  rule(string: simple(:v)) { v.to_s }
84
- rule(variable: simple(:v)) { VariableIdentifier.new(name: v.to_s) }
85
- rule(enum: simple(:v)) { Enum.new(name: v.to_s)}
87
+ rule(variable: simple(:v)) { VariableIdentifier.new(name: v.to_s, position_source: v) }
88
+ rule(enum: simple(:v)) { Enum.new(name: v.to_s, position_source: v)}
86
89
  end
@@ -9,8 +9,13 @@
9
9
  # # => 6
10
10
  #
11
11
  class GraphQL::Visitor
12
+ SKIP = :_skip
13
+
14
+ attr_reader :enter, :leave
12
15
  def initialize
13
16
  @visitors = {}
17
+ @enter = []
18
+ @leave = []
14
19
  end
15
20
 
16
21
  def [](node_class)
@@ -18,11 +23,30 @@ class GraphQL::Visitor
18
23
  end
19
24
 
20
25
  # Apply built-up vistors to `document`
21
- def visit(root)
22
- node_visitor = self[root.class]
23
- node_visitor.begin_visit(root)
24
- root.children.map { |child| visit(child) }
25
- node_visitor.end_visit(root)
26
+ def visit(root, parent=nil)
27
+ begin_visit(root, parent) &&
28
+ root.children.reduce(true) { |memo, child| memo && visit(child, root) }
29
+ end_visit(root, parent)
30
+ end
31
+
32
+ private
33
+
34
+ def begin_visit(node, parent)
35
+ self.class.apply_hooks(enter, node, parent)
36
+ node_visitor = self[node.class]
37
+ self.class.apply_hooks(node_visitor.enter, node, parent)
38
+ end
39
+
40
+ # Should global `leave` visitors come first or last?
41
+ def end_visit(node, parent)
42
+ self.class.apply_hooks(leave, node, parent)
43
+ node_visitor = self[node.class]
44
+ self.class.apply_hooks(node_visitor.leave, node, parent)
45
+ end
46
+
47
+ # If one of the visitors returns SKIP, stop visiting this node
48
+ def self.apply_hooks(hooks, node, parent)
49
+ hooks.reduce(true) { |memo, proc| memo && (proc.call(node, parent) != SKIP) }
26
50
  end
27
51
 
28
52
  class NodeVisitor
@@ -35,13 +59,5 @@ class GraphQL::Visitor
35
59
  def <<(hook)
36
60
  enter << hook
37
61
  end
38
-
39
- def begin_visit(node)
40
- enter.map{ |proc| proc.call(node) }
41
- end
42
-
43
- def end_visit(node)
44
- leave.map{ |proc| proc.call(node) }
45
- end
46
62
  end
47
63
  end
@@ -1,23 +1,21 @@
1
1
  class GraphQL::Query
2
+ # If a resolve function returns `GraphQL::Query::DEFAULT_RESOLVE`,
3
+ # The executor will send the field's name to the target object
4
+ # and use the result.
2
5
  DEFAULT_RESOLVE = :__default_resolve
3
- extend ActiveSupport::Autoload
4
- autoload(:Arguments)
5
- autoload(:FieldResolutionStrategy)
6
- autoload(:FragmentSpreadResolutionStrategy)
7
- autoload(:InlineFragmentResolutionStrategy)
8
- autoload(:OperationResolver)
9
- autoload(:SelectionResolver)
10
- autoload(:TypeResolver)
11
6
  attr_reader :schema, :document, :context, :fragments, :params
12
7
 
13
- def initialize(schema, query_string, context: nil, params: {})
8
+ def initialize(schema, query_string, context: nil, params: {}, debug: true, validate: true)
14
9
  @schema = schema
15
- @document = GraphQL.parse(query_string)
10
+ @debug = debug
11
+ @query_string = query_string
16
12
  @context = context
17
13
  @params = params
14
+ @validate = validate
18
15
  @fragments = {}
19
16
  @operations = {}
20
17
 
18
+ @document = GraphQL.parse(@query_string)
21
19
  @document.parts.each do |part|
22
20
  if part.is_a?(GraphQL::Nodes::FragmentDefinition)
23
21
  @fragments[part.name] = part
@@ -29,22 +27,47 @@ class GraphQL::Query
29
27
 
30
28
  # Get the result for this query, executing it once
31
29
  def result
30
+ if validation_errors.any?
31
+ return { "errors" => validation_errors }
32
+ end
33
+
32
34
  @result ||= {
33
35
  "data" => execute,
34
36
  }
35
37
  rescue StandardError => err
36
- message = "Something went wrong during query execution: #{err}"
37
- {"errors" => [{"message" => message}]}
38
+ if @debug
39
+ raise err
40
+ else
41
+ message = "Something went wrong during query execution: #{err}" # \n #{err.backtrace.join("\n ")}"
42
+ {"errors" => [{"message" => message}]}
43
+ end
38
44
  end
39
45
 
40
46
  private
41
47
 
42
48
  def execute
43
- response = {}
44
- @operations.each do |name, operation|
49
+ @operations.reduce({}) do |memo, (name, operation)|
45
50
  resolver = OperationResolver.new(operation, self)
46
- response[name] = resolver.result
51
+ memo[name] = resolver.result
52
+ memo
53
+ end
54
+ end
55
+
56
+ def validation_errors
57
+ @validation_errors ||= begin
58
+ if @validate
59
+ @schema.static_validator.validate(@document)
60
+ else
61
+ []
62
+ end
47
63
  end
48
- response
49
64
  end
50
65
  end
66
+
67
+ require 'graph_ql/query/arguments'
68
+ require 'graph_ql/query/field_resolution_strategy'
69
+ require 'graph_ql/query/fragment_spread_resolution_strategy'
70
+ require 'graph_ql/query/inline_fragment_resolution_strategy'
71
+ require 'graph_ql/query/operation_resolver'
72
+ require 'graph_ql/query/selection_resolver'
73
+ require 'graph_ql/query/type_resolver'
@@ -12,18 +12,23 @@ class GraphQL::Query::FieldResolutionStrategy
12
12
  if value == GraphQL::Query::DEFAULT_RESOLVE
13
13
  begin
14
14
  value = target.send(field_name)
15
- rescue NoMethodError => e
16
- raise("Couldn't resolve field '#{field_name}' to #{target.class} '#{target}' (resulted in NoMethodError)")
15
+ rescue NoMethodError => err
16
+ raise("Couldn't resolve field '#{field_name}' to #{target.class} '#{target}' (resulted in #{err})")
17
17
  end
18
18
  end
19
- strategy_class = FIELD_TYPE_KIND_STRATEGIES[field.type.kind] || raise("No strategy found for #{field.type.kind}")
20
- result_strategy = strategy_class.new(value, field.type, target, parent_type, ast_field, operation_resolver)
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)
21
22
  @result_value = result_strategy.result
22
23
  end
23
24
  result_name = ast_field.alias || ast_field.name
24
25
  @result = { result_name => @result_value}
25
26
  end
26
27
 
28
+ def self.get_strategy_for_kind(kind)
29
+ FIELD_TYPE_KIND_STRATEGIES[kind] || raise("No strategy for #{kind}")
30
+ end
31
+
27
32
  class ScalarResolutionStrategy
28
33
  attr_reader :result
29
34
  def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
@@ -35,9 +40,10 @@ class GraphQL::Query::FieldResolutionStrategy
35
40
  attr_reader :result
36
41
  def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
37
42
  wrapped_type = field_type.of_type
38
- strategy_class = FIELD_TYPE_KIND_STRATEGIES[wrapped_type.kind]
43
+ resolved_type = wrapped_type.kind.resolve(wrapped_type, value)
44
+ strategy_class = GraphQL::Query::FieldResolutionStrategy.get_strategy_for_kind(resolved_type.kind)
39
45
  @result = value.map do |item|
40
- inner_strategy = strategy_class.new(item, wrapped_type, target, parent_type, ast_field, operation_resolver)
46
+ inner_strategy = strategy_class.new(item, resolved_type, target, parent_type, ast_field, operation_resolver)
41
47
  inner_strategy.result
42
48
  end
43
49
  end
@@ -51,7 +57,6 @@ class GraphQL::Query::FieldResolutionStrategy
51
57
  end
52
58
  end
53
59
 
54
-
55
60
  class EnumResolutionStrategy
56
61
  attr_reader :result
57
62
  def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
@@ -63,8 +68,9 @@ class GraphQL::Query::FieldResolutionStrategy
63
68
  attr_reader :result
64
69
  def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
65
70
  wrapped_type = field_type.of_type
66
- strategy_class = FIELD_TYPE_KIND_STRATEGIES[wrapped_type.kind]
67
- inner_strategy = strategy_class.new(value, wrapped_type, target, parent_type, ast_field, operation_resolver)
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)
68
74
  @result = inner_strategy.result
69
75
  end
70
76
  end
@@ -75,8 +81,6 @@ class GraphQL::Query::FieldResolutionStrategy
75
81
  GraphQL::TypeKinds::SCALAR => ScalarResolutionStrategy,
76
82
  GraphQL::TypeKinds::LIST => ListResolutionStrategy,
77
83
  GraphQL::TypeKinds::OBJECT => ObjectResolutionStrategy,
78
- GraphQL::TypeKinds::UNION => ObjectResolutionStrategy,
79
- GraphQL::TypeKinds::INTERFACE => ObjectResolutionStrategy,
80
84
  GraphQL::TypeKinds::ENUM => EnumResolutionStrategy,
81
85
  GraphQL::TypeKinds::NON_NULL => NonNullResolutionStrategy,
82
86
  }
@@ -6,9 +6,11 @@ class GraphQL::Query::TypeResolver
6
6
  def initialize(target, child_type, parent_type)
7
7
  @type = if child_type.nil?
8
8
  nil
9
- elsif GraphQL::TypeKinds::UNION == parent_type.kind
9
+ elsif parent_type.kind.union?
10
10
  parent_type.resolve_type(target)
11
- elsif GraphQL::TypeKinds::INTERFACE == child_type.kind
11
+ elsif child_type.kind.union? && child_type.include?(parent_type)
12
+ parent_type
13
+ elsif child_type.kind.interface?
12
14
  child_type.resolve_type(target)
13
15
  elsif child_type == parent_type
14
16
  parent_type
@@ -19,7 +19,7 @@ class GraphQL::Repl
19
19
  def execute_query(query_string)
20
20
  begin
21
21
  query = GraphQL::Query.new(@schema, query_string)
22
- puts JSON.pretty_generate(query.execute)
22
+ puts JSON.pretty_generate(query.result)
23
23
  rescue StandardError => err
24
24
  puts "Couldn't parse: #{err}\n\n" # #{err.backtrace.join("\n")}"
25
25
  end