launchdarkly-server-sdk 5.7.3 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +28 -122
  3. data/.gitignore +1 -1
  4. data/.ldrelease/build-docs.sh +18 -0
  5. data/.ldrelease/circleci/linux/execute.sh +18 -0
  6. data/.ldrelease/circleci/mac/execute.sh +18 -0
  7. data/.ldrelease/circleci/template/build.sh +29 -0
  8. data/.ldrelease/circleci/template/publish.sh +23 -0
  9. data/.ldrelease/circleci/template/set-gem-home.sh +7 -0
  10. data/.ldrelease/circleci/template/test.sh +10 -0
  11. data/.ldrelease/circleci/template/update-version.sh +8 -0
  12. data/.ldrelease/circleci/windows/execute.ps1 +19 -0
  13. data/.ldrelease/config.yml +14 -2
  14. data/CHANGELOG.md +36 -0
  15. data/CONTRIBUTING.md +1 -1
  16. data/Gemfile.lock +92 -76
  17. data/README.md +5 -3
  18. data/azure-pipelines.yml +1 -1
  19. data/docs/Makefile +26 -0
  20. data/docs/index.md +9 -0
  21. data/launchdarkly-server-sdk.gemspec +20 -13
  22. data/lib/ldclient-rb.rb +0 -1
  23. data/lib/ldclient-rb/config.rb +15 -3
  24. data/lib/ldclient-rb/evaluation_detail.rb +293 -0
  25. data/lib/ldclient-rb/events.rb +1 -4
  26. data/lib/ldclient-rb/file_data_source.rb +1 -1
  27. data/lib/ldclient-rb/impl/evaluator.rb +225 -0
  28. data/lib/ldclient-rb/impl/evaluator_bucketing.rb +74 -0
  29. data/lib/ldclient-rb/impl/evaluator_operators.rb +160 -0
  30. data/lib/ldclient-rb/impl/event_sender.rb +56 -40
  31. data/lib/ldclient-rb/impl/integrations/consul_impl.rb +5 -5
  32. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +5 -5
  33. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +8 -7
  34. data/lib/ldclient-rb/impl/model/serialization.rb +62 -0
  35. data/lib/ldclient-rb/impl/unbounded_pool.rb +34 -0
  36. data/lib/ldclient-rb/integrations/redis.rb +3 -0
  37. data/lib/ldclient-rb/ldclient.rb +16 -11
  38. data/lib/ldclient-rb/polling.rb +1 -4
  39. data/lib/ldclient-rb/redis_store.rb +1 -0
  40. data/lib/ldclient-rb/requestor.rb +25 -23
  41. data/lib/ldclient-rb/stream.rb +10 -30
  42. data/lib/ldclient-rb/user_filter.rb +3 -2
  43. data/lib/ldclient-rb/util.rb +12 -8
  44. data/lib/ldclient-rb/version.rb +1 -1
  45. data/spec/evaluation_detail_spec.rb +135 -0
  46. data/spec/event_sender_spec.rb +20 -2
  47. data/spec/events_spec.rb +11 -0
  48. data/spec/http_util.rb +11 -1
  49. data/spec/impl/evaluator_bucketing_spec.rb +111 -0
  50. data/spec/impl/evaluator_clause_spec.rb +55 -0
  51. data/spec/impl/evaluator_operators_spec.rb +141 -0
  52. data/spec/impl/evaluator_rule_spec.rb +96 -0
  53. data/spec/impl/evaluator_segment_spec.rb +125 -0
  54. data/spec/impl/evaluator_spec.rb +305 -0
  55. data/spec/impl/evaluator_spec_base.rb +75 -0
  56. data/spec/impl/model/serialization_spec.rb +41 -0
  57. data/spec/launchdarkly-server-sdk_spec.rb +1 -1
  58. data/spec/ldclient_end_to_end_spec.rb +34 -0
  59. data/spec/ldclient_spec.rb +10 -8
  60. data/spec/polling_spec.rb +2 -2
  61. data/spec/redis_feature_store_spec.rb +32 -3
  62. data/spec/requestor_spec.rb +11 -45
  63. data/spec/spec_helper.rb +0 -3
  64. data/spec/stream_spec.rb +1 -16
  65. metadata +110 -60
  66. data/.yardopts +0 -9
  67. data/lib/ldclient-rb/evaluation.rb +0 -462
  68. data/scripts/gendocs.sh +0 -11
  69. data/scripts/release.sh +0 -27
  70. data/spec/evaluation_spec.rb +0 -789
@@ -1,4 +1,7 @@
1
+ require "ldclient-rb/impl/unbounded_pool"
2
+
1
3
  require "securerandom"
4
+ require "http"
2
5
 
3
6
  module LaunchDarkly
4
7
  module Impl
@@ -9,62 +12,75 @@ module LaunchDarkly
9
12
  DEFAULT_RETRY_INTERVAL = 1
10
13
 
11
14
  def initialize(sdk_key, config, http_client = nil, retry_interval = DEFAULT_RETRY_INTERVAL)
12
- @client = http_client ? http_client : LaunchDarkly::Util.new_http_client(config.events_uri, config)
13
15
  @sdk_key = sdk_key
14
16
  @config = config
15
17
  @events_uri = config.events_uri + "/bulk"
16
18
  @diagnostic_uri = config.events_uri + "/diagnostic"
17
19
  @logger = config.logger
18
20
  @retry_interval = retry_interval
21
+ @http_client_pool = UnboundedPool.new(
22
+ lambda { LaunchDarkly::Util.new_http_client(@config.events_uri, @config) },
23
+ lambda { |client| client.close })
24
+ end
25
+
26
+ def stop
27
+ @http_client_pool.dispose_all()
19
28
  end
20
29
 
21
30
  def send_event_data(event_data, description, is_diagnostic)
22
31
  uri = is_diagnostic ? @diagnostic_uri : @events_uri
23
32
  payload_id = is_diagnostic ? nil : SecureRandom.uuid
24
- res = nil
25
- (0..1).each do |attempt|
26
- if attempt > 0
27
- @logger.warn { "[LDClient] Will retry posting events after #{@retry_interval} second" }
28
- sleep(@retry_interval)
29
- end
30
- begin
31
- @client.start if !@client.started?
32
- @logger.debug { "[LDClient] sending #{description}: #{event_data}" }
33
- req = Net::HTTP::Post.new(uri)
34
- req.content_type = "application/json"
35
- req.body = event_data
36
- Impl::Util.default_http_headers(@sdk_key, @config).each { |k, v| req[k] = v }
37
- if !is_diagnostic
38
- req["X-LaunchDarkly-Event-Schema"] = CURRENT_SCHEMA_VERSION.to_s
39
- req["X-LaunchDarkly-Payload-ID"] = payload_id
33
+ begin
34
+ http_client = @http_client_pool.acquire()
35
+ response = nil
36
+ (0..1).each do |attempt|
37
+ if attempt > 0
38
+ @logger.warn { "[LDClient] Will retry posting events after #{@retry_interval} second" }
39
+ sleep(@retry_interval)
40
40
  end
41
- req["Connection"] = "keep-alive"
42
- res = @client.request(req)
43
- rescue StandardError => exn
44
- @logger.warn { "[LDClient] Error sending events: #{exn.inspect}." }
45
- next
46
- end
47
- status = res.code.to_i
48
- if status >= 200 && status < 300
49
- res_time = nil
50
- if !res["date"].nil?
51
- begin
52
- res_time = Time.httpdate(res["date"])
53
- rescue ArgumentError
41
+ begin
42
+ @logger.debug { "[LDClient] sending #{description}: #{event_data}" }
43
+ headers = {}
44
+ headers["content-type"] = "application/json"
45
+ Impl::Util.default_http_headers(@sdk_key, @config).each { |k, v| headers[k] = v }
46
+ if !is_diagnostic
47
+ headers["X-LaunchDarkly-Event-Schema"] = CURRENT_SCHEMA_VERSION.to_s
48
+ headers["X-LaunchDarkly-Payload-ID"] = payload_id
54
49
  end
50
+ response = http_client.request("POST", uri, {
51
+ headers: headers,
52
+ body: event_data
53
+ })
54
+ rescue StandardError => exn
55
+ @logger.warn { "[LDClient] Error sending events: #{exn.inspect}." }
56
+ next
57
+ end
58
+ status = response.status.code
59
+ # must fully read body for persistent connections
60
+ body = response.to_s
61
+ if status >= 200 && status < 300
62
+ res_time = nil
63
+ if !response.headers["date"].nil?
64
+ begin
65
+ res_time = Time.httpdate(response.headers["date"])
66
+ rescue ArgumentError
67
+ end
68
+ end
69
+ return EventSenderResult.new(true, false, res_time)
70
+ end
71
+ must_shutdown = !LaunchDarkly::Util.http_error_recoverable?(status)
72
+ can_retry = !must_shutdown && attempt == 0
73
+ message = LaunchDarkly::Util.http_error_message(status, "event delivery", can_retry ? "will retry" : "some events were dropped")
74
+ @logger.error { "[LDClient] #{message}" }
75
+ if must_shutdown
76
+ return EventSenderResult.new(false, true, nil)
55
77
  end
56
- return EventSenderResult.new(true, false, res_time)
57
- end
58
- must_shutdown = !LaunchDarkly::Util.http_error_recoverable?(status)
59
- can_retry = !must_shutdown && attempt == 0
60
- message = LaunchDarkly::Util.http_error_message(status, "event delivery", can_retry ? "will retry" : "some events were dropped")
61
- @logger.error { "[LDClient] #{message}" }
62
- if must_shutdown
63
- return EventSenderResult.new(false, true, nil)
64
78
  end
79
+ # used up our retries
80
+ return EventSenderResult.new(false, false, nil)
81
+ ensure
82
+ @http_client_pool.release(http_client)
65
83
  end
66
- # used up our retries
67
- return EventSenderResult.new(false, false, nil)
68
84
  end
69
85
  end
70
86
  end
@@ -39,7 +39,7 @@ module LaunchDarkly
39
39
  # Insert or update every provided item
40
40
  all_data.each do |kind, items|
41
41
  items.values.each do |item|
42
- value = item.to_json
42
+ value = Model.serialize(kind, item)
43
43
  key = item_key(kind, item[:key])
44
44
  ops.push({ 'KV' => { 'Verb' => 'set', 'Key' => key, 'Value' => value } })
45
45
  unused_old_keys.delete(key)
@@ -62,7 +62,7 @@ module LaunchDarkly
62
62
 
63
63
  def get_internal(kind, key)
64
64
  value = Diplomat::Kv.get(item_key(kind, key), {}, :return) # :return means "don't throw an error if not found"
65
- (value.nil? || value == "") ? nil : JSON.parse(value, symbolize_names: true)
65
+ (value.nil? || value == "") ? nil : Model.deserialize(kind, value)
66
66
  end
67
67
 
68
68
  def get_all_internal(kind)
@@ -71,7 +71,7 @@ module LaunchDarkly
71
71
  (results == "" ? [] : results).each do |result|
72
72
  value = result[:value]
73
73
  if !value.nil?
74
- item = JSON.parse(value, symbolize_names: true)
74
+ item = Model.deserialize(kind, value)
75
75
  items_out[item[:key].to_sym] = item
76
76
  end
77
77
  end
@@ -80,7 +80,7 @@ module LaunchDarkly
80
80
 
81
81
  def upsert_internal(kind, new_item)
82
82
  key = item_key(kind, new_item[:key])
83
- json = new_item.to_json
83
+ json = Model.serialize(kind, new_item)
84
84
 
85
85
  # We will potentially keep retrying indefinitely until someone's write succeeds
86
86
  while true
@@ -88,7 +88,7 @@ module LaunchDarkly
88
88
  if old_value.nil? || old_value == ""
89
89
  mod_index = 0
90
90
  else
91
- old_item = JSON.parse(old_value[0]["Value"], symbolize_names: true)
91
+ old_item = Model.deserialize(kind, old_value[0]["Value"])
92
92
  # Check whether the item is stale. If so, don't do the update (and return the existing item to
93
93
  # FeatureStoreWrapper so it can be cached)
94
94
  if old_item[:version] >= new_item[:version]
@@ -77,7 +77,7 @@ module LaunchDarkly
77
77
 
78
78
  def get_internal(kind, key)
79
79
  resp = get_item_by_keys(namespace_for_kind(kind), key)
80
- unmarshal_item(resp.item)
80
+ unmarshal_item(kind, resp.item)
81
81
  end
82
82
 
83
83
  def get_all_internal(kind)
@@ -86,7 +86,7 @@ module LaunchDarkly
86
86
  while true
87
87
  resp = @client.query(req)
88
88
  resp.items.each do |item|
89
- item_out = unmarshal_item(item)
89
+ item_out = unmarshal_item(kind, item)
90
90
  items_out[item_out[:key].to_sym] = item_out
91
91
  end
92
92
  break if resp.last_evaluated_key.nil? || resp.last_evaluated_key.length == 0
@@ -196,15 +196,15 @@ module LaunchDarkly
196
196
  def marshal_item(kind, item)
197
197
  make_keys_hash(namespace_for_kind(kind), item[:key]).merge({
198
198
  VERSION_ATTRIBUTE => item[:version],
199
- ITEM_JSON_ATTRIBUTE => item.to_json
199
+ ITEM_JSON_ATTRIBUTE => Model.serialize(kind, item)
200
200
  })
201
201
  end
202
202
 
203
- def unmarshal_item(item)
203
+ def unmarshal_item(kind, item)
204
204
  return nil if item.nil? || item.length == 0
205
205
  json_attr = item[ITEM_JSON_ATTRIBUTE]
206
206
  raise RuntimeError.new("DynamoDB map did not contain expected item string") if json_attr.nil?
207
- JSON.parse(json_attr, symbolize_names: true)
207
+ Model.deserialize(kind, json_attr)
208
208
  end
209
209
  end
210
210
 
@@ -33,6 +33,8 @@ module LaunchDarkly
33
33
  @pool = opts[:pool] || ConnectionPool.new(size: max_connections) do
34
34
  ::Redis.new(@redis_opts)
35
35
  end
36
+ # shutdown pool on close unless the client passed a custom pool and specified not to shutdown
37
+ @pool_shutdown_on_close = (!opts[:pool] || opts.fetch(:pool_shutdown_on_close, true))
36
38
  @prefix = opts[:prefix] || LaunchDarkly::Integrations::Redis::default_prefix
37
39
  @logger = opts[:logger] || Config.default_logger
38
40
  @test_hook = opts[:test_hook] # used for unit tests, deliberately undocumented
@@ -53,7 +55,7 @@ module LaunchDarkly
53
55
  multi.del(items_key(kind))
54
56
  count = count + items.count
55
57
  items.each do |key, item|
56
- multi.hset(items_key(kind), key, item.to_json)
58
+ multi.hset(items_key(kind), key, Model.serialize(kind,item))
57
59
  end
58
60
  end
59
61
  multi.set(inited_key, inited_key)
@@ -73,8 +75,7 @@ module LaunchDarkly
73
75
  with_connection do |redis|
74
76
  hashfs = redis.hgetall(items_key(kind))
75
77
  hashfs.each do |k, json_item|
76
- f = JSON.parse(json_item, symbolize_names: true)
77
- fs[k.to_sym] = f
78
+ fs[k.to_sym] = Model.deserialize(kind, json_item)
78
79
  end
79
80
  end
80
81
  fs
@@ -93,7 +94,7 @@ module LaunchDarkly
93
94
  before_update_transaction(base_key, key)
94
95
  if old_item.nil? || old_item[:version] < new_item[:version]
95
96
  result = redis.multi do |multi|
96
- multi.hset(base_key, key, new_item.to_json)
97
+ multi.hset(base_key, key, Model.serialize(kind, new_item))
97
98
  end
98
99
  if result.nil?
99
100
  @logger.debug { "RedisFeatureStore: concurrent modification detected, retrying" }
@@ -113,11 +114,12 @@ module LaunchDarkly
113
114
  end
114
115
 
115
116
  def initialized_internal?
116
- with_connection { |redis| redis.exists(inited_key) }
117
+ with_connection { |redis| redis.exists?(inited_key) }
117
118
  end
118
119
 
119
120
  def stop
120
121
  if @stopped.make_true
122
+ return unless @pool_shutdown_on_close
121
123
  @pool.shutdown { |redis| redis.close }
122
124
  end
123
125
  end
@@ -145,8 +147,7 @@ module LaunchDarkly
145
147
  end
146
148
 
147
149
  def get_redis(redis, kind, key)
148
- json_item = redis.hget(items_key(kind), key)
149
- json_item.nil? ? nil : JSON.parse(json_item, symbolize_names: true)
150
+ Model.deserialize(kind, redis.hget(items_key(kind), key))
150
151
  end
151
152
  end
152
153
  end
@@ -0,0 +1,62 @@
1
+
2
+ module LaunchDarkly
3
+ module Impl
4
+ module Model
5
+ # Abstraction of deserializing a feature flag or segment that was read from a data store or
6
+ # received from LaunchDarkly.
7
+ def self.deserialize(kind, json)
8
+ return nil if json.nil?
9
+ item = JSON.parse(json, symbolize_names: true)
10
+ postprocess_item_after_deserializing!(kind, item)
11
+ item
12
+ end
13
+
14
+ # Abstraction of serializing a feature flag or segment that will be written to a data store.
15
+ # Currently we just call to_json.
16
+ def self.serialize(kind, item)
17
+ item.to_json
18
+ end
19
+
20
+ # Translates a { flags: ..., segments: ... } object received from LaunchDarkly to the data store format.
21
+ def self.make_all_store_data(received_data)
22
+ flags = received_data[:flags]
23
+ postprocess_items_after_deserializing!(FEATURES, flags)
24
+ segments = received_data[:segments]
25
+ postprocess_items_after_deserializing!(SEGMENTS, segments)
26
+ { FEATURES => flags, SEGMENTS => segments }
27
+ end
28
+
29
+ # Called after we have deserialized a model item from JSON (because we received it from LaunchDarkly,
30
+ # or read it from a persistent data store). This allows us to precompute some derived attributes that
31
+ # will never change during the lifetime of that item.
32
+ def self.postprocess_item_after_deserializing!(kind, item)
33
+ return if !item
34
+ # Currently we are special-casing this for FEATURES; eventually it will be handled by delegating
35
+ # to the "kind" object or the item class.
36
+ if kind.eql? FEATURES
37
+ # For feature flags, we precompute all possible parameterized EvaluationReason instances.
38
+ prereqs = item[:prerequisites]
39
+ if !prereqs.nil?
40
+ prereqs.each do |prereq|
41
+ prereq[:_reason] = EvaluationReason::prerequisite_failed(prereq[:key])
42
+ end
43
+ end
44
+ rules = item[:rules]
45
+ if !rules.nil?
46
+ rules.each_index do |i|
47
+ rule = rules[i]
48
+ rule[:_reason] = EvaluationReason::rule_match(i, rule[:id])
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def self.postprocess_items_after_deserializing!(kind, items_map)
55
+ return items_map if !items_map
56
+ items_map.each do |key, item|
57
+ postprocess_item_after_deserializing!(kind, item)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,34 @@
1
+ module LaunchDarkly
2
+ module Impl
3
+ # A simple thread safe generic unbounded resource pool abstraction
4
+ class UnboundedPool
5
+ def initialize(instance_creator, instance_destructor)
6
+ @pool = Array.new
7
+ @lock = Mutex.new
8
+ @instance_creator = instance_creator
9
+ @instance_destructor = instance_destructor
10
+ end
11
+
12
+ def acquire
13
+ @lock.synchronize {
14
+ if @pool.length == 0
15
+ @instance_creator.call()
16
+ else
17
+ @pool.pop()
18
+ end
19
+ }
20
+ end
21
+
22
+ def release(instance)
23
+ @lock.synchronize { @pool.push(instance) }
24
+ end
25
+
26
+ def dispose_all
27
+ @lock.synchronize {
28
+ @pool.map { |instance| @instance_destructor.call(instance) } if !@instance_destructor.nil?
29
+ @pool.clear()
30
+ }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -45,6 +45,9 @@ module LaunchDarkly
45
45
  # @option opts [Integer] :expiration (15) expiration time for the in-memory cache, in seconds; 0 for no local caching
46
46
  # @option opts [Integer] :capacity (1000) maximum number of items in the cache
47
47
  # @option opts [Object] :pool custom connection pool, if desired
48
+ # @option opts [Boolean] :pool_shutdown_on_close whether calling `close` should shutdown the custom connection pool;
49
+ # this is true by default, and should be set to false only if you are managing the pool yourself and want its
50
+ # lifecycle to be independent of the SDK client
48
51
  # @return [LaunchDarkly::Interfaces::FeatureStore] a feature store object
49
52
  #
50
53
  def self.new_feature_store(opts)
@@ -1,4 +1,5 @@
1
1
  require "ldclient-rb/impl/diagnostic_events"
2
+ require "ldclient-rb/impl/evaluator"
2
3
  require "ldclient-rb/impl/event_factory"
3
4
  require "ldclient-rb/impl/store_client_wrapper"
4
5
  require "concurrent/atomics"
@@ -14,7 +15,6 @@ module LaunchDarkly
14
15
  # should create a single client instance for the lifetime of the application.
15
16
  #
16
17
  class LDClient
17
- include Evaluation
18
18
  include Impl
19
19
  #
20
20
  # Creates a new client instance that connects to LaunchDarkly. A custom
@@ -57,6 +57,10 @@ module LaunchDarkly
57
57
  updated_config.instance_variable_set(:@feature_store, @store)
58
58
  @config = updated_config
59
59
 
60
+ get_flag = lambda { |key| @store.get(FEATURES, key) }
61
+ get_segment = lambda { |key| @store.get(SEGMENTS, key) }
62
+ @evaluator = LaunchDarkly::Impl::Evaluator.new(get_flag, get_segment, @config.logger)
63
+
60
64
  if !@config.offline? && @config.send_events && !@config.diagnostic_opt_out?
61
65
  diagnostic_accumulator = Impl::DiagnosticAccumulator.new(Impl::DiagnosticAccumulator.create_diagnostic_id(sdk_key))
62
66
  else
@@ -333,12 +337,13 @@ module LaunchDarkly
333
337
  next
334
338
  end
335
339
  begin
336
- result = evaluate(f, user, @store, @config.logger, @event_factory_default)
340
+ result = @evaluator.evaluate(f, user, @event_factory_default)
337
341
  state.add_flag(f, result.detail.value, result.detail.variation_index, with_reasons ? result.detail.reason : nil,
338
342
  details_only_if_tracked)
339
343
  rescue => exn
340
344
  Util.log_exception(@config.logger, "Error evaluating flag \"#{k}\" in all_flags_state", exn)
341
- state.add_flag(f, nil, nil, with_reasons ? { kind: 'ERROR', errorKind: 'EXCEPTION' } : nil, details_only_if_tracked)
345
+ state.add_flag(f, nil, nil, with_reasons ? EvaluationReason::error(EvaluationReason::ERROR_EXCEPTION) : nil,
346
+ details_only_if_tracked)
342
347
  end
343
348
  end
344
349
 
@@ -363,12 +368,12 @@ module LaunchDarkly
363
368
  return NullUpdateProcessor.new
364
369
  end
365
370
  raise ArgumentError, "sdk_key must not be nil" if sdk_key.nil? # see LDClient constructor comment on sdk_key
366
- requestor = Requestor.new(sdk_key, config)
367
371
  if config.stream?
368
- StreamProcessor.new(sdk_key, config, requestor, diagnostic_accumulator)
372
+ StreamProcessor.new(sdk_key, config, diagnostic_accumulator)
369
373
  else
370
374
  config.logger.info { "Disabling streaming API" }
371
375
  config.logger.warn { "You should only disable the streaming API if instructed to do so by LaunchDarkly support" }
376
+ requestor = Requestor.new(sdk_key, config)
372
377
  PollingProcessor.new(config, requestor)
373
378
  end
374
379
  end
@@ -376,7 +381,7 @@ module LaunchDarkly
376
381
  # @return [EvaluationDetail]
377
382
  def evaluate_internal(key, user, default, event_factory)
378
383
  if @config.offline?
379
- return error_result('CLIENT_NOT_READY', default)
384
+ return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
380
385
  end
381
386
 
382
387
  if !initialized?
@@ -384,7 +389,7 @@ module LaunchDarkly
384
389
  @config.logger.warn { "[LDClient] Client has not finished initializing; using last known values from feature store" }
385
390
  else
386
391
  @config.logger.error { "[LDClient] Client has not finished initializing; feature store unavailable, returning default value" }
387
- detail = error_result('CLIENT_NOT_READY', default)
392
+ detail = Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
388
393
  @event_processor.add_event(event_factory.new_unknown_flag_event(key, user, default, detail.reason))
389
394
  return detail
390
395
  end
@@ -394,20 +399,20 @@ module LaunchDarkly
394
399
 
395
400
  if feature.nil?
396
401
  @config.logger.info { "[LDClient] Unknown feature flag \"#{key}\". Returning default value" }
397
- detail = error_result('FLAG_NOT_FOUND', default)
402
+ detail = Evaluator.error_result(EvaluationReason::ERROR_FLAG_NOT_FOUND, default)
398
403
  @event_processor.add_event(event_factory.new_unknown_flag_event(key, user, default, detail.reason))
399
404
  return detail
400
405
  end
401
406
 
402
407
  unless user
403
408
  @config.logger.error { "[LDClient] Must specify user" }
404
- detail = error_result('USER_NOT_SPECIFIED', default)
409
+ detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
405
410
  @event_processor.add_event(event_factory.new_default_event(feature, user, default, detail.reason))
406
411
  return detail
407
412
  end
408
413
 
409
414
  begin
410
- res = evaluate(feature, user, @store, @config.logger, event_factory)
415
+ res = @evaluator.evaluate(feature, user, event_factory)
411
416
  if !res.events.nil?
412
417
  res.events.each do |event|
413
418
  @event_processor.add_event(event)
@@ -421,7 +426,7 @@ module LaunchDarkly
421
426
  return detail
422
427
  rescue => exn
423
428
  Util.log_exception(@config.logger, "Error evaluating feature flag \"#{key}\"", exn)
424
- detail = error_result('EXCEPTION', default)
429
+ detail = Evaluator.error_result(EvaluationReason::ERROR_EXCEPTION, default)
425
430
  @event_processor.add_event(event_factory.new_default_event(feature, user, default, detail.reason))
426
431
  return detail
427
432
  end