graphql-client 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/graphql/client.rb +20 -10
- data/lib/graphql/client/definition.rb +116 -23
- data/lib/graphql/client/deprecation.rb +2 -2
- data/lib/graphql/client/document_types.rb +3 -0
- data/lib/graphql/client/error.rb +3 -0
- data/lib/graphql/client/response.rb +2 -2
- data/lib/graphql/client/schema.rb +106 -0
- data/lib/graphql/client/schema/base_type.rb +39 -0
- data/lib/graphql/client/schema/enum_type.rb +56 -0
- data/lib/graphql/client/schema/include_directive.rb +44 -0
- data/lib/graphql/client/schema/interface_type.rb +31 -0
- data/lib/graphql/client/schema/list_type.rb +57 -0
- data/lib/graphql/client/schema/non_null_type.rb +52 -0
- data/lib/graphql/client/schema/object_type.rb +162 -0
- data/lib/graphql/client/schema/possible_types.rb +55 -0
- data/lib/graphql/client/schema/scalar_type.rb +47 -0
- data/lib/graphql/client/schema/skip_directive.rb +44 -0
- data/lib/graphql/client/schema/union_type.rb +31 -0
- metadata +14 -3
- data/lib/graphql/client/query_result.rb +0 -402
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54d0c609ffe31a1375380c8647fe4a1502f19a76
|
4
|
+
data.tar.gz: 57dc7311a2da955fa6a7297aa383eacf7473bee6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 645186f0b676ade5ec368fe51c0a0fc97110fb6d100eb08411b8f079b8e793c5837c94bde44d44a4ec119fe40593ddce826c943a3ee660635e4c8899dbcdfdcd
|
7
|
+
data.tar.gz: ad0e699625c3f34dfec487284e94ca6a4e4dc31b2522f4a7909a162f9d7db6d27d42873b6a057e3b95158027a93f9eb3708dd3f12535a2d51f0da3fa1604a31c
|
data/README.md
CHANGED
@@ -94,7 +94,7 @@ end
|
|
94
94
|
|
95
95
|
### Executing queries
|
96
96
|
|
97
|
-
Pass the reference of a parsed query definition to `GraphQL::Client#query`. Data is returned back in a wrapped `GraphQL::
|
97
|
+
Pass the reference of a parsed query definition to `GraphQL::Client#query`. Data is returned back in a wrapped `GraphQL::Client::Schema::ObjectType` struct that provides Ruby-ish accessors.
|
98
98
|
|
99
99
|
``` ruby
|
100
100
|
result = SWAPI::Client.query(Hero::Query)
|
data/lib/graphql/client.rb
CHANGED
@@ -9,9 +9,9 @@ require "graphql/client/error"
|
|
9
9
|
require "graphql/client/errors"
|
10
10
|
require "graphql/client/fragment_definition"
|
11
11
|
require "graphql/client/operation_definition"
|
12
|
-
require "graphql/client/query_result"
|
13
12
|
require "graphql/client/query_typename"
|
14
13
|
require "graphql/client/response"
|
14
|
+
require "graphql/client/schema"
|
15
15
|
require "graphql/language/nodes/deep_freeze_ext"
|
16
16
|
require "json"
|
17
17
|
|
@@ -31,6 +31,8 @@ module GraphQL
|
|
31
31
|
|
32
32
|
attr_reader :schema, :execute
|
33
33
|
|
34
|
+
attr_reader :types
|
35
|
+
|
34
36
|
attr_accessor :document_tracking_enabled
|
35
37
|
|
36
38
|
# Public: Check if collocated caller enforcement is enabled.
|
@@ -89,6 +91,8 @@ module GraphQL
|
|
89
91
|
@document_tracking_enabled = false
|
90
92
|
@allow_dynamic_queries = false
|
91
93
|
@enforce_collocated_callers = enforce_collocated_callers
|
94
|
+
|
95
|
+
@types = Schema.generate(@schema)
|
92
96
|
end
|
93
97
|
|
94
98
|
def parse(str, filename = nil, lineno = nil)
|
@@ -146,6 +150,9 @@ module GraphQL
|
|
146
150
|
|
147
151
|
doc = GraphQL.parse(str)
|
148
152
|
|
153
|
+
document_types = DocumentTypes.analyze_types(self.schema, doc).freeze
|
154
|
+
QueryTypename.insert_typename_fields(doc, types: document_types)
|
155
|
+
|
149
156
|
doc.definitions.each do |node|
|
150
157
|
node.name ||= "__anonymous__"
|
151
158
|
end
|
@@ -168,21 +175,24 @@ module GraphQL
|
|
168
175
|
raise error
|
169
176
|
end
|
170
177
|
|
171
|
-
document_types = DocumentTypes.analyze_types(self.schema, doc).freeze
|
172
|
-
|
173
|
-
QueryTypename.insert_typename_fields(doc, types: document_types)
|
174
|
-
|
175
178
|
definitions = {}
|
176
179
|
doc.definitions.each do |node|
|
180
|
+
irep_node = case node
|
181
|
+
when GraphQL::Language::Nodes::OperationDefinition
|
182
|
+
errors[:irep].operation_definitions[node.name]
|
183
|
+
when GraphQL::Language::Nodes::FragmentDefinition
|
184
|
+
errors[:irep].fragment_definitions[node.name]
|
185
|
+
else
|
186
|
+
raise TypeError, "unexpected #{node.class}"
|
187
|
+
end
|
188
|
+
|
177
189
|
node.name = nil if node.name == "__anonymous__"
|
178
190
|
sliced_document = Language::DefinitionSlice.slice(document_dependencies, node.name)
|
179
191
|
definition = Definition.for(
|
180
|
-
|
181
|
-
|
192
|
+
client: self,
|
193
|
+
irep_node: irep_node,
|
182
194
|
document: sliced_document,
|
183
|
-
|
184
|
-
source_location: source_location,
|
185
|
-
enforce_collocated_callers: enforce_collocated_callers
|
195
|
+
source_location: source_location
|
186
196
|
)
|
187
197
|
definitions[node.name] = definition
|
188
198
|
end
|
@@ -1,4 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "graphql"
|
4
|
+
require "graphql/client/collocated_enforcement"
|
5
|
+
require "graphql/client/schema/object_type"
|
6
|
+
require "graphql/client/schema/possible_types"
|
7
|
+
require "set"
|
8
|
+
|
2
9
|
module GraphQL
|
3
10
|
class Client
|
4
11
|
# Definitions are constructed by Client.parse and wrap a parsed AST of the
|
@@ -7,31 +14,48 @@ module GraphQL
|
|
7
14
|
#
|
8
15
|
# Definitions MUST be assigned to a constant.
|
9
16
|
class Definition < Module
|
10
|
-
def self.for(
|
11
|
-
case
|
17
|
+
def self.for(irep_node:, **kargs)
|
18
|
+
case irep_node.ast_node
|
12
19
|
when Language::Nodes::OperationDefinition
|
13
|
-
OperationDefinition.new(
|
20
|
+
OperationDefinition.new(irep_node: irep_node, **kargs)
|
14
21
|
when Language::Nodes::FragmentDefinition
|
15
|
-
FragmentDefinition.new(
|
22
|
+
FragmentDefinition.new(irep_node: irep_node, **kargs)
|
16
23
|
else
|
17
|
-
raise TypeError, "expected node to be a definition type, but was #{
|
24
|
+
raise TypeError, "expected node to be a definition type, but was #{irep_node.ast_node.class}"
|
18
25
|
end
|
19
26
|
end
|
20
27
|
|
21
|
-
def initialize(
|
22
|
-
@
|
28
|
+
def initialize(client:, document:, irep_node:, source_location:)
|
29
|
+
@client = client
|
23
30
|
@document = document
|
24
|
-
@
|
25
|
-
@document_types = document_types
|
31
|
+
@definition_irep_node = irep_node
|
26
32
|
@source_location = source_location
|
27
|
-
@
|
33
|
+
@schema_class = client.types.define_class(self, definition_irep_node, definition_irep_node.return_type)
|
28
34
|
end
|
29
35
|
|
36
|
+
# Internal: Get associated owner GraphQL::Client instance.
|
37
|
+
attr_reader :client
|
38
|
+
|
39
|
+
# Internal root schema class for defintion. Returns
|
40
|
+
# GraphQL::Client::Schema::ObjectType or
|
41
|
+
# GraphQL::Client::Schema::PossibleTypes.
|
42
|
+
attr_reader :schema_class
|
43
|
+
|
44
|
+
# Deprecated: Use schema_class
|
45
|
+
alias_method :type, :schema_class
|
46
|
+
|
47
|
+
# Internal: Get underlying IRep Node for the definition.
|
48
|
+
#
|
49
|
+
# Returns GraphQL::InternalRepresentation::Node object.
|
50
|
+
attr_reader :definition_irep_node
|
51
|
+
|
30
52
|
# Internal: Get underlying operation or fragment defintion AST node for
|
31
53
|
# definition.
|
32
54
|
#
|
33
55
|
# Returns OperationDefinition or FragmentDefinition object.
|
34
|
-
|
56
|
+
def definition_node
|
57
|
+
definition_irep_node.ast_node
|
58
|
+
end
|
35
59
|
|
36
60
|
# Public: Global name of definition in client document.
|
37
61
|
#
|
@@ -57,27 +81,96 @@ module GraphQL
|
|
57
81
|
# and any FragmentDefinition dependencies.
|
58
82
|
attr_reader :document
|
59
83
|
|
60
|
-
# Internal: Mapping of document nodes to schema types.
|
61
|
-
attr_reader :document_types
|
62
|
-
|
63
|
-
attr_reader :schema
|
64
|
-
|
65
84
|
# Public: Returns the Ruby source filename and line number containing this
|
66
85
|
# definition was not defined in Ruby.
|
67
86
|
#
|
68
87
|
# Returns Array pair of [String, Fixnum].
|
69
88
|
attr_reader :source_location
|
70
89
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
90
|
+
def new(obj, errors = Errors.new)
|
91
|
+
case schema_class
|
92
|
+
when GraphQL::Client::Schema::PossibleTypes
|
93
|
+
case obj
|
94
|
+
when NilClass
|
95
|
+
nil
|
96
|
+
else
|
97
|
+
schema_class.cast(obj.to_h, obj.errors)
|
98
|
+
end
|
99
|
+
when GraphQL::Client::Schema::ObjectType
|
100
|
+
case obj
|
101
|
+
when NilClass, schema_class
|
102
|
+
obj
|
103
|
+
when Hash
|
104
|
+
schema_class.new(obj, errors)
|
105
|
+
else
|
106
|
+
if obj.class.is_a?(GraphQL::Client::Schema::ObjectType)
|
107
|
+
unless obj.class._spreads.include?(definition_node.name)
|
108
|
+
raise TypeError, "#{definition_node.name} is not included in #{obj.class.source_definition.name}"
|
109
|
+
end
|
110
|
+
schema_class.cast(obj.to_h, obj.errors)
|
111
|
+
else
|
112
|
+
raise TypeError, "unexpected #{obj.class}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
else
|
116
|
+
raise TypeError, "unexpected #{schema_class}"
|
117
|
+
end
|
75
118
|
end
|
76
119
|
|
77
|
-
|
78
|
-
|
79
|
-
@
|
120
|
+
# Internal: Nodes AST indexes.
|
121
|
+
def indexes
|
122
|
+
@indexes ||= begin
|
123
|
+
visitor = GraphQL::Language::Visitor.new(document)
|
124
|
+
definitions = index_node_definitions(visitor)
|
125
|
+
spreads = index_spreads(visitor)
|
126
|
+
visitor.visit
|
127
|
+
{ definitions: definitions, spreads: spreads }
|
128
|
+
end
|
80
129
|
end
|
130
|
+
|
131
|
+
private
|
132
|
+
def index_spreads(visitor)
|
133
|
+
spreads = {}
|
134
|
+
on_node = ->(node, _parent) { spreads[node] = Set.new(flatten_spreads(node).map(&:name)) }
|
135
|
+
|
136
|
+
visitor[GraphQL::Language::Nodes::Field] << on_node
|
137
|
+
visitor[GraphQL::Language::Nodes::FragmentDefinition] << on_node
|
138
|
+
visitor[GraphQL::Language::Nodes::OperationDefinition] << on_node
|
139
|
+
|
140
|
+
spreads
|
141
|
+
end
|
142
|
+
|
143
|
+
def flatten_spreads(node)
|
144
|
+
node.selections.flat_map do |selection|
|
145
|
+
case selection
|
146
|
+
when Language::Nodes::FragmentSpread
|
147
|
+
selection
|
148
|
+
when Language::Nodes::InlineFragment
|
149
|
+
flatten_spreads(selection)
|
150
|
+
else
|
151
|
+
[]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def index_node_definitions(visitor)
|
157
|
+
current_definition = nil
|
158
|
+
enter_definition = ->(node, _parent) { current_definition = node }
|
159
|
+
leave_definition = ->(node, _parent) { current_definition = nil }
|
160
|
+
|
161
|
+
visitor[GraphQL::Language::Nodes::FragmentDefinition].enter << enter_definition
|
162
|
+
visitor[GraphQL::Language::Nodes::FragmentDefinition].leave << leave_definition
|
163
|
+
visitor[GraphQL::Language::Nodes::OperationDefinition].enter << enter_definition
|
164
|
+
visitor[GraphQL::Language::Nodes::OperationDefinition].leave << leave_definition
|
165
|
+
|
166
|
+
definitions = {}
|
167
|
+
on_node = ->(node, _parent) { definitions[node] = current_definition }
|
168
|
+
visitor[GraphQL::Language::Nodes::Field] << on_node
|
169
|
+
visitor[GraphQL::Language::Nodes::FragmentDefinition] << on_node
|
170
|
+
visitor[GraphQL::Language::Nodes::InlineFragment] << on_node
|
171
|
+
visitor[GraphQL::Language::Nodes::OperationDefinition] << on_node
|
172
|
+
definitions
|
173
|
+
end
|
81
174
|
end
|
82
175
|
end
|
83
176
|
end
|
@@ -4,7 +4,7 @@ require "active_support/deprecation"
|
|
4
4
|
module GraphQL
|
5
5
|
class Client
|
6
6
|
if ActiveSupport::Deprecation.is_a?(Class)
|
7
|
-
Deprecation = ActiveSupport::Deprecation.new("0
|
7
|
+
Deprecation = ActiveSupport::Deprecation.new("11.0", "graphql-client")
|
8
8
|
else
|
9
9
|
module Deprecation
|
10
10
|
extend self
|
@@ -22,7 +22,7 @@ module GraphQL
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil)
|
25
|
-
warn "#{deprecated_method_name} is deprecated and will be removed from graphql-client 0.
|
25
|
+
warn "#{deprecated_method_name} is deprecated and will be removed from graphql-client 0.11 (#{message})"
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
data/lib/graphql/client/error.rb
CHANGED
@@ -7,11 +7,11 @@ module GraphQL
|
|
7
7
|
#
|
8
8
|
# https://facebook.github.io/graphql/#sec-Response-Format
|
9
9
|
class Response
|
10
|
-
# Public: Wrapped
|
10
|
+
# Public: Wrapped ObjectType of data returned from the server.
|
11
11
|
#
|
12
12
|
# https://facebook.github.io/graphql/#sec-Data
|
13
13
|
#
|
14
|
-
# Returns instance of
|
14
|
+
# Returns instance of ObjectType subclass.
|
15
15
|
attr_reader :data
|
16
16
|
|
17
17
|
# Public: Get partial failures from response.
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "graphql"
|
4
|
+
require "graphql/client/schema/enum_type"
|
5
|
+
require "graphql/client/schema/include_directive"
|
6
|
+
require "graphql/client/schema/interface_type"
|
7
|
+
require "graphql/client/schema/list_type"
|
8
|
+
require "graphql/client/schema/non_null_type"
|
9
|
+
require "graphql/client/schema/object_type"
|
10
|
+
require "graphql/client/schema/scalar_type"
|
11
|
+
require "graphql/client/schema/skip_directive"
|
12
|
+
require "graphql/client/schema/union_type"
|
13
|
+
|
14
|
+
module GraphQL
|
15
|
+
class Client
|
16
|
+
module Schema
|
17
|
+
module ClassMethods
|
18
|
+
def define_class(definition, irep_node, type)
|
19
|
+
type_klass = case type
|
20
|
+
when GraphQL::NonNullType
|
21
|
+
define_class(definition, irep_node, type.of_type).to_non_null_type
|
22
|
+
when GraphQL::ListType
|
23
|
+
define_class(definition, irep_node, type.of_type).to_list_type
|
24
|
+
else
|
25
|
+
const_get(type.name).define_class(definition, irep_node)
|
26
|
+
end
|
27
|
+
|
28
|
+
irep_node.ast_node.directives.inject(type_klass) do |klass, directive|
|
29
|
+
if directive = self.directives[directive.name.to_sym]
|
30
|
+
directive.new(klass)
|
31
|
+
else
|
32
|
+
klass
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.generate(schema)
|
39
|
+
mod = Module.new
|
40
|
+
mod.extend ClassMethods
|
41
|
+
|
42
|
+
mod.define_singleton_method :schema do
|
43
|
+
schema
|
44
|
+
end
|
45
|
+
|
46
|
+
cache = {}
|
47
|
+
schema.types.each do |name, type|
|
48
|
+
next if name.start_with?("__")
|
49
|
+
if klass = class_for(schema, type, cache)
|
50
|
+
klass.schema_module = mod
|
51
|
+
mod.const_set(name, klass)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
directives = {}
|
56
|
+
mod.define_singleton_method(:directives) { directives }
|
57
|
+
directives[:include] = IncludeDirective
|
58
|
+
directives[:skip] = SkipDirective
|
59
|
+
|
60
|
+
mod
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.class_for(schema, type, cache)
|
64
|
+
return cache[type] if cache[type]
|
65
|
+
|
66
|
+
case type
|
67
|
+
when GraphQL::InputObjectType
|
68
|
+
nil
|
69
|
+
when GraphQL::ScalarType
|
70
|
+
cache[type] = ScalarType.new(type)
|
71
|
+
when GraphQL::EnumType
|
72
|
+
cache[type] = EnumType.new(type)
|
73
|
+
when GraphQL::ListType
|
74
|
+
cache[type] = class_for(schema, type.of_type, cache).to_list_type
|
75
|
+
when GraphQL::NonNullType
|
76
|
+
cache[type] = class_for(schema, type.of_type, cache).to_non_null_type
|
77
|
+
when GraphQL::UnionType
|
78
|
+
klass = cache[type] = UnionType.new(type)
|
79
|
+
|
80
|
+
type.possible_types.each do |possible_type|
|
81
|
+
possible_klass = class_for(schema, possible_type, cache)
|
82
|
+
possible_klass.send :include, klass
|
83
|
+
end
|
84
|
+
|
85
|
+
klass
|
86
|
+
when GraphQL::InterfaceType
|
87
|
+
cache[type] = InterfaceType.new(type)
|
88
|
+
when GraphQL::ObjectType
|
89
|
+
klass = cache[type] = ObjectType.new(type)
|
90
|
+
|
91
|
+
type.interfaces.each do |interface|
|
92
|
+
klass.send :include, class_for(schema, interface, cache)
|
93
|
+
end
|
94
|
+
|
95
|
+
type.all_fields.each do |field|
|
96
|
+
klass.fields[field.name.to_sym] = class_for(schema, field.type, cache)
|
97
|
+
end
|
98
|
+
|
99
|
+
klass
|
100
|
+
else
|
101
|
+
raise TypeError, "unexpected #{type.class}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Client
|
5
|
+
module Schema
|
6
|
+
module BaseType
|
7
|
+
# Public: Get associated GraphQL::BaseType with for this class.
|
8
|
+
attr_reader :type
|
9
|
+
|
10
|
+
# Internal: Get owner schema Module container.
|
11
|
+
attr_accessor :schema_module
|
12
|
+
|
13
|
+
# Internal: Cast JSON value to wrapped value.
|
14
|
+
#
|
15
|
+
# value - JSON value
|
16
|
+
# errors - Errors instance
|
17
|
+
#
|
18
|
+
# Returns BaseType instance.
|
19
|
+
def cast(value, errors)
|
20
|
+
raise NotImplementedError, "subclasses must implement #cast(value, errors)"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Internal: Get non-nullable wrapper of this type class.
|
24
|
+
#
|
25
|
+
# Returns NonNullType instance.
|
26
|
+
def to_non_null_type
|
27
|
+
@null_type ||= NonNullType.new(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Internal: Get list wrapper of this type class.
|
31
|
+
#
|
32
|
+
# Returns ListType instance.
|
33
|
+
def to_list_type
|
34
|
+
@list_type ||= ListType.new(self)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|