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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 490142029326d682bd6956d1f825d9429aa49389
4
- data.tar.gz: 3cc776110140c024c801f8b3a9eb52bf72cc924f
3
+ metadata.gz: 88afea7eaa70819f11b2a000886ef3924e2b0e12
4
+ data.tar.gz: 62a8706efe7fe20a73da2affaa3e362dce686970
5
5
  SHA512:
6
- metadata.gz: 2e3cba4f02d0fff593cadcbd0d39bcdecb0576f087bb9088f2dc5902426bb54b7829ea3eb7d8d96c86fae155eb6ba248c5414ba24006834c8a4d806063e1be4c
7
- data.tar.gz: bb8b0a56f1f1d0be7a4315aa626af65a08e7285adb03fb02e382a0fb6fdb53e67326ed6654f171709f267897141e396813ac2245f6c45097157b78f492dba7da
6
+ metadata.gz: b9774d7f5229a0202924ff253b251076ed7b5f71dd3e32f0707467663868faace9a331b79878e70159058ebbdde59b477892b13c662c1e3f9492fa31e6901551
7
+ data.tar.gz: 0d4e18520e7b23a28012edf77a98913358ef7793e70f46ddbdf881ad63c51eafc83325ef7a388d37b752914d55b0f6e9d23dc50efbb23afc98bf1f5aebd25be0
@@ -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", types: @document_types)
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? || lineno.nil?
162
- filename, lineno, = caller(1, 1).first.split(":", 3)
163
- lineno = lineno.to_i
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
- if filename && lineno
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) if filename && lineno
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, types: {})
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
- field_klass = wrap(selection, name: "#{name}[:#{field_name}]", types: types) if selection.selections.any?
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, types: types).fields.each do |fragment_name, klass|
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, type: types[node] && types[node].unwrap)
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: {}, type: nil)
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.3.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-29 00:00:00.000000000 Z
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: 0.19.2
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: 0.19.2
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