absmartly-sdk 0.1.2

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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +180 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +5 -0
  6. data/CODE_OF_CONDUCT.md +84 -0
  7. data/Gemfile +21 -0
  8. data/Gemfile.lock +92 -0
  9. data/LICENSE.txt +21 -0
  10. data/README +180 -0
  11. data/Rakefile +12 -0
  12. data/example/example.rb +48 -0
  13. data/lib/a_b_smartly.rb +92 -0
  14. data/lib/a_b_smartly_config.rb +49 -0
  15. data/lib/absmartly/md5.rb +178 -0
  16. data/lib/absmartly/variant_assigner.rb +36 -0
  17. data/lib/absmartly/version.rb +5 -0
  18. data/lib/absmartly.rb +57 -0
  19. data/lib/audience_deserializer.rb +8 -0
  20. data/lib/audience_matcher.rb +38 -0
  21. data/lib/client.rb +80 -0
  22. data/lib/client_config.rb +43 -0
  23. data/lib/context.rb +528 -0
  24. data/lib/context_config.rb +70 -0
  25. data/lib/context_data_deserializer.rb +8 -0
  26. data/lib/context_data_provider.rb +8 -0
  27. data/lib/context_event_handler.rb +8 -0
  28. data/lib/context_event_logger.rb +9 -0
  29. data/lib/context_event_serializer.rb +8 -0
  30. data/lib/default_audience_deserializer.rb +13 -0
  31. data/lib/default_context_data_deserializer.rb +16 -0
  32. data/lib/default_context_data_provider.rb +15 -0
  33. data/lib/default_context_event_handler.rb +15 -0
  34. data/lib/default_context_event_serializer.rb +58 -0
  35. data/lib/default_http_client.rb +61 -0
  36. data/lib/default_http_client_config.rb +19 -0
  37. data/lib/default_variable_parser.rb +13 -0
  38. data/lib/hashing.rb +9 -0
  39. data/lib/http_client.rb +20 -0
  40. data/lib/json/attribute.rb +30 -0
  41. data/lib/json/context_data.rb +31 -0
  42. data/lib/json/experiment.rb +83 -0
  43. data/lib/json/experiment_application.rb +27 -0
  44. data/lib/json/experiment_variant.rb +29 -0
  45. data/lib/json/exposure.rb +60 -0
  46. data/lib/json/goal_achievement.rb +32 -0
  47. data/lib/json/publish_event.rb +42 -0
  48. data/lib/json/unit.rb +30 -0
  49. data/lib/json_expr/evaluator.rb +27 -0
  50. data/lib/json_expr/expr_evaluator.rb +122 -0
  51. data/lib/json_expr/json_expr.rb +37 -0
  52. data/lib/json_expr/operator.rb +8 -0
  53. data/lib/json_expr/operators/and_combinator.rb +14 -0
  54. data/lib/json_expr/operators/binary_operator.rb +22 -0
  55. data/lib/json_expr/operators/boolean_combinator.rb +15 -0
  56. data/lib/json_expr/operators/equals_operator.rb +12 -0
  57. data/lib/json_expr/operators/greater_than_operator.rb +12 -0
  58. data/lib/json_expr/operators/greater_than_or_equal_operator.rb +12 -0
  59. data/lib/json_expr/operators/in_operator.rb +23 -0
  60. data/lib/json_expr/operators/less_than_operator.rb +12 -0
  61. data/lib/json_expr/operators/less_than_or_equal_operator.rb +12 -0
  62. data/lib/json_expr/operators/match_operator.rb +17 -0
  63. data/lib/json_expr/operators/nil_operator.rb +11 -0
  64. data/lib/json_expr/operators/not_operator.rb +11 -0
  65. data/lib/json_expr/operators/or_combinator.rb +14 -0
  66. data/lib/json_expr/operators/unary_operator.rb +13 -0
  67. data/lib/json_expr/operators/value_operator.rb +11 -0
  68. data/lib/json_expr/operators/var_operator.rb +21 -0
  69. data/lib/scheduled_executor_service.rb +8 -0
  70. data/lib/scheduled_thread_pool_executor.rb +14 -0
  71. data/lib/string.rb +30 -0
  72. data/lib/variable_parser.rb +8 -0
  73. data/lib/variant_assigner.rb +47 -0
  74. data/sig/absmartly.rbs +4 -0
  75. metadata +120 -0
data/lib/context.rb ADDED
@@ -0,0 +1,528 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "hashing"
4
+ require_relative "variant_assigner"
5
+ require_relative "json/unit"
6
+ require_relative "json/attribute"
7
+ require_relative "json/exposure"
8
+ require_relative "json/publish_event"
9
+ require_relative "json/goal_achievement"
10
+
11
+ class Context
12
+ attr_reader :data, :pending_count
13
+
14
+ def self.create(clock, config, scheduler, data_future, data_provider,
15
+ event_handler, event_logger, variable_parser, audience_matcher)
16
+ Context.new(clock, config, scheduler, data_future, data_provider,
17
+ event_handler, event_logger, variable_parser, audience_matcher)
18
+ end
19
+
20
+ def initialize(clock, config, scheduler, data_future, data_provider,
21
+ event_handler, event_logger, variable_parser, audience_matcher)
22
+ @index = []
23
+ @achievements = []
24
+ @assignment_cache = {}
25
+ @assignments = {}
26
+ @clock = clock.is_a?(String) ? Time.iso8601(clock) : clock
27
+ @publish_delay = config.publish_delay
28
+ @refresh_interval = config.refresh_interval
29
+ @event_handler = event_handler
30
+ @event_logger = !config.event_logger.nil? ? config.event_logger : event_logger
31
+ @data_provider = data_provider
32
+ @variable_parser = variable_parser
33
+ @audience_matcher = audience_matcher
34
+ @scheduler = scheduler
35
+ @closed = false
36
+
37
+ @units = {}
38
+ @attributes = []
39
+ @overrides = {}
40
+ @cassignments = {}
41
+ @assigners = {}
42
+ @hashed_units = {}
43
+ @pending_count = 0
44
+ @exposures ||= []
45
+
46
+ set_units(config.units) if config.units
47
+ set_attributes(config.attributes) if config.attributes
48
+ set_overrides(config.overrides) if config.overrides
49
+ set_custom_assignments(config.custom_assignments) if config.custom_assignments
50
+ if data_future.success?
51
+ assign_data(data_future.data_future)
52
+ else
53
+ set_data_failed(data_future.exception)
54
+ end
55
+ end
56
+
57
+ def ready?
58
+ !@data.nil?
59
+ end
60
+
61
+ def failed?
62
+ @failed
63
+ end
64
+
65
+ def closed?
66
+ @closed
67
+ end
68
+
69
+ def experiments
70
+ check_ready?(true)
71
+
72
+ @data.experiments.map(&:name)
73
+ end
74
+
75
+ def set_override(experiment_name, variant)
76
+ check_not_closed?
77
+
78
+ @overrides[experiment_name.to_s.to_sym] = variant
79
+ end
80
+
81
+ def set_overrides(overrides)
82
+ check_not_closed?
83
+
84
+ @overrides.merge!(overrides.transform_keys(&:to_sym))
85
+ end
86
+
87
+ def override(experiment_name)
88
+ check_not_closed?
89
+
90
+ @overrides[experiment_name.to_s.to_sym]
91
+ end
92
+
93
+ def set_custom_assignment(experiment_name, variant)
94
+ check_not_closed?
95
+
96
+ @cassignments[experiment_name.to_s.to_sym] = variant
97
+ end
98
+
99
+ def set_custom_assignments(custom_assignments)
100
+ check_not_closed?
101
+
102
+ @cassignments.merge!(custom_assignments.transform_keys(&:to_sym))
103
+ end
104
+
105
+ def custom_assignment(experiment_name)
106
+ check_not_closed?
107
+
108
+ @cassignments[experiment_name.to_s.to_sym]
109
+ end
110
+
111
+ def set_unit(unit_type, uid)
112
+ check_not_closed?
113
+
114
+ previous = @units[unit_type.to_sym]
115
+ if !previous.nil? && previous != uid
116
+ raise IllegalStateException.new("Unit '#{unit_type}' already set.")
117
+ end
118
+
119
+ trimmed = uid.to_s.strip
120
+ if trimmed.empty?
121
+ raise IllegalStateException.new("Unit '#{unit_type}' UID must not be blank.")
122
+ end
123
+
124
+ @units[unit_type] = trimmed
125
+ end
126
+
127
+ def set_units(units)
128
+ check_not_closed?
129
+
130
+ units.each { |key, value| self.set_unit(key, value) }
131
+ end
132
+
133
+ def set_attribute(name, value)
134
+ check_not_closed?
135
+
136
+ @attributes.push(Attribute.new(name, value, @clock.to_i))
137
+ end
138
+
139
+ def set_attributes(attributes)
140
+ check_not_closed?
141
+
142
+ attributes.each { |key, value| self.set_attribute(key, value) }
143
+ end
144
+
145
+ def treatment(experiment_name)
146
+ check_ready?(true)
147
+ assignment = assignment(experiment_name)
148
+ unless assignment.exposed
149
+ queue_exposure(assignment)
150
+ end
151
+
152
+ assignment.variant
153
+ end
154
+
155
+ def queue_exposure(assignment)
156
+ unless assignment.exposed
157
+ assignment.exposed = true
158
+
159
+ exposure = Exposure.new
160
+ exposure.id = assignment.id
161
+ exposure.name = assignment.name
162
+ exposure.unit = assignment.unit_type
163
+ exposure.variant = assignment.variant
164
+ exposure.exposed_at = @clock
165
+ exposure.assigned = assignment.assigned
166
+ exposure.eligible = assignment.eligible
167
+ exposure.overridden = assignment.overridden
168
+ exposure.full_on = assignment.full_on
169
+ exposure.custom = assignment.custom
170
+ exposure.audience_mismatch = assignment.audience_mismatch
171
+
172
+ @pending_count += 1
173
+ @exposures.push(exposure)
174
+ end
175
+ end
176
+
177
+ def peek_treatment(experiment_name)
178
+ check_ready?(true)
179
+
180
+ assignment(experiment_name).variant
181
+ end
182
+
183
+ def variable_keys
184
+ check_ready?(true)
185
+
186
+ hsh = {}
187
+ @index_variables.each { |key, value| hsh[key] = value.data.name }
188
+ hsh
189
+ end
190
+
191
+ def variable_value(key, default_value)
192
+ check_ready?(true)
193
+
194
+ assignment = variable_assignment(key)
195
+ unless assignment.nil? || assignment.variables.nil?
196
+ queue_exposure(assignment) unless assignment.exposed
197
+ return assignment.variables[key.to_s.to_sym] if assignment.variables.key?(key.to_s.to_sym)
198
+ end
199
+
200
+ default_value
201
+ end
202
+
203
+ def peek_variable_value(key, default_value)
204
+ check_ready?(true)
205
+
206
+ assignment = variable_assignment(key)
207
+ return assignment.variables[key.to_s.to_sym] if !assignment.nil? &&
208
+ !assignment.variables.nil? &&
209
+ assignment.variables.key?(key.to_s.to_sym)
210
+
211
+ default_value
212
+ end
213
+
214
+ def track(goal_name, properties)
215
+ check_not_closed?
216
+
217
+ achievement = GoalAchievement.new
218
+ achievement.achieved_at = @clock.to_i
219
+ achievement.name = goal_name
220
+ achievement.properties = properties
221
+
222
+ @pending_count += 1
223
+ @achievements.push(achievement)
224
+ end
225
+
226
+ def publish
227
+ check_not_closed?
228
+
229
+ flush
230
+ end
231
+
232
+ def refresh
233
+ check_not_closed?
234
+
235
+ unless @failed
236
+ data_future = @data_provider.context_data
237
+ if data_future.success?
238
+ assign_data(data_future.data_future)
239
+ else
240
+ set_data_failed(data_future.exception)
241
+ end
242
+ end
243
+ end
244
+
245
+ def close
246
+ unless @closed
247
+ if @pending_count > 0
248
+ flush
249
+ end
250
+ @closed = true
251
+ end
252
+ end
253
+
254
+ def data
255
+ check_ready?(true)
256
+
257
+ @data
258
+ end
259
+
260
+ private
261
+ def flush
262
+ if !@failed
263
+ if @pending_count > 0
264
+ exposures = nil
265
+ achievements = nil
266
+ event_count = @pending_count
267
+
268
+ if event_count > 0
269
+ unless @exposures.empty?
270
+ exposures = @exposures
271
+ @exposures = []
272
+ end
273
+
274
+ unless @achievements.empty?
275
+ achievements = @achievements
276
+ @achievements = []
277
+ end
278
+
279
+ @pending_count = 0
280
+
281
+ event = PublishEvent.new
282
+ event.hashed = true
283
+ event.published_at = @clock.to_i
284
+ event.units = @units.map do |key, value|
285
+ Unit.new(key, unit_hash(key, value))
286
+ end
287
+ event.attributes = @attributes.empty? ? nil : @attributes
288
+ event.exposures = exposures
289
+ event.goals = achievements
290
+ return @event_handler.publish(self, event)
291
+ end
292
+ end
293
+ else
294
+ @exposures = []
295
+ @achievements = []
296
+ @pending_count = 0
297
+ end
298
+ end
299
+
300
+ def check_not_closed?
301
+ if @closed
302
+ raise IllegalStateException.new("ABSmartly Context is closed")
303
+ end
304
+ end
305
+
306
+ def check_ready?(expect_not_closed)
307
+ if !ready?
308
+ raise IllegalStateException.new("ABSmartly Context is not yet ready")
309
+ elsif expect_not_closed
310
+ check_not_closed?
311
+ end
312
+ end
313
+
314
+ def experiment_matches(experiment, assignment)
315
+ experiment.id == assignment.id &&
316
+ experiment.unit_type == assignment.unit_type &&
317
+ experiment.iteration == assignment.iteration &&
318
+ experiment.full_on_variant == assignment.full_on_variant &&
319
+ experiment.traffic_split == assignment.traffic_split
320
+ end
321
+
322
+ def assignment(experiment_name)
323
+ assignment = @assignment_cache[experiment_name.to_s]
324
+
325
+ if !assignment.nil?
326
+ custom = @cassignments.transform_keys(&:to_sym)[experiment_name.to_s.to_sym]
327
+ override = @overrides.transform_keys(&:to_sym)[experiment_name.to_s.to_sym]
328
+ experiment = experiment(experiment_name.to_s)
329
+ if !override.nil?
330
+ if assignment.overridden && assignment.variant == override
331
+ return assignment
332
+ end
333
+ elsif experiment.nil?
334
+ if !assignment.assigned
335
+ return assignment
336
+ end
337
+ elsif custom.nil? || custom == assignment.variant
338
+ return assignment if experiment_matches(experiment.data, assignment)
339
+ end
340
+ end
341
+
342
+ custom = @cassignments.transform_keys(&:to_sym)[experiment_name.to_s.to_sym]
343
+ override = @overrides.transform_keys(&:to_sym)[experiment_name.to_s.to_sym]
344
+ experiment = experiment(experiment_name.to_s)
345
+
346
+ assignment = Assignment.new
347
+ assignment.name = experiment_name
348
+ assignment.eligible = true
349
+
350
+ if !override.nil?
351
+ unless experiment.nil?
352
+ assignment.id = experiment.data.id
353
+ assignment.unit_type = experiment.data.unit_type
354
+ end
355
+
356
+ assignment.overridden = true
357
+ assignment.variant = override
358
+ else
359
+ unless experiment.nil?
360
+ unit_type = experiment.data.unit_type
361
+
362
+ if !experiment.data.audience.nil? && experiment.data.audience.size > 0
363
+ attrs = @attributes.inject({}) do |hash, attr|
364
+ hash[attr.name] = attr.value
365
+ hash
366
+ end
367
+ match = @audience_matcher.evaluate(experiment.data.audience, attrs)
368
+ if match.nil? || !match.result
369
+ assignment.audience_mismatch = true
370
+ end
371
+ end
372
+
373
+ if experiment.data.audience_strict && assignment.audience_mismatch
374
+ assignment.variant = 0
375
+ elsif experiment.data.full_on_variant == 0
376
+ uid = @units.transform_keys(&:to_sym)[experiment.data.unit_type.to_sym]
377
+ unless uid.nil?
378
+ assigner = VariantAssigner.new(uid)
379
+ eligible = assigner.assign(experiment.data.traffic_split,
380
+ experiment.data.traffic_seed_hi,
381
+ experiment.data.traffic_seed_lo) == 1
382
+ if eligible
383
+ if !custom.nil?
384
+ assignment.variant = custom
385
+ assignment.custom = true
386
+ else
387
+ assignment.variant = assigner.assign(experiment.data.split,
388
+ experiment.data.seed_hi,
389
+ experiment.data.seed_lo)
390
+ end
391
+ else
392
+ assignment.eligible = false
393
+ assignment.variant = 0
394
+ end
395
+ assignment.assigned = true
396
+ end
397
+ else
398
+ assignment.assigned = true
399
+ assignment.variant = experiment.data.full_on_variant
400
+ assignment.full_on = true
401
+ end
402
+
403
+ assignment.unit_type = unit_type
404
+ assignment.id = experiment.data.id
405
+ assignment.iteration = experiment.data.iteration
406
+ assignment.traffic_split = experiment.data.traffic_split
407
+ assignment.full_on_variant = experiment.data.full_on_variant
408
+ end
409
+ end
410
+
411
+ if !experiment.nil? && assignment.variant < experiment.data.variants.length
412
+ assignment.variables = experiment.variables[assignment.variant] || {}
413
+ end
414
+
415
+ @assignment_cache[experiment_name.to_s] = assignment
416
+ assignment
417
+ end
418
+
419
+ def variable_assignment(key)
420
+ experiment = variable_experiment(key)
421
+
422
+ assignment(experiment.data.name) unless experiment.nil?
423
+ end
424
+
425
+ def experiment(experiment)
426
+ @index.transform_keys(&:to_sym)[experiment.to_s.to_sym]
427
+ end
428
+
429
+ def variable_experiment(key)
430
+ @index_variables.transform_keys(&:to_sym)[key.to_s.to_sym]
431
+ end
432
+
433
+ def unit_hash(unit_type, unit_uid)
434
+ @hashed_units[unit_type] = Hashing.hash_unit(unit_uid)
435
+ end
436
+
437
+ def variant_assigner(unit_type, unit_hash)
438
+ @assigners[unit_type] ||= VariantAssigner.new(unit_hash)
439
+ end
440
+
441
+ def assign_data(data)
442
+ @data = data
443
+ @index = {}
444
+ @index_variables = {}
445
+ if data && !data.experiments.nil? && !data.experiments.empty?
446
+ data.experiments.each do |experiment|
447
+ experiment_variables = ExperimentVariables.new
448
+ experiment_variables.data = experiment
449
+ experiment_variables.variables ||= []
450
+ experiment.variants.each do |variant|
451
+ if !variant.config.nil? && !variant.config.empty?
452
+ variables = @variable_parser.parse(self, experiment.name, variant.name,
453
+ variant.config)
454
+ variables.keys.each { |key| @index_variables[key] = experiment_variables }
455
+ experiment_variables.variables.push(variables)
456
+ else
457
+ experiment_variables.variables.push({})
458
+ end
459
+ end
460
+
461
+ @index[experiment.name] = experiment_variables
462
+ end
463
+ end
464
+ end
465
+
466
+ def set_data_failed(exception)
467
+ @data_failed = exception
468
+ @index = {}
469
+ @index_variables = {}
470
+ @data = ContextData.new
471
+ @failed = true
472
+ end
473
+
474
+ attr_accessor :clock,
475
+ :publish_delay,
476
+ :event_handler,
477
+ :event_logger,
478
+ :data_provider,
479
+ :variable_parser,
480
+ :audience_matcher,
481
+ :scheduler,
482
+ :units,
483
+ :failed,
484
+ :data_lock,
485
+ :index,
486
+ :index_variables,
487
+ :context_lock,
488
+ :hashed_units,
489
+ :assigners,
490
+ :assignment_cache,
491
+ :event_lock,
492
+ :exposures,
493
+ :achievements,
494
+ :attributes,
495
+ :overrides,
496
+ :cassignments,
497
+ :closed,
498
+ :refreshing,
499
+ :ready_future,
500
+ :refresh_future
501
+ attr_writer :data, :pending_count
502
+ end
503
+
504
+ class Assignment
505
+ attr_accessor :id, :iteration, :full_on_variant, :name, :unit_type,
506
+ :traffic_split, :variant, :assigned, :overridden, :eligible,
507
+ :full_on, :custom, :audience_mismatch, :variables, :exposed
508
+
509
+ def initialize
510
+ @variant = 0
511
+ @iteration = 0
512
+ @full_on_variant = 0
513
+ @overridden = false
514
+ @assigned = false
515
+ @exposed = false
516
+ @eligible = true
517
+ @full_on = false
518
+ @custom = false
519
+ @audience_mismatch = false
520
+ end
521
+ end
522
+
523
+ class ExperimentVariables
524
+ attr_accessor :data, :variables
525
+ end
526
+
527
+ class IllegalStateException < StandardError
528
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ContextConfig
4
+ attr_accessor :units, :attributes, :custom_assignments, :overrides,
5
+ :event_logger, :publish_delay, :refresh_interval
6
+
7
+ def self.create
8
+ ContextConfig.new
9
+ end
10
+
11
+ def initialize
12
+ @units = {}
13
+ @attributes = {}
14
+ @overrides = {}
15
+ @custom_assignments = {}
16
+ end
17
+
18
+ def set_unit(unit_type, uid)
19
+ @units[unit_type.to_sym] = uid
20
+ self
21
+ end
22
+
23
+ def set_units(units)
24
+ units.map { |k, v| set_unit(k, v) }
25
+ end
26
+
27
+ def unit(unit_type)
28
+ @units[unit_type.to_sym]
29
+ end
30
+
31
+ def set_attributes(attributes)
32
+ @attributes ||= attributes.transform_keys(&:to_sym)
33
+ end
34
+
35
+ def set_attribute(name, value)
36
+ @attributes[name.to_sym] = value
37
+ self
38
+ end
39
+
40
+ def attribute(name)
41
+ @attributes[name.to_sym]
42
+ end
43
+
44
+ def set_overrides(overrides)
45
+ @overrides.merge!(overrides.transform_keys(&:to_sym))
46
+ end
47
+
48
+ def set_override(experiment_name, variant)
49
+ @overrides[experiment_name.to_sym] = variant
50
+ self
51
+ end
52
+
53
+ def override(experiment_name)
54
+ @overrides[experiment_name.to_sym]
55
+ end
56
+
57
+ def set_custom_assignment(experiment_name, variant)
58
+ @custom_assignments[experiment_name.to_sym] = variant
59
+ self
60
+ end
61
+
62
+ def set_custom_assignments(customAssignments)
63
+ @custom_assignments.merge!(customAssignments.transform_keys(&:to_sym))
64
+ self
65
+ end
66
+
67
+ def custom_assignment(experiment_name)
68
+ @custom_assignments[experiment_name.to_sym]
69
+ end
70
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ContextDataDeserializer
4
+ # @interface method
5
+ def deserialize(bytes, offset, length)
6
+ raise NotImplementedError.new("You must implement deserialize method.")
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ContextDataProvider
4
+ # @interface method
5
+ def context_data
6
+ raise NotImplementedError.new("You must implement context_data method.")
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ContextEventHandler
4
+ # @interface method
5
+ def publish(context, event)
6
+ raise NotImplementedError.new("You must implement publish method.")
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ContextEventLogger
4
+ EVENT_TYPE = %w[error ready refresh publish exposure goal close]
5
+ # @interface method
6
+ def handle_event
7
+ raise NotImplementedError.new("You must implement handleEvent method.")
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ContextEventSerializer
4
+ # @interface method
5
+ def serialize(publish_event)
6
+ raise NotImplementedError.new("You must implement serialize method.")
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "audience_deserializer"
4
+
5
+ class DefaultAudienceDeserializer < AudienceDeserializer
6
+ attr_accessor :log, :reader
7
+
8
+ def deserialize(bytes, offset, length)
9
+ JSON.parse(bytes[offset..length], symbolize_names: true)
10
+ rescue JSON::ParserError
11
+ nil
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "context_data_deserializer"
5
+ require_relative "json/context_data"
6
+
7
+ class DefaultContextDataDeserializer < ContextDataDeserializer
8
+ attr_accessor :log, :reader
9
+
10
+ def deserialize(bytes, offset, length)
11
+ parse = JSON.parse(bytes[offset..length], symbolize_names: true)
12
+ @reader = ContextData.new(parse[:experiments])
13
+ rescue JSON::ParserError
14
+ nil
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "context_data_provider"
4
+
5
+ class DefaultContextDataProvider < ContextDataProvider
6
+ attr_accessor :client
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def context_data
13
+ @client.context_data
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "context_data_provider"
4
+
5
+ class DefaultContextEventHandler < ContextDataProvider
6
+ attr_accessor :client
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def publish(context, event)
13
+ @client.publish(event)
14
+ end
15
+ end