graphql 0.0.3 → 0.0.4
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 +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
|