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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 290e890d0a805c3587000af2fbbacf910cf8baf0
|
4
|
+
data.tar.gz: 745511749686efb279e0ee7b7af2484d0ccd82ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81ba92e0aed50365b5ccd78ab02c035bc0a1ae212669f7d5c668cbe7fec3e48158f9a53a5859b2ece3ebc54d3634187635477a85cf50f2b8d24a822476517b53
|
7
|
+
data.tar.gz: 3a46fb67c848c7b85768979071d28df3b2e21e29087395b5a3ab2297d0660bd51ba84c628678e098a1faae392ef9a4e30d8be0007575ef32468f98c2c043badf
|
data/lib/graphql.rb
CHANGED
@@ -1,26 +1,95 @@
|
|
1
|
-
require "parslet"
|
2
|
-
require "active_support/core_ext/string/inflections"
|
3
1
|
require "active_support/core_ext/object/blank"
|
2
|
+
require "active_support/core_ext/string/inflections"
|
3
|
+
require "json"
|
4
|
+
require "parslet"
|
5
|
+
require "singleton"
|
4
6
|
|
5
7
|
module GraphQL
|
6
|
-
|
7
|
-
|
8
|
-
autoload(:
|
8
|
+
autoload(:Call, "graphql/call")
|
9
|
+
autoload(:Connection, "graphql/connection")
|
10
|
+
autoload(:Field, "graphql/field")
|
11
|
+
autoload(:FieldDefiner, "graphql/field_definer")
|
12
|
+
autoload(:Node, "graphql/node")
|
9
13
|
autoload(:Parser, "graphql/parser")
|
10
14
|
autoload(:Query, "graphql/query")
|
11
|
-
autoload(:
|
15
|
+
autoload(:RootCall, "graphql/root_call")
|
16
|
+
autoload(:RootCallArgument, "graphql/root_call_argument")
|
17
|
+
autoload(:RootCallArgumentDefiner, "graphql/root_call_argument_definer")
|
18
|
+
autoload(:Schema, "graphql/schema")
|
12
19
|
autoload(:Transform, "graphql/transform")
|
20
|
+
autoload(:VERSION, "graphql/version")
|
21
|
+
|
22
|
+
module Introspection
|
23
|
+
autoload(:CallNode, "graphql/introspection/call_node")
|
24
|
+
autoload(:Connection, "graphql/introspection/connection")
|
25
|
+
autoload(:FieldNode, "graphql/introspection/field_node")
|
26
|
+
autoload(:RootCallArgumentNode, "graphql/introspection/root_call_argument_node")
|
27
|
+
autoload(:RootCallNode, "graphql/introspection/root_call_node")
|
28
|
+
autoload(:SchemaCall, "graphql/introspection/schema_call")
|
29
|
+
autoload(:SchemaNode, "graphql/introspection/schema_node")
|
30
|
+
autoload(:TypeCall, "graphql/introspection/type_call")
|
31
|
+
autoload(:TypeNode, "graphql/introspection/type_node")
|
32
|
+
end
|
33
|
+
|
13
34
|
|
14
35
|
module Syntax
|
15
36
|
autoload(:Call, "graphql/syntax/call")
|
16
|
-
autoload(:Edge, "graphql/syntax/edge")
|
17
37
|
autoload(:Field, "graphql/syntax/field")
|
38
|
+
autoload(:Query, "graphql/syntax/query")
|
18
39
|
autoload(:Node, "graphql/syntax/node")
|
40
|
+
autoload(:Variable, "graphql/syntax/variable")
|
41
|
+
end
|
42
|
+
|
43
|
+
module Types
|
44
|
+
autoload(:BooleanField, "graphql/types/boolean_field")
|
45
|
+
autoload(:ConnectionField, "graphql/types/connection_field")
|
46
|
+
autoload(:CursorField, "graphql/types/cursor_field")
|
47
|
+
autoload(:NumberField, "graphql/types/number_field")
|
48
|
+
autoload(:ObjectField, "graphql/types/object_field")
|
49
|
+
autoload(:StringField, "graphql/types/string_field")
|
50
|
+
autoload(:TypeField, "graphql/types/type_field")
|
51
|
+
end
|
52
|
+
|
53
|
+
class Error < RuntimeError; end
|
54
|
+
class FieldNotDefinedError < Error
|
55
|
+
def initialize(class_name, field_name)
|
56
|
+
super("#{class_name}##{field_name} was requested, but it isn't defined. Defined fields are: #{SCHEMA.field_names}")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
class NodeNotDefinedError < Error
|
60
|
+
def initialize(node_name)
|
61
|
+
super("#{node_name} was requested but was not found. Defined nodes are: #{SCHEMA.type_names}")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
class ConnectionNotDefinedError < Error
|
65
|
+
def initialize(node_name)
|
66
|
+
super("#{node_name} was requested but was not found. Defined connections are: #{SCHEMA.connection_names}")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
class RootCallNotDefinedError < Error
|
70
|
+
def initialize(name)
|
71
|
+
super("Call '#{name}' was requested but was not found. Defined calls are: #{SCHEMA.call_names}")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
class SyntaxError < Error
|
75
|
+
def initialize(line, col, string)
|
76
|
+
lines = string.split("\n")
|
77
|
+
super("Syntax Error at (#{line}, #{col}), check usage: #{string}")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class RootCallArgumentError < Error
|
82
|
+
def initialize(declaration, actual)
|
83
|
+
super("Wrong type for #{declaration.name}: expected a #{declaration.type} but got #{actual}")
|
84
|
+
end
|
19
85
|
end
|
20
86
|
|
21
87
|
PARSER = Parser.new
|
88
|
+
SCHEMA = Schema.instance
|
22
89
|
TRANSFORM = Transform.new
|
23
|
-
|
24
|
-
|
90
|
+
# preload these so they're in SCHEMA
|
91
|
+
["types", "introspection"].each do |preload_dir|
|
92
|
+
Dir["#{File.dirname(__FILE__)}/graphql/#{preload_dir}/*.rb"].each { |f| require f }
|
25
93
|
end
|
94
|
+
Node.field.__type__(:__type__)
|
26
95
|
end
|
data/lib/graphql/call.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
class GraphQL::Connection < GraphQL::Node
|
2
|
+
exposes "Array"
|
3
|
+
field.any(:edges)
|
4
|
+
|
5
|
+
attr_reader :calls, :syntax_fields, :query
|
6
|
+
|
7
|
+
def initialize(items, query:, fields: [])
|
8
|
+
@target = items
|
9
|
+
@syntax_fields = fields
|
10
|
+
@query = query
|
11
|
+
end
|
12
|
+
|
13
|
+
def items
|
14
|
+
@target
|
15
|
+
end
|
16
|
+
|
17
|
+
def edge_fields
|
18
|
+
@edge_fields ||= syntax_fields.find { |f| f.identifier == "edges" }.fields
|
19
|
+
end
|
20
|
+
|
21
|
+
def edges
|
22
|
+
raise "#{self.class} expected a connection, but got `nil`" if items.nil?
|
23
|
+
items.map do |item|
|
24
|
+
node_class = GraphQL::SCHEMA.type_for_object(item)
|
25
|
+
node = node_class.new(item, fields: edge_fields, query: query)
|
26
|
+
res = node.as_result
|
27
|
+
res
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
def default_schema_name
|
33
|
+
name.split("::").last.sub(/Connection$/, '').underscore
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_accessor :default_connection
|
37
|
+
def default_connection!
|
38
|
+
GraphQL::Connection.default_connection = self
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
self.default_connection!
|
44
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
class GraphQL::Field
|
2
|
+
attr_reader :query, :owner, :calls, :fields
|
3
|
+
def initialize(query: nil, owner: nil, calls: [], fields: [])
|
4
|
+
@query = query
|
5
|
+
@owner = owner
|
6
|
+
@calls = calls
|
7
|
+
@fields = fields
|
8
|
+
end
|
9
|
+
|
10
|
+
def raw_value
|
11
|
+
owner.send(method)
|
12
|
+
end
|
13
|
+
|
14
|
+
def as_result
|
15
|
+
finished_value
|
16
|
+
end
|
17
|
+
|
18
|
+
def finished_value
|
19
|
+
@finished_value ||= begin
|
20
|
+
val = raw_value
|
21
|
+
calls.each do |call|
|
22
|
+
registered_call = self.class.calls[call.identifier]
|
23
|
+
if registered_call.nil?
|
24
|
+
raise "Call not found: #{self.class.name}##{call.identifier}"
|
25
|
+
end
|
26
|
+
val = registered_call.lambda.call(val, *call.arguments)
|
27
|
+
end
|
28
|
+
val
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# instance `const_get` reaches up to class namespace
|
33
|
+
def const_get(const_name)
|
34
|
+
self.class.const_get(const_name)
|
35
|
+
rescue
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# delegate to class constant
|
40
|
+
["name", "description"].each do |method_name|
|
41
|
+
define_method(method_name) do
|
42
|
+
const_get(method_name.upcase)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def method; name; end
|
47
|
+
|
48
|
+
class << self
|
49
|
+
def inherited(child_class)
|
50
|
+
GraphQL::SCHEMA.add_field(child_class)
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_class(name:, owner_class:, type:, description: nil, connection_class_name: nil, node_class_name: nil)
|
54
|
+
if type.is_a?(Symbol)
|
55
|
+
type = GraphQL::SCHEMA.get_field(type)
|
56
|
+
end
|
57
|
+
|
58
|
+
field_superclass = type || self
|
59
|
+
new_class = Class.new(field_superclass)
|
60
|
+
new_class.const_set :NAME, name
|
61
|
+
new_class.const_set :OWNER_CLASS, owner_class
|
62
|
+
new_class.const_set :DESCRIPTION , description
|
63
|
+
new_class.const_set :CONNECTION_CLASS_NAME, connection_class_name
|
64
|
+
new_class.const_set :NODE_CLASS_NAME, node_class_name
|
65
|
+
new_class
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
if const_defined?(:NAME)
|
70
|
+
"<FieldClass: #{const_get(:OWNER_CLASS).name}::#{const_get(:NAME)}>"
|
71
|
+
else
|
72
|
+
super
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def type(value_type_name)
|
77
|
+
@value_type = value_type_name.to_s
|
78
|
+
GraphQL::SCHEMA.add_field(self)
|
79
|
+
end
|
80
|
+
|
81
|
+
def value_type
|
82
|
+
@value_type || superclass.value_type
|
83
|
+
end
|
84
|
+
|
85
|
+
def schema_name
|
86
|
+
@value_type || (name.present? ? default_schema_name : nil)
|
87
|
+
end
|
88
|
+
|
89
|
+
def default_schema_name
|
90
|
+
name.split("::").last.sub(/Field$/, '').underscore
|
91
|
+
end
|
92
|
+
|
93
|
+
def field_name
|
94
|
+
const_get(:NAME)
|
95
|
+
end
|
96
|
+
|
97
|
+
def description
|
98
|
+
const_get(:DESCRIPTION)
|
99
|
+
end
|
100
|
+
|
101
|
+
def calls
|
102
|
+
superclass.calls.merge(_calls)
|
103
|
+
rescue NoMethodError
|
104
|
+
{}
|
105
|
+
end
|
106
|
+
|
107
|
+
def _calls
|
108
|
+
@calls ||= {}
|
109
|
+
end
|
110
|
+
|
111
|
+
def call(name, lambda)
|
112
|
+
_calls[name.to_s] = GraphQL::Call.new(name: name.to_s, lambda: lambda)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
type :any
|
117
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class GraphQL::FieldDefiner
|
2
|
+
attr_reader :owner_class
|
3
|
+
def initialize(owner_class)
|
4
|
+
@owner_class = owner_class
|
5
|
+
end
|
6
|
+
|
7
|
+
def method_missing(method_name, *args, &block)
|
8
|
+
type = GraphQL::SCHEMA.get_field(method_name)
|
9
|
+
if type.present?
|
10
|
+
create_field(args[0], type: type, description: args[1])
|
11
|
+
else
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_field(field_name, type: nil, description: nil)
|
17
|
+
field_name = field_name.to_s
|
18
|
+
field_class = GraphQL::Field.create_class({
|
19
|
+
name: field_name,
|
20
|
+
type: type,
|
21
|
+
owner_class: owner_class,
|
22
|
+
description: description,
|
23
|
+
})
|
24
|
+
|
25
|
+
field_class_name = field_name.camelize + "Field"
|
26
|
+
owner_class.const_set(field_class_name, field_class)
|
27
|
+
owner_class.own_fields[field_name] = field_class
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class GraphQL::Introspection::CallNode < GraphQL::Node
|
2
|
+
exposes "GraphQL::Call"
|
3
|
+
field.string(:name)
|
4
|
+
field.string(:arguments)
|
5
|
+
|
6
|
+
def arguments
|
7
|
+
args = target.lambda.parameters
|
8
|
+
args.shift
|
9
|
+
args.map { |p| "#{p[1]} (#{p[0]})" }.join(", ")
|
10
|
+
rescue StandardError => e
|
11
|
+
raise "Errored on #{name} (#{self}): #{e}"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class GraphQL::Introspection::FieldNode < GraphQL::Node
|
2
|
+
exposes "GraphQL::Field"
|
3
|
+
field.string(:name)
|
4
|
+
field.string(:type)
|
5
|
+
field.string(:description)
|
6
|
+
field.connection(:calls)
|
7
|
+
|
8
|
+
def calls
|
9
|
+
target.calls.values
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
schema_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def type
|
17
|
+
value_type
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class GraphQL::Introspection::RootCallNode < GraphQL::Node
|
2
|
+
exposes "GraphQL::RootCall"
|
3
|
+
field.string(:name)
|
4
|
+
field.string(:returns)
|
5
|
+
field.connection(:arguments)
|
6
|
+
|
7
|
+
|
8
|
+
def returns
|
9
|
+
return_declarations = @target.return_declarations
|
10
|
+
return_declarations.keys.map(&:to_s)
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
schema_name
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class GraphQL::Introspection::SchemaNode < GraphQL::Node
|
2
|
+
exposes "GraphQL::Schema"
|
3
|
+
field.connection(:calls)
|
4
|
+
field.connection(:types)
|
5
|
+
|
6
|
+
def cursor
|
7
|
+
"schema"
|
8
|
+
end
|
9
|
+
|
10
|
+
def types
|
11
|
+
@target.types.values
|
12
|
+
end
|
13
|
+
|
14
|
+
def calls
|
15
|
+
target.calls.values
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class GraphQL::Introspection::TypeNode < GraphQL::Node
|
2
|
+
exposes "GraphQL::Node"
|
3
|
+
field.string(:name)
|
4
|
+
field.string(:description)
|
5
|
+
field.connection(:fields)
|
6
|
+
|
7
|
+
cursor :name
|
8
|
+
|
9
|
+
def fields
|
10
|
+
target.all_fields.values
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
schema_name
|
15
|
+
end
|
16
|
+
end
|