graphql-client 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/graphql/client.rb +42 -11
- data/lib/graphql/client/collocated_enforcement.rb +51 -0
- data/lib/graphql/client/query_result.rb +48 -7
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 88afea7eaa70819f11b2a000886ef3924e2b0e12
|
4
|
+
data.tar.gz: 62a8706efe7fe20a73da2affaa3e362dce686970
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b9774d7f5229a0202924ff253b251076ed7b5f71dd3e32f0707467663868faace9a331b79878e70159058ebbdde59b477892b13c662c1e3f9492fa31e6901551
|
7
|
+
data.tar.gz: 0d4e18520e7b23a28012edf77a98913358ef7793e70f46ddbdf881ad63c51eafc83325ef7a388d37b752914d55b0f6e9d23dc50efbb23afc98bf1f5aebd25be0
|
data/lib/graphql/client.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
require "active_support/inflector"
|
3
3
|
require "active_support/notifications"
|
4
4
|
require "graphql"
|
5
|
+
require "graphql/client/collocated_enforcement"
|
5
6
|
require "graphql/client/error"
|
6
7
|
require "graphql/client/errors"
|
7
8
|
require "graphql/client/query_result"
|
@@ -22,10 +23,15 @@ module GraphQL
|
|
22
23
|
class NotImplementedError < Error; end
|
23
24
|
class ValidationError < Error; end
|
24
25
|
|
26
|
+
extend CollocatedEnforcement
|
27
|
+
|
25
28
|
attr_reader :schema, :execute
|
26
29
|
|
27
30
|
attr_accessor :document_tracking_enabled
|
28
31
|
|
32
|
+
# Public: Check if collocated caller enforcement is enabled.
|
33
|
+
attr_reader :enforce_collocated_callers
|
34
|
+
|
29
35
|
# Deprecated: Allow dynamically generated queries to be passed to
|
30
36
|
# Client#query.
|
31
37
|
#
|
@@ -72,12 +78,13 @@ module GraphQL
|
|
72
78
|
result
|
73
79
|
end
|
74
80
|
|
75
|
-
def initialize(schema: nil, execute: nil)
|
81
|
+
def initialize(schema: nil, execute: nil, enforce_collocated_callers: false)
|
76
82
|
@schema = self.class.load_schema(schema)
|
77
83
|
@execute = execute
|
78
84
|
@document = GraphQL::Language::Nodes::Document.new(definitions: [])
|
79
85
|
@document_tracking_enabled = false
|
80
86
|
@allow_dynamic_queries = false
|
87
|
+
@enforce_collocated_callers = enforce_collocated_callers
|
81
88
|
end
|
82
89
|
|
83
90
|
# Definitions are constructed by Client.parse and wrap a parsed AST of the
|
@@ -97,11 +104,13 @@ module GraphQL
|
|
97
104
|
end
|
98
105
|
end
|
99
106
|
|
100
|
-
def initialize(node:, document:, schema:, document_types:)
|
107
|
+
def initialize(node:, document:, schema:, document_types:, source_location:, enforce_collocated_callers:)
|
101
108
|
@definition_node = node
|
102
109
|
@document = document
|
103
110
|
@schema = schema
|
104
111
|
@document_types = document_types
|
112
|
+
@source_location = source_location
|
113
|
+
@enforce_collocated_callers = enforce_collocated_callers
|
105
114
|
end
|
106
115
|
|
107
116
|
# Internal: Get underlying operation or fragment defintion AST node for
|
@@ -134,15 +143,26 @@ module GraphQL
|
|
134
143
|
# and any FragmentDefinition dependencies.
|
135
144
|
attr_reader :document
|
136
145
|
|
146
|
+
# Internal: Mapping of document nodes to schema types.
|
147
|
+
attr_reader :document_types
|
148
|
+
|
137
149
|
attr_reader :schema
|
138
150
|
|
151
|
+
# Public: Returns the Ruby source filename and line number containing this
|
152
|
+
# definition was not defined in Ruby.
|
153
|
+
#
|
154
|
+
# Returns Array pair of [String, Fixnum].
|
155
|
+
attr_reader :source_location
|
156
|
+
|
157
|
+
attr_reader :enforce_collocated_callers
|
158
|
+
|
139
159
|
def new(*args)
|
140
160
|
type.new(*args)
|
141
161
|
end
|
142
162
|
|
143
163
|
def type
|
144
164
|
# TODO: Fix type indirection
|
145
|
-
@type ||= GraphQL::Client::QueryResult.wrap(definition_node, name: "#{name}.type"
|
165
|
+
@type ||= GraphQL::Client::QueryResult.wrap(self, definition_node, name: "#{name}.type")
|
146
166
|
end
|
147
167
|
end
|
148
168
|
|
@@ -158,11 +178,22 @@ module GraphQL
|
|
158
178
|
end
|
159
179
|
|
160
180
|
def parse(str, filename = nil, lineno = nil)
|
161
|
-
if filename.nil?
|
162
|
-
|
163
|
-
|
181
|
+
if filename.nil? && lineno.nil?
|
182
|
+
location = caller_locations(1, 1).first
|
183
|
+
filename = location.path
|
184
|
+
lineno = location.lineno
|
164
185
|
end
|
165
186
|
|
187
|
+
unless filename.is_a?(String)
|
188
|
+
raise TypeError, "expected filename to be a String, but was #{filename.class}"
|
189
|
+
end
|
190
|
+
|
191
|
+
unless lineno.is_a?(Integer)
|
192
|
+
raise TypeError, "expected lineno to be a Integer, but was #{lineno.class}"
|
193
|
+
end
|
194
|
+
|
195
|
+
source_location = [filename, lineno].freeze
|
196
|
+
|
166
197
|
definition_dependencies = Set.new
|
167
198
|
|
168
199
|
str = str.gsub(/\.\.\.([a-zA-Z0-9_]+(::[a-zA-Z0-9_]+)+)/) do
|
@@ -186,9 +217,7 @@ module GraphQL
|
|
186
217
|
end
|
187
218
|
|
188
219
|
error = ValidationError.new(message)
|
189
|
-
|
190
|
-
error.set_backtrace(["#{filename}:#{lineno + match.pre_match.count("\n") + 1}"] + caller)
|
191
|
-
end
|
220
|
+
error.set_backtrace(["#{filename}:#{lineno + match.pre_match.count("\n") + 1}"] + caller)
|
192
221
|
raise error
|
193
222
|
end
|
194
223
|
end
|
@@ -211,7 +240,7 @@ module GraphQL
|
|
211
240
|
error_hash = error.to_h
|
212
241
|
validation_line = error_hash["locations"][0]["line"]
|
213
242
|
error = ValidationError.new(error_hash["message"])
|
214
|
-
error.set_backtrace(["#{filename}:#{lineno + validation_line}"] + caller)
|
243
|
+
error.set_backtrace(["#{filename}:#{lineno + validation_line}"] + caller)
|
215
244
|
raise error
|
216
245
|
end
|
217
246
|
|
@@ -230,7 +259,9 @@ module GraphQL
|
|
230
259
|
schema: @schema,
|
231
260
|
node: node,
|
232
261
|
document: sliced_document,
|
233
|
-
document_types: document_types
|
262
|
+
document_types: document_types,
|
263
|
+
source_location: source_location,
|
264
|
+
enforce_collocated_callers: enforce_collocated_callers
|
234
265
|
)
|
235
266
|
definitions[node.name] = definition
|
236
267
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "graphql/client/error"
|
3
|
+
|
4
|
+
module GraphQL
|
5
|
+
class Client
|
6
|
+
# Raised when method is called from outside the expected file scope.
|
7
|
+
class NonCollocatedCallerError < Error; end
|
8
|
+
|
9
|
+
# Enforcements collocated object access best practices.
|
10
|
+
module CollocatedEnforcement
|
11
|
+
# Public: Ignore collocated caller enforcement for the scope of the block.
|
12
|
+
def allow_noncollocated_callers
|
13
|
+
Thread.current[:query_result_caller_location_ignore] = true
|
14
|
+
yield
|
15
|
+
ensure
|
16
|
+
Thread.current[:query_result_caller_location_ignore] = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# Internal: Decorate method with collocated caller enforcement.
|
20
|
+
#
|
21
|
+
# mod - Target Module/Class
|
22
|
+
# methods - Array of Symbol method names
|
23
|
+
# path - String filename to assert calling from
|
24
|
+
#
|
25
|
+
# Returns nothing.
|
26
|
+
def enforce_collocated_callers(mod, methods, path)
|
27
|
+
mod.prepend(Module.new do
|
28
|
+
methods.each do |method|
|
29
|
+
define_method(method) do |*args, &block|
|
30
|
+
return super(*args, &block) if Thread.current[:query_result_caller_location_ignore]
|
31
|
+
|
32
|
+
locations = caller_locations(1)
|
33
|
+
if locations.first.path != path
|
34
|
+
error = NonCollocatedCallerError.new("#{method} was called outside of '#{path}' https://git.io/v1syX")
|
35
|
+
error.set_backtrace(locations.map(&:to_s))
|
36
|
+
raise error
|
37
|
+
end
|
38
|
+
|
39
|
+
begin
|
40
|
+
Thread.current[:query_result_caller_location_ignore] = true
|
41
|
+
super(*args, &block)
|
42
|
+
ensure
|
43
|
+
Thread.current[:query_result_caller_location_ignore] = nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -17,7 +17,7 @@ module GraphQL
|
|
17
17
|
# Internal: Get QueryResult class for result of query.
|
18
18
|
#
|
19
19
|
# Returns subclass of QueryResult or nil.
|
20
|
-
def self.wrap(node, name: nil
|
20
|
+
def self.wrap(source_definition, node, name: nil)
|
21
21
|
fields = {}
|
22
22
|
|
23
23
|
node.selections.each do |selection|
|
@@ -27,40 +27,74 @@ module GraphQL
|
|
27
27
|
when Language::Nodes::Field
|
28
28
|
field_name = selection.alias || selection.name
|
29
29
|
field_klass = nil
|
30
|
-
|
30
|
+
if selection.selections.any?
|
31
|
+
field_klass = wrap(source_definition, selection, name: "#{name}[:#{field_name}]")
|
32
|
+
end
|
31
33
|
fields[field_name] ? fields[field_name] |= field_klass : fields[field_name] = field_klass
|
32
34
|
when Language::Nodes::InlineFragment
|
33
|
-
wrap(selection, name: name
|
35
|
+
wrap(source_definition, selection, name: name).fields.each do |fragment_name, klass|
|
34
36
|
fields[fragment_name.to_s] ? fields[fragment_name.to_s] |= klass : fields[fragment_name.to_s] = klass
|
35
37
|
end
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
|
-
define(name: name, source_node: node, fields: fields
|
41
|
+
define(name: name, source_definition: source_definition, source_node: node, fields: fields)
|
42
|
+
end
|
43
|
+
|
44
|
+
# :nodoc:
|
45
|
+
class Scalar
|
46
|
+
def initialize(type)
|
47
|
+
@type = type
|
48
|
+
end
|
49
|
+
|
50
|
+
def cast(value, _errors = nil)
|
51
|
+
@type.coerce_input(value)
|
52
|
+
end
|
53
|
+
|
54
|
+
def |(_other)
|
55
|
+
# XXX: How would scalars merge?
|
56
|
+
self
|
57
|
+
end
|
40
58
|
end
|
41
59
|
|
42
60
|
# Internal
|
43
|
-
def self.define(name:, source_node:, fields: {}
|
61
|
+
def self.define(name:, source_definition:, source_node:, fields: {})
|
62
|
+
type = source_definition.document_types[source_node]
|
63
|
+
type = type.unwrap if type
|
64
|
+
|
44
65
|
Class.new(self) do
|
45
66
|
@name = name
|
46
67
|
@type = type
|
47
68
|
@source_node = source_node
|
69
|
+
@source_definition = source_definition
|
48
70
|
@fields = {}
|
49
71
|
|
72
|
+
field_readers = Set.new
|
73
|
+
|
50
74
|
fields.each do |field, klass|
|
75
|
+
if @type.is_a?(GraphQL::ObjectType)
|
76
|
+
field_node = @type.fields[field.to_s]
|
77
|
+
if field_node && field_node.type.unwrap.is_a?(GraphQL::ScalarType)
|
78
|
+
klass = Scalar.new(field_node.type.unwrap)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
51
82
|
@fields[field.to_sym] = klass
|
52
83
|
|
53
84
|
send :attr_reader, field
|
85
|
+
field_readers << field.to_sym
|
54
86
|
|
55
87
|
# Convert GraphQL camelcase to snake case: commitComments -> commit_comments
|
56
88
|
field_alias = ActiveSupport::Inflector.underscore(field)
|
57
89
|
send :alias_method, field_alias, field if field != field_alias
|
90
|
+
field_readers << field_alias.to_sym
|
58
91
|
|
59
92
|
class_eval <<-RUBY, __FILE__, __LINE__
|
60
93
|
def #{field_alias}?
|
61
94
|
#{field_alias} ? true : false
|
62
95
|
end
|
63
96
|
RUBY
|
97
|
+
field_readers << "#{field_alias}?".to_sym
|
64
98
|
|
65
99
|
next unless field == "edges"
|
66
100
|
class_eval <<-RUBY, __FILE__, __LINE__
|
@@ -70,9 +104,10 @@ module GraphQL
|
|
70
104
|
self
|
71
105
|
end
|
72
106
|
RUBY
|
107
|
+
field_readers << :each_node
|
73
108
|
end
|
74
109
|
|
75
|
-
assigns = fields.map do |field, klass|
|
110
|
+
assigns = @fields.map do |field, klass|
|
76
111
|
if klass
|
77
112
|
<<-RUBY
|
78
113
|
@#{field} = self.class.fields[:#{field}].cast(@data["#{field}"], @errors.filter_by_path("#{field}"))
|
@@ -97,12 +132,18 @@ module GraphQL
|
|
97
132
|
freeze
|
98
133
|
end
|
99
134
|
RUBY
|
135
|
+
|
136
|
+
if @source_definition.enforce_collocated_callers
|
137
|
+
Client.enforce_collocated_callers(self, field_readers, source_definition.source_location[0])
|
138
|
+
end
|
100
139
|
end
|
101
140
|
end
|
102
141
|
|
103
142
|
class << self
|
104
143
|
attr_reader :type
|
105
144
|
|
145
|
+
attr_reader :source_definition
|
146
|
+
|
106
147
|
attr_reader :source_node
|
107
148
|
|
108
149
|
attr_reader :fields
|
@@ -175,7 +216,7 @@ module GraphQL
|
|
175
216
|
end
|
176
217
|
end
|
177
218
|
# TODO: Picking first source node seems error prone
|
178
|
-
define(name: self.name, source_node: source_node, fields: new_fields)
|
219
|
+
define(name: self.name, source_definition: source_definition, source_node: source_node, fields: new_fields)
|
179
220
|
end
|
180
221
|
|
181
222
|
# Public: Return errors associated with data.
|
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.
|
4
|
+
version: 0.4.0
|
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-12-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -34,16 +34,16 @@ dependencies:
|
|
34
34
|
name: graphql
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
|
-
- - "
|
37
|
+
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version:
|
39
|
+
version: '1.2'
|
40
40
|
type: :runtime
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
|
-
- - "
|
44
|
+
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version:
|
46
|
+
version: '1.2'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: actionpack
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -115,6 +115,7 @@ files:
|
|
115
115
|
- LICENSE
|
116
116
|
- README.md
|
117
117
|
- lib/graphql/client.rb
|
118
|
+
- lib/graphql/client/collocated_enforcement.rb
|
118
119
|
- lib/graphql/client/document_types.rb
|
119
120
|
- lib/graphql/client/error.rb
|
120
121
|
- lib/graphql/client/errors.rb
|