graphql 0.1.0 → 0.2.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 +9 -5
- data/lib/graph_ql/directives/include_directive.rb +2 -2
- data/lib/graph_ql/directives/skip_directive.rb +2 -2
- data/lib/graph_ql/enum.rb +5 -8
- data/lib/graph_ql/field.rb +50 -0
- data/lib/graph_ql/interface.rb +4 -0
- data/lib/graph_ql/introspection/arguments_field.rb +2 -2
- data/lib/graph_ql/introspection/directive_type.rb +10 -10
- data/lib/graph_ql/introspection/enum_value_type.rb +11 -8
- data/lib/graph_ql/introspection/enum_values_field.rb +5 -5
- data/lib/graph_ql/introspection/field_type.rb +14 -10
- data/lib/graph_ql/introspection/fields_field.rb +4 -4
- data/lib/graph_ql/introspection/input_fields_field.rb +3 -3
- data/lib/graph_ql/introspection/input_value_type.rb +8 -8
- data/lib/graph_ql/introspection/interfaces_field.rb +5 -0
- data/lib/graph_ql/introspection/of_type_field.rb +3 -9
- data/lib/graph_ql/introspection/possible_types_field.rb +3 -10
- data/lib/graph_ql/introspection/schema_type.rb +8 -14
- data/lib/graph_ql/introspection/type_kind_enum.rb +1 -1
- data/lib/graph_ql/introspection/type_type.rb +17 -19
- data/lib/graph_ql/introspection/typename_field.rb +15 -0
- data/lib/graph_ql/parser.rb +9 -0
- data/lib/graph_ql/parser/nodes.rb +17 -7
- data/lib/graph_ql/parser/parser.rb +7 -7
- data/lib/graph_ql/parser/transform.rb +23 -20
- data/lib/graph_ql/parser/visitor.rb +29 -13
- data/lib/graph_ql/query.rb +39 -16
- data/lib/graph_ql/query/field_resolution_strategy.rb +15 -11
- data/lib/graph_ql/query/type_resolver.rb +4 -2
- data/lib/graph_ql/repl.rb +1 -1
- data/lib/graph_ql/schema.rb +19 -7
- data/lib/graph_ql/schema/each_item_validator.rb +12 -0
- data/lib/graph_ql/schema/field_validator.rb +13 -0
- data/lib/graph_ql/schema/implementation_validator.rb +21 -0
- data/lib/graph_ql/schema/schema_validator.rb +10 -0
- data/lib/graph_ql/schema/type_reducer.rb +6 -6
- data/lib/graph_ql/schema/type_validator.rb +47 -0
- data/lib/graph_ql/static_validation.rb +18 -0
- data/lib/graph_ql/static_validation/argument_literals_are_compatible.rb +13 -0
- data/lib/graph_ql/static_validation/arguments_are_defined.rb +10 -0
- data/lib/graph_ql/static_validation/arguments_validator.rb +16 -0
- data/lib/graph_ql/static_validation/directives_are_defined.rb +18 -0
- data/lib/graph_ql/static_validation/fields_are_defined_on_type.rb +29 -0
- data/lib/graph_ql/static_validation/fields_have_appropriate_selections.rb +31 -0
- data/lib/graph_ql/static_validation/fields_will_merge.rb +93 -0
- data/lib/graph_ql/static_validation/fragment_types_exist.rb +24 -0
- data/lib/graph_ql/static_validation/fragments_are_used.rb +30 -0
- data/lib/graph_ql/static_validation/literal_validator.rb +27 -0
- data/lib/graph_ql/static_validation/message.rb +29 -0
- data/lib/graph_ql/static_validation/required_arguments_are_present.rb +13 -0
- data/lib/graph_ql/static_validation/type_stack.rb +87 -0
- data/lib/graph_ql/static_validation/validator.rb +48 -0
- data/lib/graph_ql/type_kinds.rb +50 -12
- data/lib/graph_ql/types/argument_definer.rb +7 -0
- data/lib/graph_ql/types/boolean_type.rb +3 -3
- data/lib/graph_ql/types/field_definer.rb +19 -0
- data/lib/graph_ql/types/float_type.rb +3 -3
- data/lib/graph_ql/types/input_object_type.rb +4 -0
- data/lib/graph_ql/types/input_value.rb +1 -1
- data/lib/graph_ql/types/int_type.rb +4 -4
- data/lib/graph_ql/types/list_type.rb +5 -1
- data/lib/graph_ql/types/non_null_type.rb +4 -0
- data/lib/graph_ql/types/object_type.rb +9 -20
- data/lib/graph_ql/types/scalar_type.rb +4 -0
- data/lib/graph_ql/types/string_type.rb +3 -3
- data/lib/graph_ql/types/type_definer.rb +5 -9
- data/lib/graph_ql/union.rb +6 -17
- data/lib/graph_ql/version.rb +1 -1
- data/lib/graphql.rb +58 -78
- data/readme.md +80 -7
- data/spec/graph_ql/interface_spec.rb +15 -1
- data/spec/graph_ql/introspection/directive_type_spec.rb +2 -2
- data/spec/graph_ql/introspection/schema_type_spec.rb +2 -1
- data/spec/graph_ql/introspection/type_type_spec.rb +16 -1
- data/spec/graph_ql/parser/parser_spec.rb +3 -1
- data/spec/graph_ql/parser/transform_spec.rb +12 -2
- data/spec/graph_ql/parser/visitor_spec.rb +13 -5
- data/spec/graph_ql/query_spec.rb +25 -13
- data/spec/graph_ql/schema/field_validator_spec.rb +21 -0
- data/spec/graph_ql/schema/type_reducer_spec.rb +2 -2
- data/spec/graph_ql/schema/type_validator_spec.rb +54 -0
- data/spec/graph_ql/static_validation/argument_literals_are_compatible_spec.rb +41 -0
- data/spec/graph_ql/static_validation/arguments_are_defined_spec.rb +40 -0
- data/spec/graph_ql/static_validation/directives_are_defined_spec.rb +33 -0
- data/spec/graph_ql/static_validation/fields_are_defined_on_type_spec.rb +59 -0
- data/spec/graph_ql/static_validation/fields_have_appropriate_selections_spec.rb +30 -0
- data/spec/graph_ql/{validations → static_validation}/fields_will_merge_spec.rb +24 -17
- data/spec/graph_ql/static_validation/fragment_types_exist_spec.rb +38 -0
- data/spec/graph_ql/static_validation/fragments_are_used_spec.rb +24 -0
- data/spec/graph_ql/static_validation/required_arguments_are_present_spec.rb +41 -0
- data/spec/graph_ql/static_validation/type_stack_spec.rb +35 -0
- data/spec/graph_ql/static_validation/validator_spec.rb +28 -0
- data/spec/graph_ql/types/object_type_spec.rb +1 -1
- data/spec/graph_ql/union_spec.rb +1 -14
- data/spec/support/dummy_app.rb +75 -53
- metadata +53 -31
- data/lib/graph_ql/fields/abstract_field.rb +0 -37
- data/lib/graph_ql/fields/access_field.rb +0 -24
- data/lib/graph_ql/fields/field.rb +0 -34
- data/lib/graph_ql/types/abstract_type.rb +0 -14
- data/lib/graph_ql/validations/fields_are_defined_on_type.rb +0 -44
- data/lib/graph_ql/validations/fields_will_merge.rb +0 -80
- data/lib/graph_ql/validations/fragments_are_used.rb +0 -24
- data/lib/graph_ql/validator.rb +0 -29
- data/spec/graph_ql/validations/fields_are_defined_on_type_spec.rb +0 -28
- data/spec/graph_ql/validations/fragments_are_used_spec.rb +0 -28
- data/spec/graph_ql/validator_spec.rb +0 -24
@@ -0,0 +1,15 @@
|
|
1
|
+
# A wrapper to create `__typename`.
|
2
|
+
# Uses `.create` because I couldn't figure out how to
|
3
|
+
# pass `DEFINITION` via `super` (then I could extend GraphQL::Field)
|
4
|
+
class GraphQL::Introspection::TypenameField
|
5
|
+
DEFINITION = Proc.new { |f, wrapped_type|
|
6
|
+
f.name "__typename"
|
7
|
+
f.description "The name of this type"
|
8
|
+
f.type -> { !GraphQL::STRING_TYPE }
|
9
|
+
f.resolve -> (obj, a, c) { wrapped_type.name }
|
10
|
+
}
|
11
|
+
|
12
|
+
def self.create(wrapped_type)
|
13
|
+
GraphQL::Field.new { |f| DEFINITION.call(f, wrapped_type) }
|
14
|
+
end
|
15
|
+
end
|
@@ -3,20 +3,30 @@ module GraphQL::Nodes
|
|
3
3
|
# - require their keyword arguments, throw ArgumentError if they don't match
|
4
4
|
# - expose accessors for keyword arguments
|
5
5
|
class AbstractNode
|
6
|
+
attr_accessor :line, :col
|
6
7
|
def initialize(options)
|
7
8
|
required_keys = self.class.required_attrs
|
9
|
+
allowed_keys = required_keys + [:line, :col]
|
10
|
+
position_source = options.delete(:position_source)
|
11
|
+
if !position_source.nil?
|
12
|
+
options[:line], options[:col] = position_source.line_and_column
|
13
|
+
end
|
8
14
|
|
9
|
-
|
15
|
+
present_keys = options.keys
|
16
|
+
extra_keys = present_keys - allowed_keys
|
10
17
|
if extra_keys.any?
|
11
18
|
raise ArgumentError, "#{self.class.name} Extra arguments: #{extra_keys}"
|
12
19
|
end
|
13
20
|
|
14
|
-
required_keys
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
missing_keys = required_keys - present_keys
|
22
|
+
if missing_keys.any?
|
23
|
+
raise ArgumentError, "#{self.class.name} Missing arguments: #{missing_keys}"
|
24
|
+
end
|
25
|
+
|
26
|
+
allowed_keys.each do |key|
|
27
|
+
if options.has_key?(key)
|
28
|
+
value = options[key]
|
29
|
+
self.send("#{key}=", value)
|
20
30
|
end
|
21
31
|
end
|
22
32
|
end
|
@@ -12,7 +12,7 @@ class GraphQL::Parser < Parslet::Parser
|
|
12
12
|
|
13
13
|
# TODO: whitespace sensitive regarding `on`, eg `onFood`, see lookahead note in spec
|
14
14
|
rule(:fragment_definition) {
|
15
|
-
str("fragment") >>
|
15
|
+
str("fragment").as(:fragment_keyword) >>
|
16
16
|
space? >> name.as(:fragment_name) >>
|
17
17
|
space? >> str("on") >> space? >> name.as(:type_condition) >>
|
18
18
|
space? >> directives.maybe.as(:optional_directives).as(:directives) >>
|
@@ -20,14 +20,14 @@ class GraphQL::Parser < Parslet::Parser
|
|
20
20
|
}
|
21
21
|
|
22
22
|
rule(:fragment_spread) {
|
23
|
-
|
23
|
+
spread.as(:fragment_spread_keyword) >> space? >>
|
24
24
|
name.as(:fragment_spread_name) >> space? >>
|
25
25
|
directives.maybe.as(:optional_directives).as(:directives)
|
26
26
|
}
|
27
|
-
|
27
|
+
rule(:spread) { str("...") }
|
28
28
|
# TODO: `on` bug, see spec
|
29
29
|
rule(:inline_fragment) {
|
30
|
-
|
30
|
+
spread.as(:fragment_spread_keyword) >> space? >>
|
31
31
|
str("on ") >> name.as(:inline_fragment_type) >> space? >>
|
32
32
|
directives.maybe.as(:optional_directives).as(:directives) >> space? >>
|
33
33
|
selections.as(:selections)
|
@@ -37,7 +37,7 @@ class GraphQL::Parser < Parslet::Parser
|
|
37
37
|
rule(:unnamed_selections) { selections.as(:unnamed_selections)}
|
38
38
|
rule(:named_operation_definition) {
|
39
39
|
operation_type.as(:operation_type) >> space? >>
|
40
|
-
name.as(:name) >>
|
40
|
+
name.as(:name) >> space? >>
|
41
41
|
operation_variable_definitions.maybe.as(:optional_variables).as(:variables) >> space? >>
|
42
42
|
directives.maybe.as(:optional_directives).as(:directives) >> space? >>
|
43
43
|
selections.as(:selections)
|
@@ -88,10 +88,10 @@ class GraphQL::Parser < Parslet::Parser
|
|
88
88
|
value_variable |
|
89
89
|
value_enum
|
90
90
|
)}
|
91
|
-
rule(:value_sign?) {
|
91
|
+
rule(:value_sign?) { match('[\-\+]').maybe }
|
92
92
|
rule(:value_array) { (str("[") >> (value >> separator?).repeat(0) >> str("]")).as(:array) }
|
93
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) >> (
|
94
|
+
rule(:value_float) { (value_sign? >> match('\d').repeat(1) >> str(".") >> match('\d').repeat(1) >> (match("[eE]") >> value_sign? >> match('\d').repeat(1)).maybe).as(:float) }
|
95
95
|
rule(:value_input_object) { str("{") >> value_input_object_pair.repeat(1).as(:input_object) >> str("}") }
|
96
96
|
rule(:value_input_object_pair) { space? >> name.as(:input_object_name) >> space? >> str(":") >> space? >> value.as(:input_object_value) >> separator? }
|
97
97
|
rule(:value_int) { (value_sign? >> match('\d').repeat(1)).as(:int) }
|
@@ -11,26 +11,29 @@ class GraphQL::Transform < Parslet::Transform
|
|
11
11
|
end
|
12
12
|
|
13
13
|
# Document
|
14
|
-
rule(document_parts: sequence(:p)) { Document.new(parts: p)}
|
14
|
+
rule(document_parts: sequence(:p)) { Document.new(parts: p, line: p.first.line, col: p.first.col)}
|
15
15
|
|
16
16
|
# Fragment Definition
|
17
17
|
rule(
|
18
|
+
fragment_keyword: simple(:kw),
|
18
19
|
fragment_name: simple(:name),
|
19
20
|
type_condition: simple(:type),
|
20
21
|
directives: sequence(:directives),
|
21
22
|
selections: sequence(:selections)
|
22
|
-
) {FragmentDefinition.new(name: name.to_s, type: type.to_s, directives: directives, selections: selections)}
|
23
|
+
) {FragmentDefinition.new(name: name.to_s, type: type.to_s, directives: directives, selections: selections, position_source: kw)}
|
23
24
|
|
24
25
|
rule(
|
26
|
+
fragment_spread_keyword: simple(:kw),
|
25
27
|
fragment_spread_name: simple(:n),
|
26
28
|
directives: sequence(:d)
|
27
|
-
) { FragmentSpread.new(name: n.to_s, directives: d)}
|
29
|
+
) { FragmentSpread.new(name: n.to_s, directives: d, position_source: kw)}
|
28
30
|
|
29
31
|
rule(
|
32
|
+
fragment_spread_keyword: simple(:kw),
|
30
33
|
inline_fragment_type: simple(:n),
|
31
34
|
directives: sequence(:d),
|
32
35
|
selections: sequence(:s),
|
33
|
-
) { InlineFragment.new(type: n.to_s, directives: d, selections: s)}
|
36
|
+
) { InlineFragment.new(type: n.to_s, directives: d, selections: s, position_source: kw)}
|
34
37
|
|
35
38
|
# Operation Definition
|
36
39
|
rule(
|
@@ -39,14 +42,14 @@ class GraphQL::Transform < Parslet::Transform
|
|
39
42
|
variables: sequence(:v),
|
40
43
|
directives: sequence(:d),
|
41
44
|
selections: sequence(:s),
|
42
|
-
) { OperationDefinition.new(operation_type: ot.to_s, name: n.to_s, variables: v, directives: d, selections: s) }
|
45
|
+
) { OperationDefinition.new(operation_type: ot.to_s, name: n.to_s, variables: v, directives: d, selections: s, position_source: ot) }
|
43
46
|
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)}
|
47
|
+
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, line: n.line, col: n.col)}
|
48
|
+
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, line: n.line, col: n.col)}
|
46
49
|
rule(variable_default_value: simple(:v) ) { v }
|
47
50
|
rule(variable_default_value: sequence(:v) ) { v }
|
48
51
|
# Query short-hand
|
49
|
-
rule(unnamed_selections: sequence(:s)) { OperationDefinition.new(selections: s, operation_type: "query", name: nil, variables: [], directives: [])}
|
52
|
+
rule(unnamed_selections: sequence(:s)) { OperationDefinition.new(selections: s, operation_type: "query", name: nil, variables: [], directives: [], line: s.first.line, col: s.first.col)}
|
50
53
|
|
51
54
|
# Field
|
52
55
|
rule(
|
@@ -55,32 +58,32 @@ class GraphQL::Transform < Parslet::Transform
|
|
55
58
|
field_arguments: sequence(:args),
|
56
59
|
directives: sequence(:dir),
|
57
60
|
selections: sequence(:sel)
|
58
|
-
) { Field.new(alias: a, name: name.to_s, arguments: args, directives: dir, selections: sel) }
|
61
|
+
) { Field.new(alias: a && a.to_s, name: name.to_s, arguments: args, directives: dir, selections: sel, position_source: [a, name].find { |part| !part.nil? }) }
|
59
62
|
|
60
|
-
rule(alias_name: simple(:a)) { a
|
63
|
+
rule(alias_name: simple(:a)) { a }
|
61
64
|
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)}
|
65
|
+
rule(field_argument_name: simple(:n), field_argument_value: simple(:v)) { Argument.new(name: n.to_s, value: v, position_source: n)}
|
63
66
|
optional_sequence(:optional_selections)
|
64
67
|
optional_sequence(:optional_directives)
|
65
68
|
|
66
69
|
# 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)}
|
70
|
+
rule(directive_name: simple(:name), directive_arguments: sequence(:args)) { Directive.new(name: name.to_s, arguments: args, position_source: name ) }
|
71
|
+
rule(directive_argument_name: simple(:n), directive_argument_value: simple(:v)) { Argument.new(name: n.to_s, value: v, position_source: n)}
|
69
72
|
optional_sequence(:optional_directive_arguments)
|
70
73
|
|
71
74
|
# 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
|
+
rule(type_name: simple(:n)) { TypeName.new(name: n.to_s, position_source: n) }
|
76
|
+
rule(list_type: simple(:t)) { ListType.new(of_type: t, line: t.line, col: t.col)}
|
77
|
+
rule(non_null_type: simple(:t)) { NonNullType.new(of_type: t, line: t.line, col: t.col)}
|
75
78
|
|
76
79
|
# Values
|
77
80
|
rule(array: sequence(:v)) { v }
|
78
81
|
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)}
|
82
|
+
rule(input_object: sequence(:v)) { InputObject.new(pairs: v, line: v.first.line, col: v.first.col) }
|
83
|
+
rule(input_object_name: simple(:n), input_object_value: simple(:v)) { Argument.new(name: n.to_s, value: v, position_source: n)}
|
81
84
|
rule(int: simple(:v)) { v.to_i }
|
82
85
|
rule(float: simple(:v)) { v.to_f }
|
83
86
|
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)}
|
87
|
+
rule(variable: simple(:v)) { VariableIdentifier.new(name: v.to_s, position_source: v) }
|
88
|
+
rule(enum: simple(:v)) { Enum.new(name: v.to_s, position_source: v)}
|
86
89
|
end
|
@@ -9,8 +9,13 @@
|
|
9
9
|
# # => 6
|
10
10
|
#
|
11
11
|
class GraphQL::Visitor
|
12
|
+
SKIP = :_skip
|
13
|
+
|
14
|
+
attr_reader :enter, :leave
|
12
15
|
def initialize
|
13
16
|
@visitors = {}
|
17
|
+
@enter = []
|
18
|
+
@leave = []
|
14
19
|
end
|
15
20
|
|
16
21
|
def [](node_class)
|
@@ -18,11 +23,30 @@ class GraphQL::Visitor
|
|
18
23
|
end
|
19
24
|
|
20
25
|
# Apply built-up vistors to `document`
|
21
|
-
def visit(root)
|
22
|
-
|
23
|
-
|
24
|
-
root
|
25
|
-
|
26
|
+
def visit(root, parent=nil)
|
27
|
+
begin_visit(root, parent) &&
|
28
|
+
root.children.reduce(true) { |memo, child| memo && visit(child, root) }
|
29
|
+
end_visit(root, parent)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def begin_visit(node, parent)
|
35
|
+
self.class.apply_hooks(enter, node, parent)
|
36
|
+
node_visitor = self[node.class]
|
37
|
+
self.class.apply_hooks(node_visitor.enter, node, parent)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Should global `leave` visitors come first or last?
|
41
|
+
def end_visit(node, parent)
|
42
|
+
self.class.apply_hooks(leave, node, parent)
|
43
|
+
node_visitor = self[node.class]
|
44
|
+
self.class.apply_hooks(node_visitor.leave, node, parent)
|
45
|
+
end
|
46
|
+
|
47
|
+
# If one of the visitors returns SKIP, stop visiting this node
|
48
|
+
def self.apply_hooks(hooks, node, parent)
|
49
|
+
hooks.reduce(true) { |memo, proc| memo && (proc.call(node, parent) != SKIP) }
|
26
50
|
end
|
27
51
|
|
28
52
|
class NodeVisitor
|
@@ -35,13 +59,5 @@ class GraphQL::Visitor
|
|
35
59
|
def <<(hook)
|
36
60
|
enter << hook
|
37
61
|
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
62
|
end
|
47
63
|
end
|
data/lib/graph_ql/query.rb
CHANGED
@@ -1,23 +1,21 @@
|
|
1
1
|
class GraphQL::Query
|
2
|
+
# If a resolve function returns `GraphQL::Query::DEFAULT_RESOLVE`,
|
3
|
+
# The executor will send the field's name to the target object
|
4
|
+
# and use the result.
|
2
5
|
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
6
|
attr_reader :schema, :document, :context, :fragments, :params
|
12
7
|
|
13
|
-
def initialize(schema, query_string, context: nil, params: {})
|
8
|
+
def initialize(schema, query_string, context: nil, params: {}, debug: true, validate: true)
|
14
9
|
@schema = schema
|
15
|
-
@
|
10
|
+
@debug = debug
|
11
|
+
@query_string = query_string
|
16
12
|
@context = context
|
17
13
|
@params = params
|
14
|
+
@validate = validate
|
18
15
|
@fragments = {}
|
19
16
|
@operations = {}
|
20
17
|
|
18
|
+
@document = GraphQL.parse(@query_string)
|
21
19
|
@document.parts.each do |part|
|
22
20
|
if part.is_a?(GraphQL::Nodes::FragmentDefinition)
|
23
21
|
@fragments[part.name] = part
|
@@ -29,22 +27,47 @@ class GraphQL::Query
|
|
29
27
|
|
30
28
|
# Get the result for this query, executing it once
|
31
29
|
def result
|
30
|
+
if validation_errors.any?
|
31
|
+
return { "errors" => validation_errors }
|
32
|
+
end
|
33
|
+
|
32
34
|
@result ||= {
|
33
35
|
"data" => execute,
|
34
36
|
}
|
35
37
|
rescue StandardError => err
|
36
|
-
|
37
|
-
|
38
|
+
if @debug
|
39
|
+
raise err
|
40
|
+
else
|
41
|
+
message = "Something went wrong during query execution: #{err}" # \n #{err.backtrace.join("\n ")}"
|
42
|
+
{"errors" => [{"message" => message}]}
|
43
|
+
end
|
38
44
|
end
|
39
45
|
|
40
46
|
private
|
41
47
|
|
42
48
|
def execute
|
43
|
-
|
44
|
-
@operations.each do |name, operation|
|
49
|
+
@operations.reduce({}) do |memo, (name, operation)|
|
45
50
|
resolver = OperationResolver.new(operation, self)
|
46
|
-
|
51
|
+
memo[name] = resolver.result
|
52
|
+
memo
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def validation_errors
|
57
|
+
@validation_errors ||= begin
|
58
|
+
if @validate
|
59
|
+
@schema.static_validator.validate(@document)
|
60
|
+
else
|
61
|
+
[]
|
62
|
+
end
|
47
63
|
end
|
48
|
-
response
|
49
64
|
end
|
50
65
|
end
|
66
|
+
|
67
|
+
require 'graph_ql/query/arguments'
|
68
|
+
require 'graph_ql/query/field_resolution_strategy'
|
69
|
+
require 'graph_ql/query/fragment_spread_resolution_strategy'
|
70
|
+
require 'graph_ql/query/inline_fragment_resolution_strategy'
|
71
|
+
require 'graph_ql/query/operation_resolver'
|
72
|
+
require 'graph_ql/query/selection_resolver'
|
73
|
+
require 'graph_ql/query/type_resolver'
|
@@ -12,18 +12,23 @@ class GraphQL::Query::FieldResolutionStrategy
|
|
12
12
|
if value == GraphQL::Query::DEFAULT_RESOLVE
|
13
13
|
begin
|
14
14
|
value = target.send(field_name)
|
15
|
-
rescue NoMethodError =>
|
16
|
-
raise("Couldn't resolve field '#{field_name}' to #{target.class} '#{target}' (resulted in
|
15
|
+
rescue NoMethodError => err
|
16
|
+
raise("Couldn't resolve field '#{field_name}' to #{target.class} '#{target}' (resulted in #{err})")
|
17
17
|
end
|
18
18
|
end
|
19
|
-
|
20
|
-
|
19
|
+
resolved_type = field.type.kind.resolve(field.type, value)
|
20
|
+
strategy_class = self.class.get_strategy_for_kind(resolved_type.kind)
|
21
|
+
result_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_field, operation_resolver)
|
21
22
|
@result_value = result_strategy.result
|
22
23
|
end
|
23
24
|
result_name = ast_field.alias || ast_field.name
|
24
25
|
@result = { result_name => @result_value}
|
25
26
|
end
|
26
27
|
|
28
|
+
def self.get_strategy_for_kind(kind)
|
29
|
+
FIELD_TYPE_KIND_STRATEGIES[kind] || raise("No strategy for #{kind}")
|
30
|
+
end
|
31
|
+
|
27
32
|
class ScalarResolutionStrategy
|
28
33
|
attr_reader :result
|
29
34
|
def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
|
@@ -35,9 +40,10 @@ class GraphQL::Query::FieldResolutionStrategy
|
|
35
40
|
attr_reader :result
|
36
41
|
def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
|
37
42
|
wrapped_type = field_type.of_type
|
38
|
-
|
43
|
+
resolved_type = wrapped_type.kind.resolve(wrapped_type, value)
|
44
|
+
strategy_class = GraphQL::Query::FieldResolutionStrategy.get_strategy_for_kind(resolved_type.kind)
|
39
45
|
@result = value.map do |item|
|
40
|
-
inner_strategy = strategy_class.new(item,
|
46
|
+
inner_strategy = strategy_class.new(item, resolved_type, target, parent_type, ast_field, operation_resolver)
|
41
47
|
inner_strategy.result
|
42
48
|
end
|
43
49
|
end
|
@@ -51,7 +57,6 @@ class GraphQL::Query::FieldResolutionStrategy
|
|
51
57
|
end
|
52
58
|
end
|
53
59
|
|
54
|
-
|
55
60
|
class EnumResolutionStrategy
|
56
61
|
attr_reader :result
|
57
62
|
def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
|
@@ -63,8 +68,9 @@ class GraphQL::Query::FieldResolutionStrategy
|
|
63
68
|
attr_reader :result
|
64
69
|
def initialize(value, field_type, target, parent_type, ast_field, operation_resolver)
|
65
70
|
wrapped_type = field_type.of_type
|
66
|
-
|
67
|
-
|
71
|
+
resolved_type = wrapped_type.kind.resolve(wrapped_type, value)
|
72
|
+
strategy_class = GraphQL::Query::FieldResolutionStrategy.get_strategy_for_kind(resolved_type.kind)
|
73
|
+
inner_strategy = strategy_class.new(value, resolved_type, target, parent_type, ast_field, operation_resolver)
|
68
74
|
@result = inner_strategy.result
|
69
75
|
end
|
70
76
|
end
|
@@ -75,8 +81,6 @@ class GraphQL::Query::FieldResolutionStrategy
|
|
75
81
|
GraphQL::TypeKinds::SCALAR => ScalarResolutionStrategy,
|
76
82
|
GraphQL::TypeKinds::LIST => ListResolutionStrategy,
|
77
83
|
GraphQL::TypeKinds::OBJECT => ObjectResolutionStrategy,
|
78
|
-
GraphQL::TypeKinds::UNION => ObjectResolutionStrategy,
|
79
|
-
GraphQL::TypeKinds::INTERFACE => ObjectResolutionStrategy,
|
80
84
|
GraphQL::TypeKinds::ENUM => EnumResolutionStrategy,
|
81
85
|
GraphQL::TypeKinds::NON_NULL => NonNullResolutionStrategy,
|
82
86
|
}
|
@@ -6,9 +6,11 @@ class GraphQL::Query::TypeResolver
|
|
6
6
|
def initialize(target, child_type, parent_type)
|
7
7
|
@type = if child_type.nil?
|
8
8
|
nil
|
9
|
-
elsif
|
9
|
+
elsif parent_type.kind.union?
|
10
10
|
parent_type.resolve_type(target)
|
11
|
-
elsif
|
11
|
+
elsif child_type.kind.union? && child_type.include?(parent_type)
|
12
|
+
parent_type
|
13
|
+
elsif child_type.kind.interface?
|
12
14
|
child_type.resolve_type(target)
|
13
15
|
elsif child_type == parent_type
|
14
16
|
parent_type
|
data/lib/graph_ql/repl.rb
CHANGED
@@ -19,7 +19,7 @@ class GraphQL::Repl
|
|
19
19
|
def execute_query(query_string)
|
20
20
|
begin
|
21
21
|
query = GraphQL::Query.new(@schema, query_string)
|
22
|
-
puts JSON.pretty_generate(query.
|
22
|
+
puts JSON.pretty_generate(query.result)
|
23
23
|
rescue StandardError => err
|
24
24
|
puts "Couldn't parse: #{err}\n\n" # #{err.backtrace.join("\n")}"
|
25
25
|
end
|