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