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.
- 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
|