inigorb 0.27.8 → 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 +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
|