graphql 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|