graphql 0.0.4 → 0.1.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 (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