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
data/lib/graphql/node.rb
CHANGED
@@ -1,61 +1,168 @@
|
|
1
|
+
# Node is the base class for your GraphQL nodes.
|
2
|
+
# It's essentially a delegator that only delegates methods you whitelist with {.field}.
|
3
|
+
# To use it:
|
4
|
+
#
|
5
|
+
# - Extend `GraphQL::Node`
|
6
|
+
# - Declare what this node will wrap with {.exposes}
|
7
|
+
# - Declare fields with {.field}
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# class PostNode < GraphQL::Node
|
11
|
+
# exposes('Post')
|
12
|
+
#
|
13
|
+
# cursor(:id)
|
14
|
+
#
|
15
|
+
# field.number(:id)
|
16
|
+
# field.string(:title)
|
17
|
+
# field.string(:content)
|
18
|
+
# field.connection(:comments)
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
|
1
22
|
class GraphQL::Node
|
2
|
-
|
23
|
+
# The object wrapped by this `Node`
|
24
|
+
attr_reader :target
|
25
|
+
# Fields parsed from the query string
|
26
|
+
attr_reader :syntax_fields
|
27
|
+
# The query to which this `Node` belongs. Used for accessing its {Query#context}.
|
28
|
+
attr_reader :query
|
3
29
|
|
4
|
-
def initialize(target=nil)
|
5
|
-
# DONT EXPOSE Node#target! otherwise you might be able to access it
|
30
|
+
def initialize(target=nil, fields:, query:)
|
6
31
|
@target = target
|
32
|
+
@query = query
|
33
|
+
@syntax_fields = fields
|
7
34
|
end
|
8
35
|
|
9
|
-
|
10
|
-
|
11
|
-
|
36
|
+
# If the target responds to `method_name`, send it to target.
|
37
|
+
def method_missing(method_name, *args, &block)
|
38
|
+
if target.respond_to?(method_name)
|
39
|
+
target.public_send(method_name, *args, &block)
|
12
40
|
else
|
13
|
-
|
41
|
+
super
|
14
42
|
end
|
15
43
|
end
|
16
44
|
|
17
|
-
|
45
|
+
# Looks up {#syntax_fields} against this node and returns the results
|
46
|
+
def as_result
|
18
47
|
json = {}
|
19
|
-
|
20
|
-
|
21
|
-
if
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
48
|
+
syntax_fields.each do |syntax_field|
|
49
|
+
key_name = syntax_field.alias_name || syntax_field.identifier
|
50
|
+
if key_name == 'node'
|
51
|
+
clone_node = self.class.new(target, fields: syntax_field.fields, query: query)
|
52
|
+
json[key_name] = clone_node.as_result
|
53
|
+
elsif key_name == 'cursor'
|
54
|
+
json[key_name] = cursor
|
55
|
+
else
|
56
|
+
field = get_field(syntax_field)
|
57
|
+
json[key_name] = field.as_result
|
28
58
|
end
|
29
59
|
end
|
30
60
|
json
|
31
61
|
end
|
32
62
|
|
33
|
-
|
34
|
-
def
|
35
|
-
|
63
|
+
# The object passed to {Query#initialize} as `context`.
|
64
|
+
def context
|
65
|
+
query.context
|
36
66
|
end
|
37
67
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
68
|
+
|
69
|
+
class << self
|
70
|
+
# Registers this node in {GraphQL::SCHEMA}
|
71
|
+
def inherited(child_class)
|
72
|
+
# use name to prevent autoloading Connection
|
73
|
+
if child_class.ancestors.map(&:name).include?("GraphQL::Connection")
|
74
|
+
GraphQL::SCHEMA.add_connection(child_class)
|
42
75
|
end
|
43
76
|
end
|
44
|
-
end
|
45
77
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
78
|
+
# @param [String] class_name name of the class this node will wrap.
|
79
|
+
def exposes(ruby_class_name)
|
80
|
+
@ruby_class_name = ruby_class_name
|
81
|
+
GraphQL::SCHEMA.add_type(self)
|
82
|
+
end
|
83
|
+
|
84
|
+
# The name of the class wrapped by this node
|
85
|
+
def ruby_class_name
|
86
|
+
@ruby_class_name
|
87
|
+
end
|
88
|
+
|
89
|
+
# @param [String] describe
|
90
|
+
# Provide a description for this node which will be accessible from {SCHEMA}
|
91
|
+
def desc(describe)
|
92
|
+
@description = describe
|
93
|
+
end
|
94
|
+
|
95
|
+
# The description of this node
|
96
|
+
def description
|
97
|
+
@description || raise("#{name}.description isn't defined")
|
98
|
+
end
|
99
|
+
|
100
|
+
# @param [String] type_name
|
101
|
+
# Declares an alternative name to use in {GraphQL::SCHEMA}
|
102
|
+
def type(type_name)
|
103
|
+
@type_name = type_name.to_s
|
104
|
+
GraphQL::SCHEMA.add_type(self)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns the name of this node used by {GraphQL::SCHEMA}
|
108
|
+
def schema_name
|
109
|
+
@type_name || default_schema_name
|
110
|
+
end
|
111
|
+
|
112
|
+
def default_schema_name
|
113
|
+
name.split("::").last.sub(/Node$/, '').underscore
|
114
|
+
end
|
115
|
+
|
116
|
+
# @param [String] field_name name of the field to be used as the cursor
|
117
|
+
# Declares what field will be used as the cursor for this node.
|
118
|
+
def cursor(field_name)
|
119
|
+
define_method "cursor" do
|
120
|
+
field_class = self.class.all_fields[field_name.to_s]
|
121
|
+
field = field_class.new(query: query, owner: self, calls: [])
|
122
|
+
cursor = GraphQL::Types::CursorField.new(field.as_result)
|
123
|
+
cursor.as_result
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# All accessible fields on this node (including those defined in parent classes)
|
128
|
+
def all_fields
|
129
|
+
superclass.all_fields.merge(own_fields)
|
130
|
+
rescue NoMethodError
|
131
|
+
own_fields
|
132
|
+
end
|
133
|
+
|
134
|
+
# Fields defined by this class, but not its parents
|
135
|
+
def own_fields
|
136
|
+
@own_fields ||= {}
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return [GraphQL::FieldDefiner] definer
|
140
|
+
def field
|
141
|
+
@field_definer ||= GraphQL::FieldDefiner.new(self)
|
142
|
+
end
|
143
|
+
|
144
|
+
# @param [String] field_name
|
145
|
+
# Un-define field with name `field_name`
|
146
|
+
def remove_field(field_name)
|
147
|
+
own_fields.delete(field_name.to_s)
|
52
148
|
end
|
53
149
|
end
|
54
150
|
|
151
|
+
private
|
55
152
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
153
|
+
def get_field(syntax_field)
|
154
|
+
field_class = self.class.all_fields[syntax_field.identifier]
|
155
|
+
if syntax_field.identifier == "cursor"
|
156
|
+
cursor
|
157
|
+
elsif field_class.nil?
|
158
|
+
raise GraphQL::FieldNotDefinedError.new(self.class.name, syntax_field.identifier)
|
159
|
+
else
|
160
|
+
field_class.new(
|
161
|
+
query: query,
|
162
|
+
owner: self,
|
163
|
+
calls: syntax_field.calls,
|
164
|
+
fields: syntax_field.fields,
|
165
|
+
)
|
59
166
|
end
|
60
167
|
end
|
61
168
|
end
|
data/lib/graphql/parser.rb
CHANGED
@@ -1,18 +1,29 @@
|
|
1
1
|
class GraphQL::Parser < Parslet::Parser
|
2
|
-
root(:
|
3
|
-
|
2
|
+
root(:query)
|
3
|
+
rule(:query) { node.repeat.as(:nodes) >> variable.repeat.as(:variables) }
|
4
|
+
# node
|
4
5
|
rule(:node) { space? >> call >> space? >> fields.as(:fields) }
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
rule(:edge) { call_chain >> space? >> fields.as(:fields) }
|
9
|
-
rule(:call_chain) { identifier >> (dot >> call).repeat(0).as(:calls) }
|
7
|
+
# field set
|
8
|
+
rule(:fields) { str("{") >> space? >> (field >> separator?).repeat(1) >> space? >> str("}") >> space?}
|
10
9
|
|
11
|
-
|
10
|
+
#call
|
11
|
+
rule(:call) { identifier >> str("(") >> (argument.as(:argument) >> separator?).repeat(0).as(:arguments) >> str(")") }
|
12
12
|
rule(:dot) { str(".") }
|
13
|
+
rule(:argument) { (identifier | variable_identifier)}
|
14
|
+
|
15
|
+
# field
|
16
|
+
rule(:field) { identifier >> call_chain.maybe >> alias_name.maybe >> space? >> fields.as(:fields).maybe }
|
17
|
+
rule(:call_chain) { (dot >> call).repeat(0).as(:calls) }
|
18
|
+
rule(:alias_name) { space >> str("as") >> space >> name.as(:alias_name) }
|
13
19
|
|
14
|
-
|
20
|
+
# variable
|
21
|
+
rule(:variable) { space? >> variable_identifier >> str(":") >> space? >> (name | json_string ).as(:json_string) >> space?}
|
22
|
+
rule(:json_string) { str("{") >> (match('[^{}]') | json_string).repeat >> str("}")}
|
23
|
+
rule(:variable_identifier) { (str("<") >> name >> str(">")).as(:identifier) }
|
15
24
|
|
25
|
+
# general purpose
|
26
|
+
rule(:separator?) { str(",").maybe >> space? }
|
16
27
|
rule(:identifier) { name.as(:identifier) }
|
17
28
|
rule(:name) { match('\w').repeat(1) }
|
18
29
|
rule(:space) { match('[\s\n]+').repeat(1) }
|
data/lib/graphql/query.rb
CHANGED
@@ -1,41 +1,84 @@
|
|
1
|
+
# Executes queries from strings against {GraphQL::SCHEMA}.
|
2
|
+
#
|
3
|
+
# @example
|
4
|
+
# query_str = "post(1) { title, comments { count } }"
|
5
|
+
# query_ctx = {user: current_user}
|
6
|
+
# query = GraphQL::Query.new(query_str, context: query_ctx)
|
7
|
+
# result = query.as_result
|
8
|
+
|
1
9
|
class GraphQL::Query
|
2
|
-
attr_reader :query_string, :root, :
|
3
|
-
|
10
|
+
attr_reader :query_string, :root, :context
|
11
|
+
|
12
|
+
# @param [String] query_string the string to be parsed
|
13
|
+
# @param [Object] context an object which will be available to all nodes and fields in the schema
|
14
|
+
def initialize(query_string, context: nil)
|
4
15
|
if !query_string.is_a?(String) || query_string.length == 0
|
5
16
|
raise "You must send a query string, not a #{query_string.class.name}"
|
6
17
|
end
|
7
18
|
@query_string = query_string
|
8
19
|
@root = parse(query_string)
|
9
|
-
@
|
10
|
-
end
|
11
|
-
|
12
|
-
def to_json
|
13
|
-
root_node = make_call(nil, root.identifier, root.argument)
|
14
|
-
raise "Couldn't find root for #{root.identifier}(#{root.argument})" if root.nil?
|
15
|
-
|
16
|
-
root_node.fields = root.fields
|
17
|
-
{
|
18
|
-
root_node.cursor => root_node.to_json
|
19
|
-
}
|
20
|
+
@context = context
|
20
21
|
end
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
# @return [Hash] result the JSON response to this query
|
24
|
+
# Calling {#as_result} more than once won't cause the query to be re-run
|
25
|
+
def as_result
|
26
|
+
@as_result ||= execute!
|
25
27
|
end
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
# @param [String] identifier
|
30
|
+
# returns a query variable named `identifier`, otherwise raises.
|
31
|
+
def get_variable(identifier)
|
32
|
+
syntax_var = @root.variables.find { |v| v.identifier == identifier }
|
33
|
+
if syntax_var.blank?
|
34
|
+
raise "No variable found for #{identifier}, defined variables are #{@root.variables.map(&:identifier)}"
|
31
35
|
end
|
32
|
-
|
36
|
+
syntax_var
|
33
37
|
end
|
34
38
|
|
35
39
|
private
|
36
40
|
|
41
|
+
def execute!
|
42
|
+
root_syntax_node = root.nodes[0]
|
43
|
+
root_call_identifier = root_syntax_node.identifier
|
44
|
+
root_call_class = GraphQL::SCHEMA.get_call(root_call_identifier)
|
45
|
+
root_call = root_call_class.new(query: self, syntax_arguments: root_syntax_node.arguments)
|
46
|
+
result_object = root_call.as_result
|
47
|
+
return_declarations = root_call_class.return_declarations
|
48
|
+
result = {}
|
49
|
+
|
50
|
+
if result_object.is_a?(Hash)
|
51
|
+
result_object.each do |name, value|
|
52
|
+
node_class = GraphQL::SCHEMA.type_for_object(value)
|
53
|
+
field_for_node = root_syntax_node.fields.find {|f| f.identifier == name.to_s }
|
54
|
+
if field_for_node.present?
|
55
|
+
fields_for_node = field_for_node.fields
|
56
|
+
node_value = node_class.new(value,query: self, fields: fields_for_node)
|
57
|
+
result[name.to_s] = node_value.as_result
|
58
|
+
end
|
59
|
+
end
|
60
|
+
elsif result_object.is_a?(Array)
|
61
|
+
fields_for_node = root_syntax_node.fields
|
62
|
+
result_object.each do |item|
|
63
|
+
node_class = GraphQL::SCHEMA.type_for_object(item)
|
64
|
+
node_value = node_class.new(item, query: self, fields: fields_for_node)
|
65
|
+
result[node_value.cursor] = node_value.as_result
|
66
|
+
end
|
67
|
+
else
|
68
|
+
node_class = GraphQL::SCHEMA.type_for_object(result_object)
|
69
|
+
fields_for_node = root_syntax_node.fields
|
70
|
+
node_value = node_class.new(result_object, query: self, fields: fields_for_node)
|
71
|
+
result[node_value.cursor] = node_value.as_result
|
72
|
+
end
|
73
|
+
|
74
|
+
result
|
75
|
+
end
|
76
|
+
|
37
77
|
def parse(query_string)
|
38
78
|
parsed_hash = GraphQL::PARSER.parse(query_string)
|
39
79
|
root_node = GraphQL::TRANSFORM.apply(parsed_hash)
|
80
|
+
rescue Parslet::ParseFailed => error
|
81
|
+
line, col = error.cause.source.line_and_column
|
82
|
+
raise GraphQL::SyntaxError.new(line, col, query_string)
|
40
83
|
end
|
41
84
|
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# Every query begins with a root call. It might find data or mutate data and return some results.
|
2
|
+
#
|
3
|
+
# A root call should:
|
4
|
+
#
|
5
|
+
# - declare any arguments with {.argument}, or declare `argument.none`
|
6
|
+
# - declare returns with {.return}
|
7
|
+
# - implement {#execute!} to take those arguments and return values
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# FindPostCall < GraphQL::RootCall
|
11
|
+
# argument.number(:ids, any_number: true)
|
12
|
+
# returns :post
|
13
|
+
#
|
14
|
+
# def execute!(*ids)
|
15
|
+
# ids.map { |id| Post.find(id) }
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# CreateCommentCall < GraphQL::RootCall
|
21
|
+
# argument.number(:post_id)
|
22
|
+
# argument.object(:comment)
|
23
|
+
# returns :post, :comment
|
24
|
+
#
|
25
|
+
# def execute!(post_id, comment)
|
26
|
+
# post = Post.find(post_id)
|
27
|
+
# new_comment = post.comments.create!(comment)
|
28
|
+
# {
|
29
|
+
# comment: new_comment,
|
30
|
+
# post: post,
|
31
|
+
# }
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
class GraphQL::RootCall
|
36
|
+
attr_reader :query, :arguments
|
37
|
+
def initialize(query:, syntax_arguments:)
|
38
|
+
@query = query
|
39
|
+
|
40
|
+
raise "#{self.class.name} must declare arguments" unless self.class.arguments
|
41
|
+
@arguments = syntax_arguments.each_with_index.map do |syntax_arg, idx|
|
42
|
+
|
43
|
+
value = if syntax_arg[0] == "<"
|
44
|
+
query.get_variable(syntax_arg).json_string
|
45
|
+
else
|
46
|
+
syntax_arg
|
47
|
+
end
|
48
|
+
|
49
|
+
self.class.typecast(idx, value)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def execute!(*args)
|
54
|
+
raise NotImplementedError, "Do work in this method"
|
55
|
+
end
|
56
|
+
|
57
|
+
def context
|
58
|
+
query.context
|
59
|
+
end
|
60
|
+
|
61
|
+
def as_result
|
62
|
+
return_declarations = self.class.return_declarations
|
63
|
+
raise "#{self.class.name} must declare returns" unless return_declarations.present?
|
64
|
+
return_values = execute!(*arguments)
|
65
|
+
|
66
|
+
if return_values.is_a?(Hash)
|
67
|
+
unexpected_returns = return_values.keys - return_declarations.keys
|
68
|
+
missing_returns = return_declarations.keys - return_values.keys
|
69
|
+
if unexpected_returns.any?
|
70
|
+
raise "#{self.class.name} returned #{unexpected_returns}, but didn't declare them."
|
71
|
+
elsif missing_returns.any?
|
72
|
+
raise "#{self.class.name} declared #{missing_returns}, but didn't return them."
|
73
|
+
end
|
74
|
+
end
|
75
|
+
return_values
|
76
|
+
end
|
77
|
+
|
78
|
+
class << self
|
79
|
+
# @param [String] ident_name
|
80
|
+
# Declare an alternative name used in a query string
|
81
|
+
def indentifier(ident_name)
|
82
|
+
@identifier = ident_name
|
83
|
+
GraphQL::SCHEMA.add_call(self)
|
84
|
+
end
|
85
|
+
|
86
|
+
# The name used by {GraphQL::SCHEMA}. Uses {.identifier} or derives a name from the class name.
|
87
|
+
def schema_name
|
88
|
+
@identifier || name.split("::").last.sub(/Call$/, '').underscore
|
89
|
+
end
|
90
|
+
|
91
|
+
def inherited(child_class)
|
92
|
+
GraphQL::SCHEMA.add_call(child_class)
|
93
|
+
end
|
94
|
+
|
95
|
+
# This call won't be visible in `schema()`
|
96
|
+
def abstract!
|
97
|
+
GraphQL::SCHEMA.remove_call(self)
|
98
|
+
end
|
99
|
+
|
100
|
+
# @param [Symbol] return_declarations
|
101
|
+
# Name of returned values from this call
|
102
|
+
def returns(*return_declaration_names)
|
103
|
+
if return_declaration_names.last.is_a?(Hash)
|
104
|
+
return_declarations_hash = return_declaration_names.pop
|
105
|
+
else
|
106
|
+
return_declarations_hash = {}
|
107
|
+
end
|
108
|
+
|
109
|
+
raise "Return keys must be symbols" if (return_declarations.keys + return_declaration_names).any? { |k| !k.is_a?(Symbol) }
|
110
|
+
|
111
|
+
return_declaration_names.each do |return_sym|
|
112
|
+
return_type = return_sym.to_s
|
113
|
+
return_declarations[return_sym] = return_type
|
114
|
+
end
|
115
|
+
|
116
|
+
return_declarations_hash.each do |return_sym, return_type|
|
117
|
+
return_declarations[return_sym] = return_type
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def return_declarations
|
122
|
+
@return_declarations ||= {}
|
123
|
+
end
|
124
|
+
|
125
|
+
# @return [GraphQL::RootCallArgumentDefiner] definer
|
126
|
+
# Use this object to declare arguments.
|
127
|
+
def argument
|
128
|
+
@argument ||= GraphQL::RootCallArgumentDefiner.new(self)
|
129
|
+
end
|
130
|
+
|
131
|
+
def own_arguments
|
132
|
+
@argument && @argument.arguments
|
133
|
+
end
|
134
|
+
|
135
|
+
def arguments
|
136
|
+
own = own_arguments || []
|
137
|
+
own + superclass.arguments
|
138
|
+
rescue NoMethodError
|
139
|
+
own
|
140
|
+
end
|
141
|
+
|
142
|
+
def argument_for_index(idx)
|
143
|
+
if arguments.first.any_number
|
144
|
+
arguments.first
|
145
|
+
else
|
146
|
+
arguments[idx]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
TYPE_CHECKS = {
|
151
|
+
"object" => Hash,
|
152
|
+
"number" => Numeric,
|
153
|
+
"string" => String,
|
154
|
+
}
|
155
|
+
|
156
|
+
def typecast(idx, value)
|
157
|
+
arg_dec = argument_for_index(idx)
|
158
|
+
expected_type = arg_dec.type
|
159
|
+
expected_type_class = TYPE_CHECKS[expected_type]
|
160
|
+
|
161
|
+
if expected_type == "string"
|
162
|
+
parsed_value = value
|
163
|
+
else
|
164
|
+
parsed_value = JSON.parse('{ "value" : ' + value + '}')["value"]
|
165
|
+
end
|
166
|
+
|
167
|
+
if !parsed_value.is_a?(expected_type_class)
|
168
|
+
raise GraphQL::RootCallArgumentError.new(arg_dec, value)
|
169
|
+
end
|
170
|
+
|
171
|
+
parsed_value
|
172
|
+
rescue JSON::ParserError
|
173
|
+
raise GraphQL::RootCallArgumentError.new(arg_dec, value)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|