graphql-client 0.0.7 → 0.0.8
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/query_result.rb +52 -8
- data/lib/graphql/client.rb +131 -7
- data/lib/graphql/language/mutator.rb +54 -0
- data/lib/graphql/language/operation_slice.rb +40 -0
- data/lib/graphql/relay/parser.rb +27 -0
- metadata +5 -14
- data/lib/graphql/client/document.rb +0 -48
- data/lib/graphql/client/fragment.rb +0 -32
- data/lib/graphql/client/node.rb +0 -36
- data/lib/graphql/client/query.rb +0 -30
- data/lib/graphql/language/nodes/inject_selection_ext.rb +0 -44
- data/lib/graphql/language/nodes/query_result_class_ext.rb +0 -70
- data/lib/graphql/language/nodes/replace_fragment_spread_ext.rb +0 -44
- data/lib/graphql/language/nodes/selection_ext.rb +0 -37
- data/lib/graphql/language/nodes/validate_ext.rb +0 -45
- data/lib/graphql/relay/node_query.rb +0 -19
- data/lib/graphql/schema/json_loader.rb +0 -101
- data/lib/graphql/schema_load_json.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cee45489051065d2c8d47c6f8c68c6b3c98d75ca
|
4
|
+
data.tar.gz: e462bb5c2fb1997f8679bb0fea315f8abd751539
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f335b9446f04fe5e684218b1f0f377b16412ac4918065ac610b2abff61d00792a461131b1ab615cd6512ba611a8dde6929af9c1b229f4a36c713e7e0983a22d
|
7
|
+
data.tar.gz: 428487913828790e402b3a3c51daceafe8c338ebabfd234d0570424c8af426bc54bfa6f8fbf306d365fd674174a5708936e8a350cce5fcdf5c6426f5d3f1bd37
|
@@ -1,15 +1,38 @@
|
|
1
|
-
require "graphql"
|
2
1
|
require "active_support/inflector"
|
2
|
+
require "graphql"
|
3
|
+
require "set"
|
3
4
|
|
4
5
|
module GraphQL
|
5
|
-
|
6
|
+
class Client
|
6
7
|
class QueryResult
|
7
|
-
|
8
|
-
|
8
|
+
# Internal: Get QueryResult class for result of query.
|
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)
|
9
29
|
end
|
10
30
|
|
11
|
-
|
31
|
+
# Internal
|
32
|
+
def self.define(name:, source_node:, fields: {})
|
12
33
|
Class.new(self) do
|
34
|
+
@name = name
|
35
|
+
@source_node = source_node
|
13
36
|
@fields = {}
|
14
37
|
|
15
38
|
fields.each do |field, type|
|
@@ -62,8 +85,20 @@ module GraphQL
|
|
62
85
|
end
|
63
86
|
end
|
64
87
|
|
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
|
+
|
65
100
|
def self.inspect
|
66
|
-
"
|
101
|
+
"#<#{self.name} fields=#{@fields.keys.inspect}>"
|
67
102
|
end
|
68
103
|
|
69
104
|
def self.cast(obj)
|
@@ -71,6 +106,14 @@ module GraphQL
|
|
71
106
|
when Hash
|
72
107
|
new(obj)
|
73
108
|
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
|
74
117
|
cast(obj.to_h)
|
75
118
|
when Array
|
76
119
|
obj.map { |e| cast(e) }
|
@@ -99,7 +142,8 @@ module GraphQL
|
|
99
142
|
new_fields[name] = value
|
100
143
|
end
|
101
144
|
end
|
102
|
-
|
145
|
+
# TODO: Picking first source node seems error prone
|
146
|
+
define(name: self.name, source_node: self.source_node, fields: new_fields)
|
103
147
|
end
|
104
148
|
|
105
149
|
attr_reader :data
|
@@ -107,7 +151,7 @@ module GraphQL
|
|
107
151
|
|
108
152
|
def inspect
|
109
153
|
ivars = (self.class.fields.keys - [:__typename]).map { |sym| "#{sym}=#{instance_variable_get("@#{sym}").inspect}" }
|
110
|
-
buf = "
|
154
|
+
buf = "#<#{self.class.name}"
|
111
155
|
buf << " " << @__typename if @__typename
|
112
156
|
buf << " " << ivars.join(" ") if ivars.any?
|
113
157
|
buf << ">"
|
data/lib/graphql/client.rb
CHANGED
@@ -1,14 +1,138 @@
|
|
1
|
+
require "active_support/inflector"
|
1
2
|
require "graphql"
|
2
|
-
require "graphql/client/document"
|
3
|
-
require "graphql/client/fragment"
|
4
|
-
require "graphql/client/node"
|
5
3
|
require "graphql/client/query_result"
|
6
|
-
require "graphql/
|
4
|
+
require "graphql/language/mutator"
|
5
|
+
require "graphql/language/nodes/deep_freeze_ext"
|
6
|
+
require "graphql/language/operation_slice"
|
7
|
+
require "graphql/relay/parser"
|
7
8
|
|
8
9
|
module GraphQL
|
9
|
-
|
10
|
-
class
|
11
|
-
|
10
|
+
class Client
|
11
|
+
class Error < StandardError; end
|
12
|
+
class ValidationError < Error; end
|
13
|
+
|
14
|
+
attr_reader :schema
|
15
|
+
|
16
|
+
def initialize(schema:)
|
17
|
+
@schema = schema
|
18
|
+
@definitions = []
|
19
|
+
@document = GraphQL::Language::Nodes::Document.new(definitions: @definitions)
|
20
|
+
@document_slices = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
class Definition < Module
|
24
|
+
def initialize(node:)
|
25
|
+
@definition_node = node
|
26
|
+
end
|
27
|
+
|
28
|
+
# Internal: Get underlying operation or fragment defintion AST node for
|
29
|
+
# definition.
|
30
|
+
#
|
31
|
+
# Returns OperationDefinition or FragmentDefinition object.
|
32
|
+
attr_reader :definition_node
|
33
|
+
|
34
|
+
# Public: Ruby constant name of definition.
|
35
|
+
#
|
36
|
+
# Returns String or errors if definition was not assigned to a constant.
|
37
|
+
def name
|
38
|
+
@name ||= super || raise(RuntimeError, "definition must be assigned to a constant")
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public: Global name of definition in client document.
|
42
|
+
#
|
43
|
+
# Returns a GraphQL safe name of the Ruby constant String.
|
44
|
+
#
|
45
|
+
# "Users::UserQuery" #=> "Users__UserQuery"
|
46
|
+
#
|
47
|
+
# Returns String.
|
48
|
+
def definition_name
|
49
|
+
@definition_name ||= name.gsub("::", "__").freeze
|
50
|
+
end
|
51
|
+
|
52
|
+
def new(*args)
|
53
|
+
query_result_class.new(*args)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def query_result_class
|
58
|
+
@query_result_class ||= GraphQL::Client::QueryResult.wrap(definition_node, name: name)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class OperationDefinition < Definition
|
63
|
+
# Public: Alias for definition name.
|
64
|
+
alias_method :operation_name, :definition_name
|
65
|
+
end
|
66
|
+
|
67
|
+
class FragmentDefinition < Definition
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse(str)
|
71
|
+
str = str.gsub(/\.\.\.([a-zA-Z0-9_]+(::[a-zA-Z0-9_]+)+)/) { |m|
|
72
|
+
const_name = $1
|
73
|
+
case fragment = ActiveSupport::Inflector.safe_constantize(const_name)
|
74
|
+
when FragmentDefinition
|
75
|
+
"...#{fragment.definition_name}"
|
76
|
+
when nil
|
77
|
+
raise NameError, "uninitialized constant #{const_name}\n#{str}"
|
78
|
+
else
|
79
|
+
raise TypeError, "expected #{const_name} to be a #{FragmentDefinition}, but was a #{fragment.class}"
|
80
|
+
end
|
81
|
+
}
|
82
|
+
|
83
|
+
doc = GraphQL::Relay::Parser.parse(str)
|
84
|
+
|
85
|
+
mutator = GraphQL::Language::Mutator.new(doc)
|
86
|
+
|
87
|
+
# TODO: Make this __typename injection optional
|
88
|
+
mutator.prepend_selection(GraphQL::Language::Nodes::Field.new(name: "__typename").deep_freeze)
|
89
|
+
|
90
|
+
definitions, renames = {}, {}
|
91
|
+
doc.definitions.each do |node|
|
92
|
+
local_name = node.name
|
93
|
+
definition = case node
|
94
|
+
when Language::Nodes::OperationDefinition
|
95
|
+
OperationDefinition.new(node: node)
|
96
|
+
when Language::Nodes::FragmentDefinition
|
97
|
+
FragmentDefinition.new(node: node)
|
98
|
+
end
|
99
|
+
definitions[local_name] = definition
|
100
|
+
renames[local_name] = -> { definition.definition_name }
|
101
|
+
end
|
102
|
+
mutator.rename_definitions(renames)
|
103
|
+
|
104
|
+
doc.deep_freeze
|
105
|
+
|
106
|
+
self.document.definitions.concat(doc.definitions)
|
107
|
+
|
108
|
+
if definitions[nil]
|
109
|
+
definitions[nil]
|
110
|
+
else
|
111
|
+
Module.new do
|
112
|
+
definitions.each do |name, definition|
|
113
|
+
const_set(name, definition)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def document
|
120
|
+
@document
|
121
|
+
end
|
122
|
+
|
123
|
+
def document_slice(operation_name)
|
124
|
+
@document_slices[operation_name] ||= Language::OperationSlice.slice(document, operation_name).deep_freeze
|
125
|
+
end
|
126
|
+
|
127
|
+
def validate!
|
128
|
+
validator = StaticValidation::Validator.new(schema: @schema)
|
129
|
+
query = Query.new(@schema, document: document)
|
130
|
+
|
131
|
+
validator.validate(query).fetch(:errors).each do |error|
|
132
|
+
raise ValidationError, error["message"]
|
133
|
+
end
|
134
|
+
|
135
|
+
nil
|
12
136
|
end
|
13
137
|
end
|
14
138
|
end
|
@@ -0,0 +1,54 @@
|
|
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
|
+
module LazyName
|
12
|
+
def name
|
13
|
+
@name.call
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def rename_definitions(definitions)
|
18
|
+
rename_node = -> (node, parent) {
|
19
|
+
if name = definitions[node.name]
|
20
|
+
node.extend(LazyName) if name.is_a?(Proc)
|
21
|
+
node.name = name
|
22
|
+
end
|
23
|
+
}
|
24
|
+
|
25
|
+
visitor = Visitor.new(@document)
|
26
|
+
visitor[Nodes::FragmentDefinition].leave << rename_node
|
27
|
+
visitor[Nodes::OperationDefinition].leave << rename_node
|
28
|
+
visitor[Nodes::FragmentSpread].leave << rename_node
|
29
|
+
visitor.visit
|
30
|
+
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def prepend_selection(selection)
|
35
|
+
on_selections = -> (node, parent) {
|
36
|
+
return if !node.selections.any?
|
37
|
+
# TODO: Simplify if AbstractNode#eql? is implemented
|
38
|
+
existing_selections = Set.new(node.selections.map { |s| s.respond_to?(:name) ? s.name : nil }.compact)
|
39
|
+
selections_to_prepend = [selection].reject { |s| existing_selections.include?(s.name) }
|
40
|
+
node.selections = selections_to_prepend + node.selections
|
41
|
+
}
|
42
|
+
|
43
|
+
visitor = Visitor.new(@document)
|
44
|
+
visitor[Nodes::Field].leave << on_selections
|
45
|
+
visitor[Nodes::FragmentDefinition].leave << on_selections
|
46
|
+
visitor[Nodes::InlineFragment].leave << on_selections
|
47
|
+
visitor[Nodes::OperationDefinition].leave << on_selections
|
48
|
+
visitor.visit
|
49
|
+
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,40 @@
|
|
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
|
+
seen = Set.new([operation_name])
|
17
|
+
stack = [operation_name]
|
18
|
+
|
19
|
+
while name = stack.pop
|
20
|
+
names = find_definition_fragment_spreads(document, name)
|
21
|
+
seen.merge(names)
|
22
|
+
stack.concat(names.to_a)
|
23
|
+
end
|
24
|
+
|
25
|
+
Nodes::Document.new(definitions: document.definitions.select { |node| seen.include?(node.name) })
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.find_definition_fragment_spreads(document, definition_name)
|
29
|
+
definition = document.definitions.find { |node| node.name == definition_name }
|
30
|
+
spreads = Set.new
|
31
|
+
visitor = Visitor.new(definition)
|
32
|
+
visitor[Nodes::FragmentSpread].enter << -> (node, parent) {
|
33
|
+
spreads << node.name
|
34
|
+
}
|
35
|
+
visitor.visit
|
36
|
+
spreads
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,27 @@
|
|
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
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-09-
|
11
|
+
date: 2016-09-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -74,20 +74,11 @@ extra_rdoc_files: []
|
|
74
74
|
files:
|
75
75
|
- LICENSE
|
76
76
|
- lib/graphql/client.rb
|
77
|
-
- lib/graphql/client/document.rb
|
78
|
-
- lib/graphql/client/fragment.rb
|
79
|
-
- lib/graphql/client/node.rb
|
80
|
-
- lib/graphql/client/query.rb
|
81
77
|
- lib/graphql/client/query_result.rb
|
78
|
+
- lib/graphql/language/mutator.rb
|
82
79
|
- lib/graphql/language/nodes/deep_freeze_ext.rb
|
83
|
-
- lib/graphql/language/
|
84
|
-
- lib/graphql/
|
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
|
80
|
+
- lib/graphql/language/operation_slice.rb
|
81
|
+
- lib/graphql/relay/parser.rb
|
91
82
|
homepage:
|
92
83
|
licenses:
|
93
84
|
- MIT
|
@@ -1,48 +0,0 @@
|
|
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
|
@@ -1,32 +0,0 @@
|
|
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
|
data/lib/graphql/client/node.rb
DELETED
@@ -1,36 +0,0 @@
|
|
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
|
data/lib/graphql/client/query.rb
DELETED
@@ -1,30 +0,0 @@
|
|
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,44 +0,0 @@
|
|
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
|
@@ -1,70 +0,0 @@
|
|
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
|
@@ -1,44 +0,0 @@
|
|
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
|
@@ -1,37 +0,0 @@
|
|
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
|
@@ -1,45 +0,0 @@
|
|
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
|
@@ -1,19 +0,0 @@
|
|
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
|
@@ -1,101 +0,0 @@
|
|
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
|