graphql-client 0.0.6 → 0.0.7
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/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
|