graphql-client 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql/client.rb +93 -7
- data/lib/graphql/client/const_proxy.rb +29 -0
- data/lib/graphql/client/query_result.rb +52 -8
- data/lib/graphql/language/mutator.rb +45 -0
- data/lib/graphql/language/operation_slice.rb +31 -0
- data/lib/graphql/relay/parser.rb +27 -0
- metadata +7 -16
- data/lib/graphql/client/document.rb +0 -47
- 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: 852686ecf24561871545329aeb4868baaccb99de
|
4
|
+
data.tar.gz: 2bd0845ad82f0ba555497e0a46c315a9d8a6fa0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 58701e7031146155bcf9584bf61cdbee25f6ac9bf61121eccd5e915970624d2755aef071f18c781d38fcbf3ca1878fccff4f22d33b7dbe3c537598a597658f23
|
7
|
+
data.tar.gz: 8dfe674b8d2c39233fcdfd533e8ea516c010e0cecb7be38fb01c2128f76dc466e61e7dd334e0dff36e810465d9e9f6064d7c5f9969de9e65e081fa0dba0bc9ea
|
data/lib/graphql/client.rb
CHANGED
@@ -1,14 +1,100 @@
|
|
1
|
+
require "active_support/inflector"
|
1
2
|
require "graphql"
|
2
|
-
require "graphql/client/
|
3
|
-
require "graphql/client/fragment"
|
4
|
-
require "graphql/client/node"
|
3
|
+
require "graphql/client/const_proxy"
|
5
4
|
require "graphql/client/query_result"
|
6
|
-
require "graphql/
|
5
|
+
require "graphql/language/mutator"
|
6
|
+
require "graphql/language/nodes/deep_freeze_ext"
|
7
|
+
require "graphql/language/operation_slice"
|
8
|
+
require "graphql/relay/parser"
|
7
9
|
|
8
10
|
module GraphQL
|
9
|
-
|
10
|
-
class
|
11
|
-
|
11
|
+
class Client
|
12
|
+
class Error < StandardError; end
|
13
|
+
class ValidationError < Error; end
|
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
|
12
98
|
end
|
13
99
|
end
|
14
100
|
end
|
@@ -0,0 +1,29 @@
|
|
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,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 << ">"
|
@@ -0,0 +1,45 @@
|
|
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
|
@@ -0,0 +1,31 @@
|
|
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
|
@@ -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.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -74,20 +74,12 @@ extra_rdoc_files: []
|
|
74
74
|
files:
|
75
75
|
- LICENSE
|
76
76
|
- lib/graphql/client.rb
|
77
|
-
- lib/graphql/client/
|
78
|
-
- lib/graphql/client/fragment.rb
|
79
|
-
- lib/graphql/client/node.rb
|
80
|
-
- lib/graphql/client/query.rb
|
77
|
+
- lib/graphql/client/const_proxy.rb
|
81
78
|
- lib/graphql/client/query_result.rb
|
79
|
+
- lib/graphql/language/mutator.rb
|
82
80
|
- 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
|
81
|
+
- lib/graphql/language/operation_slice.rb
|
82
|
+
- lib/graphql/relay/parser.rb
|
91
83
|
homepage:
|
92
84
|
licenses:
|
93
85
|
- MIT
|
@@ -108,9 +100,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
100
|
version: '0'
|
109
101
|
requirements: []
|
110
102
|
rubyforge_project:
|
111
|
-
rubygems_version: 2.5.1
|
103
|
+
rubygems_version: 2.4.5.1
|
112
104
|
signing_key:
|
113
105
|
specification_version: 4
|
114
106
|
summary: "???"
|
115
107
|
test_files: []
|
116
|
-
has_rdoc:
|
@@ -1,47 +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
|
-
|
26
|
-
document.definitions.inject({}) do |doc, definition|
|
27
|
-
name = definition.name.to_sym
|
28
|
-
|
29
|
-
case definition
|
30
|
-
when GraphQL::Language::Nodes::OperationDefinition
|
31
|
-
query = GraphQL::Client::Query.new(definition.deep_freeze, fragments.values).freeze
|
32
|
-
query.node.validate!(schema: schema) if schema
|
33
|
-
doc[name] = query
|
34
|
-
|
35
|
-
when GraphQL::Language::Nodes::FragmentDefinition
|
36
|
-
definition = GraphQL::Language::Nodes::InlineFragment.new(type: definition.type, directives: definition.directives, selections: definition.selections)
|
37
|
-
fragment = GraphQL::Client::Fragment.new(definition.deep_freeze, fragments.values).freeze
|
38
|
-
fragment.node.validate!(schema: schema) if schema
|
39
|
-
doc[name] = fragment
|
40
|
-
end
|
41
|
-
|
42
|
-
doc
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
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
|