graphql 0.0.3 → 0.0.4
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 +46 -29
- data/lib/graphql/call.rb +1 -1
- data/lib/graphql/connection.rb +12 -35
- data/lib/graphql/field.rb +7 -177
- data/lib/graphql/field_definer.rb +5 -15
- data/lib/graphql/introspection/{call_node.rb → call_type.rb} +1 -1
- data/lib/graphql/introspection/field_type.rb +10 -0
- data/lib/graphql/introspection/{root_call_node.rb → root_call_type.rb} +6 -2
- data/lib/graphql/introspection/schema_type.rb +17 -0
- data/lib/graphql/introspection/{type_node.rb → type_type.rb} +4 -2
- data/lib/graphql/node.rb +118 -42
- data/lib/graphql/{parser.rb → parser/parser.rb} +9 -4
- data/lib/graphql/{transform.rb → parser/transform.rb} +4 -2
- data/lib/graphql/query.rb +33 -10
- data/lib/graphql/root_call.rb +26 -13
- data/lib/graphql/root_call_argument.rb +3 -1
- data/lib/graphql/root_call_argument_definer.rb +3 -7
- data/lib/graphql/schema/all.rb +46 -0
- data/lib/graphql/{schema.rb → schema/schema.rb} +27 -39
- data/lib/graphql/schema/schema_validation.rb +32 -0
- data/lib/graphql/syntax/fragment.rb +7 -0
- data/lib/graphql/syntax/query.rb +3 -2
- data/lib/graphql/types/boolean_type.rb +3 -0
- data/lib/graphql/types/number_type.rb +3 -0
- data/lib/graphql/types/object_type.rb +6 -0
- data/lib/graphql/types/string_type.rb +3 -0
- data/lib/graphql/version.rb +1 -1
- data/readme.md +46 -5
- data/spec/graphql/node_spec.rb +6 -5
- data/spec/graphql/{parser_spec.rb → parser/parser_spec.rb} +31 -2
- data/spec/graphql/{transform_spec.rb → parser/transform_spec.rb} +16 -2
- data/spec/graphql/query_spec.rb +27 -9
- data/spec/graphql/root_call_spec.rb +15 -1
- data/spec/graphql/{schema_spec.rb → schema/schema_spec.rb} +15 -50
- data/spec/graphql/schema/schema_validation_spec.rb +48 -0
- data/spec/support/nodes.rb +31 -28
- metadata +47 -47
- data/lib/graphql/introspection/field_node.rb +0 -19
- data/lib/graphql/introspection/schema_node.rb +0 -17
- data/lib/graphql/types/boolean_field.rb +0 -3
- data/lib/graphql/types/connection_field.rb +0 -30
- data/lib/graphql/types/cursor_field.rb +0 -9
- data/lib/graphql/types/number_field.rb +0 -3
- data/lib/graphql/types/object_field.rb +0 -8
- data/lib/graphql/types/string_field.rb +0 -3
- data/lib/graphql/types/type_field.rb +0 -6
- data/spec/graphql/field_spec.rb +0 -63
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea9221c87a055fadbddc1b61de970e61b993e074
|
4
|
+
data.tar.gz: 289bff890b6c2fb4ff3487cd74f8333375dcdbe9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28d366cd1a906295fe885728ad08f1b13bcbd3b36ad35ac3aed6603d54a680a9e01ed048393f040f59d3b649c261e82958ef2432f652fa78d8a97d88ea69ca2b
|
7
|
+
data.tar.gz: eb17ca9aa091ccc42a85ecbf59b45bbbe61fa9dc154cb0794cbe59cae6bdc0a1e082fe35f95f8878c6b0dbe59432a420c58068f00f40eb594c00ef083d5c73a6
|
data/lib/graphql.rb
CHANGED
@@ -2,62 +2,77 @@ require "active_support/core_ext/object/blank"
|
|
2
2
|
require "active_support/core_ext/string/inflections"
|
3
3
|
require "json"
|
4
4
|
require "parslet"
|
5
|
-
require "singleton"
|
6
5
|
|
7
6
|
module GraphQL
|
8
7
|
autoload(:Call, "graphql/call")
|
9
8
|
autoload(:Connection, "graphql/connection")
|
10
|
-
autoload(:Field, "graphql/field")
|
11
9
|
autoload(:FieldDefiner, "graphql/field_definer")
|
10
|
+
autoload(:Field, "graphql/field")
|
12
11
|
autoload(:Node, "graphql/node")
|
13
|
-
autoload(:Parser, "graphql/parser")
|
14
12
|
autoload(:Query, "graphql/query")
|
15
13
|
autoload(:RootCall, "graphql/root_call")
|
16
14
|
autoload(:RootCallArgument, "graphql/root_call_argument")
|
17
15
|
autoload(:RootCallArgumentDefiner, "graphql/root_call_argument_definer")
|
18
|
-
autoload(:Schema, "graphql/schema")
|
19
|
-
autoload(:Transform, "graphql/transform")
|
20
16
|
autoload(:VERSION, "graphql/version")
|
21
17
|
|
22
18
|
# These objects are used for introspections (eg, responding to `schema()` calls).
|
23
19
|
module Introspection
|
24
|
-
autoload(:
|
20
|
+
autoload(:CallType, "graphql/introspection/call_type")
|
25
21
|
autoload(:Connection, "graphql/introspection/connection")
|
26
|
-
autoload(:
|
22
|
+
autoload(:FieldType, "graphql/introspection/field_type")
|
27
23
|
autoload(:RootCallArgumentNode, "graphql/introspection/root_call_argument_node")
|
28
|
-
autoload(:
|
24
|
+
autoload(:RootCallType, "graphql/introspection/root_call_type")
|
29
25
|
autoload(:SchemaCall, "graphql/introspection/schema_call")
|
30
|
-
autoload(:
|
26
|
+
autoload(:SchemaType, "graphql/introspection/schema_type")
|
31
27
|
autoload(:TypeCall, "graphql/introspection/type_call")
|
32
|
-
autoload(:
|
28
|
+
autoload(:TypeType, "graphql/introspection/type_type")
|
29
|
+
end
|
30
|
+
|
31
|
+
# These objects are singletons used to parse queries
|
32
|
+
module Parser
|
33
|
+
autoload(:Parser, "graphql/parser/parser")
|
34
|
+
autoload(:Transform, "graphql/parser/transform")
|
35
|
+
end
|
36
|
+
|
37
|
+
# These objects are used to track the schema of the graph
|
38
|
+
module Schema
|
39
|
+
autoload(:ALL, "graphql/schema/all")
|
40
|
+
autoload(:Schema, "graphql/schema/schema")
|
41
|
+
autoload(:SchemaValidation, "graphql/schema/schema_validation")
|
33
42
|
end
|
34
43
|
|
35
44
|
# These objects are skinny wrappers for going from the AST to actual {Node} and {Field} instances.
|
36
45
|
module Syntax
|
37
|
-
autoload(:Call,
|
38
|
-
autoload(:Field,
|
39
|
-
autoload(:Query,
|
40
|
-
autoload(:
|
41
|
-
autoload(:
|
46
|
+
autoload(:Call, "graphql/syntax/call")
|
47
|
+
autoload(:Field, "graphql/syntax/field")
|
48
|
+
autoload(:Query, "graphql/syntax/query")
|
49
|
+
autoload(:Fragment, "graphql/syntax/fragment")
|
50
|
+
autoload(:Node, "graphql/syntax/node")
|
51
|
+
autoload(:Variable, "graphql/syntax/variable")
|
42
52
|
end
|
43
53
|
|
44
|
-
# These
|
54
|
+
# These objects expose values
|
45
55
|
module Types
|
46
|
-
autoload(:
|
47
|
-
autoload(:
|
48
|
-
autoload(:
|
49
|
-
autoload(:
|
50
|
-
autoload(:ObjectField, "graphql/types/object_field")
|
51
|
-
autoload(:StringField, "graphql/types/string_field")
|
52
|
-
autoload(:TypeField, "graphql/types/type_field")
|
56
|
+
autoload(:BooleanType, "graphql/types/boolean_type")
|
57
|
+
autoload(:ObjectType, "graphql/types/object_type")
|
58
|
+
autoload(:StringType, "graphql/types/string_type")
|
59
|
+
autoload(:NumberType, "graphql/types/number_type")
|
53
60
|
end
|
54
61
|
# @abstract
|
55
62
|
# Base class for all errors, so you can rescue from all graphql errors at once.
|
56
63
|
class Error < RuntimeError; end
|
57
64
|
# This node doesn't have a field with that name.
|
58
65
|
class FieldNotDefinedError < Error
|
59
|
-
def initialize(
|
60
|
-
|
66
|
+
def initialize(node_class, field_name)
|
67
|
+
class_name = node_class.name
|
68
|
+
defined_field_names = node_class.all_fields.keys
|
69
|
+
super("#{class_name}##{field_name} was requested, but it isn't defined. Defined fields are: #{defined_field_names}")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
# The class that this node is supposed to expose isn't defined
|
73
|
+
class ExposesClassMissingError < Error
|
74
|
+
def initialize(node_class)
|
75
|
+
super("#{node_class.name} exposes #{node_class.exposes_class_names.join(", ")}, but that class wasn't found.")
|
61
76
|
end
|
62
77
|
end
|
63
78
|
# There's no Node defined for that kind of object.
|
@@ -92,12 +107,14 @@ module GraphQL
|
|
92
107
|
end
|
93
108
|
end
|
94
109
|
|
95
|
-
|
110
|
+
# Singleton {Parser::Parser} instance
|
111
|
+
PARSER = Parser::Parser.new
|
96
112
|
# This singleton contains all defined nodes and fields.
|
97
|
-
SCHEMA = Schema.instance
|
98
|
-
|
113
|
+
SCHEMA = Schema::Schema.instance
|
114
|
+
# Singleton {Parser::Transform} instance
|
115
|
+
TRANSFORM = Parser::Transform.new
|
99
116
|
# preload these so they're in SCHEMA
|
100
|
-
["
|
117
|
+
["introspection", "types"].each do |preload_dir|
|
101
118
|
Dir["#{File.dirname(__FILE__)}/graphql/#{preload_dir}/*.rb"].each { |f| require f }
|
102
119
|
end
|
103
120
|
Node.field.__type__(:__type__)
|
data/lib/graphql/call.rb
CHANGED
data/lib/graphql/connection.rb
CHANGED
@@ -2,27 +2,27 @@
|
|
2
2
|
#
|
3
3
|
# Out of the box, the only field it has is `edges`, which provides access to the members of the collection.
|
4
4
|
#
|
5
|
-
# You can define a custom {Connection} to use. This allows you to define fields at the collection level (rather than the item level)
|
5
|
+
# You can define a custom {Connection} to use. This allows you to define fields and calls at the collection level (rather than the item level)
|
6
6
|
#
|
7
|
-
#
|
7
|
+
# You can access the collection as `target` ({Node#target}).
|
8
8
|
#
|
9
9
|
# @example
|
10
10
|
# class UpvotesConnection < GraphQL::Collection
|
11
|
-
#
|
12
|
-
#
|
11
|
+
# type :upvotes # adds it to the schema
|
12
|
+
# call :first, -> (prev_items, first) { prev_items.first(first.to_i) }
|
13
|
+
# call :after, -> (prev_items, after) { prev_items.select {|i| i.id > after.to_i } }
|
13
14
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
# end
|
15
|
+
# field.number(:count) # delegates to the underlying collection
|
16
|
+
# field.boolean(:any)
|
17
17
|
#
|
18
18
|
# def any
|
19
|
-
#
|
19
|
+
# target.any?
|
20
20
|
# end
|
21
21
|
# end
|
22
22
|
#
|
23
23
|
# # Then, this connection will be used for connections whose names match:
|
24
24
|
# class PostNode < GraphQL::Node
|
25
|
-
# field.
|
25
|
+
# field.upvotes(:upvotes)
|
26
26
|
# # ^^ uses `UpvotesConnection` based on naming convention
|
27
27
|
# end
|
28
28
|
#
|
@@ -41,28 +41,15 @@
|
|
41
41
|
# QUERY
|
42
42
|
class GraphQL::Connection < GraphQL::Node
|
43
43
|
exposes "Array"
|
44
|
-
field.
|
45
|
-
|
46
|
-
attr_reader :calls, :syntax_fields, :query
|
47
|
-
|
48
|
-
def initialize(items, query:, fields: [])
|
49
|
-
@target = items
|
50
|
-
@syntax_fields = fields
|
51
|
-
@query = query
|
52
|
-
end
|
53
|
-
|
54
|
-
# Returns the members of the collection, after any calls on the corresponding {Field} have been applied
|
55
|
-
def items
|
56
|
-
@target
|
57
|
-
end
|
44
|
+
field.object(:edges)
|
58
45
|
|
59
46
|
def edge_fields
|
60
47
|
@edge_fields ||= syntax_fields.find { |f| f.identifier == "edges" }.fields
|
61
48
|
end
|
62
49
|
|
63
50
|
def edges
|
64
|
-
raise "#{self.class} expected a connection, but got `nil`" if
|
65
|
-
|
51
|
+
raise "#{self.class} expected a connection, but got `nil`" if target.nil?
|
52
|
+
target.map do |item|
|
66
53
|
node_class = GraphQL::SCHEMA.type_for_object(item)
|
67
54
|
node = node_class.new(item, fields: edge_fields, query: query)
|
68
55
|
res = node.as_result
|
@@ -74,15 +61,5 @@ class GraphQL::Connection < GraphQL::Node
|
|
74
61
|
def default_schema_name
|
75
62
|
name.split("::").last.sub(/Connection$/, '').underscore
|
76
63
|
end
|
77
|
-
|
78
|
-
attr_accessor :default_connection
|
79
|
-
# Call this to make a the class the default connection
|
80
|
-
# when one isn't found by name.
|
81
|
-
def default_connection!
|
82
|
-
GraphQL::Connection.default_connection = self
|
83
|
-
end
|
84
|
-
|
85
64
|
end
|
86
|
-
|
87
|
-
self.default_connection!
|
88
65
|
end
|
data/lib/graphql/field.rb
CHANGED
@@ -1,182 +1,12 @@
|
|
1
|
-
# {
|
2
|
-
#
|
3
|
-
# `graphql` has built-in fields for some Ruby data types:
|
4
|
-
# - `boolean`: {Types::BooleanField}
|
5
|
-
# - `number`: {Types::NumberField}
|
6
|
-
# - `string`: {Types::StringField}
|
7
|
-
#
|
8
|
-
# You can define custom fields that allow you to control how values are exposed.
|
9
|
-
# - {Field.type} defines how it can be used inside {Node.field} calls.
|
10
|
-
# - {Field.call} defines calls that can mutate the value before it is added to the response.
|
11
|
-
#
|
12
|
-
# @example
|
13
|
-
# # For example, an `AddressField` which wraps a string but exposes address-specific information
|
14
|
-
# class AddressField < GraphQL::Field
|
15
|
-
# type :address
|
16
|
-
# # ^^ now you can use it with `field.address` in node definitions
|
17
|
-
#
|
18
|
-
# # calls can modify the value:
|
19
|
-
# # eg, get the numbers at the beginning:
|
20
|
-
# call :house_number, -> (prev_value) { prev_value[/^\d*/]}
|
21
|
-
# # get everything after a space:
|
22
|
-
# call :street_name, -> (prev_value) { prev_value[/\s.*$/].strip }
|
23
|
-
# end
|
24
|
-
#
|
25
|
-
# # Then, use it in a node definition:
|
26
|
-
# class HouseNode < GraphQL::Node
|
27
|
-
# exposes("House")
|
28
|
-
# # create an `AddressField` for this node called `street_address`:
|
29
|
-
# field.address(:street_address)
|
30
|
-
# end
|
31
|
-
#
|
32
|
-
# # Then, use the field in queries:
|
33
|
-
# <<QUERY
|
34
|
-
# find_house(1) {
|
35
|
-
# street_address,
|
36
|
-
# street_address.house_number() as number,
|
37
|
-
# street_address.street_name() as street,
|
38
|
-
# }
|
39
|
-
# QUERY
|
1
|
+
# Each {Node} has {Field}s that are used to look up connected nodes at query-time
|
40
2
|
class GraphQL::Field
|
41
|
-
attr_reader :
|
42
|
-
def initialize(
|
43
|
-
@
|
44
|
-
@
|
45
|
-
@calls = calls
|
46
|
-
@fields = fields
|
3
|
+
attr_reader :type, :name
|
4
|
+
def initialize(name:, type:)
|
5
|
+
@name = name
|
6
|
+
@type = type.to_s
|
47
7
|
end
|
48
8
|
|
49
|
-
def
|
50
|
-
|
9
|
+
def type_class
|
10
|
+
GraphQL::SCHEMA.get_type(type)
|
51
11
|
end
|
52
|
-
|
53
|
-
def as_result
|
54
|
-
finished_value
|
55
|
-
end
|
56
|
-
|
57
|
-
def finished_value
|
58
|
-
@finished_value ||= begin
|
59
|
-
val = raw_value
|
60
|
-
calls.each do |call|
|
61
|
-
registered_call = self.class.calls[call.identifier]
|
62
|
-
if registered_call.nil?
|
63
|
-
raise "Call not found: #{self.class.name}##{call.identifier}"
|
64
|
-
end
|
65
|
-
val = registered_call.lambda.call(val, *call.arguments)
|
66
|
-
end
|
67
|
-
val
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
# instance `const_get` reaches up to class namespace
|
72
|
-
def const_get(const_name)
|
73
|
-
self.class.const_get(const_name)
|
74
|
-
rescue
|
75
|
-
nil
|
76
|
-
end
|
77
|
-
|
78
|
-
# delegate to class constant
|
79
|
-
["name", "description"].each do |method_name|
|
80
|
-
define_method(method_name) do
|
81
|
-
const_get(method_name.upcase)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
class << self
|
86
|
-
def inherited(child_class)
|
87
|
-
GraphQL::SCHEMA.add_field(child_class)
|
88
|
-
end
|
89
|
-
|
90
|
-
def create_class(name:, owner_class:, type:, description: nil, connection_class_name: nil, node_class_name: nil)
|
91
|
-
if type.is_a?(Symbol)
|
92
|
-
type = GraphQL::SCHEMA.get_field(type)
|
93
|
-
end
|
94
|
-
|
95
|
-
field_superclass = type || self
|
96
|
-
new_class = Class.new(field_superclass)
|
97
|
-
new_class.const_set :NAME, name
|
98
|
-
new_class.const_set :OWNER_CLASS, owner_class
|
99
|
-
new_class.const_set :DESCRIPTION , description
|
100
|
-
new_class.const_set :CONNECTION_CLASS_NAME, connection_class_name
|
101
|
-
new_class.const_set :NODE_CLASS_NAME, node_class_name
|
102
|
-
new_class
|
103
|
-
end
|
104
|
-
|
105
|
-
def to_s
|
106
|
-
if const_defined?(:NAME)
|
107
|
-
"<FieldClass: #{const_get(:OWNER_CLASS).name}::#{const_get(:NAME)}>"
|
108
|
-
else
|
109
|
-
super
|
110
|
-
end
|
111
|
-
end
|
112
|
-
# @param [Symbol] type_name the name used for getting this field from the {GraphQL::SCHEMA}.
|
113
|
-
# Defines the name used for getting fields of this type from the schema.
|
114
|
-
# @example
|
115
|
-
# # define the field with its type:
|
116
|
-
# class IPAddressField < GraphQL::Field
|
117
|
-
# type :ip_address
|
118
|
-
# end
|
119
|
-
#
|
120
|
-
# # then, attach fields of this type to your nodes:
|
121
|
-
# class ServerNode < GraphQL::Field
|
122
|
-
# field.ip_address(:static_ip_address)
|
123
|
-
# end
|
124
|
-
def type(value_type_name)
|
125
|
-
@value_type = value_type_name.to_s
|
126
|
-
GraphQL::SCHEMA.add_field(self)
|
127
|
-
end
|
128
|
-
|
129
|
-
def value_type
|
130
|
-
@value_type || superclass.value_type
|
131
|
-
end
|
132
|
-
|
133
|
-
def schema_name
|
134
|
-
@value_type || (name.present? ? default_schema_name : nil)
|
135
|
-
end
|
136
|
-
|
137
|
-
def default_schema_name
|
138
|
-
name.split("::").last.sub(/Field$/, '').underscore
|
139
|
-
end
|
140
|
-
|
141
|
-
def field_name
|
142
|
-
const_get(:NAME)
|
143
|
-
end
|
144
|
-
|
145
|
-
def description
|
146
|
-
const_get(:DESCRIPTION)
|
147
|
-
end
|
148
|
-
|
149
|
-
def calls
|
150
|
-
superclass.calls.merge(_calls)
|
151
|
-
rescue NoMethodError
|
152
|
-
{}
|
153
|
-
end
|
154
|
-
# @param [String] name the identifier for this call
|
155
|
-
# @param [lambda] operation the transformation this call makes
|
156
|
-
#
|
157
|
-
# Define a call that can be made on this field.
|
158
|
-
# The `lambda` receives arguments:
|
159
|
-
# - 1: `previous_value` -- the value of this field
|
160
|
-
# - *: arguments passed in the query (as strings)
|
161
|
-
#
|
162
|
-
# @example
|
163
|
-
# # upcase a string field:
|
164
|
-
# call :upcase, -> (prev_value) { prev_value.upcase }
|
165
|
-
# @example
|
166
|
-
# # tests a number field:
|
167
|
-
# call :greater_than, -> (prev_value, test_value) { prev_value > test_value.to_f }
|
168
|
-
# # (`test_value` is passed in as a string)
|
169
|
-
def call(name, lambda)
|
170
|
-
_calls[name.to_s] = GraphQL::Call.new(name: name.to_s, lambda: lambda)
|
171
|
-
end
|
172
|
-
|
173
|
-
private
|
174
|
-
|
175
|
-
def _calls
|
176
|
-
@calls ||= {}
|
177
|
-
end
|
178
|
-
|
179
|
-
end
|
180
|
-
|
181
|
-
type :any
|
182
12
|
end
|
@@ -9,27 +9,17 @@ class GraphQL::FieldDefiner
|
|
9
9
|
# `method_name` is used as a field type and looked up against {GraphQL::SCHEMA}.
|
10
10
|
# `args[0]` is the name for the field of that type.
|
11
11
|
def method_missing(method_name, *args, &block)
|
12
|
-
type
|
13
|
-
if type.present?
|
14
|
-
create_field(args[0], type: type, description: args[1])
|
15
|
-
else
|
16
|
-
super
|
17
|
-
end
|
12
|
+
map_field(args[0], type: method_name, description: args[1])
|
18
13
|
end
|
19
14
|
|
20
15
|
private
|
21
16
|
|
22
|
-
def
|
17
|
+
def map_field(field_name, type: nil, description: nil)
|
23
18
|
field_name = field_name.to_s
|
24
|
-
|
19
|
+
mapping = GraphQL::Field.new(
|
25
20
|
name: field_name,
|
26
21
|
type: type,
|
27
|
-
|
28
|
-
|
29
|
-
})
|
30
|
-
|
31
|
-
field_class_name = field_name.camelize + "Field"
|
32
|
-
owner_class.const_set(field_class_name, field_class)
|
33
|
-
owner_class.own_fields[field_name] = field_class
|
22
|
+
)
|
23
|
+
owner_class.own_fields[field_name] = mapping
|
34
24
|
end
|
35
25
|
end
|