graphql 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graph_ql/directive.rb +36 -0
  3. data/lib/graph_ql/directives/directive_chain.rb +33 -0
  4. data/lib/graph_ql/directives/include_directive.rb +15 -0
  5. data/lib/graph_ql/directives/skip_directive.rb +15 -0
  6. data/lib/graph_ql/enum.rb +34 -0
  7. data/lib/graph_ql/fields/abstract_field.rb +37 -0
  8. data/lib/graph_ql/fields/access_field.rb +24 -0
  9. data/lib/graph_ql/fields/field.rb +34 -0
  10. data/lib/graph_ql/interface.rb +14 -0
  11. data/lib/graph_ql/introspection/arguments_field.rb +5 -0
  12. data/lib/graph_ql/introspection/directive_type.rb +12 -0
  13. data/lib/graph_ql/introspection/enum_value_type.rb +10 -0
  14. data/lib/graph_ql/introspection/enum_values_field.rb +15 -0
  15. data/lib/graph_ql/introspection/field_type.rb +11 -0
  16. data/lib/graph_ql/introspection/fields_field.rb +14 -0
  17. data/lib/graph_ql/introspection/input_fields_field.rb +12 -0
  18. data/lib/graph_ql/introspection/input_value_type.rb +10 -0
  19. data/lib/graph_ql/introspection/of_type_field.rb +12 -0
  20. data/lib/graph_ql/introspection/possible_types_field.rb +12 -0
  21. data/lib/graph_ql/introspection/schema_type.rb +32 -0
  22. data/lib/graph_ql/introspection/type_kind_enum.rb +7 -0
  23. data/lib/graph_ql/introspection/type_type.rb +22 -0
  24. data/lib/graph_ql/parser/nodes.rb +72 -0
  25. data/lib/graph_ql/parser/parser.rb +108 -0
  26. data/lib/graph_ql/parser/transform.rb +86 -0
  27. data/lib/graph_ql/parser/visitor.rb +47 -0
  28. data/lib/graph_ql/query.rb +50 -0
  29. data/lib/graph_ql/query/arguments.rb +25 -0
  30. data/lib/graph_ql/query/field_resolution_strategy.rb +83 -0
  31. data/lib/graph_ql/query/fragment_spread_resolution_strategy.rb +16 -0
  32. data/lib/graph_ql/query/inline_fragment_resolution_strategy.rb +14 -0
  33. data/lib/graph_ql/query/operation_resolver.rb +28 -0
  34. data/lib/graph_ql/query/selection_resolver.rb +20 -0
  35. data/lib/graph_ql/query/type_resolver.rb +19 -0
  36. data/lib/graph_ql/repl.rb +27 -0
  37. data/lib/graph_ql/schema.rb +30 -0
  38. data/lib/graph_ql/schema/type_reducer.rb +44 -0
  39. data/lib/graph_ql/type_kinds.rb +15 -0
  40. data/lib/graph_ql/types/abstract_type.rb +14 -0
  41. data/lib/graph_ql/types/boolean_type.rb +6 -0
  42. data/lib/graph_ql/types/float_type.rb +6 -0
  43. data/lib/graph_ql/types/input_object_type.rb +17 -0
  44. data/lib/graph_ql/types/input_value.rb +10 -0
  45. data/lib/graph_ql/types/int_type.rb +6 -0
  46. data/lib/graph_ql/types/list_type.rb +10 -0
  47. data/lib/graph_ql/types/non_null_type.rb +18 -0
  48. data/lib/graph_ql/types/non_null_with_bang.rb +5 -0
  49. data/lib/graph_ql/types/object_type.rb +62 -0
  50. data/lib/graph_ql/types/scalar_type.rb +5 -0
  51. data/lib/graph_ql/types/string_type.rb +6 -0
  52. data/lib/graph_ql/types/type_definer.rb +16 -0
  53. data/lib/graph_ql/union.rb +35 -0
  54. data/lib/graph_ql/validations/fields_are_defined_on_type.rb +44 -0
  55. data/lib/graph_ql/validations/fields_will_merge.rb +80 -0
  56. data/lib/graph_ql/validations/fragments_are_used.rb +24 -0
  57. data/lib/graph_ql/validator.rb +29 -0
  58. data/lib/graph_ql/version.rb +3 -0
  59. data/lib/graphql.rb +92 -99
  60. data/readme.md +17 -177
  61. data/spec/graph_ql/directive_spec.rb +81 -0
  62. data/spec/graph_ql/enum_spec.rb +5 -0
  63. data/spec/graph_ql/fields/field_spec.rb +10 -0
  64. data/spec/graph_ql/interface_spec.rb +13 -0
  65. data/spec/graph_ql/introspection/directive_type_spec.rb +40 -0
  66. data/spec/graph_ql/introspection/schema_type_spec.rb +39 -0
  67. data/spec/graph_ql/introspection/type_type_spec.rb +104 -0
  68. data/spec/graph_ql/parser/parser_spec.rb +120 -0
  69. data/spec/graph_ql/parser/transform_spec.rb +109 -0
  70. data/spec/graph_ql/parser/visitor_spec.rb +31 -0
  71. data/spec/graph_ql/query/operation_resolver_spec.rb +14 -0
  72. data/spec/graph_ql/query_spec.rb +82 -0
  73. data/spec/graph_ql/schema/type_reducer_spec.rb +24 -0
  74. data/spec/graph_ql/types/input_object_type_spec.rb +12 -0
  75. data/spec/graph_ql/types/object_type_spec.rb +35 -0
  76. data/spec/graph_ql/union_spec.rb +27 -0
  77. data/spec/graph_ql/validations/fields_are_defined_on_type_spec.rb +28 -0
  78. data/spec/graph_ql/validations/fields_will_merge_spec.rb +40 -0
  79. data/spec/graph_ql/validations/fragments_are_used_spec.rb +28 -0
  80. data/spec/graph_ql/validator_spec.rb +24 -0
  81. data/spec/spec_helper.rb +2 -2
  82. data/spec/support/dummy_app.rb +123 -63
  83. data/spec/support/dummy_data.rb +11 -0
  84. metadata +107 -59
  85. data/lib/graphql/call.rb +0 -8
  86. data/lib/graphql/connection.rb +0 -65
  87. data/lib/graphql/field.rb +0 -12
  88. data/lib/graphql/field_definer.rb +0 -25
  89. data/lib/graphql/introspection/call_type.rb +0 -13
  90. data/lib/graphql/introspection/connection.rb +0 -9
  91. data/lib/graphql/introspection/field_type.rb +0 -10
  92. data/lib/graphql/introspection/root_call_argument_node.rb +0 -5
  93. data/lib/graphql/introspection/root_call_type.rb +0 -20
  94. data/lib/graphql/introspection/schema_call.rb +0 -8
  95. data/lib/graphql/introspection/schema_type.rb +0 -17
  96. data/lib/graphql/introspection/type_call.rb +0 -8
  97. data/lib/graphql/introspection/type_type.rb +0 -18
  98. data/lib/graphql/node.rb +0 -244
  99. data/lib/graphql/parser/parser.rb +0 -39
  100. data/lib/graphql/parser/transform.rb +0 -22
  101. data/lib/graphql/query.rb +0 -109
  102. data/lib/graphql/root_call.rb +0 -202
  103. data/lib/graphql/root_call_argument.rb +0 -11
  104. data/lib/graphql/root_call_argument_definer.rb +0 -17
  105. data/lib/graphql/schema/all.rb +0 -46
  106. data/lib/graphql/schema/schema.rb +0 -87
  107. data/lib/graphql/schema/schema_validation.rb +0 -32
  108. data/lib/graphql/syntax/call.rb +0 -8
  109. data/lib/graphql/syntax/field.rb +0 -9
  110. data/lib/graphql/syntax/fragment.rb +0 -7
  111. data/lib/graphql/syntax/node.rb +0 -8
  112. data/lib/graphql/syntax/query.rb +0 -8
  113. data/lib/graphql/syntax/variable.rb +0 -7
  114. data/lib/graphql/types/boolean_type.rb +0 -3
  115. data/lib/graphql/types/number_type.rb +0 -3
  116. data/lib/graphql/types/object_type.rb +0 -6
  117. data/lib/graphql/types/string_type.rb +0 -3
  118. data/lib/graphql/version.rb +0 -3
  119. data/spec/graphql/node_spec.rb +0 -69
  120. data/spec/graphql/parser/parser_spec.rb +0 -168
  121. data/spec/graphql/parser/transform_spec.rb +0 -157
  122. data/spec/graphql/query_spec.rb +0 -274
  123. data/spec/graphql/root_call_spec.rb +0 -69
  124. data/spec/graphql/schema/schema_spec.rb +0 -93
  125. data/spec/graphql/schema/schema_validation_spec.rb +0 -48
  126. data/spec/support/nodes.rb +0 -175
@@ -0,0 +1,72 @@
1
+ module GraphQL::Nodes
2
+ # AbstractNode creates classes who:
3
+ # - require their keyword arguments, throw ArgumentError if they don't match
4
+ # - expose accessors for keyword arguments
5
+ class AbstractNode
6
+ def initialize(options)
7
+ required_keys = self.class.required_attrs
8
+
9
+ extra_keys = options.keys - required_keys
10
+ if extra_keys.any?
11
+ raise ArgumentError, "#{self.class.name} Extra arguments: #{extra_keys}"
12
+ end
13
+
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)
20
+ end
21
+ end
22
+ end
23
+
24
+ def children
25
+ self.class.required_attrs
26
+ .map { |attr| send(attr) }
27
+ .flatten # eg #fields is a list of children
28
+ .select { |val| val.is_a?(GraphQL::Nodes::AbstractNode) }
29
+ end
30
+
31
+ class << self
32
+ attr_reader :required_attrs
33
+ def attr_required(*attr_names)
34
+ @required_attrs ||= []
35
+ @required_attrs += attr_names
36
+ attr_accessor(*attr_names)
37
+ end
38
+
39
+ # Create a new AbstractNode child which
40
+ # requires and exposes {attr_names}.
41
+ def create(*attr_names, &block)
42
+ cls = Class.new(self, &block)
43
+ cls.attr_required(*attr_names)
44
+ cls
45
+ end
46
+ end
47
+ end
48
+
49
+ Argument = AbstractNode.create(:name, :value)
50
+ Directive = AbstractNode.create(:name, :arguments)
51
+ Document = AbstractNode.create(:parts)
52
+ Enum = AbstractNode.create(:name)
53
+ Field = AbstractNode.create(:name, :alias, :arguments, :directives, :selections)
54
+ FragmentDefinition = AbstractNode.create(:name, :type, :directives, :selections)
55
+ FragmentSpread = AbstractNode.create(:name, :directives)
56
+ InlineFragment = AbstractNode.create(:type, :directives, :selections)
57
+ InputObject = AbstractNode.create(:pairs) do
58
+ def to_h(options={})
59
+ pairs.inject({}) do |memo, pair|
60
+ v = pair.value
61
+ memo[pair.name] = v.is_a?(InputObject) ? v.to_h : v
62
+ memo
63
+ end
64
+ end
65
+ end
66
+ ListType = AbstractNode.create(:of_type)
67
+ NonNullType = AbstractNode.create(:of_type)
68
+ OperationDefinition = AbstractNode.create(:operation_type, :name, :variables, :directives, :selections)
69
+ TypeName = AbstractNode.create(:name)
70
+ Variable = AbstractNode.create(:name, :type, :default_value)
71
+ VariableIdentifier = AbstractNode.create(:name)
72
+ end
@@ -0,0 +1,108 @@
1
+ # Parser is a [parslet](http://kschiess.github.io/parslet/) parser for parsing queries.
2
+ #
3
+ # If it failes to parse, a {SyntaxError} is raised.
4
+ class GraphQL::Parser < Parslet::Parser
5
+ root(:document)
6
+ rule(:document) { (
7
+ space |
8
+ operation_definition |
9
+ fragment_definition
10
+ ).repeat(1).as(:document_parts)
11
+ }
12
+
13
+ # TODO: whitespace sensitive regarding `on`, eg `onFood`, see lookahead note in spec
14
+ rule(:fragment_definition) {
15
+ str("fragment") >>
16
+ space? >> name.as(:fragment_name) >>
17
+ space? >> str("on") >> space? >> name.as(:type_condition) >>
18
+ space? >> directives.maybe.as(:optional_directives).as(:directives) >>
19
+ space? >> selections.as(:selections)
20
+ }
21
+
22
+ rule(:fragment_spread) {
23
+ str("...") >> space? >>
24
+ name.as(:fragment_spread_name) >> space? >>
25
+ directives.maybe.as(:optional_directives).as(:directives)
26
+ }
27
+
28
+ # TODO: `on` bug, see spec
29
+ rule(:inline_fragment) {
30
+ str("...") >> space? >>
31
+ str("on ") >> name.as(:inline_fragment_type) >> space? >>
32
+ directives.maybe.as(:optional_directives).as(:directives) >> space? >>
33
+ selections.as(:selections)
34
+ }
35
+
36
+ rule(:operation_definition) { (unnamed_selections | named_operation_definition) }
37
+ rule(:unnamed_selections) { selections.as(:unnamed_selections)}
38
+ rule(:named_operation_definition) {
39
+ operation_type.as(:operation_type) >> space? >>
40
+ name.as(:name) >>
41
+ operation_variable_definitions.maybe.as(:optional_variables).as(:variables) >> space? >>
42
+ directives.maybe.as(:optional_directives).as(:directives) >> space? >>
43
+ selections.as(:selections)
44
+ }
45
+ rule(:operation_type) { (str("query") | str("mutation")) }
46
+ rule(:operation_variable_definitions) { str("(") >> space? >> (operation_variable_definition >> separator?).repeat(1) >> space? >> str(")") }
47
+ rule(:operation_variable_definition) {
48
+ value_variable.as(:variable_name) >> space? >>
49
+ str(":") >> space? >>
50
+ type.as(:variable_type) >> space? >>
51
+ (str("=") >> space? >> value.as(:variable_default_value)).maybe.as(:variable_optional_default_value)}
52
+
53
+ rule(:selection) { (inline_fragment | fragment_spread | field) >> space? >> separator? }
54
+ rule(:selections) { str("{") >> space? >> selection.repeat(1) >> space? >> str("}")}
55
+
56
+ rule(:field) {
57
+ field_alias.maybe.as(:alias) >>
58
+ name.as(:field_name) >>
59
+ field_arguments.maybe.as(:optional_field_arguments).as(:field_arguments) >> space? >>
60
+ directives.maybe.as(:optional_directives).as(:directives) >> space? >>
61
+ selections.maybe.as(:optional_selections).as(:selections)
62
+ }
63
+
64
+ rule(:field_alias) { name.as(:alias_name) >> space? >> str(":") >> space? }
65
+ rule(:field_arguments) { str("(") >> field_argument.repeat(1) >> str(")") }
66
+ rule(:field_argument) { name.as(:field_argument_name) >> str(":") >> space? >> value.as(:field_argument_value) >> separator? }
67
+
68
+ rule(:directives) { (directive >> separator?).repeat(1) }
69
+ rule(:directive) {
70
+ str("@") >> name.as(:directive_name) >>
71
+ directive_arguments.maybe.as(:optional_directive_arguments).as(:directive_arguments)
72
+ }
73
+ rule(:directive_arguments) { str("(") >> directive_argument.repeat(1) >> str(")") }
74
+ rule(:directive_argument) { name.as(:directive_argument_name) >> str(":") >> space? >> value.as(:directive_argument_value) >> separator? }
75
+
76
+ rule(:type) { (non_null_type | list_type | type_name)}
77
+ rule(:list_type) { str("[") >> type.as(:list_type) >> str("]")}
78
+ rule(:non_null_type) { (list_type | type_name).as(:non_null_type) >> str("!") }
79
+ rule(:type_name) { name.as(:type_name) }
80
+
81
+ rule(:value) {(
82
+ value_input_object |
83
+ value_float |
84
+ value_int |
85
+ value_string |
86
+ value_boolean |
87
+ value_array |
88
+ value_variable |
89
+ value_enum
90
+ )}
91
+ rule(:value_sign?) { str("-").maybe }
92
+ rule(:value_array) { (str("[") >> (value >> separator?).repeat(0) >> str("]")).as(:array) }
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) }
95
+ rule(:value_input_object) { str("{") >> value_input_object_pair.repeat(1).as(:input_object) >> str("}") }
96
+ rule(:value_input_object_pair) { space? >> name.as(:input_object_name) >> space? >> str(":") >> space? >> value.as(:input_object_value) >> separator? }
97
+ rule(:value_int) { (value_sign? >> match('\d').repeat(1)).as(:int) }
98
+ # TODO: support unicode, escaped chars (match the spec)
99
+ rule(:value_string) { str('"') >> match('[^\"]').repeat(1).as(:string) >> str('"')}
100
+ rule(:value_enum) { name.as(:enum) }
101
+ rule(:value_variable) { str("$") >> name.as(:variable) }
102
+
103
+ rule(:separator?) { (space? >> str(",") >> space?).maybe }
104
+ rule(:name) { match('[_A-Za-z]') >> match('[_0-9A-Za-z]').repeat(0) }
105
+ rule(:comment) { str("#") >> match('[^\r\n]').repeat(0) }
106
+ rule(:space) { (match('[\s\n]+') | comment).repeat(1) }
107
+ rule(:space?) { space.maybe }
108
+ end
@@ -0,0 +1,86 @@
1
+ # {Transform} is a [parslet](http://kschiess.github.io/parslet/) transform for for turning the AST into objects in {GraphQL::Nodes} objects.
2
+ class GraphQL::Transform < Parslet::Transform
3
+ # Get syntax classes by shallow name:
4
+ def self.const_missing(constant_name)
5
+ GraphQL::Nodes.const_get(constant_name)
6
+ end
7
+
8
+ def self.optional_sequence(name)
9
+ rule(name => simple(:val)) { [] }
10
+ rule(name => sequence(:val)) { val }
11
+ end
12
+
13
+ # Document
14
+ rule(document_parts: sequence(:p)) { Document.new(parts: p)}
15
+
16
+ # Fragment Definition
17
+ rule(
18
+ fragment_name: simple(:name),
19
+ type_condition: simple(:type),
20
+ directives: sequence(:directives),
21
+ selections: sequence(:selections)
22
+ ) {FragmentDefinition.new(name: name.to_s, type: type.to_s, directives: directives, selections: selections)}
23
+
24
+ rule(
25
+ fragment_spread_name: simple(:n),
26
+ directives: sequence(:d)
27
+ ) { FragmentSpread.new(name: n.to_s, directives: d)}
28
+
29
+ rule(
30
+ inline_fragment_type: simple(:n),
31
+ directives: sequence(:d),
32
+ selections: sequence(:s),
33
+ ) { InlineFragment.new(type: n.to_s, directives: d, selections: s)}
34
+
35
+ # Operation Definition
36
+ rule(
37
+ operation_type: simple(:ot),
38
+ name: simple(:n),
39
+ variables: sequence(:v),
40
+ directives: sequence(:d),
41
+ selections: sequence(:s),
42
+ ) { OperationDefinition.new(operation_type: ot.to_s, name: n.to_s, variables: v, directives: d, selections: s) }
43
+ 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)}
46
+ rule(variable_default_value: simple(:v) ) { v }
47
+ rule(variable_default_value: sequence(:v) ) { v }
48
+ # Query short-hand
49
+ rule(unnamed_selections: sequence(:s)) { OperationDefinition.new(selections: s, operation_type: "query", name: nil, variables: [], directives: [])}
50
+
51
+ # Field
52
+ rule(
53
+ alias: simple(:a),
54
+ field_name: simple(:name),
55
+ field_arguments: sequence(:args),
56
+ directives: sequence(:dir),
57
+ selections: sequence(:sel)
58
+ ) { Field.new(alias: a, name: name.to_s, arguments: args, directives: dir, selections: sel) }
59
+
60
+ rule(alias_name: simple(:a)) { a.to_s }
61
+ 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)}
63
+ optional_sequence(:optional_selections)
64
+ optional_sequence(:optional_directives)
65
+
66
+ # 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)}
69
+ optional_sequence(:optional_directive_arguments)
70
+
71
+ # 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
+
76
+ # Values
77
+ rule(array: sequence(:v)) { v }
78
+ 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)}
81
+ rule(int: simple(:v)) { v.to_i }
82
+ rule(float: simple(:v)) { v.to_f }
83
+ 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)}
86
+ end
@@ -0,0 +1,47 @@
1
+ # Depth-first traversal through the tree, calling hooks at each stop.
2
+ #
3
+ # @example: Create a visitor, add hooks, then search a document
4
+ # total_field_count = 0
5
+ # visitor = GraphQL::Visitor.new
6
+ # visitor[GraphQL::Nodes::Field] << -> (node) { total_field_count += 1 }
7
+ # visitor[GraphQL::Nodes::Document].leave << -> (node) { p total_field_count }
8
+ # visitor.visit(document)
9
+ # # => 6
10
+ #
11
+ class GraphQL::Visitor
12
+ def initialize
13
+ @visitors = {}
14
+ end
15
+
16
+ def [](node_class)
17
+ @visitors[node_class] ||= NodeVisitor.new
18
+ end
19
+
20
+ # 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
+ end
27
+
28
+ class NodeVisitor
29
+ attr_reader :enter, :leave
30
+ def initialize
31
+ @enter = []
32
+ @leave = []
33
+ end
34
+
35
+ def <<(hook)
36
+ enter << hook
37
+ 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
+ end
47
+ end
@@ -0,0 +1,50 @@
1
+ class GraphQL::Query
2
+ 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
+ attr_reader :schema, :document, :context, :fragments, :params
12
+
13
+ def initialize(schema, query_string, context: nil, params: {})
14
+ @schema = schema
15
+ @document = GraphQL.parse(query_string)
16
+ @context = context
17
+ @params = params
18
+ @fragments = {}
19
+ @operations = {}
20
+
21
+ @document.parts.each do |part|
22
+ if part.is_a?(GraphQL::Nodes::FragmentDefinition)
23
+ @fragments[part.name] = part
24
+ elsif part.is_a?(GraphQL::Nodes::OperationDefinition)
25
+ @operations[part.name] = part
26
+ end
27
+ end
28
+ end
29
+
30
+ # Get the result for this query, executing it once
31
+ def result
32
+ @result ||= {
33
+ "data" => execute,
34
+ }
35
+ rescue StandardError => err
36
+ message = "Something went wrong during query execution: #{err}"
37
+ {"errors" => [{"message" => message}]}
38
+ end
39
+
40
+ private
41
+
42
+ def execute
43
+ response = {}
44
+ @operations.each do |name, operation|
45
+ resolver = OperationResolver.new(operation, self)
46
+ response[name] = resolver.result
47
+ end
48
+ response
49
+ end
50
+ end
@@ -0,0 +1,25 @@
1
+ # Creates a plain hash out of arguments, looking up variables if necessary
2
+ class GraphQL::Query::Arguments
3
+ attr_reader :to_h
4
+ def initialize(ast_arguments, variables)
5
+ @to_h = ast_arguments.reduce({}) do |memo, arg|
6
+ value = reduce_value(arg.value, variables)
7
+ memo[arg.name] = value
8
+ memo
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def reduce_value(value, variables)
15
+ if value.is_a?(GraphQL::Nodes::VariableIdentifier)
16
+ value = variables[value.name]
17
+ elsif value.is_a?(GraphQL::Nodes::Enum)
18
+ value = value.name
19
+ elsif value.is_a?(GraphQL::Nodes::InputObject)
20
+ value = self.class.new(value.pairs, variables).to_h
21
+ else
22
+ value
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,83 @@
1
+ class GraphQL::Query::FieldResolutionStrategy
2
+ attr_reader :result, :result_value
3
+
4
+ def initialize(ast_field, parent_type, target, operation_resolver)
5
+ arguments = GraphQL::Query::Arguments.new(ast_field.arguments, operation_resolver.variables).to_h
6
+ field_name = ast_field.name
7
+ field = parent_type.fields[field_name] || raise("No field found on #{parent_type.name} '#{parent_type}' for '#{field_name}'")
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 => e
16
+ raise("Couldn't resolve field '#{field_name}' to #{target.class} '#{target}' (resulted in NoMethodError)")
17
+ end
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)
21
+ @result_value = result_strategy.result
22
+ end
23
+ result_name = ast_field.alias || ast_field.name
24
+ @result = { result_name => @result_value}
25
+ end
26
+
27
+ class ScalarResolutionStrategy
28
+ attr_reader :result
29
+ def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
30
+ @result = field_type.coerce(value)
31
+ end
32
+ end
33
+
34
+ class ListResolutionStrategy
35
+ attr_reader :result
36
+ def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
37
+ wrapped_type = field_type.of_type
38
+ strategy_class = FIELD_TYPE_KIND_STRATEGIES[wrapped_type.kind]
39
+ @result = value.map do |item|
40
+ inner_strategy = strategy_class.new(item, wrapped_type, target, parent_type, ast_field, operation_resolver)
41
+ inner_strategy.result
42
+ end
43
+ end
44
+ end
45
+
46
+ class ObjectResolutionStrategy
47
+ attr_reader :result
48
+ def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
49
+ resolver = GraphQL::Query::SelectionResolver.new(value, field_type, ast_field.selections, operation_resolver)
50
+ @result = resolver.result
51
+ end
52
+ end
53
+
54
+
55
+ class EnumResolutionStrategy
56
+ attr_reader :result
57
+ def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
58
+ @result = value.to_s
59
+ end
60
+ end
61
+
62
+ class NonNullResolutionStrategy
63
+ attr_reader :result
64
+ def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
65
+ 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)
68
+ @result = inner_strategy.result
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ FIELD_TYPE_KIND_STRATEGIES = {
75
+ GraphQL::TypeKinds::SCALAR => ScalarResolutionStrategy,
76
+ GraphQL::TypeKinds::LIST => ListResolutionStrategy,
77
+ GraphQL::TypeKinds::OBJECT => ObjectResolutionStrategy,
78
+ GraphQL::TypeKinds::UNION => ObjectResolutionStrategy,
79
+ GraphQL::TypeKinds::INTERFACE => ObjectResolutionStrategy,
80
+ GraphQL::TypeKinds::ENUM => EnumResolutionStrategy,
81
+ GraphQL::TypeKinds::NON_NULL => NonNullResolutionStrategy,
82
+ }
83
+ end