graphql 0.9.5 → 0.10.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/graphql/base_type.rb +24 -0
- data/lib/graphql/definition_helpers/defined_by_config.rb +5 -4
- data/lib/graphql/definition_helpers/non_null_with_bang.rb +1 -1
- data/lib/graphql/definition_helpers/type_definer.rb +1 -1
- data/lib/graphql/enum_type.rb +16 -3
- data/lib/graphql/input_object_type.rb +10 -0
- data/lib/graphql/language.rb +0 -5
- data/lib/graphql/language/nodes.rb +79 -75
- data/lib/graphql/language/parser.rb +109 -106
- data/lib/graphql/language/transform.rb +100 -91
- data/lib/graphql/language/visitor.rb +78 -74
- data/lib/graphql/list_type.rb +5 -0
- data/lib/graphql/non_null_type.rb +6 -2
- data/lib/graphql/query.rb +58 -9
- data/lib/graphql/query/arguments.rb +29 -26
- data/lib/graphql/query/base_execution/value_resolution.rb +3 -3
- data/lib/graphql/query/directive_chain.rb +1 -1
- data/lib/graphql/query/executor.rb +6 -27
- data/lib/graphql/query/literal_input.rb +89 -0
- data/lib/graphql/query/ruby_input.rb +20 -0
- data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
- data/lib/graphql/query/variables.rb +39 -0
- data/lib/graphql/scalar_type.rb +27 -5
- data/lib/graphql/schema.rb +5 -0
- data/lib/graphql/schema/type_expression.rb +28 -0
- data/lib/graphql/static_validation/literal_validator.rb +5 -2
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +0 -2
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/readme.md +3 -2
- data/spec/graphql/enum_type_spec.rb +7 -2
- data/spec/graphql/input_object_type_spec.rb +45 -0
- data/spec/graphql/language/parser_spec.rb +2 -1
- data/spec/graphql/language/transform_spec.rb +5 -0
- data/spec/graphql/query/base_execution/value_resolution_spec.rb +46 -0
- data/spec/graphql/query/context_spec.rb +37 -0
- data/spec/graphql/query/executor_spec.rb +33 -0
- data/spec/graphql/query_spec.rb +110 -26
- data/spec/graphql/schema/type_expression_spec.rb +38 -0
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +2 -2
- data/spec/support/dairy_app.rb +17 -17
- data/spec/support/dairy_data.rb +2 -2
- metadata +12 -2
@@ -1,108 +1,117 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
1
|
+
module GraphQL
|
2
|
+
module Language
|
3
|
+
# Extend `Parslet::Context` with a convenience method
|
4
|
+
# for building AST nodes
|
5
|
+
module ParsletContextCreateNode
|
6
|
+
refine Parslet::Context do
|
7
|
+
# @param [Symbol] name of a node constant
|
8
|
+
# @param [Hash] attributes to initialize the {Node} with
|
9
|
+
# @return [GraphQL::Language::Node] a node of type `name` with attributes `attributes`
|
10
|
+
def create_node(name, attributes)
|
11
|
+
node_class = GraphQL::Language::Nodes.const_get(name)
|
12
|
+
node_class.new(attributes)
|
13
|
+
end
|
14
|
+
end
|
14
15
|
end
|
15
16
|
|
17
|
+
using ParsletContextCreateNode
|
16
18
|
|
19
|
+
# {Transform} is a [parslet](http://kschiess.github.io/parslet/) transform for for turning the AST into objects in {GraphQL::Language::Nodes} objects.
|
20
|
+
class Transform < Parslet::Transform
|
21
|
+
def self.optional_sequence(name)
|
22
|
+
rule(name => simple(:val)) { [] }
|
23
|
+
rule(name => sequence(:val)) { val }
|
24
|
+
end
|
17
25
|
|
18
|
-
|
19
|
-
|
20
|
-
|
26
|
+
# Document
|
27
|
+
rule(document_parts: sequence(:p)) { create_node(:Document, parts: p, line: (p.first ? p.first.line : 1), col: (p.first ? p.first.col : 1))}
|
28
|
+
rule(document_parts: simple(:p)) { create_node(:Document, parts: [], line: 1, col: 1)}
|
21
29
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
# Fragment Definition
|
31
|
+
rule(
|
32
|
+
fragment_keyword: simple(:kw),
|
33
|
+
fragment_name: simple(:name),
|
34
|
+
type_condition: simple(:type),
|
35
|
+
directives: sequence(:directives),
|
36
|
+
selections: sequence(:selections)
|
37
|
+
) { create_node(:FragmentDefinition, name: name.to_s, type: type.to_s, directives: directives, selections: selections, position_source: kw)}
|
30
38
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
39
|
+
rule(
|
40
|
+
fragment_spread_keyword: simple(:kw),
|
41
|
+
fragment_spread_name: simple(:n),
|
42
|
+
directives: sequence(:d)
|
43
|
+
) { create_node(:FragmentSpread, name: n.to_s, directives: d, position_source: kw)}
|
36
44
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
45
|
+
rule(
|
46
|
+
fragment_spread_keyword: simple(:kw),
|
47
|
+
inline_fragment_type: simple(:n),
|
48
|
+
directives: sequence(:d),
|
49
|
+
selections: sequence(:s),
|
50
|
+
) { create_node(:InlineFragment, type: n.to_s, directives: d, selections: s, position_source: kw)}
|
43
51
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
52
|
+
# Operation Definition
|
53
|
+
rule(
|
54
|
+
operation_type: simple(:ot),
|
55
|
+
name: simple(:n),
|
56
|
+
variables: sequence(:v),
|
57
|
+
directives: sequence(:d),
|
58
|
+
selections: sequence(:s),
|
59
|
+
) { create_node(:OperationDefinition, operation_type: ot.to_s, name: n.to_s, variables: v, directives: d, selections: s, position_source: ot) }
|
60
|
+
optional_sequence(:optional_variables)
|
61
|
+
rule(variable_name: simple(:n), variable_type: simple(:t), variable_optional_default_value: simple(:v)) { create_node(:Variable, name: n.name, type: t, default_value: v, line: n.line, col: n.col)}
|
62
|
+
rule(variable_name: simple(:n), variable_type: simple(:t), variable_optional_default_value: sequence(:v)) { create_node(:Variable, name: n.name, type: t, default_value: v, line: n.line, col: n.col)}
|
63
|
+
rule(variable_default_value: simple(:v) ) { v }
|
64
|
+
rule(variable_default_value: sequence(:v) ) { v }
|
65
|
+
# Query short-hand
|
66
|
+
rule(unnamed_selections: sequence(:s)) { create_node(:OperationDefinition, selections: s, operation_type: "query", name: nil, variables: [], directives: [], line: s.first.line, col: s.first.col)}
|
59
67
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
+
# Field
|
69
|
+
rule(
|
70
|
+
alias: simple(:a),
|
71
|
+
field_name: simple(:name),
|
72
|
+
field_arguments: sequence(:args),
|
73
|
+
directives: sequence(:dir),
|
74
|
+
selections: sequence(:sel)
|
75
|
+
) { create_node(:Field, alias: a && a.to_s, name: name.to_s, arguments: args, directives: dir, selections: sel, position_source: [a, name].find { |part| !part.nil? }) }
|
68
76
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
77
|
+
rule(alias_name: simple(:a)) { a }
|
78
|
+
optional_sequence(:optional_field_arguments)
|
79
|
+
rule(field_argument_name: simple(:n), field_argument_value: simple(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
|
80
|
+
rule(field_argument_name: simple(:n), field_argument_value: sequence(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
|
81
|
+
optional_sequence(:optional_selections)
|
82
|
+
optional_sequence(:optional_directives)
|
75
83
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
84
|
+
# Directive
|
85
|
+
rule(directive_name: simple(:name), directive_arguments: sequence(:args)) { create_node(:Directive, name: name.to_s, arguments: args, position_source: name ) }
|
86
|
+
rule(directive_argument_name: simple(:n), directive_argument_value: simple(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
|
87
|
+
optional_sequence(:optional_directive_arguments)
|
80
88
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
89
|
+
# Type Defs
|
90
|
+
rule(type_name: simple(:n)) { create_node(:TypeName, name: n.to_s, position_source: n) }
|
91
|
+
rule(list_type: simple(:t)) { create_node(:ListType, of_type: t, line: t.line, col: t.col)}
|
92
|
+
rule(non_null_type: simple(:t)) { create_node(:NonNullType, of_type: t, line: t.line, col: t.col)}
|
85
93
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
+
# Values
|
95
|
+
rule(array: sequence(:v)) { v }
|
96
|
+
rule(boolean: simple(:v)) { v == "true" ? true : false }
|
97
|
+
rule(input_object: sequence(:v)) { create_node(:InputObject, pairs: v, line: v.first.line, col: v.first.col) }
|
98
|
+
rule(input_object_name: simple(:n), input_object_value: simple(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
|
99
|
+
rule(input_object_name: simple(:n), input_object_value: sequence(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
|
100
|
+
rule(int: simple(:v)) { v.to_i }
|
101
|
+
rule(float: simple(:v)) { v.to_f }
|
94
102
|
|
95
|
-
|
96
|
-
|
97
|
-
|
103
|
+
ESCAPES = /\\(["\\\/bfnrt])/
|
104
|
+
UTF_8 = /\\u[\da-f]{4}/i
|
105
|
+
UTF_8_REPLACE = -> (m) { [m[-4..-1].to_i(16)].pack('U') }
|
98
106
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
+
rule(string: simple(:v)) {
|
108
|
+
string = v.to_s
|
109
|
+
string.gsub!(ESCAPES, '\1')
|
110
|
+
string.gsub!(UTF_8, &UTF_8_REPLACE)
|
111
|
+
string
|
112
|
+
}
|
113
|
+
rule(variable: simple(:v)) { create_node(:VariableIdentifier, name: v.to_s, position_source: v) }
|
114
|
+
rule(enum: simple(:v)) { create_node(:Enum, name: v.to_s, position_source: v)}
|
115
|
+
end
|
107
116
|
end
|
108
117
|
end
|
@@ -1,88 +1,92 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# visitor
|
8
|
-
# #
|
9
|
-
# visitor[GraphQL::Language::Nodes::
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
1
|
+
module GraphQL
|
2
|
+
module Language
|
3
|
+
# Depth-first traversal through the tree, calling hooks at each stop.
|
4
|
+
#
|
5
|
+
# @example Create a visitor, add hooks, then search a document
|
6
|
+
# total_field_count = 0
|
7
|
+
# visitor = GraphQL::Language::Visitor.new
|
8
|
+
# # Whenever you find a field, increment the field count:
|
9
|
+
# visitor[GraphQL::Language::Nodes::Field] << -> (node) { total_field_count += 1 }
|
10
|
+
# # When we finish, print the field count:
|
11
|
+
# visitor[GraphQL::Language::Nodes::Document].leave << -> (node) { p total_field_count }
|
12
|
+
# visitor.visit(document)
|
13
|
+
# # => 6
|
14
|
+
#
|
15
|
+
class Visitor
|
16
|
+
# If any hook returns this value, the {Visitor} stops visiting this
|
17
|
+
# node right away
|
18
|
+
SKIP = :_skip
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
# @return [Array<Proc>] Hooks to call when entering _any_ node
|
21
|
+
attr_reader :enter
|
22
|
+
# @return [Array<Proc>] Hooks to call when leaving _any_ node
|
23
|
+
attr_reader :leave
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
def initialize
|
26
|
+
@visitors = {}
|
27
|
+
@enter = []
|
28
|
+
@leave = []
|
29
|
+
end
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
31
|
+
# Get a {NodeVisitor} for `node_class`
|
32
|
+
# @param node_class [Class] The node class that you want to listen to
|
33
|
+
# @return [NodeVisitor]
|
34
|
+
#
|
35
|
+
# @example Run a hook whenever you enter a new Field
|
36
|
+
# visitor[GraphQL::Language::Nodes::Field] << -> (node, parent) { p "Here's a field" }
|
37
|
+
def [](node_class)
|
38
|
+
@visitors[node_class] ||= NodeVisitor.new
|
39
|
+
end
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
41
|
+
# Visit `root` and all children, applying hooks as you go
|
42
|
+
# @param root [GraphQL::Language::Nodes::AbstractNode] some node to start parsing on
|
43
|
+
# @return [void]
|
44
|
+
def visit(root, parent=nil)
|
45
|
+
begin_visit(root, parent) &&
|
46
|
+
root.children.reduce(true) { |memo, child| memo && visit(child, root) }
|
47
|
+
end_visit(root, parent)
|
48
|
+
end
|
47
49
|
|
48
|
-
|
50
|
+
private
|
49
51
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
def begin_visit(node, parent)
|
53
|
+
self.class.apply_hooks(enter, node, parent)
|
54
|
+
node_visitor = self[node.class]
|
55
|
+
self.class.apply_hooks(node_visitor.enter, node, parent)
|
56
|
+
end
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
58
|
+
# Should global `leave` visitors come first or last?
|
59
|
+
def end_visit(node, parent)
|
60
|
+
self.class.apply_hooks(leave, node, parent)
|
61
|
+
node_visitor = self[node.class]
|
62
|
+
self.class.apply_hooks(node_visitor.leave, node, parent)
|
63
|
+
end
|
62
64
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
65
|
+
# If one of the visitors returns SKIP, stop visiting this node
|
66
|
+
def self.apply_hooks(hooks, node, parent)
|
67
|
+
hooks.reduce(true) { |memo, proc| memo && (proc.call(node, parent) != SKIP) }
|
68
|
+
end
|
67
69
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
70
|
+
# Collect `enter` and `leave` hooks for classes in {GraphQL::Language::Nodes}
|
71
|
+
#
|
72
|
+
# Access {NodeVisitor}s via {GraphQL::Language::Visitor#[]}
|
73
|
+
class NodeVisitor
|
74
|
+
# @return [Array<Proc>] Hooks to call when entering a node of this type
|
75
|
+
attr_reader :enter
|
76
|
+
# @return [Array<Proc>] Hooks to call when leaving a node of this type
|
77
|
+
attr_reader :leave
|
76
78
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
79
|
+
def initialize
|
80
|
+
@enter = []
|
81
|
+
@leave = []
|
82
|
+
end
|
81
83
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
84
|
+
# Shorthand to add a hook to the {#enter} array
|
85
|
+
# @param hook [Proc] A hook to add
|
86
|
+
def <<(hook)
|
87
|
+
enter << hook
|
88
|
+
end
|
89
|
+
end
|
86
90
|
end
|
87
91
|
end
|
88
92
|
end
|
data/lib/graphql/list_type.rb
CHANGED
@@ -13,8 +13,12 @@ class GraphQL::NonNullType < GraphQL::BaseType
|
|
13
13
|
"Non-Null"
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
17
|
-
of_type.
|
16
|
+
def coerce_input(value)
|
17
|
+
of_type.coerce_input(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def coerce_result(value)
|
21
|
+
of_type.coerce_result(value)
|
18
22
|
end
|
19
23
|
|
20
24
|
def kind
|
data/lib/graphql/query.rb
CHANGED
@@ -1,9 +1,23 @@
|
|
1
1
|
class GraphQL::Query
|
2
|
+
class OperationNameMissingError < StandardError
|
3
|
+
def initialize(names)
|
4
|
+
msg = "You must provide an operation name from: #{names.join(", ")}"
|
5
|
+
super(msg)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class VariableMissingError < StandardError
|
10
|
+
def initialize(name, type)
|
11
|
+
msg = "Variable #{name} of type #{type} can't be null"
|
12
|
+
super(msg)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
2
16
|
# If a resolve function returns `GraphQL::Query::DEFAULT_RESOLVE`,
|
3
17
|
# The executor will send the field's name to the target object
|
4
18
|
# and use the result.
|
5
19
|
DEFAULT_RESOLVE = :__default_resolve
|
6
|
-
attr_reader :schema, :document, :context, :fragments, :
|
20
|
+
attr_reader :schema, :document, :context, :fragments, :operations, :debug
|
7
21
|
|
8
22
|
# Prepare query `query_string` on `schema`
|
9
23
|
# @param schema [GraphQL::Schema]
|
@@ -17,13 +31,11 @@ class GraphQL::Query
|
|
17
31
|
@schema = schema
|
18
32
|
@debug = debug
|
19
33
|
@context = Context.new(values: context)
|
20
|
-
|
21
|
-
@variables = variables
|
22
34
|
@validate = validate
|
23
35
|
@operation_name = operation_name
|
24
36
|
@fragments = {}
|
25
37
|
@operations = {}
|
26
|
-
|
38
|
+
@provided_variables = variables
|
27
39
|
@document = GraphQL.parse(query_string)
|
28
40
|
@document.parts.each do |part|
|
29
41
|
if part.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
|
@@ -40,20 +52,57 @@ class GraphQL::Query
|
|
40
52
|
return { "errors" => validation_errors }
|
41
53
|
end
|
42
54
|
|
43
|
-
@result ||= Executor.new(self
|
55
|
+
@result ||= Executor.new(self).result
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# This is the operation to run for this query.
|
60
|
+
# If more than one operation is present, it must be named at runtime.
|
61
|
+
# @return [GraphQL::Language::Nodes::OperationDefinition, nil]
|
62
|
+
def selected_operation
|
63
|
+
@selected_operation ||= find_operation(@operations, @operation_name)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Determine the values for variables of this query, using default values
|
67
|
+
# if a value isn't provided at runtime.
|
68
|
+
#
|
69
|
+
# Raises if a non-null variable isn't provided at runtime.
|
70
|
+
# @return [GraphQL::Query::Variables] Variables to apply to this query
|
71
|
+
def variables
|
72
|
+
@variables ||= GraphQL::Query::Variables.new(
|
73
|
+
schema,
|
74
|
+
selected_operation.variables,
|
75
|
+
@provided_variables
|
76
|
+
)
|
44
77
|
end
|
45
78
|
|
46
79
|
private
|
47
80
|
|
48
81
|
def validation_errors
|
49
|
-
@validation_errors ||=
|
82
|
+
@validation_errors ||= schema.static_validator.validate(document)
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def find_operation(operations, operation_name)
|
87
|
+
if operations.length == 1
|
88
|
+
operations.values.first
|
89
|
+
elsif operations.length == 0
|
90
|
+
nil
|
91
|
+
elsif !operations.key?(operation_name)
|
92
|
+
raise OperationNameMissingError, operations.keys
|
93
|
+
else
|
94
|
+
operations[operation_name]
|
95
|
+
end
|
50
96
|
end
|
51
97
|
end
|
52
98
|
|
53
99
|
require 'graphql/query/arguments'
|
54
100
|
require 'graphql/query/base_execution'
|
55
|
-
require 'graphql/query/
|
56
|
-
require 'graphql/query/type_resolver'
|
101
|
+
require 'graphql/query/context'
|
57
102
|
require 'graphql/query/directive_chain'
|
58
103
|
require 'graphql/query/executor'
|
59
|
-
require 'graphql/query/
|
104
|
+
require 'graphql/query/literal_input'
|
105
|
+
require 'graphql/query/ruby_input'
|
106
|
+
require 'graphql/query/serial_execution'
|
107
|
+
require 'graphql/query/type_resolver'
|
108
|
+
require 'graphql/query/variables'
|