graphql-client 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql/client.rb +7 -93
- data/lib/graphql/client/document.rb +48 -0
- data/lib/graphql/client/fragment.rb +32 -0
- data/lib/graphql/client/node.rb +36 -0
- data/lib/graphql/client/query.rb +30 -0
- data/lib/graphql/client/query_result.rb +8 -52
- data/lib/graphql/language/nodes/inject_selection_ext.rb +44 -0
- data/lib/graphql/language/nodes/query_result_class_ext.rb +70 -0
- data/lib/graphql/language/nodes/replace_fragment_spread_ext.rb +44 -0
- data/lib/graphql/language/nodes/selection_ext.rb +37 -0
- data/lib/graphql/language/nodes/validate_ext.rb +45 -0
- data/lib/graphql/relay/node_query.rb +19 -0
- data/lib/graphql/schema/json_loader.rb +101 -0
- data/lib/graphql/schema_load_json.rb +10 -0
- metadata +13 -5
- data/lib/graphql/client/const_proxy.rb +0 -29
- data/lib/graphql/language/mutator.rb +0 -45
- data/lib/graphql/language/operation_slice.rb +0 -31
- data/lib/graphql/relay/parser.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8414ab402cf8454eb7d34081edc3c17fff0611de
|
4
|
+
data.tar.gz: 2a1351fe377f4e9999b9a0c5cfedd04081240753
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b1b30fc5a6c9f06f03a8c3ed9e69614bb1f7d3a0cbd449bb72a2077d34a154a6b2b48bd3d0b988997b2d59716941a3121fe2958f5948dff6afcce17c779c3cdd
|
7
|
+
data.tar.gz: dfbe13c52c3bd8d9df76129b8de2ec7f4fe2bb430c1788f9d77966c8a75f57d8fcbdc973e280239f763fba1d6976be8babaffdb86590b0da35819b40e1c0fb26
|
data/lib/graphql/client.rb
CHANGED
@@ -1,100 +1,14 @@
|
|
1
|
-
require "active_support/inflector"
|
2
1
|
require "graphql"
|
3
|
-
require "graphql/client/
|
2
|
+
require "graphql/client/document"
|
3
|
+
require "graphql/client/fragment"
|
4
|
+
require "graphql/client/node"
|
4
5
|
require "graphql/client/query_result"
|
5
|
-
require "graphql/
|
6
|
-
require "graphql/language/nodes/deep_freeze_ext"
|
7
|
-
require "graphql/language/operation_slice"
|
8
|
-
require "graphql/relay/parser"
|
6
|
+
require "graphql/client/query"
|
9
7
|
|
10
8
|
module GraphQL
|
11
|
-
|
12
|
-
class
|
13
|
-
|
14
|
-
|
15
|
-
attr_reader :schema
|
16
|
-
|
17
|
-
def initialize(schema:)
|
18
|
-
@schema = schema
|
19
|
-
@definitions = []
|
20
|
-
end
|
21
|
-
|
22
|
-
class Definition
|
23
|
-
def initialize(name:, client:, source:)
|
24
|
-
@name = name
|
25
|
-
@client = client
|
26
|
-
@_nodes = @client._parse(@name, source)
|
27
|
-
@query_result = GraphQL::Client::QueryResult.wrap(@_nodes.first, name: name)
|
28
|
-
end
|
29
|
-
|
30
|
-
attr_reader :_nodes
|
31
|
-
|
32
|
-
def operation_name
|
33
|
-
if op = @_nodes.find { |d| d.is_a?(GraphQL::Language::Nodes::OperationDefinition) }
|
34
|
-
op.name
|
35
|
-
else
|
36
|
-
nil
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def document
|
41
|
-
@document ||= Language::OperationSlice.slice(@client.document, operation_name).deep_freeze
|
42
|
-
end
|
43
|
-
|
44
|
-
def new(*args)
|
45
|
-
@query_result.new(*args)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def parse(str)
|
50
|
-
definition = ConstProxy.new { |name| Definition.new(client: self, name: name, source: str) }
|
51
|
-
@definitions << definition
|
52
|
-
definition
|
53
|
-
end
|
54
|
-
|
55
|
-
def _parse(name, str)
|
56
|
-
str = str.strip
|
57
|
-
|
58
|
-
str = str.gsub(/\.\.\.([a-zA-Z0-9_]+(::[a-zA-Z0-9_]+)+)(\.([a-zA-Z0-9_]+))?/) { |m|
|
59
|
-
const_name, fragment_name = $1, $4
|
60
|
-
nodes = ActiveSupport::Inflector.constantize(const_name)._nodes
|
61
|
-
|
62
|
-
fragment_name = fragment_name ?
|
63
|
-
nodes.find { |n| n.name.end_with?(fragment_name) }.name : # XXX
|
64
|
-
nodes.first.name
|
65
|
-
|
66
|
-
"...#{fragment_name}"
|
67
|
-
}
|
68
|
-
|
69
|
-
doc = GraphQL::Relay::Parser.parse(str)
|
70
|
-
|
71
|
-
mutator = GraphQL::Language::Mutator.new(doc)
|
72
|
-
|
73
|
-
aliases = {}
|
74
|
-
doc.definitions.each do |definition|
|
75
|
-
aliases[definition.name] = (name.split("::") << definition.name).compact.join("__")
|
76
|
-
end
|
77
|
-
mutator.rename_definitions(aliases)
|
78
|
-
|
79
|
-
# TODO: Make this __typename injection optional
|
80
|
-
mutator.prepend_selection(GraphQL::Language::Nodes::Field.new(name: "__typename").deep_freeze)
|
81
|
-
|
82
|
-
doc.definitions.map(&:deep_freeze)
|
83
|
-
end
|
84
|
-
|
85
|
-
def document
|
86
|
-
GraphQL::Language::Nodes::Document.new(definitions: @definitions.flat_map(&:_nodes)).deep_freeze
|
87
|
-
end
|
88
|
-
|
89
|
-
def validate!
|
90
|
-
validator = StaticValidation::Validator.new(schema: @schema)
|
91
|
-
query = Query.new(@schema, document: document)
|
92
|
-
|
93
|
-
validator.validate(query).fetch(:errors).each do |error|
|
94
|
-
raise ValidationError, error["message"]
|
95
|
-
end
|
96
|
-
|
97
|
-
nil
|
9
|
+
module Client
|
10
|
+
class << self
|
11
|
+
attr_accessor :schema
|
98
12
|
end
|
99
13
|
end
|
100
14
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require "graphql"
|
2
|
+
require "graphql/client/fragment"
|
3
|
+
require "graphql/client/node"
|
4
|
+
require "graphql/client/query"
|
5
|
+
require "graphql/language/nodes/deep_freeze_ext"
|
6
|
+
require "graphql/language/nodes/inject_selection_ext"
|
7
|
+
require "graphql/language/nodes/replace_fragment_spread_ext"
|
8
|
+
require "graphql/language/nodes/validate_ext"
|
9
|
+
|
10
|
+
module GraphQL
|
11
|
+
module Client
|
12
|
+
class Document < Node
|
13
|
+
def self.parse(str, schema: GraphQL::Client.schema)
|
14
|
+
str = str.strip
|
15
|
+
str, fragments = scan_interpolated_fragments(str)
|
16
|
+
|
17
|
+
document = GraphQL.parse(str)
|
18
|
+
document = document.inject_selection(GraphQL::Language::Nodes::Field.new(name: "__typename"))
|
19
|
+
|
20
|
+
document.definitions.each do |definition|
|
21
|
+
fragments[definition.name.to_sym] = definition if definition.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
|
22
|
+
end
|
23
|
+
|
24
|
+
document = document.replace_fragment_spread(fragments)
|
25
|
+
document = document.replace_fragment_spread(fragments) # XXX: Multiple pass
|
26
|
+
|
27
|
+
document.definitions.inject({}) do |doc, definition|
|
28
|
+
name = definition.name.to_sym
|
29
|
+
|
30
|
+
case definition
|
31
|
+
when GraphQL::Language::Nodes::OperationDefinition
|
32
|
+
query = GraphQL::Client::Query.new(definition.deep_freeze, fragments.values).freeze
|
33
|
+
query.node.validate!(schema: schema) if schema
|
34
|
+
doc[name] = query
|
35
|
+
|
36
|
+
when GraphQL::Language::Nodes::FragmentDefinition
|
37
|
+
definition = GraphQL::Language::Nodes::InlineFragment.new(type: definition.type, directives: definition.directives, selections: definition.selections)
|
38
|
+
fragment = GraphQL::Client::Fragment.new(definition.deep_freeze, fragments.values).freeze
|
39
|
+
fragment.node.validate!(schema: schema) if schema
|
40
|
+
doc[name] = fragment
|
41
|
+
end
|
42
|
+
|
43
|
+
doc
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "graphql"
|
2
|
+
require "graphql/client/node"
|
3
|
+
require "graphql/language/nodes/deep_freeze_ext"
|
4
|
+
require "graphql/language/nodes/inject_selection_ext"
|
5
|
+
require "graphql/language/nodes/replace_fragment_spread_ext"
|
6
|
+
require "graphql/language/nodes/validate_ext"
|
7
|
+
|
8
|
+
module GraphQL
|
9
|
+
module Client
|
10
|
+
class Fragment < Node
|
11
|
+
def self.parse(str, schema: GraphQL::Client.schema)
|
12
|
+
str = str.strip
|
13
|
+
str, fragments = scan_interpolated_fragments(str)
|
14
|
+
|
15
|
+
if str.start_with?("fragment")
|
16
|
+
str = str.sub(/^fragment on /, "fragment __anonymous__ on ")
|
17
|
+
doc = GraphQL.parse(str)
|
18
|
+
doc = doc.inject_selection(GraphQL::Language::Nodes::Field.new(name: "__typename"))
|
19
|
+
doc = doc.replace_fragment_spread(fragments)
|
20
|
+
fragment = doc.definitions.first
|
21
|
+
node = GraphQL::Language::Nodes::InlineFragment.new(type: fragment.type, directives: fragment.directives, selections: fragment.selections)
|
22
|
+
else
|
23
|
+
raise ArgumentError, "expected string to be a fragment:\n#{str}"
|
24
|
+
end
|
25
|
+
|
26
|
+
fragment = new(node.deep_freeze, fragments.values).freeze
|
27
|
+
fragment.node.validate!(schema: schema) if schema
|
28
|
+
fragment
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "active_support/inflector"
|
2
|
+
require "graphql"
|
3
|
+
require "graphql/language/nodes/query_result_class_ext"
|
4
|
+
|
5
|
+
module GraphQL
|
6
|
+
module Client
|
7
|
+
class Node
|
8
|
+
def self.scan_interpolated_fragments(str)
|
9
|
+
fragments, index = {}, 1
|
10
|
+
str = str.gsub(/\.\.\.([a-zA-Z0-9_]+(::[a-zA-Z0-9_]+)+)/) { |m|
|
11
|
+
index += 1
|
12
|
+
name = "__fragment#{index}__"
|
13
|
+
fragments[name.to_sym] = ActiveSupport::Inflector.constantize($1).node
|
14
|
+
"...#{name}"
|
15
|
+
}
|
16
|
+
return str, fragments
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :node
|
20
|
+
|
21
|
+
attr_reader :fragments
|
22
|
+
|
23
|
+
attr_reader :type
|
24
|
+
|
25
|
+
def initialize(node, fragments)
|
26
|
+
@node = node
|
27
|
+
@fragments = fragments
|
28
|
+
@type = node.query_result_class(shadow: fragments)
|
29
|
+
end
|
30
|
+
|
31
|
+
def new(*args)
|
32
|
+
type.new(*args)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "graphql"
|
2
|
+
require "graphql/client/node"
|
3
|
+
require "graphql/language/nodes/deep_freeze_ext"
|
4
|
+
require "graphql/language/nodes/inject_selection_ext"
|
5
|
+
require "graphql/language/nodes/replace_fragment_spread_ext"
|
6
|
+
require "graphql/language/nodes/validate_ext"
|
7
|
+
|
8
|
+
module GraphQL
|
9
|
+
module Client
|
10
|
+
class Query < Node
|
11
|
+
def self.parse(str, schema: GraphQL::Client.schema)
|
12
|
+
str = str.strip
|
13
|
+
str, fragments = scan_interpolated_fragments(str)
|
14
|
+
|
15
|
+
if str.start_with?("query")
|
16
|
+
doc = GraphQL.parse(str)
|
17
|
+
doc = doc.inject_selection(GraphQL::Language::Nodes::Field.new(name: "__typename"))
|
18
|
+
doc = doc.replace_fragment_spread(fragments)
|
19
|
+
node = doc.definitions.first
|
20
|
+
else
|
21
|
+
raise ArgumentError, "expected string to be a query:\n#{str}"
|
22
|
+
end
|
23
|
+
|
24
|
+
query = new(node.deep_freeze, fragments.values).freeze
|
25
|
+
query.node.validate!(schema: schema) if schema
|
26
|
+
query
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,38 +1,15 @@
|
|
1
|
-
require "active_support/inflector"
|
2
1
|
require "graphql"
|
3
|
-
require "
|
2
|
+
require "active_support/inflector"
|
4
3
|
|
5
4
|
module GraphQL
|
6
|
-
|
5
|
+
module Client
|
7
6
|
class QueryResult
|
8
|
-
|
9
|
-
|
10
|
-
# Returns subclass of QueryResult or nil.
|
11
|
-
def self.wrap(node, name: nil)
|
12
|
-
fields = {}
|
13
|
-
|
14
|
-
node.selections.each do |selection|
|
15
|
-
case selection
|
16
|
-
when Language::Nodes::FragmentSpread
|
17
|
-
when Language::Nodes::Field
|
18
|
-
field_name = selection.alias || selection.name
|
19
|
-
field_klass = selection.selections.any? ? wrap(selection, name: "#{name}.#{field_name}") : nil
|
20
|
-
fields[field_name] ? fields[field_name] |= field_klass : fields[field_name] = field_klass
|
21
|
-
when Language::Nodes::InlineFragment
|
22
|
-
wrap(selection, name: name).fields.each do |fragment_name, klass|
|
23
|
-
fields[fragment_name.to_s] ? fields[fragment_name.to_s] |= klass : fields[fragment_name.to_s] = klass
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
define(name: name, source_node: node, fields: fields)
|
7
|
+
def self.fields
|
8
|
+
@fields
|
29
9
|
end
|
30
10
|
|
31
|
-
|
32
|
-
def self.define(name:, source_node:, fields: {})
|
11
|
+
def self.define(fields: {})
|
33
12
|
Class.new(self) do
|
34
|
-
@name = name
|
35
|
-
@source_node = source_node
|
36
13
|
@fields = {}
|
37
14
|
|
38
15
|
fields.each do |field, type|
|
@@ -85,20 +62,8 @@ module GraphQL
|
|
85
62
|
end
|
86
63
|
end
|
87
64
|
|
88
|
-
def self.source_node
|
89
|
-
@source_node
|
90
|
-
end
|
91
|
-
|
92
|
-
def self.fields
|
93
|
-
@fields
|
94
|
-
end
|
95
|
-
|
96
|
-
def self.name
|
97
|
-
@name || super || GraphQL::Client::QueryResult.name
|
98
|
-
end
|
99
|
-
|
100
65
|
def self.inspect
|
101
|
-
"
|
66
|
+
"#<GraphQL::Client::QueryResult fields=#{@fields.keys.inspect}>"
|
102
67
|
end
|
103
68
|
|
104
69
|
def self.cast(obj)
|
@@ -106,14 +71,6 @@ module GraphQL
|
|
106
71
|
when Hash
|
107
72
|
new(obj)
|
108
73
|
when QueryResult
|
109
|
-
spreads = Set.new(obj.class.source_node.selections.select { |s| s.is_a?(GraphQL::Language::Nodes::FragmentSpread) }.map(&:name))
|
110
|
-
|
111
|
-
if !spreads.include?(self.source_node.name)
|
112
|
-
message = "couldn't cast #{obj.inspect} to #{self.inspect}\n\n"
|
113
|
-
suggestion = "\n ...#{name || "YourFragment"} # SUGGESTION"
|
114
|
-
message << GraphQL::Language::Generation.generate(obj.class.source_node).sub(/\n}$/, "#{suggestion}\n}")
|
115
|
-
raise TypeError, message
|
116
|
-
end
|
117
74
|
cast(obj.to_h)
|
118
75
|
when Array
|
119
76
|
obj.map { |e| cast(e) }
|
@@ -142,8 +99,7 @@ module GraphQL
|
|
142
99
|
new_fields[name] = value
|
143
100
|
end
|
144
101
|
end
|
145
|
-
|
146
|
-
define(name: self.name, source_node: self.source_node, fields: new_fields)
|
102
|
+
define(fields: new_fields)
|
147
103
|
end
|
148
104
|
|
149
105
|
attr_reader :data
|
@@ -151,7 +107,7 @@ module GraphQL
|
|
151
107
|
|
152
108
|
def inspect
|
153
109
|
ivars = (self.class.fields.keys - [:__typename]).map { |sym| "#{sym}=#{instance_variable_get("@#{sym}").inspect}" }
|
154
|
-
buf = "
|
110
|
+
buf = "#<GraphQL::Client::QueryResult"
|
155
111
|
buf << " " << @__typename if @__typename
|
156
112
|
buf << " " << ivars.join(" ") if ivars.any?
|
157
113
|
buf << ">"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "graphql"
|
2
|
+
require "graphql/language/nodes/selection_ext"
|
3
|
+
|
4
|
+
module GraphQL
|
5
|
+
module Language
|
6
|
+
module Nodes
|
7
|
+
module Selections
|
8
|
+
def inject_selection(*selections)
|
9
|
+
other = self.dup
|
10
|
+
other.selections = selections + self.selections.map do |selection|
|
11
|
+
case selection
|
12
|
+
when Selections
|
13
|
+
selection.inject_selection(*selections)
|
14
|
+
else
|
15
|
+
selection
|
16
|
+
end
|
17
|
+
end
|
18
|
+
other
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Document < AbstractNode
|
23
|
+
def inject_selection(*args)
|
24
|
+
other = self.dup
|
25
|
+
other.definitions = self.definitions.map do |definition|
|
26
|
+
case definition
|
27
|
+
when Selections
|
28
|
+
definition.inject_selection(*args)
|
29
|
+
else
|
30
|
+
definition
|
31
|
+
end
|
32
|
+
end
|
33
|
+
other
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Field < AbstractNode
|
38
|
+
def inject_selection(*selections)
|
39
|
+
self.selections.any? ? super : self
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "graphql"
|
2
|
+
require "graphql/language/nodes/selection_ext"
|
3
|
+
require "graphql/client/query_result"
|
4
|
+
require "set"
|
5
|
+
|
6
|
+
module GraphQL
|
7
|
+
module Language
|
8
|
+
module Nodes
|
9
|
+
module Selections
|
10
|
+
# Public: Get GraphQL::QueryResult class for result of query.
|
11
|
+
#
|
12
|
+
# Returns subclass of QueryResult or nil.
|
13
|
+
def query_result_class(**kargs)
|
14
|
+
GraphQL::Client::QueryResult.define(fields: selections_query_result_classes(**kargs))
|
15
|
+
end
|
16
|
+
|
17
|
+
def selection_query_result_classes(**kargs)
|
18
|
+
selections_query_result_classes(**kargs)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Internal: Gather QueryResult classes for each selection.
|
22
|
+
#
|
23
|
+
# Returns a Hash[String => (QueryResult|nil)].
|
24
|
+
def selections_query_result_classes(shadow: Set.new, **kargs)
|
25
|
+
self.selections.inject({}) do |h, selection|
|
26
|
+
case selection
|
27
|
+
when Selection
|
28
|
+
if !shadow.include?(selection)
|
29
|
+
selection.selection_query_result_classes(shadow: shadow, **kargs).each do |name, klass|
|
30
|
+
h[name] ? h[name] |= klass : h[name] = klass
|
31
|
+
end
|
32
|
+
end
|
33
|
+
h
|
34
|
+
else
|
35
|
+
raise TypeError, "expected selection to be of type Selection, but was #{selection.class}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Field < AbstractNode
|
42
|
+
# Public: Get GraphQL::QueryResult class for result of query.
|
43
|
+
#
|
44
|
+
# Returns subclass of QueryResult or nil.
|
45
|
+
def query_result_class(**kargs)
|
46
|
+
if self.selections.any?
|
47
|
+
super
|
48
|
+
else
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def selection_query_result_classes(**kargs)
|
54
|
+
name = self.alias || self.name
|
55
|
+
{ name => query_result_class(**kargs) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class FragmentSpread < AbstractNode
|
60
|
+
def selection_query_result_classes(fragments: {}, shadow: Set.new, **kargs)
|
61
|
+
unless fragment = fragments[name.to_sym]
|
62
|
+
raise ArgumentError, "missing fragment '#{name}'"
|
63
|
+
end
|
64
|
+
return {} if shadow.include?(fragment)
|
65
|
+
fragment.selection_query_result_classes(fragments: fragments, shadow: shadow, **kargs)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "graphql"
|
2
|
+
require "graphql/language/nodes/selection_ext"
|
3
|
+
|
4
|
+
module GraphQL
|
5
|
+
module Language
|
6
|
+
module Nodes
|
7
|
+
module Selections
|
8
|
+
def replace_fragment_spread(fragments)
|
9
|
+
other = self.dup
|
10
|
+
other.selections = self.selections.map do |selection|
|
11
|
+
case selection
|
12
|
+
when FragmentSpread
|
13
|
+
if fragment = fragments[selection.name.to_sym]
|
14
|
+
InlineFragment.new(type: fragment.type, directives: fragment.directives, selections: fragment.selections)
|
15
|
+
else
|
16
|
+
selection
|
17
|
+
end
|
18
|
+
when Selections
|
19
|
+
selection.replace_fragment_spread(fragments)
|
20
|
+
else
|
21
|
+
selection
|
22
|
+
end
|
23
|
+
end
|
24
|
+
other
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Document < AbstractNode
|
29
|
+
def replace_fragment_spread(fragments)
|
30
|
+
other = self.dup
|
31
|
+
other.definitions = self.definitions.map do |definition|
|
32
|
+
case definition
|
33
|
+
when Selections
|
34
|
+
definition.replace_fragment_spread(fragments)
|
35
|
+
else
|
36
|
+
definition
|
37
|
+
end
|
38
|
+
end
|
39
|
+
other
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "graphql"
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Language
|
5
|
+
module Nodes
|
6
|
+
# Public: Define shared trait for Nodes that have a "selections" collection.
|
7
|
+
module Selections
|
8
|
+
end
|
9
|
+
|
10
|
+
# Public: Define shared trait for Nodes that may be in a "selections" collection.
|
11
|
+
module Selection
|
12
|
+
end
|
13
|
+
|
14
|
+
class Field < AbstractNode
|
15
|
+
include Selection
|
16
|
+
include Selections
|
17
|
+
end
|
18
|
+
|
19
|
+
class FragmentDefinition < AbstractNode
|
20
|
+
include Selections
|
21
|
+
end
|
22
|
+
|
23
|
+
class FragmentSpread < AbstractNode
|
24
|
+
include Selection
|
25
|
+
end
|
26
|
+
|
27
|
+
class InlineFragment < AbstractNode
|
28
|
+
include Selection
|
29
|
+
include Selections
|
30
|
+
end
|
31
|
+
|
32
|
+
class OperationDefinition < AbstractNode
|
33
|
+
include Selections
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "graphql"
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class ValidationError < GraphQL::ExecutionError
|
5
|
+
end
|
6
|
+
|
7
|
+
module Language
|
8
|
+
module Nodes
|
9
|
+
class Document < AbstractNode
|
10
|
+
def validate!(schema:, rules: StaticValidation::ALL_RULES)
|
11
|
+
validator = StaticValidation::Validator.new(schema: schema, rules: rules)
|
12
|
+
query = Query.new(schema, document: self)
|
13
|
+
|
14
|
+
validator.validate(query).fetch(:errors).each do |error|
|
15
|
+
raise ValidationError, error["message"]
|
16
|
+
end
|
17
|
+
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class FragmentDefinition < AbstractNode
|
23
|
+
def validate!(schema:, **kargs)
|
24
|
+
document = Document.new(definitions: [self])
|
25
|
+
rules = StaticValidation::ALL_RULES - [StaticValidation::FragmentsAreUsed]
|
26
|
+
document.validate!(schema: schema, rules: rules, **kargs)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class OperationDefinition < AbstractNode
|
31
|
+
def validate!(schema:, **kargs)
|
32
|
+
document = Document.new(definitions: [self])
|
33
|
+
document.validate!(schema: schema, **kargs)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class InlineFragment < AbstractNode
|
38
|
+
def validate!(schema:, **kargs)
|
39
|
+
fragment = FragmentDefinition.new(name: "FooFragment", type: self.type, directives: self.directives, selections: self.selections)
|
40
|
+
fragment.validate!(schema: schema, **kargs)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "graphql"
|
2
|
+
require "graphql/language/nodes/deep_freeze_ext"
|
3
|
+
|
4
|
+
module GraphQL
|
5
|
+
module Relay
|
6
|
+
NODE_QUERY = GraphQL.parse(<<-'GRAPHQL').definitions.first.deep_freeze
|
7
|
+
query($id: ID!) {
|
8
|
+
node(id: $id) {
|
9
|
+
...NodeFragment
|
10
|
+
}
|
11
|
+
}
|
12
|
+
GRAPHQL
|
13
|
+
|
14
|
+
def self.NodeQuery(fragment)
|
15
|
+
fragment = GraphQL::Language::Nodes::FragmentDefinition.new(name: "NodeFragment", type: fragment.type, selections: fragment.selections)
|
16
|
+
GraphQL::Language::Nodes::Document.new(definitions: [NODE_QUERY, fragment])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require "graphql"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module GraphQL
|
5
|
+
class Schema
|
6
|
+
module JSONLoader
|
7
|
+
def self.define_schema(json)
|
8
|
+
schema = JSON.load(json).fetch("data").fetch("__schema")
|
9
|
+
types = Schema::JSONLoader.define_types(schema)
|
10
|
+
# TODO: handle schema["mutationType"]
|
11
|
+
# TODO: handle schema["subscriptionType"]
|
12
|
+
query = types.fetch(schema.fetch("queryType").fetch("name"))
|
13
|
+
Schema.new(query: query, types: types.values)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.define_types(schema)
|
17
|
+
schema.fetch("types").inject({}) do |types, type|
|
18
|
+
type_kind, type_name = type.fetch("kind"), type.fetch("name")
|
19
|
+
|
20
|
+
if !type_name.start_with?("__")
|
21
|
+
case type_kind
|
22
|
+
when "INTERFACE"
|
23
|
+
types[type_name] = define_interface(types, type)
|
24
|
+
when "OBJECT"
|
25
|
+
types[type_name] = define_object(types, type)
|
26
|
+
when "SCALAR"
|
27
|
+
types[type_name] = define_scalar(types, type)
|
28
|
+
else
|
29
|
+
# TODO: handle other type kinds
|
30
|
+
fail NotImplementedError, type_kind + " not implemented"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
types
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.resolve_type(types, type)
|
39
|
+
case kind = type.fetch("kind")
|
40
|
+
when "INTERFACE"
|
41
|
+
types.fetch(type.fetch("name"))
|
42
|
+
when "LIST"
|
43
|
+
ListType.new(of_type: resolve_type(types, type.fetch("ofType")))
|
44
|
+
when "NON_NULL"
|
45
|
+
NonNullType.new(of_type: resolve_type(types, type.fetch("ofType")))
|
46
|
+
when "OBJECT"
|
47
|
+
types.fetch(type.fetch("name"))
|
48
|
+
when "SCALAR"
|
49
|
+
types.fetch(type.fetch("name"))
|
50
|
+
else
|
51
|
+
# TODO: handle other type kinds
|
52
|
+
fail NotImplementedError, kind + " not implemented"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.define_interface(types, type)
|
57
|
+
InterfaceType.define do
|
58
|
+
name type.fetch("name")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.define_object(types, type)
|
63
|
+
ObjectType.define do
|
64
|
+
name type.fetch("name")
|
65
|
+
description type["description"]
|
66
|
+
|
67
|
+
Array(type["fields"]).each do |field_data|
|
68
|
+
field field_data["name"] do
|
69
|
+
type JSONLoader.resolve_type(types, field_data["type"])
|
70
|
+
description field_data["description"]
|
71
|
+
field_data["args"].each do |arg|
|
72
|
+
argument arg["name"] do
|
73
|
+
type JSONLoader.resolve_type(types, arg["type"])
|
74
|
+
description arg["description"]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.define_scalar(types, type)
|
83
|
+
case name = type.fetch("name")
|
84
|
+
when "Int"
|
85
|
+
INT_TYPE
|
86
|
+
when "String"
|
87
|
+
STRING_TYPE
|
88
|
+
when "Float"
|
89
|
+
FLOAT_TYPE
|
90
|
+
when "Boolean"
|
91
|
+
BOOLEAN_TYPE
|
92
|
+
when "ID"
|
93
|
+
ID_TYPE
|
94
|
+
else
|
95
|
+
# TODO: handle other scalar names
|
96
|
+
fail NotImplementedError, name + " scalar not implemented"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub
|
@@ -74,12 +74,20 @@ extra_rdoc_files: []
|
|
74
74
|
files:
|
75
75
|
- LICENSE
|
76
76
|
- lib/graphql/client.rb
|
77
|
-
- lib/graphql/client/
|
77
|
+
- lib/graphql/client/document.rb
|
78
|
+
- lib/graphql/client/fragment.rb
|
79
|
+
- lib/graphql/client/node.rb
|
80
|
+
- lib/graphql/client/query.rb
|
78
81
|
- lib/graphql/client/query_result.rb
|
79
|
-
- lib/graphql/language/mutator.rb
|
80
82
|
- lib/graphql/language/nodes/deep_freeze_ext.rb
|
81
|
-
- lib/graphql/language/
|
82
|
-
- lib/graphql/
|
83
|
+
- lib/graphql/language/nodes/inject_selection_ext.rb
|
84
|
+
- lib/graphql/language/nodes/query_result_class_ext.rb
|
85
|
+
- lib/graphql/language/nodes/replace_fragment_spread_ext.rb
|
86
|
+
- lib/graphql/language/nodes/selection_ext.rb
|
87
|
+
- lib/graphql/language/nodes/validate_ext.rb
|
88
|
+
- lib/graphql/relay/node_query.rb
|
89
|
+
- lib/graphql/schema/json_loader.rb
|
90
|
+
- lib/graphql/schema_load_json.rb
|
83
91
|
homepage:
|
84
92
|
licenses:
|
85
93
|
- MIT
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module GraphQL
|
2
|
-
class Client
|
3
|
-
# Internal: Hack to track the constant name an object is assigned to.
|
4
|
-
#
|
5
|
-
# FooConstant = ConstProxy.new { |name|
|
6
|
-
# name # "FooConstant"
|
7
|
-
# }
|
8
|
-
#
|
9
|
-
module ConstProxy
|
10
|
-
def self.new(&initializer)
|
11
|
-
raise ArgumentError, "initializer required" unless block_given?
|
12
|
-
|
13
|
-
Module.new do
|
14
|
-
extend ConstProxy
|
15
|
-
@initializer = initializer
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def name
|
20
|
-
super || raise(RuntimeError, "expected object to be assigned to a constant")
|
21
|
-
end
|
22
|
-
|
23
|
-
def method_missing(*args, &block)
|
24
|
-
@target ||= @initializer.call(self.name)
|
25
|
-
@target.send(*args, &block)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
@@ -1,45 +0,0 @@
|
|
1
|
-
require "graphql"
|
2
|
-
require "set"
|
3
|
-
|
4
|
-
module GraphQL
|
5
|
-
module Language
|
6
|
-
class Mutator
|
7
|
-
def initialize(document)
|
8
|
-
@document = document
|
9
|
-
end
|
10
|
-
|
11
|
-
def rename_definitions(definitions)
|
12
|
-
rename_node = -> (node, parent) {
|
13
|
-
node.name = definitions.fetch(node.name, node.name)
|
14
|
-
}
|
15
|
-
|
16
|
-
visitor = Visitor.new(@document)
|
17
|
-
visitor[Nodes::FragmentDefinition].leave << rename_node
|
18
|
-
visitor[Nodes::OperationDefinition].leave << rename_node
|
19
|
-
visitor[Nodes::FragmentSpread].leave << rename_node
|
20
|
-
visitor.visit
|
21
|
-
|
22
|
-
nil
|
23
|
-
end
|
24
|
-
|
25
|
-
def prepend_selection(selection)
|
26
|
-
on_selections = -> (node, parent) {
|
27
|
-
return if !node.selections.any?
|
28
|
-
# TODO: Simplify if AbstractNode#eql? is implemented
|
29
|
-
existing_selections = Set.new(node.selections.map { |s| s.respond_to?(:name) ? s.name : nil }.compact)
|
30
|
-
selections_to_prepend = [selection].reject { |s| existing_selections.include?(s.name) }
|
31
|
-
node.selections = selections_to_prepend + node.selections
|
32
|
-
}
|
33
|
-
|
34
|
-
visitor = Visitor.new(@document)
|
35
|
-
visitor[Nodes::Field].leave << on_selections
|
36
|
-
visitor[Nodes::FragmentDefinition].leave << on_selections
|
37
|
-
visitor[Nodes::InlineFragment].leave << on_selections
|
38
|
-
visitor[Nodes::OperationDefinition].leave << on_selections
|
39
|
-
visitor.visit
|
40
|
-
|
41
|
-
nil
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
require "graphql"
|
2
|
-
|
3
|
-
module GraphQL
|
4
|
-
module Language
|
5
|
-
module OperationSlice
|
6
|
-
# Public: Return's minimal document to represent operation.
|
7
|
-
#
|
8
|
-
# Find's target operation and any fragment dependencies and returns a
|
9
|
-
# new document with just those definitions.
|
10
|
-
#
|
11
|
-
# document - The Nodes::Document to find definitions.
|
12
|
-
# operation_name - The String name of Nodes::OperationDefinition
|
13
|
-
#
|
14
|
-
# Returns new Nodes::Document.
|
15
|
-
def self.slice(document, operation_name)
|
16
|
-
definitions = []
|
17
|
-
definitions << document.definitions.find { |d| d.name == operation_name }
|
18
|
-
|
19
|
-
visitor = Visitor.new(document)
|
20
|
-
visitor[Nodes::FragmentSpread] << -> (node, parent) {
|
21
|
-
if fragment = document.definitions.find { |d| d.name == node.name }
|
22
|
-
definitions << fragment
|
23
|
-
end
|
24
|
-
}
|
25
|
-
visitor.visit
|
26
|
-
|
27
|
-
Nodes::Document.new(definitions: definitions.uniq)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
data/lib/graphql/relay/parser.rb
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
require "graphql"
|
2
|
-
|
3
|
-
module GraphQL
|
4
|
-
module Relay
|
5
|
-
module Parser
|
6
|
-
ANONYMOUS_SENTINEL = "__anonymous__".freeze
|
7
|
-
|
8
|
-
# Public: Extended GraphQL.parse that supports Relay style anonymous
|
9
|
-
# fragments.
|
10
|
-
#
|
11
|
-
# TODO: See about getting support for this upstreamed to the graphql-ruby
|
12
|
-
# gem.
|
13
|
-
#
|
14
|
-
# str - A GraphQL String
|
15
|
-
#
|
16
|
-
# Returns a GraphQL::Language::Nodes::Document.
|
17
|
-
def self.parse(str)
|
18
|
-
str = str.sub(/fragment on /, "fragment #{ANONYMOUS_SENTINEL} on ")
|
19
|
-
document = GraphQL.parse(str)
|
20
|
-
document.definitions.each do |node|
|
21
|
-
node.name = nil if node.name == ANONYMOUS_SENTINEL
|
22
|
-
end
|
23
|
-
document
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|