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
@@ -1,18 +0,0 @@
1
- class GraphQL::Introspection::TypeType < GraphQL::Node
2
- exposes "GraphQL::Node"
3
- type "__type__"
4
- field.string(:name)
5
- field.string(:description)
6
- field.introspection_connection(:fields)
7
-
8
- cursor :name
9
-
10
- # they're actually {FieldMapping}s
11
- def fields
12
- target.all_fields.values
13
- end
14
-
15
- def name
16
- schema_name
17
- end
18
- end
@@ -1,244 +0,0 @@
1
- # Node is the base class for your GraphQL nodes.
2
- # It's essentially a delegator that only delegates methods you whitelist with {.field}.
3
- # To use it:
4
- #
5
- # - Extend `GraphQL::Node`
6
- # - Declare what this node will wrap with {.exposes}
7
- # - Declare fields with {.field}
8
- # - Declare calls with {.call}
9
- #
10
- # @example Expose a class in your app
11
- # class PostNode < GraphQL::Node
12
- # exposes('Post')
13
- #
14
- # cursor(:id)
15
- #
16
- # field.number(:id)
17
- # field.string(:title)
18
- # field.string(:content)
19
- # field.connection(:comments)
20
- # end
21
- #
22
- # @example Expose a data type
23
- # class DateType < GraphQL::Node
24
- # exposes "Date"
25
- # type :date
26
- # call :minus_days, -> (prev_value, minus_days) { prev_value - minus_days.to_i }
27
- # field.number(:year)
28
- # field.number(:month)
29
- # end
30
- #
31
- # # now you could use it
32
- # class PostNode
33
- # field.date(:published_at)
34
- # end
35
- class GraphQL::Node
36
- # The object wrapped by this `Node`, _before_ calls are applied
37
- attr_reader :original_target
38
- # The object wrapped by this `Node`, _after_ calls are applied
39
- attr_reader :target
40
- # Fields parsed from the query string
41
- attr_reader :syntax_fields
42
- # The query to which this `Node` belongs. Used for accessing its {Query#context}.
43
- attr_reader :query
44
-
45
- def initialize(target=nil, fields:, query:, calls: [])
46
- @query = query
47
- @calls = calls
48
- @syntax_fields = fields
49
- @original_target = target
50
- @target = apply_calls(target)
51
- end
52
-
53
- # If the target responds to `method_name`, send it to target.
54
- def method_missing(method_name, *args, &block)
55
- if target.respond_to?(method_name)
56
- target.public_send(method_name, *args, &block)
57
- else
58
- super
59
- end
60
- end
61
-
62
- # Looks up {#syntax_fields} against this node and returns the results
63
- def as_result
64
- @as_result ||= begin
65
- json = {}
66
- syntax_fields.each do |syntax_field|
67
- key_name = syntax_field.alias_name || syntax_field.identifier
68
- if key_name == 'node'
69
- clone_node = self.class.new(target, fields: syntax_field.fields, query: query, calls: syntax_field.calls)
70
- json[key_name] = clone_node.as_result
71
- elsif key_name == 'cursor'
72
- json[key_name] = cursor
73
- elsif key_name[0] == "$"
74
- fragment = query.fragments[key_name]
75
- # execute the fragment and merge it into this result
76
- clone_node = self.class.new(target, fields: fragment.fields, query: query, calls: @calls)
77
- json.merge!(clone_node.as_result)
78
- else
79
- field = get_field(syntax_field)
80
- new_target = public_send(field.name)
81
- new_node = field.type_class.new(new_target, fields: syntax_field.fields, query: query, calls: syntax_field.calls)
82
- json[key_name] = new_node.as_result
83
- end
84
- end
85
- json
86
- end
87
- end
88
-
89
- # The object passed to {Query#initialize} as `context`.
90
- def context
91
- query.context
92
- end
93
-
94
- def __type__
95
- self.class
96
- end
97
-
98
- def apply_calls(value)
99
- finished_value(value)
100
- end
101
-
102
- def finished_value(raw_value)
103
- @finished_value ||= begin
104
- val = raw_value
105
- @calls.each do |call|
106
- registered_call = self.class.calls[call.identifier]
107
- if registered_call.nil?
108
- raise "Call not found: #{self.class.name}##{call.identifier}"
109
- end
110
- val = registered_call.lambda.call(val, *call.arguments)
111
- end
112
- val
113
- end
114
- end
115
-
116
- class << self
117
- # @param [String] class_name name of the class this node will wrap.
118
- def exposes(*exposes_class_names)
119
- @exposes_class_names = exposes_class_names
120
- GraphQL::SCHEMA.add_type(self)
121
- end
122
-
123
- # The names of the classes wrapped by this node
124
- def exposes_class_names
125
- @exposes_class_names || []
126
- end
127
-
128
- # @param [String] describe
129
- # Provide a description for this node which will be accessible from {SCHEMA}
130
- def desc(describe)
131
- @description = describe
132
- end
133
-
134
- # The description of this node
135
- def description
136
- @description || raise("#{name}.description isn't defined")
137
- end
138
-
139
- # @param [String] type_name
140
- # Declares an alternative name to use in {GraphQL::SCHEMA}
141
- def type(type_name)
142
- @type_name = type_name.to_s
143
- GraphQL::SCHEMA.add_type(self)
144
- end
145
-
146
- # Returns the name of this node used by {GraphQL::SCHEMA}
147
- def schema_name
148
- @type_name || default_schema_name
149
- end
150
-
151
- def default_schema_name
152
- name.split("::").last.sub(/(Node|Type)$/, '').underscore
153
- end
154
-
155
- # @param [String] field_name name of the field to be used as the cursor
156
- # Declares what field will be used as the cursor for this node.
157
- def cursor(field_name)
158
- define_method "cursor" do
159
- field_mapping = self.class.all_fields[field_name.to_s]
160
- public_send(field_mapping.name).to_s
161
- end
162
- end
163
-
164
- # All accessible fields on this node (including those defined in parent classes)
165
- def all_fields
166
- superclass.all_fields.merge(own_fields)
167
- rescue NoMethodError
168
- own_fields
169
- end
170
-
171
- # Fields defined by this class, but not its parents
172
- def own_fields
173
- @own_fields ||= {}
174
- end
175
-
176
- # @return [GraphQL::FieldDefiner] definer
177
- def field
178
- @field_definer ||= GraphQL::FieldDefiner.new(self)
179
- end
180
-
181
- # @param [String] field_name
182
- # Un-define field with name `field_name`
183
- def remove_field(field_name)
184
- own_fields.delete(field_name.to_s)
185
- end
186
-
187
- # Can the node handle a field with this name?
188
- def respond_to_field?(field_name)
189
- if all_fields[field_name.to_s].blank?
190
- false
191
- elsif method_defined?(field_name)
192
- true
193
- elsif exposes_class_names.any? do |exposes_class_name|
194
- exposes_class = Object.const_get(exposes_class_name)
195
- exposes_class.method_defined?(field_name) || exposes_class.respond_to?(field_name)
196
- end
197
- true
198
- else
199
- false
200
- end
201
- end
202
-
203
- def calls
204
- superclass.calls.merge(own_calls)
205
- rescue NoMethodError
206
- {}
207
- end
208
- # @param [String] name the identifier for this call
209
- # @param [lambda] operation the transformation this call makes
210
- #
211
- # Define a call that can be made on nodes of this type.
212
- # The `lambda` receives arguments:
213
- # - 1: `previous_value` -- the value of this node
214
- # - *: arguments passed in the query (as strings)
215
- #
216
- # @example
217
- # # upcase a string field:
218
- # call :upcase, -> (prev_value) { prev_value.upcase }
219
- # @example
220
- # # tests a number field:
221
- # call :greater_than, -> (prev_value, test_value) { prev_value > test_value.to_f }
222
- # # (`test_value` is passed in as a string)
223
- def call(name, lambda)
224
- own_calls[name.to_s] = GraphQL::Call.new(name: name.to_s, lambda: lambda)
225
- end
226
-
227
- def own_calls
228
- @own_calls ||= {}
229
- end
230
- end
231
-
232
- private
233
-
234
- def get_field(syntax_field)
235
- field_mapping = self.class.all_fields[syntax_field.identifier]
236
- if syntax_field.identifier == "cursor"
237
- cursor
238
- elsif field_mapping.nil?
239
- raise GraphQL::FieldNotDefinedError.new(self.class, syntax_field.identifier)
240
- else
241
- field_mapping
242
- end
243
- end
244
- end
@@ -1,39 +0,0 @@
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::Parser < Parslet::Parser
5
- root(:query)
6
- rule(:query) { node.repeat.as(:nodes) >> variable.repeat.as(:variables) >> fragment.repeat.as(:fragments) }
7
-
8
- # node
9
- rule(:node) { space? >> call >> space? >> fields.as(:fields) }
10
-
11
- # fragment
12
- rule(:fragment) { space? >> fragment_identifier >> str(":") >> space? >> fields.as(:fields) >> space?}
13
- rule(:fragment_identifier) { (str("$") >> name).as(:identifier) }
14
-
15
- # field set
16
- rule(:fields) { str("{") >> space? >> (field >> separator?).repeat(1) >> space? >> str("}") >> space?}
17
-
18
- # call
19
- rule(:call) { identifier >> str("(") >> (argument.as(:argument) >> separator?).repeat(0).as(:arguments) >> str(")") }
20
- rule(:dot) { str(".") }
21
- rule(:argument) { (identifier | variable_identifier)}
22
-
23
- # field
24
- rule(:field) { (identifier | fragment_identifier) >> call_chain.maybe >> alias_name.maybe >> space? >> fields.as(:fields).maybe }
25
- rule(:call_chain) { (dot >> call).repeat(0).as(:calls) }
26
- rule(:alias_name) { space >> str("as") >> space >> name.as(:alias_name) }
27
-
28
- # variable
29
- rule(:variable) { space? >> variable_identifier >> str(":") >> space? >> (name | json_string ).as(:json_string) >> space?}
30
- rule(:json_string) { str("{") >> (match('[^{}]') | json_string).repeat >> str("}")}
31
- rule(:variable_identifier) { (str("<") >> name >> str(">")).as(:identifier) }
32
-
33
- # general purpose
34
- rule(:separator?) { str(",").maybe >> space? }
35
- rule(:identifier) { name.as(:identifier) }
36
- rule(:name) { match('\w').repeat(1) }
37
- rule(:space) { match('[\s\n]+').repeat(1) }
38
- rule(:space?) { space.maybe }
39
- end
@@ -1,22 +0,0 @@
1
- # {Transform} is a [parslet](http://kschiess.github.io/parslet/) transform for for turning the AST into objects in {GraphQL::Syntax}.
2
- class GraphQL::Parser::Transform < Parslet::Transform
3
- # query
4
- rule(nodes: sequence(:n), variables: sequence(:v), fragments: sequence(:f)) { GraphQL::Syntax::Query.new(nodes: n, variables: v, fragments: f)}
5
- # node
6
- rule(identifier: simple(:i), arguments: sequence(:a), fields: sequence(:f)) {GraphQL::Syntax::Node.new(identifier: i.to_s, arguments: a, fields: f)}
7
- # field
8
- rule(identifier: simple(:i), calls: sequence(:c), fields: sequence(:f), alias_name: simple(:a)) { GraphQL::Syntax::Field.new(identifier: i.to_s, fields: f, calls: c, alias_name: a.to_s)}
9
- rule(identifier: simple(:i), calls: sequence(:c), alias_name: simple(:a)) { GraphQL::Syntax::Field.new(identifier: i.to_s, calls: c, alias_name: a.to_s)}
10
- rule(identifier: simple(:i), calls: sequence(:c), fields: sequence(:f)) { GraphQL::Syntax::Field.new(identifier: i.to_s, fields: f, calls: c)}
11
- rule(identifier: simple(:i), calls: sequence(:c)) { GraphQL::Syntax::Field.new(identifier: i.to_s, calls: c)}
12
- rule(identifier: simple(:i), alias_name: simple(:a)) { GraphQL::Syntax::Field.new(identifier: i.to_s, alias_name: a.to_s)}
13
- # call
14
- rule(identifier: simple(:i), arguments: sequence(:a)) { GraphQL::Syntax::Call.new(identifier: i.to_s, arguments: a) }
15
- # argument
16
- rule(argument: simple(:a)) { a.to_s }
17
- rule(identifier: simple(:i)) { i.to_s }
18
- # variable
19
- rule(identifier: simple(:i), json_string: simple(:j)) { GraphQL::Syntax::Variable.new(identifier: i.to_s, json_string: j.to_s)}
20
- # fragment
21
- rule(identifier: simple(:i), fields: sequence(:f)) { GraphQL::Syntax::Fragment.new(identifier: i, fields: f)}
22
- end
@@ -1,109 +0,0 @@
1
- # Executes queries from strings against {GraphQL::SCHEMA}.
2
- #
3
- # @example
4
- # query_str = "post(1) { title, comments { count } }"
5
- # query_ctx = {user: current_user}
6
- # query = GraphQL::Query.new(query_str, context: query_ctx)
7
- # result = query.as_result
8
-
9
- class GraphQL::Query
10
- # This is the object passed to {#initialize} as `context:`
11
- attr_reader :context
12
- attr_reader :query_string, :root
13
-
14
- # @param [String] query_string the string to be parsed
15
- # @param [Object] context an object which will be available to all nodes and fields in the schema
16
- def initialize(query_string, context: nil)
17
- if !query_string.is_a?(String) || query_string.length == 0
18
- raise "You must send a query string, not a #{query_string.class.name}"
19
- end
20
- @query_string = query_string
21
- @root = parse(query_string)
22
- @context = context
23
- end
24
-
25
- # @return [Hash] result the JSON response to this query
26
- # Calling {#as_result} more than once won't cause the query to be re-run
27
- def as_result
28
- @as_result ||= execute!
29
- end
30
-
31
- # Provides access to query variables, raises an error if not found
32
- # @example
33
- # query.variables["<person_data>"]
34
- def variables
35
- @variables ||= LookupHash.new("variable", @root.variables)
36
- end
37
-
38
- # Provides access to query fragments, raises an error if not found
39
- # @example
40
- # query.fragments["$personData"]
41
- def fragments
42
- @fragments ||= LookupHash.new("fragment", @root.fragments)
43
- end
44
-
45
- private
46
-
47
- def execute!
48
- root_syntax_node = root.nodes[0]
49
- root_call_class = GraphQL::SCHEMA.get_call(root_syntax_node.identifier)
50
- root_call = root_call_class.new(query: self, syntax_arguments: root_syntax_node.arguments)
51
- result_object = root_call.as_result
52
- return_declarations = root_call_class.return_declarations
53
- result = {}
54
-
55
- if result_object.is_a?(Hash)
56
- result_object.each do |name, value|
57
- node_class = GraphQL::SCHEMA.type_for_object(value)
58
- field_for_node = root_syntax_node.fields.find {|f| f.identifier == name.to_s }
59
- if field_for_node.present?
60
- fields_for_node = field_for_node.fields
61
- node_value = node_class.new(value,query: self, fields: fields_for_node)
62
- result[name.to_s] = node_value.as_result
63
- end
64
- end
65
- elsif result_object.is_a?(Array)
66
- fields_for_node = root_syntax_node.fields
67
- result_object.each do |item|
68
- node_class = GraphQL::SCHEMA.type_for_object(item)
69
- node_value = node_class.new(item, query: self, fields: fields_for_node)
70
- result[node_value.cursor] = node_value.as_result
71
- end
72
- else
73
- node_class = GraphQL::SCHEMA.type_for_object(result_object)
74
- fields_for_node = root_syntax_node.fields
75
- node_value = node_class.new(result_object, query: self, fields: fields_for_node)
76
- result[node_value.cursor] = node_value.as_result
77
- end
78
-
79
- result
80
- end
81
-
82
- def parse(query_string)
83
- parsed_hash = GraphQL::PARSER.parse(query_string)
84
- root_node = GraphQL::TRANSFORM.apply(parsed_hash)
85
- rescue Parslet::ParseFailed => error
86
- line, col = error.cause.source.line_and_column
87
- raise GraphQL::SyntaxError.new(line, col, query_string)
88
- end
89
-
90
- # Caches items by name, raises an error if not found
91
- class LookupHash
92
- attr_reader :item_name, :items
93
- def initialize(item_name, items)
94
- @items = items
95
- @item_name = item_name
96
- @storage = Hash.new do |hash, identifier|
97
- value = items.find {|i| i.identifier == identifier }
98
- if value.blank?
99
- "No #{item_name} found for #{identifier}, defined #{item_name}s are: #{items.map(&:identifier)}"
100
- end
101
- hash[identifier] = value
102
- end
103
- end
104
-
105
- def [](identifier)
106
- @storage[identifier]
107
- end
108
- end
109
- end
@@ -1,202 +0,0 @@
1
- # Every query begins with a root call. It might find data or mutate data and return some results.
2
- #
3
- # A root call should:
4
- #
5
- # - declare any arguments with {.argument}, or declare `argument.none`
6
- # - declare returns with {.return}
7
- # - implement {#execute!} to take those arguments and return values
8
- #
9
- # @example
10
- # FindPostCall < GraphQL::RootCall
11
- # argument.number(:ids, any_number: true)
12
- # returns :post
13
- #
14
- # def execute!(*ids)
15
- # ids.map { |id| Post.find(id) }
16
- # end
17
- # end
18
- #
19
- # @example
20
- # CreateCommentCall < GraphQL::RootCall
21
- # argument.number(:post_id)
22
- # argument.object(:comment)
23
- # returns :post, :comment
24
- #
25
- # def execute!(post_id, comment)
26
- # post = Post.find(post_id)
27
- # new_comment = post.comments.create!(comment)
28
- # {
29
- # comment: new_comment,
30
- # post: post,
31
- # }
32
- # end
33
- # end
34
- #
35
- class GraphQL::RootCall
36
- attr_reader :query, :arguments
37
-
38
- # Validates arguments against declared {.argument}s
39
- def initialize(query:, syntax_arguments:)
40
- @query = query
41
-
42
- raise "#{self.class.name} must declare arguments" unless self.class.arguments
43
- @arguments = syntax_arguments.each_with_index.map do |syntax_arg, idx|
44
-
45
- value = if syntax_arg[0] == "<"
46
- query.variables[syntax_arg].json_string
47
- else
48
- syntax_arg
49
- end
50
-
51
- typecast(idx, value)
52
- end
53
- end
54
-
55
- # @param [Array] args (splat) all args provided in query string (as strings)
56
- # This method is invoked with the arguments provided to the query.
57
- # It should do work and return values matching the {.returns} declarations
58
- def execute!(*args)
59
- raise NotImplementedError, "Do work in this method"
60
- end
61
-
62
- # The object passed to {Query#initialize} as `context`
63
- def context
64
- query.context
65
- end
66
-
67
- # Executes the call, validates the return values against declared {.returns}, then returns the return values.
68
- def as_result
69
- return_declarations = self.class.return_declarations
70
- raise "#{self.class.name} must declare returns" unless return_declarations.present?
71
- return_values = execute!(*arguments)
72
-
73
- if return_values.is_a?(Hash)
74
- unexpected_returns = return_values.keys - return_declarations.keys
75
- missing_returns = return_declarations.keys - return_values.keys
76
- if unexpected_returns.any?
77
- raise "#{self.class.name} returned #{unexpected_returns}, but didn't declare them."
78
- elsif missing_returns.any?
79
- raise "#{self.class.name} declared #{missing_returns}, but didn't return them."
80
- end
81
- end
82
- return_values
83
- end
84
-
85
- class << self
86
- # @param [String] ident_name
87
- # Declare an alternative name used in a query string
88
- def indentifier(ident_name)
89
- @identifier = ident_name
90
- GraphQL::SCHEMA.add_call(self)
91
- end
92
-
93
- # The name used by {GraphQL::SCHEMA}. Uses {.identifier} or derives a name from the class name.
94
- def schema_name
95
- @identifier || name.split("::").last.sub(/Call$/, '').underscore
96
- end
97
-
98
- def inherited(child_class)
99
- GraphQL::SCHEMA.add_call(child_class)
100
- end
101
-
102
- # This call won't be visible in `schema()`
103
- def abstract!
104
- GraphQL::SCHEMA.remove_call(self)
105
- end
106
-
107
- # @param [Symbol] return_declarations
108
- # Name of returned values from this call
109
- def returns(*return_declaration_names)
110
- if return_declaration_names.last.is_a?(Hash)
111
- return_declarations_hash = return_declaration_names.pop
112
- else
113
- return_declarations_hash = {}
114
- end
115
-
116
- raise "Return keys must be symbols" if (return_declarations.keys + return_declaration_names).any? { |k| !k.is_a?(Symbol) }
117
-
118
- return_declaration_names.each do |return_sym|
119
- return_type = return_sym.to_s
120
- return_declarations[return_sym] = return_type
121
- end
122
-
123
- return_declarations_hash.each do |return_sym, return_type|
124
- return_declarations[return_sym] = return_type
125
- end
126
- end
127
-
128
- def return_declarations
129
- @return_declarations ||= {}
130
- end
131
-
132
- # @return [GraphQL::RootCallArgumentDefiner] definer
133
- # Use this object to declare arguments. They must be declared in order
134
- # @example
135
- # argument.string("post_title")
136
- # argument.object("comment_data") # allows a JSON object
137
- def argument
138
- @argument ||= GraphQL::RootCallArgumentDefiner.new(self)
139
- end
140
-
141
- def own_arguments
142
- @own_arguments ||= {}
143
- end
144
-
145
- def arguments
146
- superclass.arguments.merge(own_arguments)
147
- rescue NoMethodError
148
- {}
149
- end
150
-
151
- def add_argument(argument)
152
- existing_argument = arguments[argument.name]
153
- if existing_argument.blank?
154
- # only assign an index if this variable wasn't already defined
155
- argument.index = arguments.keys.length
156
- else
157
- # use the same index as the already-defined one
158
- argument.index = existing_argument.index
159
- end
160
-
161
- own_arguments[argument.name] = argument
162
- end
163
-
164
- def argument_at_index(idx)
165
- if arguments.values.first.any_number
166
- arguments.values.first
167
- else
168
- arguments.values.find { |arg| arg.index == idx } || raise("No argument found for #{name} at index #{JSON.dump(idx)} (argument indexes: #{arguments.values.map(&:index)})")
169
- end
170
- end
171
- end
172
-
173
- private
174
-
175
- TYPE_CHECKS = {
176
- "object" => Hash,
177
- "number" => Numeric,
178
- "string" => String,
179
- }
180
-
181
-
182
- def typecast(idx, value)
183
- arg_dec = self.class.argument_at_index(idx)
184
- expected_type = arg_dec.type
185
- expected_type_class = TYPE_CHECKS[expected_type]
186
-
187
- if expected_type == "string"
188
- parsed_value = value
189
- else
190
- parsed_value = JSON.parse('{ "value" : ' + value + '}')["value"]
191
- end
192
-
193
- if !parsed_value.is_a?(expected_type_class)
194
- raise GraphQL::RootCallArgumentError.new(arg_dec, value)
195
- end
196
-
197
- parsed_value
198
- rescue JSON::ParserError
199
- raise GraphQL::RootCallArgumentError.new(arg_dec, value)
200
- end
201
-
202
- end