absmartly-sdk 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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