inigorb 0.27.8 → 0.28.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/{ffimod.rb → inigorb/ffimod.rb} +3 -2
- data/lib/{inigo-darwin-amd64.dylib → inigorb/inigo-darwin-amd64.dylib} +0 -0
- data/lib/{inigo-darwin-arm64.dylib → inigorb/inigo-darwin-arm64.dylib} +0 -0
- data/lib/{inigo-linux-amd64.so → inigorb/inigo-linux-amd64.so} +0 -0
- data/lib/{inigo-linux-arm64.so → inigorb/inigo-linux-arm64.so} +0 -0
- data/lib/{inigo-windows-amd64.dll → inigorb/inigo-windows-amd64.dll} +0 -0
- data/lib/{inigo-windows-arm64.dll → inigorb/inigo-windows-arm64.dll} +0 -0
- data/lib/{query.rb → inigorb/query.rb} +29 -4
- data/lib/inigorb/tracer.rb +261 -0
- data/lib/inigorb.rb +13 -6
- metadata +31 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8dd4aa6b5a222647cdeaca8bc0a812fff7ca4afa6402c3da8a182d5b87a1db1
|
4
|
+
data.tar.gz: 79e2b7554361eb3e170d63b7c07afa2d58870322b3790452b3ede1120b46732f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cec390d985f84492c6a72f2232382f6f8a93043b9c067f3d30d71f7896bcaf12c6ec47b91afab59c8e1dbd4ac78d84d141a5ba1ce982e865a0012f9b0d00cfd3
|
7
|
+
data.tar.gz: a2a42ca4d67b9e6ad518cf2d748dd09a8123815c3c327aaf382cc8e3bbfee5f6cb9454c8e3a66c80392f5c63dad0d4276cadeeda552f695071e8a23e845fc9ff
|
@@ -56,11 +56,11 @@ module Inigo
|
|
56
56
|
|
57
57
|
class Config < FFI::Struct
|
58
58
|
layout :debug, :bool,
|
59
|
-
:
|
59
|
+
:name, :pointer,
|
60
60
|
:service, :pointer,
|
61
61
|
:token, :pointer,
|
62
62
|
:schema, :pointer,
|
63
|
-
:
|
63
|
+
:runtime, :pointer,
|
64
64
|
:egress_url, :pointer,
|
65
65
|
:gateway, :u_int64_t,
|
66
66
|
:disable_response_data, :bool
|
@@ -84,6 +84,7 @@ module Inigo
|
|
84
84
|
attach_function :disposeHandle, [:u_int64_t], :void
|
85
85
|
attach_function :disposeMemory, [:pointer], :void
|
86
86
|
attach_function :check_lasterror, [], :string
|
87
|
+
attach_function :copy_querydata, [:u_int64_t], :u_int64_t
|
87
88
|
rescue LoadError => e
|
88
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']}"
|
89
90
|
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -1,8 +1,23 @@
|
|
1
1
|
require 'json'
|
2
|
-
|
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,15 +65,21 @@ 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
|
-
|
82
|
+
handle,
|
62
83
|
FFI::MemoryPointer.from_string(resp_body), resp_body.length,
|
63
84
|
output_ptr, output_len
|
64
85
|
)
|
@@ -66,7 +87,11 @@ module Inigo
|
|
66
87
|
output_data = output_ptr.read_pointer.read_string(output_len.read_int)
|
67
88
|
|
68
89
|
Inigo.disposeMemory(output_ptr.read_pointer)
|
69
|
-
|
90
|
+
|
91
|
+
# if it is initial subscription request - do not dispose handle
|
92
|
+
if !is_initial_subscription
|
93
|
+
Inigo.disposeHandle(handle)
|
94
|
+
end
|
70
95
|
|
71
96
|
output_data
|
72
97
|
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
|
-
|
4
|
-
|
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
|
@@ -211,6 +213,8 @@ module Inigo
|
|
211
213
|
|
212
214
|
config = Inigo::Config.new
|
213
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'))
|
214
218
|
|
215
219
|
if settings.fetch("INIGO_DEBUG", "false").to_s.downcase == "true"
|
216
220
|
config[:debug] = true
|
@@ -279,6 +283,10 @@ module Inigo
|
|
279
283
|
str_data.include?('operationId')
|
280
284
|
end
|
281
285
|
|
286
|
+
def has_query?(str_data)
|
287
|
+
return str_data.include?('"query":') && str_data.match(/"query"\s*:\s*"\S+"/)
|
288
|
+
end
|
289
|
+
|
282
290
|
# extracts operation id from parsed body hash
|
283
291
|
def get_operation_id(json_data)
|
284
292
|
# Relay / Apollo 1.x
|
@@ -291,6 +299,5 @@ module Inigo
|
|
291
299
|
nil
|
292
300
|
end
|
293
301
|
end
|
294
|
-
|
295
|
-
end
|
302
|
+
end
|
296
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.
|
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-
|
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/
|
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
|