graphql 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graph_ql/directive.rb +36 -0
- data/lib/graph_ql/directives/directive_chain.rb +33 -0
- data/lib/graph_ql/directives/include_directive.rb +15 -0
- data/lib/graph_ql/directives/skip_directive.rb +15 -0
- data/lib/graph_ql/enum.rb +34 -0
- data/lib/graph_ql/fields/abstract_field.rb +37 -0
- data/lib/graph_ql/fields/access_field.rb +24 -0
- data/lib/graph_ql/fields/field.rb +34 -0
- data/lib/graph_ql/interface.rb +14 -0
- data/lib/graph_ql/introspection/arguments_field.rb +5 -0
- data/lib/graph_ql/introspection/directive_type.rb +12 -0
- data/lib/graph_ql/introspection/enum_value_type.rb +10 -0
- data/lib/graph_ql/introspection/enum_values_field.rb +15 -0
- data/lib/graph_ql/introspection/field_type.rb +11 -0
- data/lib/graph_ql/introspection/fields_field.rb +14 -0
- data/lib/graph_ql/introspection/input_fields_field.rb +12 -0
- data/lib/graph_ql/introspection/input_value_type.rb +10 -0
- data/lib/graph_ql/introspection/of_type_field.rb +12 -0
- data/lib/graph_ql/introspection/possible_types_field.rb +12 -0
- data/lib/graph_ql/introspection/schema_type.rb +32 -0
- data/lib/graph_ql/introspection/type_kind_enum.rb +7 -0
- data/lib/graph_ql/introspection/type_type.rb +22 -0
- data/lib/graph_ql/parser/nodes.rb +72 -0
- data/lib/graph_ql/parser/parser.rb +108 -0
- data/lib/graph_ql/parser/transform.rb +86 -0
- data/lib/graph_ql/parser/visitor.rb +47 -0
- data/lib/graph_ql/query.rb +50 -0
- data/lib/graph_ql/query/arguments.rb +25 -0
- data/lib/graph_ql/query/field_resolution_strategy.rb +83 -0
- data/lib/graph_ql/query/fragment_spread_resolution_strategy.rb +16 -0
- data/lib/graph_ql/query/inline_fragment_resolution_strategy.rb +14 -0
- data/lib/graph_ql/query/operation_resolver.rb +28 -0
- data/lib/graph_ql/query/selection_resolver.rb +20 -0
- data/lib/graph_ql/query/type_resolver.rb +19 -0
- data/lib/graph_ql/repl.rb +27 -0
- data/lib/graph_ql/schema.rb +30 -0
- data/lib/graph_ql/schema/type_reducer.rb +44 -0
- data/lib/graph_ql/type_kinds.rb +15 -0
- data/lib/graph_ql/types/abstract_type.rb +14 -0
- data/lib/graph_ql/types/boolean_type.rb +6 -0
- data/lib/graph_ql/types/float_type.rb +6 -0
- data/lib/graph_ql/types/input_object_type.rb +17 -0
- data/lib/graph_ql/types/input_value.rb +10 -0
- data/lib/graph_ql/types/int_type.rb +6 -0
- data/lib/graph_ql/types/list_type.rb +10 -0
- data/lib/graph_ql/types/non_null_type.rb +18 -0
- data/lib/graph_ql/types/non_null_with_bang.rb +5 -0
- data/lib/graph_ql/types/object_type.rb +62 -0
- data/lib/graph_ql/types/scalar_type.rb +5 -0
- data/lib/graph_ql/types/string_type.rb +6 -0
- data/lib/graph_ql/types/type_definer.rb +16 -0
- data/lib/graph_ql/union.rb +35 -0
- data/lib/graph_ql/validations/fields_are_defined_on_type.rb +44 -0
- data/lib/graph_ql/validations/fields_will_merge.rb +80 -0
- data/lib/graph_ql/validations/fragments_are_used.rb +24 -0
- data/lib/graph_ql/validator.rb +29 -0
- data/lib/graph_ql/version.rb +3 -0
- data/lib/graphql.rb +92 -99
- data/readme.md +17 -177
- data/spec/graph_ql/directive_spec.rb +81 -0
- data/spec/graph_ql/enum_spec.rb +5 -0
- data/spec/graph_ql/fields/field_spec.rb +10 -0
- data/spec/graph_ql/interface_spec.rb +13 -0
- data/spec/graph_ql/introspection/directive_type_spec.rb +40 -0
- data/spec/graph_ql/introspection/schema_type_spec.rb +39 -0
- data/spec/graph_ql/introspection/type_type_spec.rb +104 -0
- data/spec/graph_ql/parser/parser_spec.rb +120 -0
- data/spec/graph_ql/parser/transform_spec.rb +109 -0
- data/spec/graph_ql/parser/visitor_spec.rb +31 -0
- data/spec/graph_ql/query/operation_resolver_spec.rb +14 -0
- data/spec/graph_ql/query_spec.rb +82 -0
- data/spec/graph_ql/schema/type_reducer_spec.rb +24 -0
- data/spec/graph_ql/types/input_object_type_spec.rb +12 -0
- data/spec/graph_ql/types/object_type_spec.rb +35 -0
- data/spec/graph_ql/union_spec.rb +27 -0
- data/spec/graph_ql/validations/fields_are_defined_on_type_spec.rb +28 -0
- data/spec/graph_ql/validations/fields_will_merge_spec.rb +40 -0
- data/spec/graph_ql/validations/fragments_are_used_spec.rb +28 -0
- data/spec/graph_ql/validator_spec.rb +24 -0
- data/spec/spec_helper.rb +2 -2
- data/spec/support/dummy_app.rb +123 -63
- data/spec/support/dummy_data.rb +11 -0
- metadata +107 -59
- data/lib/graphql/call.rb +0 -8
- data/lib/graphql/connection.rb +0 -65
- data/lib/graphql/field.rb +0 -12
- data/lib/graphql/field_definer.rb +0 -25
- data/lib/graphql/introspection/call_type.rb +0 -13
- data/lib/graphql/introspection/connection.rb +0 -9
- data/lib/graphql/introspection/field_type.rb +0 -10
- data/lib/graphql/introspection/root_call_argument_node.rb +0 -5
- data/lib/graphql/introspection/root_call_type.rb +0 -20
- data/lib/graphql/introspection/schema_call.rb +0 -8
- data/lib/graphql/introspection/schema_type.rb +0 -17
- data/lib/graphql/introspection/type_call.rb +0 -8
- data/lib/graphql/introspection/type_type.rb +0 -18
- data/lib/graphql/node.rb +0 -244
- data/lib/graphql/parser/parser.rb +0 -39
- data/lib/graphql/parser/transform.rb +0 -22
- data/lib/graphql/query.rb +0 -109
- data/lib/graphql/root_call.rb +0 -202
- data/lib/graphql/root_call_argument.rb +0 -11
- data/lib/graphql/root_call_argument_definer.rb +0 -17
- data/lib/graphql/schema/all.rb +0 -46
- data/lib/graphql/schema/schema.rb +0 -87
- data/lib/graphql/schema/schema_validation.rb +0 -32
- data/lib/graphql/syntax/call.rb +0 -8
- data/lib/graphql/syntax/field.rb +0 -9
- data/lib/graphql/syntax/fragment.rb +0 -7
- data/lib/graphql/syntax/node.rb +0 -8
- data/lib/graphql/syntax/query.rb +0 -8
- data/lib/graphql/syntax/variable.rb +0 -7
- data/lib/graphql/types/boolean_type.rb +0 -3
- data/lib/graphql/types/number_type.rb +0 -3
- data/lib/graphql/types/object_type.rb +0 -6
- data/lib/graphql/types/string_type.rb +0 -3
- data/lib/graphql/version.rb +0 -3
- data/spec/graphql/node_spec.rb +0 -69
- data/spec/graphql/parser/parser_spec.rb +0 -168
- data/spec/graphql/parser/transform_spec.rb +0 -157
- data/spec/graphql/query_spec.rb +0 -274
- data/spec/graphql/root_call_spec.rb +0 -69
- data/spec/graphql/schema/schema_spec.rb +0 -93
- data/spec/graphql/schema/schema_validation_spec.rb +0 -48
- 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
|
data/lib/graphql/node.rb
DELETED
@@ -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
|
data/lib/graphql/query.rb
DELETED
@@ -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
|
data/lib/graphql/root_call.rb
DELETED
@@ -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
|