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