inigorb 0.27.7 → 0.28.1

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
  SHA256:
3
- metadata.gz: c6958605469a47112e9986e2750fb21cd0828ce2f57bb826e221f96bb52549cd
4
- data.tar.gz: 42387b988b13ca15e30f99d64aee75a19eda115fd25392a4a13ec93249c388a1
3
+ metadata.gz: a8dd4aa6b5a222647cdeaca8bc0a812fff7ca4afa6402c3da8a182d5b87a1db1
4
+ data.tar.gz: 79e2b7554361eb3e170d63b7c07afa2d58870322b3790452b3ede1120b46732f
5
5
  SHA512:
6
- metadata.gz: 0d7cad1a8f9dd997d4c834d6c9d953c5e1e4c0f50d3ad9c4244740be7760e3d443badaaaa8294e32acd1b33cd53fa086af642ca99839f98ef4007254338cf7e6
7
- data.tar.gz: a09eebe058d895f2b0aee923ba708a1ca26a8fa0497a1eb9e6abcb7cd730ff808c0b944de6500b3be32c7093711e7f3da628f76e2807bc3c145a3abc852ddb35
6
+ metadata.gz: cec390d985f84492c6a72f2232382f6f8a93043b9c067f3d30d71f7896bcaf12c6ec47b91afab59c8e1dbd4ac78d84d141a5ba1ce982e865a0012f9b0d00cfd3
7
+ data.tar.gz: a2a42ca4d67b9e6ad518cf2d748dd09a8123815c3c327aaf382cc8e3bbfee5f6cb9454c8e3a66c80392f5c63dad0d4276cadeeda552f695071e8a23e845fc9ff
@@ -56,11 +56,14 @@ module Inigo
56
56
 
57
57
  class Config < FFI::Struct
58
58
  layout :debug, :bool,
59
- :ingest, :pointer,
59
+ :name, :pointer,
60
60
  :service, :pointer,
61
61
  :token, :pointer,
62
62
  :schema, :pointer,
63
- :introspection, :pointer
63
+ :runtime, :pointer,
64
+ :egress_url, :pointer,
65
+ :gateway, :u_int64_t,
66
+ :disable_response_data, :bool
64
67
  end
65
68
 
66
69
  attach_function :create, [:u_int64_t], :u_int64_t
@@ -81,6 +84,7 @@ module Inigo
81
84
  attach_function :disposeHandle, [:u_int64_t], :void
82
85
  attach_function :disposeMemory, [:pointer], :void
83
86
  attach_function :check_lasterror, [], :string
87
+ attach_function :copy_querydata, [:u_int64_t], :u_int64_t
84
88
  rescue LoadError => e
85
89
  raise ::RuntimeError, "Unable to open Inigo shared library.\n\nPlease get in touch with us for support:\nemail: support@inigo.io\nslack: https://slack.inigo.io\n\nPlease share the below info with us:\nerror: #{e.to_s}\nuname: #{RbConfig::CONFIG['host_os']}\narch: #{RbConfig::CONFIG['host_cpu']}"
86
90
  end
@@ -1,8 +1,23 @@
1
1
  require 'json'
2
- require_relative 'ffimod'
2
+
3
+ require 'inigorb/ffimod'
3
4
 
4
5
  module Inigo
6
+ # made to simplify user experience. just prepend Inigo::Data to your channel and it will work.
7
+ module Data
8
+ attr_accessor :querydata
9
+
10
+ def unsubscribed
11
+ if @querydata
12
+ Inigo.disposeHandle(@querydata.handle)
13
+ end
14
+ super
15
+ end
16
+ end
17
+
5
18
  class Query
19
+ attr_reader :handle
20
+
6
21
  def initialize(instance, request)
7
22
  @handle = 0
8
23
  @instance = instance
@@ -50,31 +65,35 @@ module Inigo
50
65
  [resp_dict, req_dict]
51
66
  end
52
67
 
53
- def process_response(resp_body)
68
+ def process_response(resp_body, is_initial_subscription: false, copy: false)
54
69
  return nil if @handle.zero?
55
70
 
56
71
  output_ptr = FFI::MemoryPointer.new(:pointer)
57
72
  output_len = FFI::MemoryPointer.new(:int)
58
73
 
74
+ handle = @handle
75
+ if copy
76
+ # if it is subscription and not initial request - reuse existing querydata
77
+ handle = Inigo.copy_querydata(@handle)
78
+ end
79
+
59
80
  Inigo.process_response(
60
81
  @instance,
61
- @handle,
82
+ handle,
62
83
  FFI::MemoryPointer.from_string(resp_body), resp_body.length,
63
84
  output_ptr, output_len
64
85
  )
65
86
 
66
- output_dict = {}
67
-
68
- output_len_value = output_len.read_int
69
- if output_len_value > 0
70
- output_data = output_ptr.read_pointer.read_string(output_len_value)
71
- output_dict = JSON.parse(output_data)
72
- end
87
+ output_data = output_ptr.read_pointer.read_string(output_len.read_int)
73
88
 
74
89
  Inigo.disposeMemory(output_ptr.read_pointer)
75
- Inigo.disposeHandle(@handle)
76
90
 
77
- output_dict
91
+ # if it is initial subscription request - do not dispose handle
92
+ if !is_initial_subscription
93
+ Inigo.disposeHandle(handle)
94
+ end
95
+
96
+ output_data
78
97
  end
79
98
  end
80
- end
99
+ end
@@ -0,0 +1,261 @@
1
+ require 'json'
2
+ require 'ffi'
3
+ require 'graphql'
4
+
5
+ require 'inigorb/ffimod'
6
+ require 'inigorb/query'
7
+
8
+ module Inigo
9
+ class Tracer < GraphQL::Tracing::PlatformTracing
10
+ @@instance = nil
11
+
12
+ def self.instance
13
+ @@instance
14
+ end
15
+
16
+ def self.instance=(value)
17
+ @@instance = value
18
+ end
19
+
20
+ def initialize(options = {})
21
+ super(options)
22
+ # add options like logger logic
23
+ end
24
+
25
+ def self.use(schema, **kwargs)
26
+ @@schema = schema
27
+ super
28
+ end
29
+
30
+ self.platform_keys = {
31
+ 'lex' => 'lex',
32
+ 'parse' => 'parse',
33
+ 'validate' => 'validate',
34
+ 'analyze_query' => 'analyze_query',
35
+ 'analyze_multiplex' => 'analyze_multiplex',
36
+ 'execute_multiplex' => 'execute_multiplex',
37
+ 'execute_query' => 'execute_query',
38
+ 'execute_query_lazy' => 'execute_query_lazy'
39
+ }
40
+
41
+ def platform_trace(platform_key, key, data)
42
+ # Ignore execution if Inigo is not initialized
43
+ if self.class.instance == 0
44
+ return yield
45
+ end
46
+
47
+ if platform_key != "execute_multiplex"
48
+ return yield
49
+ end
50
+
51
+ if !data[:multiplex] || !data[:multiplex].queries || data[:multiplex].queries.length == 0
52
+ return yield
53
+ end
54
+
55
+ queries = []
56
+ subscription_queries = {}
57
+ initial_subscriptions = {}
58
+ modified_responses = {}
59
+ cached_queries = {}
60
+
61
+ data[:multiplex].queries.each_with_index do |query, index|
62
+ is_subscription = query.context[:channel] != nil
63
+ is_init_subscription = is_subscription && !query.context[:subscription_id]
64
+
65
+ if is_subscription && !is_init_subscription
66
+ # retrieve querydata stored after initial subscription request
67
+ subscription_queries[index] = query.context[:channel].querydata
68
+ queries.append(query.context[:channel].querydata)
69
+ # no need to process request again, it will be copied
70
+ next
71
+ end
72
+
73
+ gReq = {
74
+ 'query' => query.query_string,
75
+ }
76
+
77
+ gReq['operationName'] = query.operation_name if query.operation_name && query.operation_name != ''
78
+ gReq['variables'] = query.variables.to_h if query.variables
79
+
80
+ q = Query.new(self.class.instance, JSON.dump(gReq))
81
+
82
+ incoming_request = query.context['request']
83
+ if is_subscription
84
+ incoming_request = ActionDispatch::Request.new(query.context[:channel].connection.env)
85
+ end
86
+
87
+ resp, req = q.process_request(headers(incoming_request))
88
+
89
+ # Introspection or blocked query
90
+ if resp.any?
91
+ modified_responses[index] = resp
92
+ cached_queries[index] = query
93
+
94
+ # trick not to execute actual query. we can't remove query from multiplex at all but we can fool it by modifying the query executed on the schema.
95
+ modified_query = GraphQL::Query.new(query.schema, 'query IntrospectionQuery { __schema { queryType { name } } }', context: query.context, operation_name: 'IntrospectionQuery')
96
+ modified_query.multiplex = query.multiplex
97
+ # TODO - verify works in all the cases. During the testing it works, simulate multiple queries at the same time to verify.
98
+ data[:multiplex].queries[index] = modified_query
99
+
100
+ queries.append(q)
101
+ next
102
+ end
103
+
104
+ # Modify query if required
105
+ if req.any?
106
+ modified_query = GraphQL::Query.new(query.schema, req['query'], context: query.context, operation_name: req['operationName'], variables: req['variables'])
107
+ modified_query.multiplex = query.multiplex
108
+ # TODO - verify works in all the cases. During the testing it works, simulate multiple queries at the same time to verify.
109
+ data[:multiplex].queries[index] = modified_query
110
+ end
111
+
112
+ if is_subscription
113
+ query.context[:channel].querydata = q
114
+ initial_subscriptions[index] = true
115
+ end
116
+
117
+ queries.append(q)
118
+ end
119
+
120
+ responses = yield
121
+
122
+ responses.each_with_index do |response, index|
123
+ if modified_responses[index]
124
+ # process_response is not called in this case
125
+ responses[index] = GraphQL::Query::Result.new(query: cached_queries[index], values: modified_responses[index])
126
+ next
127
+ end
128
+
129
+ # take a copy of the initial subscription request if it is subscription
130
+ needs_copy = subscription_queries[index] != nil
131
+ is_initial_subscription = initial_subscriptions[index] != nil
132
+ resp = {
133
+ 'errors' => response['errors'],
134
+ 'response_size' => 0,
135
+ 'response_body_counts' => count_response_fields(response.to_h)
136
+ }
137
+ processed_response = queries[index].process_response(JSON.dump(resp), is_initial_subscription: is_initial_subscription, copy: needs_copy)
138
+ responses[index] = GraphQL::Query::Result.new(query: data[:multiplex].queries[index], values: mod_response(response.to_h, JSON.parse(processed_response)))
139
+ end
140
+
141
+ responses
142
+ end
143
+
144
+ # compat
145
+ def platform_authorized_key(type)
146
+ "#{type.graphql_name}.authorized.graphql"
147
+ end
148
+
149
+ # compat
150
+ def platform_resolve_type_key(type)
151
+ "#{type.graphql_name}.resolve_type.graphql"
152
+ end
153
+
154
+ # compat
155
+ def platform_field_key(type, field)
156
+ "graphql.#{type.name}.#{field.name}"
157
+ end
158
+
159
+ def count_response_fields(resp)
160
+ counts = {}
161
+
162
+ if resp['data']
163
+ count_response_fields_recursive(counts, 'data', resp['data'])
164
+ end
165
+
166
+ counts['data'] ||= 1
167
+ counts['errors'] = resp['errors'] ? resp['errors'].length : 0
168
+ counts
169
+ end
170
+
171
+ def count_response_fields_recursive(hm, prefix, val)
172
+ return unless val.is_a?(Hash) || val.is_a?(Array)
173
+
174
+ incr = lambda do |key, value|
175
+ unless count_response_fields_recursive(hm, key, value)
176
+ hm[key] = (hm[key] || 0) + 1
177
+ end
178
+ end
179
+
180
+ if val.is_a?(Array)
181
+ val.each do |item|
182
+ incr.call(prefix, item)
183
+ end
184
+
185
+ return true
186
+ end
187
+
188
+ val.each do |k, v|
189
+ incr.call("#{prefix}.#{k}", v)
190
+ end
191
+
192
+ return false
193
+ end
194
+
195
+ private
196
+
197
+ def self.initialize_tracer(schema)
198
+ @@schema = schema
199
+
200
+ # get all the inigo settings from env
201
+ settings = ENV.select { |k, v| k.start_with?('INIGO') }
202
+
203
+ if settings.fetch("INIGO_ENABLE", "").to_s.downcase == "false"
204
+ @@initialized = true #not to get to this method ever again
205
+ puts 'Inigo is disabled. Skipping middleware.'
206
+ return
207
+ end
208
+
209
+ config = Inigo::Config.new
210
+
211
+ config[:disable_response_data] = false
212
+ config[:debug] = settings.fetch("INIGO_DEBUG", "false").to_s.downcase == "true"
213
+ config[:token] = FFI::MemoryPointer.from_string(settings.fetch('INIGO_SERVICE_TOKEN', '').to_s.encode('UTF-8'))
214
+ config[:schema] = FFI::MemoryPointer.from_string(schema.to_s.encode('UTF-8'))
215
+ config[:name] = FFI::MemoryPointer.from_string("inigo-rb".to_s.encode('UTF-8'))
216
+ config[:runtime] = FFI::MemoryPointer.from_string("ruby".concat(RUBY_VERSION[/\d+\.\d+/]).to_s.encode('UTF-8'))
217
+
218
+ # Create Inigo instance
219
+ @@instance = Inigo.create(config.pointer.address)
220
+
221
+ error = Inigo.check_lasterror
222
+ if error.length != 0
223
+ puts "INIGO: #{error}"
224
+ end
225
+
226
+ if error.length == 0 && @@instance == 0
227
+ puts 'INIGO: error, instance cannot be created'
228
+ end
229
+ end
230
+
231
+ def headers(request)
232
+ headers = {}
233
+
234
+ request.env.each do |key, value|
235
+ if key.start_with?('HTTP_')
236
+ header_name = key[5..].split('_').map(&:capitalize).join('-')
237
+ headers[header_name] = value.split(',').map(&:strip)
238
+ elsif key == 'CONTENT_TYPE' || key == 'REMOTE_ADDR'
239
+ headers[key] = value.split(',').map(&:strip)
240
+ end
241
+ end
242
+
243
+ JSON.dump(headers)
244
+ end
245
+
246
+ def mod_response(response, extended)
247
+ if extended['extensions']
248
+ response['extensions'] ||= {}
249
+ response['extensions'].merge!(extended['extensions'])
250
+ end
251
+
252
+ if extended['errors']
253
+ response['errors'] ||= []
254
+ response['errors'].concat(extended['errors'])
255
+ end
256
+
257
+ response
258
+ end
259
+
260
+ end
261
+ end
data/lib/inigorb.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  require 'json'
2
2
  require 'ffi'
3
- require_relative 'ffimod'
4
- require_relative 'query'
3
+
4
+ require 'inigorb/ffimod'
5
+ require 'inigorb/query'
6
+ require 'inigorb/tracer'
5
7
 
6
8
  module Inigo
7
9
  class Middleware
@@ -86,7 +88,7 @@ module Inigo
86
88
  # Read request from body
87
89
  request.body.each { |chunk| gReq += chunk }
88
90
 
89
- if self.class.operation_store.present? && has_operation_id?(gReq)
91
+ if self.class.operation_store.present? && has_operation_id?(gReq) && !has_query?(gReq)
90
92
  parsed = JSON.parse(gReq)
91
93
  operationId = get_operation_id(parsed)
92
94
 
@@ -124,7 +126,7 @@ module Inigo
124
126
  request.body.rewind
125
127
  elsif request.get?
126
128
  operation_id = get_operation_id(request.params)
127
- if operation_id && self.class.operation_store
129
+ if operation_id && self.class.operation_store && !request.params['query']
128
130
  parts = operation_id.split('/')
129
131
  # Query can't be resolved
130
132
  if parts.length != 2
@@ -181,14 +183,10 @@ module Inigo
181
183
 
182
184
  # Forward to request handler
183
185
  status, headers, response = @app.call(env)
184
-
186
+ headers.delete("Content-Length")
185
187
  # Inigo: process response
186
- processed_response = q.process_response(response.body.to_s)
187
- if processed_response
188
- return respond(status, headers, processed_response)
189
- end
190
-
191
- response
188
+ response = q.process_response(response.body.to_s)
189
+ [status, headers, [response]]
192
190
  end
193
191
 
194
192
  private
@@ -214,6 +212,9 @@ module Inigo
214
212
  end
215
213
 
216
214
  config = Inigo::Config.new
215
+ config[:disable_response_data] = false
216
+ config[:name] = FFI::MemoryPointer.from_string("inigo-rb".to_s.encode('UTF-8'))
217
+ config[:runtime] = FFI::MemoryPointer.from_string("ruby".concat(RUBY_VERSION[/\d+\.\d+/]).to_s.encode('UTF-8'))
217
218
 
218
219
  if settings.fetch("INIGO_DEBUG", "false").to_s.downcase == "true"
219
220
  config[:debug] = true
@@ -282,6 +283,10 @@ module Inigo
282
283
  str_data.include?('operationId')
283
284
  end
284
285
 
286
+ def has_query?(str_data)
287
+ return str_data.include?('"query":') && str_data.match(/"query"\s*:\s*"\S+"/)
288
+ end
289
+
285
290
  # extracts operation id from parsed body hash
286
291
  def get_operation_id(json_data)
287
292
  # Relay / Apollo 1.x
@@ -294,6 +299,5 @@ module Inigo
294
299
  nil
295
300
  end
296
301
  end
297
-
298
- end
302
+ end
299
303
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inigorb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.27.7
4
+ version: 0.28.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Inigo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-16 00:00:00.000000000 Z
11
+ date: 2023-10-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jwt
@@ -50,6 +50,26 @@ dependencies:
50
50
  - - ">="
51
51
  - !ruby/object:Gem::Version
52
52
  version: 1.15.5
53
+ - !ruby/object:Gem::Dependency
54
+ name: graphql
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: 2.0.24
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 2.0.24
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 2.0.24
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 2.0.24
53
73
  description:
54
74
  email: eitan@inigo.io
55
75
  executables: []
@@ -57,15 +77,16 @@ extensions: []
57
77
  extra_rdoc_files: []
58
78
  files:
59
79
  - README.md
60
- - lib/ffimod.rb
61
- - lib/inigo-darwin-amd64.dylib
62
- - lib/inigo-darwin-arm64.dylib
63
- - lib/inigo-linux-amd64.so
64
- - lib/inigo-linux-arm64.so
65
- - lib/inigo-windows-amd64.dll
66
- - lib/inigo-windows-arm64.dll
67
80
  - lib/inigorb.rb
68
- - lib/query.rb
81
+ - lib/inigorb/ffimod.rb
82
+ - lib/inigorb/inigo-darwin-amd64.dylib
83
+ - lib/inigorb/inigo-darwin-arm64.dylib
84
+ - lib/inigorb/inigo-linux-amd64.so
85
+ - lib/inigorb/inigo-linux-arm64.so
86
+ - lib/inigorb/inigo-windows-amd64.dll
87
+ - lib/inigorb/inigo-windows-arm64.dll
88
+ - lib/inigorb/query.rb
89
+ - lib/inigorb/tracer.rb
69
90
  homepage: https://inigo.io
70
91
  licenses:
71
92
  - MIT