graphql 0.0.1 → 0.0.2
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.rb +78 -9
- data/lib/graphql/call.rb +7 -0
- data/lib/graphql/connection.rb +44 -0
- data/lib/graphql/field.rb +117 -0
- data/lib/graphql/field_definer.rb +29 -0
- data/lib/graphql/introspection/call_node.rb +13 -0
- data/lib/graphql/introspection/connection.rb +9 -0
- data/lib/graphql/introspection/field_node.rb +19 -0
- data/lib/graphql/introspection/root_call_argument_node.rb +5 -0
- data/lib/graphql/introspection/root_call_node.rb +16 -0
- data/lib/graphql/introspection/schema_call.rb +8 -0
- data/lib/graphql/introspection/schema_node.rb +17 -0
- data/lib/graphql/introspection/type_call.rb +8 -0
- data/lib/graphql/introspection/type_node.rb +16 -0
- data/lib/graphql/node.rb +141 -34
- data/lib/graphql/parser.rb +19 -8
- data/lib/graphql/query.rb +64 -21
- data/lib/graphql/root_call.rb +176 -0
- data/lib/graphql/root_call_argument.rb +8 -0
- data/lib/graphql/root_call_argument_definer.rb +20 -0
- data/lib/graphql/schema.rb +99 -0
- data/lib/graphql/syntax/call.rb +3 -12
- data/lib/graphql/syntax/field.rb +4 -2
- data/lib/graphql/syntax/node.rb +3 -10
- data/lib/graphql/syntax/query.rb +7 -0
- data/lib/graphql/syntax/variable.rb +7 -0
- data/lib/graphql/transform.rb +14 -5
- data/lib/graphql/types/boolean_field.rb +3 -0
- data/lib/graphql/types/connection_field.rb +30 -0
- data/lib/graphql/types/cursor_field.rb +9 -0
- data/lib/graphql/types/number_field.rb +3 -0
- data/lib/graphql/types/object_field.rb +8 -0
- data/lib/graphql/types/string_field.rb +3 -0
- data/lib/graphql/types/type_field.rb +6 -0
- data/lib/graphql/version.rb +3 -0
- data/readme.md +142 -10
- data/spec/graphql/field_spec.rb +66 -0
- data/spec/graphql/node_spec.rb +68 -0
- data/spec/graphql/parser_spec.rb +75 -25
- data/spec/graphql/query_spec.rb +185 -83
- data/spec/graphql/root_call_spec.rb +55 -0
- data/spec/graphql/schema_spec.rb +128 -0
- data/spec/graphql/transform_spec.rb +124 -39
- data/spec/spec_helper.rb +2 -1
- data/spec/support/dummy_app.rb +43 -23
- data/spec/support/nodes.rb +145 -32
- metadata +78 -16
- data/lib/graphql/collection_edge.rb +0 -62
- data/lib/graphql/syntax/edge.rb +0 -12
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::RootCall do
|
4
|
+
let(:query_string) { %{
|
5
|
+
upvote_post(<post_data>, <person_id>) {
|
6
|
+
post {
|
7
|
+
likes { count, any }
|
8
|
+
}
|
9
|
+
upvote {
|
10
|
+
post_id
|
11
|
+
}
|
12
|
+
}
|
13
|
+
<post_data>: { "id" : #{@post_id} }
|
14
|
+
<person_id>: 888
|
15
|
+
}}
|
16
|
+
let(:result) { GraphQL::Query.new(query_string).as_result }
|
17
|
+
|
18
|
+
before do
|
19
|
+
# make sure tests don't conflict :(
|
20
|
+
@post_id = "#{Time.now.to_i}#{[1,2,3].sample}".to_i
|
21
|
+
@post = Post.create(id: @post_id, content: "My great post")
|
22
|
+
@like = Like.create(post_id: @post_id)
|
23
|
+
end
|
24
|
+
|
25
|
+
after do
|
26
|
+
@post.likes.map(&:destroy)
|
27
|
+
@post.destroy
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#as_result' do
|
31
|
+
it 'operates on the application' do
|
32
|
+
assert_equal 1, @post.likes.count
|
33
|
+
result
|
34
|
+
assert_equal 2, @post.likes.count
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'returns fields for the node' do
|
38
|
+
assert_equal @post_id, result["upvote"]["post_id"]
|
39
|
+
assert_equal 2, result["post"]["likes"]["count"]
|
40
|
+
assert_equal true, result["post"]["likes"]["any"]
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'when the input is the wrong type' do
|
44
|
+
let(:query_string) { %{upvote_post(bogus_arg, 888) { post { id } } } }
|
45
|
+
it 'validates the input' do
|
46
|
+
assert_raises(GraphQL::RootCallArgumentError) { result }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#__type__' do
|
52
|
+
it 'describes the input'
|
53
|
+
it 'describes the response'
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GraphQL::Schema do
|
4
|
+
let(:schema) { GraphQL::SCHEMA }
|
5
|
+
describe 'global instance' do
|
6
|
+
it 'exists as GraphQL::SCHEMA' do
|
7
|
+
assert GraphQL::SCHEMA.is_a?(GraphQL::Schema)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#get_call' do
|
12
|
+
it 'finds calls from their class name' do
|
13
|
+
assert_equal Nodes::ContextCall, schema.get_call("context")
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'finds calls from declared names' do
|
17
|
+
assert_equal Nodes::LikePostCall, schema.get_call("upvote_post")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#get_type' do
|
22
|
+
it 'finds nodes from their class name' do
|
23
|
+
assert_equal Nodes::PostNode, schema.get_type("post")
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'finds nodes from declared names' do
|
27
|
+
assert_equal Nodes::ThumbUpNode, schema.get_type("upvote")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'querying schema' do
|
32
|
+
let(:query_string) { "schema() {
|
33
|
+
calls {
|
34
|
+
count,
|
35
|
+
edges {
|
36
|
+
node {
|
37
|
+
name,
|
38
|
+
returns,
|
39
|
+
arguments {
|
40
|
+
edges {
|
41
|
+
node {
|
42
|
+
name, type
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
48
|
+
},
|
49
|
+
types {
|
50
|
+
count,
|
51
|
+
edges {
|
52
|
+
node {
|
53
|
+
name,
|
54
|
+
fields {
|
55
|
+
count,
|
56
|
+
edges {
|
57
|
+
node {
|
58
|
+
name,
|
59
|
+
type,
|
60
|
+
calls {
|
61
|
+
count,
|
62
|
+
edges {
|
63
|
+
node {
|
64
|
+
name,
|
65
|
+
arguments
|
66
|
+
}
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
}
|
75
|
+
}"}
|
76
|
+
let(:query) { GraphQL::Query.new(query_string) }
|
77
|
+
let(:result) { query.as_result }
|
78
|
+
|
79
|
+
describe 'querying calls' do
|
80
|
+
let(:upvote_post_call) { result["schema"]["calls"]["edges"].find {|e| e["node"]["name"] == "upvote_post"} }
|
81
|
+
|
82
|
+
it 'returns all calls' do
|
83
|
+
assert_equal 7, result["schema"]["calls"]["count"]
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'doesnt show abstract call classes' do
|
87
|
+
call_names = result["schema"]["calls"]["edges"].map {|e| e["node"]["name"] }
|
88
|
+
assert(!call_names.include?("find"))
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'shows return types' do
|
92
|
+
assert_equal ["post", "upvote"], upvote_post_call["node"]["returns"]
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'shows argument types' do
|
96
|
+
expected_arguments = [{"node"=>{"name"=>"post_data", "type"=>"object"}}, {"node"=>{"name"=>"person_id", "type"=>"number"}}]
|
97
|
+
assert_equal expected_arguments, upvote_post_call["node"]["arguments"]["edges"]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'querying types' do
|
102
|
+
let(:post_type) { result["schema"]["types"]["edges"].find { |e| e["node"]["name"] == "post" }["node"]}
|
103
|
+
let(:content_field) { post_type["fields"]["edges"].find { |e| e["node"]["name"] == "content" }["node"]}
|
104
|
+
let(:select_call) { content_field["calls"]["edges"].find { |e| e["node"]["name"] == "select"}["node"]}
|
105
|
+
|
106
|
+
it 'returns all types' do
|
107
|
+
assert_equal 13, result["schema"]["types"]["count"]
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'doesnt return types that dont expose anything' do
|
111
|
+
type_names = result["schema"]["types"]["edges"].map {|e| e["node"]["name"] }
|
112
|
+
assert(!type_names.include?("application"))
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'show type name & fields' do
|
116
|
+
assert_equal "post", post_type["name"]
|
117
|
+
assert_equal 8, post_type["fields"]["count"]
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'shows field type & calls' do
|
121
|
+
assert_equal "string", content_field["type"]
|
122
|
+
assert_equal 3, content_field["calls"]["count"]
|
123
|
+
assert_equal "select", select_call["name"]
|
124
|
+
assert_equal "from_chars (req), for_chars (req)", select_call["arguments"]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -5,54 +5,139 @@ describe GraphQL::Transform do
|
|
5
5
|
let(:parser) { GraphQL::PARSER }
|
6
6
|
|
7
7
|
describe '#apply' do
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
8
|
+
describe 'query' do
|
9
|
+
it 'parses node and variables' do
|
10
|
+
tree = parser.query.parse(%{
|
11
|
+
like_page(<page_info>) { page { id, likes } }
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
13
|
+
<page_info>: {
|
14
|
+
"page" : { "id": 4},
|
15
|
+
"person" : {"id": 4}
|
16
|
+
}
|
17
|
+
<other>: {
|
18
|
+
"page" : { "id": 4},
|
19
|
+
"person" : {"id": 4}
|
20
|
+
}
|
21
|
+
})
|
22
|
+
res = transform.apply(tree)
|
23
|
+
assert_equal 1, res.nodes.length
|
24
|
+
assert_equal "like_page", res.nodes[0].identifier
|
25
|
+
assert_equal ["<page_info>"], res.nodes[0].arguments
|
26
|
+
assert_equal ["<page_info>", "<other>"], res.variables.map(&:identifier)
|
27
|
+
end
|
22
28
|
end
|
23
29
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
30
|
+
describe 'nodes' do
|
31
|
+
it 'turns a simple node into a Node' do
|
32
|
+
tree = parser.node.parse("post(123) { name }")
|
33
|
+
res = transform.apply(tree)
|
34
|
+
assert(res.is_a?(GraphQL::Syntax::Node), 'it gets a node')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'turns a node into a Node' do
|
38
|
+
tree = parser.node.parse("person(1) { name, check_ins.last(4) { count, edges { node { id } } } }")
|
39
|
+
res = transform.apply(tree)
|
40
|
+
assert(res.is_a?(GraphQL::Syntax::Node), 'it gets a node')
|
41
|
+
assert(res.identifier == "person")
|
42
|
+
assert(res.fields.length == 2)
|
43
|
+
assert(res.fields[0].is_a?(GraphQL::Syntax::Field), 'it gets a field')
|
44
|
+
assert(res.fields[1].is_a?(GraphQL::Syntax::Field), 'it gets an field with fields')
|
45
|
+
assert(res.fields[1].calls.first.is_a?(GraphQL::Syntax::Call), 'it gets a call')
|
46
|
+
end
|
29
47
|
end
|
30
48
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
49
|
+
describe 'fields' do
|
50
|
+
it 'turns a field into a Field' do
|
51
|
+
tree = parser.field.parse("friends")
|
52
|
+
res = transform.apply(tree)
|
53
|
+
assert(res.is_a?(GraphQL::Syntax::Field))
|
54
|
+
assert(res.identifier == "friends")
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'gets aliases' do
|
58
|
+
tree = parser.field.parse("friends as pals")
|
59
|
+
res = transform.apply(tree)
|
60
|
+
assert(res.is_a?(GraphQL::Syntax::Field))
|
61
|
+
assert(res.identifier == "friends")
|
62
|
+
assert(res.alias_name == "pals")
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'gets calls' do
|
66
|
+
tree = parser.field.parse("friends.orderby(name, birthdate).first(3)")
|
67
|
+
res = transform.apply(tree)
|
68
|
+
assert_equal "orderby", res.calls[0].identifier
|
69
|
+
assert_equal ["name", "birthdate"], res.calls[0].arguments
|
70
|
+
assert_equal "first", res.calls[1].identifier
|
71
|
+
assert_equal ["3"], res.calls[1].arguments
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'fields that return objects' do
|
75
|
+
it 'gets them' do
|
76
|
+
tree = parser.field.parse("friends { count }")
|
77
|
+
res = transform.apply(tree)
|
78
|
+
assert_equal "friends", res.identifier
|
79
|
+
assert_equal 1, res.fields.length
|
80
|
+
end
|
81
|
+
it 'gets them with aliases' do
|
82
|
+
tree = parser.field.parse("friends as pals { count }")
|
83
|
+
res = transform.apply(tree)
|
84
|
+
assert_equal "friends", res.identifier
|
85
|
+
assert_equal "pals", res.alias_name
|
86
|
+
assert_equal 1, res.fields.length
|
87
|
+
end
|
88
|
+
it 'gets them with calls' do
|
89
|
+
tree = parser.field.parse("friends.orderby(name, birthdate).last(1) { count }")
|
90
|
+
res = transform.apply(tree)
|
91
|
+
assert_equal "friends", res.identifier
|
92
|
+
assert_equal 1, res.fields.length
|
93
|
+
assert_equal 2, res.calls.length
|
94
|
+
end
|
95
|
+
it 'gets them with calls and aliases' do
|
96
|
+
tree = parser.field.parse("friends.orderby(name, birthdate).last(1) as pals { count }")
|
97
|
+
res = transform.apply(tree)
|
98
|
+
assert_equal "friends", res.identifier
|
99
|
+
assert_equal "pals", res.alias_name
|
100
|
+
assert_equal 1, res.fields.length
|
101
|
+
assert_equal 2, res.calls.length
|
102
|
+
end
|
103
|
+
end
|
40
104
|
end
|
41
105
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
106
|
+
describe 'calls' do
|
107
|
+
it 'turns call into a Call' do
|
108
|
+
tree = parser.call.parse("node(4, 6, tree)")
|
109
|
+
res = transform.apply(tree)
|
110
|
+
assert(res.is_a?(GraphQL::Syntax::Call))
|
111
|
+
assert(res.identifier == "node")
|
112
|
+
assert(res.arguments == ["4", "6", "tree"])
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'turns a call without an argument into a Call' do
|
116
|
+
tree = parser.call.parse("viewer()")
|
117
|
+
res = transform.apply(tree)
|
118
|
+
assert(res.is_a?(GraphQL::Syntax::Call))
|
119
|
+
assert(res.identifier == "viewer")
|
120
|
+
assert(res.arguments.length == 0)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'gets calls with variable identifiers' do
|
124
|
+
tree = parser.call.parse("like_page(<page_info>)")
|
125
|
+
res = transform.apply(tree)
|
126
|
+
assert_equal "<page_info>", res.arguments[0]
|
127
|
+
end
|
48
128
|
end
|
49
129
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
130
|
+
describe 'variables' do
|
131
|
+
it 'gets variables' do
|
132
|
+
tree = parser.variable.parse(%{
|
133
|
+
<page_info>: {
|
134
|
+
"page" : { "id": 4},
|
135
|
+
"person" : {"id": 4}
|
136
|
+
}
|
137
|
+
})
|
138
|
+
res = transform.apply(tree)
|
139
|
+
assert_equal "<page_info>", res.identifier
|
140
|
+
end
|
56
141
|
end
|
57
142
|
end
|
58
143
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
require 'date'
|
1
2
|
require "minitest/autorun"
|
2
3
|
require "minitest/focus"
|
3
4
|
require "minitest/reporters"
|
4
|
-
|
5
|
+
require 'pry'
|
5
6
|
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
|
6
7
|
|
7
8
|
# Filter out Minitest backtrace while allowing backtrace from other libraries
|
data/spec/support/dummy_app.rb
CHANGED
@@ -6,56 +6,76 @@ class InadequateRecordBase
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def destroy
|
9
|
-
self.class.
|
9
|
+
self.class.all.delete(self)
|
10
10
|
end
|
11
11
|
|
12
12
|
class << self
|
13
13
|
attr_accessor :_objects
|
14
|
-
def
|
14
|
+
def all
|
15
15
|
@_objects ||= []
|
16
16
|
end
|
17
|
-
end
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
def find(id)
|
19
|
+
all.find { |object| object.id.to_s == id.to_s }
|
20
|
+
end
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
def where(query={})
|
23
|
+
result = []
|
24
|
+
all.each do |object|
|
25
|
+
match = true
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
query.each do |key, value|
|
28
|
+
if object.send(key) != value
|
29
|
+
match = false
|
30
|
+
end
|
31
31
|
end
|
32
|
-
end
|
33
32
|
|
34
|
-
|
33
|
+
result << object if match
|
34
|
+
end
|
35
|
+
result
|
35
36
|
end
|
36
|
-
result
|
37
|
-
end
|
38
37
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
def create(attributes)
|
39
|
+
@next_id ||= 0
|
40
|
+
attributes[:id] ||= @next_id += 1
|
41
|
+
instance = self.new(attributes)
|
42
|
+
all << instance
|
43
|
+
instance
|
44
|
+
end
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
46
48
|
class Post < InadequateRecordBase
|
47
|
-
attr_accessor :id, :title, :content
|
49
|
+
attr_accessor :id, :title, :content, :published_at
|
48
50
|
|
49
51
|
def comments
|
50
52
|
Comment.where(post_id: id)
|
51
53
|
end
|
54
|
+
|
55
|
+
def likes
|
56
|
+
Like.where(post_id: id)
|
57
|
+
end
|
52
58
|
end
|
53
59
|
|
54
60
|
class Comment < InadequateRecordBase
|
55
|
-
attr_accessor :id, :post_id, :content
|
61
|
+
attr_accessor :id, :post_id, :content, :rating
|
62
|
+
|
56
63
|
def post
|
57
64
|
Post.find(post_id)
|
58
65
|
end
|
59
66
|
end
|
60
67
|
|
68
|
+
class Like < InadequateRecordBase
|
69
|
+
attr_accessor :id, :post_id
|
70
|
+
|
71
|
+
def post
|
72
|
+
Post.find(post_id)
|
73
|
+
end
|
74
|
+
end
|
61
75
|
|
76
|
+
class Context
|
77
|
+
attr_reader :person_name
|
78
|
+
def initialize(person_name:)
|
79
|
+
@person_name = person_name
|
80
|
+
end
|
81
|
+
end
|