twilic 3.0.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.
@@ -0,0 +1,461 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Twilic
6
+ module Core
7
+ module Session
8
+ class UnknownReferencePolicy
9
+ Entry = Data.define(:name)
10
+ FAIL_FAST = Entry.new(:fail_fast)
11
+ STATELESS_RETRY = Entry.new(:stateless_retry)
12
+ end
13
+
14
+ class DictionaryFallback
15
+ Entry = Data.define(:name)
16
+ FAIL_FAST = Entry.new(:fail_fast)
17
+ STATELESS_RETRY = Entry.new(:stateless_retry)
18
+
19
+ def self.from_byte(b)
20
+ case b
21
+ when 0 then FAIL_FAST
22
+ when 1 then STATELESS_RETRY
23
+ end
24
+ end
25
+ end
26
+
27
+ DictionaryProfile = Data.define(:version, :hash, :expires_at, :fallback)
28
+
29
+ SessionOptions = Data.define(
30
+ :max_base_snapshots, :enable_state_patch, :enable_template_batch,
31
+ :enable_trained_dictionary, :unknown_reference_policy
32
+ ) do
33
+ def self.default
34
+ new(
35
+ max_base_snapshots: 8,
36
+ enable_state_patch: true,
37
+ enable_template_batch: true,
38
+ enable_trained_dictionary: true,
39
+ unknown_reference_policy: UnknownReferencePolicy::FAIL_FAST
40
+ )
41
+ end
42
+ end
43
+
44
+ InternTable = Data.define(:by_value, :by_id) do
45
+ def self.new_table
46
+ new(by_value: {}, by_id: [])
47
+ end
48
+
49
+ def get_id(value)
50
+ id = by_value[value]
51
+ id ? [id, true] : [0, false]
52
+ end
53
+
54
+ def get_value(id)
55
+ return ["", false] if id >= by_id.length
56
+
57
+ [by_id[id], true]
58
+ end
59
+
60
+ def register(value)
61
+ id, ok = get_id(value)
62
+ return id if ok
63
+
64
+ id = by_id.length
65
+ new(by_value: by_value.merge(value => id), by_id: by_id + [value])
66
+ end
67
+
68
+ def clear
69
+ new(by_value: {}, by_id: [])
70
+ end
71
+ end
72
+
73
+ ShapeTable = Data.define(:by_keys, :by_id, :observations, :next_id) do
74
+ def self.new_table
75
+ new(by_keys: {}, by_id: {}, observations: {}, next_id: 0)
76
+ end
77
+
78
+ def shape_key(keys)
79
+ keys.join("\0")
80
+ end
81
+
82
+ def get_id(keys)
83
+ id = by_keys[shape_key(keys)]
84
+ id ? [id, true] : [0, false]
85
+ end
86
+
87
+ def get_keys(id)
88
+ keys = by_id[id]
89
+ keys ? [keys, true] : [nil, false]
90
+ end
91
+
92
+ def register(keys)
93
+ sk = shape_key(keys)
94
+ id = by_keys[sk]
95
+ return id if id
96
+
97
+ id = next_id
98
+ keys_copy = keys.dup
99
+ new(
100
+ by_keys: by_keys.merge(sk => id),
101
+ by_id: by_id.merge(id => keys_copy),
102
+ observations: observations,
103
+ next_id: id + 1
104
+ ).then { |t| t.by_keys[sk] }
105
+ end
106
+
107
+ def register_with_id(shape_id, keys)
108
+ sk = shape_key(keys)
109
+ if by_id.key?(shape_id)
110
+ return shape_key(by_id[shape_id]) == sk
111
+ end
112
+ return false if by_keys.key?(sk) && by_keys[sk] != shape_id
113
+
114
+ keys_copy = keys.dup
115
+ new_by_id = by_id.merge(shape_id => keys_copy)
116
+ new_by_keys = by_keys.merge(sk => shape_id)
117
+ new_next = [next_id, shape_id + 1].max
118
+ replace(by_id: new_by_id, by_keys: new_by_keys, next_id: new_next)
119
+ true
120
+ end
121
+
122
+ def observe(keys)
123
+ sk = shape_key(keys)
124
+ count = (observations[sk] || 0) + 1
125
+ replace(observations: observations.merge(sk => count))
126
+ count
127
+ end
128
+
129
+ def clear
130
+ new(by_keys: {}, by_id: {}, observations: {}, next_id: 0)
131
+ end
132
+ end
133
+
134
+ BaseSnapshotEntry = Data.define(:id, :message)
135
+
136
+ SessionState = Data.define(
137
+ :options, :key_table, :string_table, :shape_table, :encode_shape_observations,
138
+ :base_snapshots, :templates, :template_columns, :field_enums, :dictionaries,
139
+ :dictionary_profiles, :schemas, :last_schema_id, :previous_message,
140
+ :previous_message_size, :next_base_id, :next_template_id, :next_dictionary_id
141
+ ) do
142
+ def self.new_state
143
+ new(
144
+ options: SessionOptions.default,
145
+ key_table: InternTable.new_table,
146
+ string_table: InternTable.new_table,
147
+ shape_table: ShapeTable.new_table,
148
+ encode_shape_observations: {},
149
+ base_snapshots: [],
150
+ templates: {},
151
+ template_columns: {},
152
+ field_enums: {},
153
+ dictionaries: {},
154
+ dictionary_profiles: {},
155
+ schemas: {},
156
+ last_schema_id: nil,
157
+ previous_message: nil,
158
+ previous_message_size: nil,
159
+ next_base_id: 0,
160
+ next_template_id: 0,
161
+ next_dictionary_id: 0
162
+ )
163
+ end
164
+
165
+ def self.with_options(options)
166
+ new_state.with(options: options)
167
+ end
168
+
169
+ def register_base_snapshot(base_id, message)
170
+ filtered = base_snapshots.reject { |e| e.id == base_id }
171
+ filtered << BaseSnapshotEntry.new(id: base_id, message: message.clone_message)
172
+ while filtered.length > options.max_base_snapshots
173
+ filtered.shift
174
+ end
175
+ with(base_snapshots: filtered)
176
+ end
177
+
178
+ def allocate_base_id
179
+ id = next_base_id
180
+ with(next_base_id: next_base_id + 1).next_base_id == id ? id : id
181
+ end
182
+
183
+ def allocate_template_id
184
+ id = next_template_id
185
+ with(next_template_id: next_template_id + 1)
186
+ id
187
+ end
188
+
189
+ def allocate_dictionary_id
190
+ id = next_dictionary_id
191
+ with(next_dictionary_id: next_dictionary_id + 1)
192
+ id
193
+ end
194
+
195
+ def get_base_snapshot(base_id)
196
+ entry = base_snapshots.find { |e| e.id == base_id }
197
+ return [nil, false] unless entry
198
+
199
+ [entry.message.clone_message, true]
200
+ end
201
+
202
+ def reset_tables
203
+ with(
204
+ key_table: InternTable.new_table,
205
+ string_table: InternTable.new_table,
206
+ shape_table: ShapeTable.new_table,
207
+ encode_shape_observations: {},
208
+ field_enums: {}
209
+ )
210
+ end
211
+
212
+ def reset_state
213
+ reset_tables.with(
214
+ base_snapshots: [],
215
+ templates: {},
216
+ template_columns: {},
217
+ dictionaries: {},
218
+ dictionary_profiles: {},
219
+ schemas: {},
220
+ last_schema_id: nil,
221
+ previous_message: nil,
222
+ previous_message_size: nil,
223
+ next_base_id: 0,
224
+ next_template_id: 0,
225
+ next_dictionary_id: 0
226
+ )
227
+ end
228
+ end
229
+
230
+ # Mutable wrapper for session state used during encode/decode
231
+ class MutableSessionState
232
+ attr_accessor :options, :key_table, :string_table, :shape_table,
233
+ :encode_shape_observations, :base_snapshots, :templates,
234
+ :template_columns, :field_enums, :dictionaries,
235
+ :dictionary_profiles, :schemas, :last_schema_id,
236
+ :previous_message, :previous_message_size,
237
+ :next_base_id, :next_template_id, :next_dictionary_id
238
+
239
+ def initialize(options = SessionOptions.default)
240
+ @options = options
241
+ @key_table = MutableInternTable.new
242
+ @string_table = MutableInternTable.new
243
+ @shape_table = MutableShapeTable.new
244
+ @encode_shape_observations = {}
245
+ @base_snapshots = []
246
+ @templates = {}
247
+ @template_columns = {}
248
+ @field_enums = {}
249
+ @dictionaries = {}
250
+ @dictionary_profiles = {}
251
+ @schemas = {}
252
+ @last_schema_id = nil
253
+ @previous_message = nil
254
+ @previous_message_size = nil
255
+ @next_base_id = 0
256
+ @next_template_id = 0
257
+ @next_dictionary_id = 0
258
+ end
259
+
260
+ def shape_key(keys)
261
+ keys.join("\0")
262
+ end
263
+
264
+ def register_base_snapshot(base_id, message)
265
+ @base_snapshots.reject! { |e| e.id == base_id }
266
+ @base_snapshots << BaseSnapshotEntry.new(id: base_id, message: message.clone_message)
267
+ while @base_snapshots.length > @options.max_base_snapshots
268
+ @base_snapshots.shift
269
+ end
270
+ end
271
+
272
+ def allocate_base_id
273
+ id = @next_base_id
274
+ @next_base_id += 1
275
+ id
276
+ end
277
+
278
+ def allocate_template_id
279
+ id = @next_template_id
280
+ @next_template_id += 1
281
+ id
282
+ end
283
+
284
+ def allocate_dictionary_id
285
+ id = @next_dictionary_id
286
+ @next_dictionary_id += 1
287
+ id
288
+ end
289
+
290
+ def get_base_snapshot(base_id)
291
+ entry = @base_snapshots.find { |e| e.id == base_id }
292
+ return [nil, false] unless entry
293
+
294
+ [entry.message.clone_message, true]
295
+ end
296
+
297
+ def reset_tables
298
+ @key_table = MutableInternTable.new
299
+ @string_table = MutableInternTable.new
300
+ @shape_table = MutableShapeTable.new
301
+ @encode_shape_observations = {}
302
+ @field_enums = {}
303
+ end
304
+
305
+ def reset_state
306
+ reset_tables
307
+ @base_snapshots = []
308
+ @templates = {}
309
+ @template_columns = {}
310
+ @dictionaries = {}
311
+ @dictionary_profiles = {}
312
+ @schemas = {}
313
+ @last_schema_id = nil
314
+ @previous_message = nil
315
+ @previous_message_size = nil
316
+ @next_base_id = 0
317
+ @next_template_id = 0
318
+ @next_dictionary_id = 0
319
+ end
320
+ end
321
+
322
+ module InternTableHelpers
323
+ module_function
324
+
325
+ def get_id(table, value)
326
+ id = table.by_value[value]
327
+ id ? [id, true] : [0, false]
328
+ end
329
+
330
+ def get_value(table, id)
331
+ return ["", false] if id >= table.by_id.length
332
+
333
+ [table.by_id[id], true]
334
+ end
335
+
336
+ def register(table, value)
337
+ id, ok = get_id(table, value)
338
+ return [table, id] if ok
339
+
340
+ id = table.by_id.length
341
+ new_table = InternTable.new(
342
+ by_value: table.by_value.merge(value => id),
343
+ by_id: table.by_id + [value]
344
+ )
345
+ [new_table, id]
346
+ end
347
+
348
+ def register_mut(table, value)
349
+ id = table.by_value[value]
350
+ return id if id
351
+
352
+ id = table.by_id.length
353
+ table.by_value[value] = id
354
+ table.by_id << value
355
+ id
356
+ end
357
+ end
358
+
359
+ class MutableInternTable
360
+ attr_reader :by_value, :by_id
361
+
362
+ def initialize
363
+ @by_value = {}
364
+ @by_id = []
365
+ end
366
+
367
+ def get_id(value)
368
+ id = @by_value[value]
369
+ id ? [id, true] : [0, false]
370
+ end
371
+
372
+ def get_value(id)
373
+ return ["", false] if id >= @by_id.length
374
+
375
+ [@by_id[id], true]
376
+ end
377
+
378
+ def register(value)
379
+ id = @by_value[value]
380
+ return id if id
381
+
382
+ id = @by_id.length
383
+ @by_value[value] = id
384
+ @by_id << value
385
+ id
386
+ end
387
+
388
+ def clear
389
+ @by_value = {}
390
+ @by_id = []
391
+ end
392
+ end
393
+
394
+ class MutableShapeTable
395
+ attr_reader :by_keys, :by_id, :observations, :next_id
396
+
397
+ def initialize
398
+ @by_keys = {}
399
+ @by_id = {}
400
+ @observations = {}
401
+ @next_id = 0
402
+ end
403
+
404
+ def shape_key(keys)
405
+ keys.join("\0")
406
+ end
407
+
408
+ def get_id(keys)
409
+ id = @by_keys[shape_key(keys)]
410
+ id ? [id, true] : [0, false]
411
+ end
412
+
413
+ def get_keys(id)
414
+ keys = @by_id[id]
415
+ keys ? [keys.dup, true] : [nil, false]
416
+ end
417
+
418
+ def register(keys)
419
+ sk = shape_key(keys)
420
+ id = @by_keys[sk]
421
+ return id if id
422
+
423
+ id = @next_id
424
+ @next_id += 1
425
+ @by_id[id] = keys.dup
426
+ @by_keys[sk] = id
427
+ id
428
+ end
429
+
430
+ def register_with_id(shape_id, keys)
431
+ sk = shape_key(keys)
432
+ if @by_id.key?(shape_id)
433
+ return shape_key(@by_id[shape_id]) == sk
434
+ end
435
+ return false if @by_keys.key?(sk) && @by_keys[sk] != shape_id
436
+
437
+ @by_id[shape_id] = keys.dup
438
+ @by_keys[sk] = shape_id
439
+ @next_id = [@next_id, shape_id + 1].max
440
+ true
441
+ end
442
+
443
+ def observe(keys)
444
+ sk = shape_key(keys)
445
+ @observations[sk] = (@observations[sk] || 0) + 1
446
+ end
447
+
448
+ def observation_count(keys)
449
+ @observations[shape_key(keys)] || 0
450
+ end
451
+
452
+ def clear
453
+ @by_keys = {}
454
+ @by_id = {}
455
+ @observations = {}
456
+ @next_id = 0
457
+ end
458
+ end
459
+ end
460
+ end
461
+ end