launchdarkly-server-sdk 5.8.0 → 6.1.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 +5 -5
- data/.circleci/config.yml +28 -122
- data/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- data/.github/ISSUE_TEMPLATE/config.yml +5 -0
- data/.gitignore +2 -1
- data/.ldrelease/build-docs.sh +18 -0
- data/.ldrelease/circleci/linux/execute.sh +18 -0
- data/.ldrelease/circleci/mac/execute.sh +18 -0
- data/.ldrelease/circleci/template/build.sh +29 -0
- data/.ldrelease/circleci/template/publish.sh +23 -0
- data/.ldrelease/circleci/template/set-gem-home.sh +7 -0
- data/.ldrelease/circleci/template/test.sh +10 -0
- data/.ldrelease/circleci/template/update-version.sh +8 -0
- data/.ldrelease/circleci/windows/execute.ps1 +19 -0
- data/.ldrelease/config.yml +14 -2
- data/CHANGELOG.md +29 -0
- data/CONTRIBUTING.md +1 -1
- data/README.md +4 -3
- data/azure-pipelines.yml +1 -1
- data/docs/Makefile +26 -0
- data/docs/index.md +9 -0
- data/launchdarkly-server-sdk.gemspec +20 -13
- data/lib/ldclient-rb.rb +0 -1
- data/lib/ldclient-rb/config.rb +15 -3
- data/lib/ldclient-rb/evaluation_detail.rb +293 -0
- data/lib/ldclient-rb/events.rb +6 -7
- data/lib/ldclient-rb/file_data_source.rb +1 -1
- data/lib/ldclient-rb/impl/evaluator.rb +225 -0
- data/lib/ldclient-rb/impl/evaluator_bucketing.rb +74 -0
- data/lib/ldclient-rb/impl/evaluator_operators.rb +160 -0
- data/lib/ldclient-rb/impl/event_factory.rb +22 -0
- data/lib/ldclient-rb/impl/event_sender.rb +56 -40
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +5 -5
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +5 -5
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +5 -7
- data/lib/ldclient-rb/impl/model/serialization.rb +62 -0
- data/lib/ldclient-rb/impl/unbounded_pool.rb +34 -0
- data/lib/ldclient-rb/ldclient.rb +38 -17
- data/lib/ldclient-rb/polling.rb +1 -4
- data/lib/ldclient-rb/requestor.rb +25 -23
- data/lib/ldclient-rb/stream.rb +10 -30
- data/lib/ldclient-rb/util.rb +12 -8
- data/lib/ldclient-rb/version.rb +1 -1
- data/spec/evaluation_detail_spec.rb +135 -0
- data/spec/event_sender_spec.rb +20 -2
- data/spec/events_spec.rb +10 -0
- data/spec/http_util.rb +11 -1
- data/spec/impl/evaluator_bucketing_spec.rb +111 -0
- data/spec/impl/evaluator_clause_spec.rb +55 -0
- data/spec/impl/evaluator_operators_spec.rb +141 -0
- data/spec/impl/evaluator_rule_spec.rb +96 -0
- data/spec/impl/evaluator_segment_spec.rb +125 -0
- data/spec/impl/evaluator_spec.rb +305 -0
- data/spec/impl/evaluator_spec_base.rb +75 -0
- data/spec/impl/model/serialization_spec.rb +41 -0
- data/spec/launchdarkly-server-sdk_spec.rb +1 -1
- data/spec/ldclient_end_to_end_spec.rb +34 -0
- data/spec/ldclient_spec.rb +64 -12
- data/spec/polling_spec.rb +2 -2
- data/spec/redis_feature_store_spec.rb +2 -2
- data/spec/requestor_spec.rb +11 -45
- data/spec/spec_helper.rb +0 -3
- data/spec/stream_spec.rb +1 -16
- metadata +111 -61
- data/.yardopts +0 -9
- data/Gemfile.lock +0 -100
- data/lib/ldclient-rb/evaluation.rb +0 -462
- data/scripts/gendocs.sh +0 -11
- data/scripts/release.sh +0 -27
- data/spec/evaluation_spec.rb +0 -789
@@ -28,6 +28,7 @@ module LaunchDarkly
|
|
28
28
|
e[:debugEventsUntilDate] = flag[:debugEventsUntilDate] if flag[:debugEventsUntilDate]
|
29
29
|
e[:prereqOf] = prereq_of_flag[:key] if !prereq_of_flag.nil?
|
30
30
|
e[:reason] = detail.reason if add_experiment_data || @with_reasons
|
31
|
+
e[:contextKind] = context_to_context_kind(user) if !user.nil? && user[:anonymous]
|
31
32
|
e
|
32
33
|
end
|
33
34
|
|
@@ -43,6 +44,7 @@ module LaunchDarkly
|
|
43
44
|
e[:trackEvents] = true if flag[:trackEvents]
|
44
45
|
e[:debugEventsUntilDate] = flag[:debugEventsUntilDate] if flag[:debugEventsUntilDate]
|
45
46
|
e[:reason] = reason if @with_reasons
|
47
|
+
e[:contextKind] = context_to_context_kind(user) if !user.nil? && user[:anonymous]
|
46
48
|
e
|
47
49
|
end
|
48
50
|
|
@@ -55,6 +57,7 @@ module LaunchDarkly
|
|
55
57
|
default: default_value
|
56
58
|
}
|
57
59
|
e[:reason] = reason if @with_reasons
|
60
|
+
e[:contextKind] = context_to_context_kind(user) if !user.nil? && user[:anonymous]
|
58
61
|
e
|
59
62
|
end
|
60
63
|
|
@@ -66,6 +69,16 @@ module LaunchDarkly
|
|
66
69
|
}
|
67
70
|
end
|
68
71
|
|
72
|
+
def new_alias_event(current_context, previous_context)
|
73
|
+
{
|
74
|
+
kind: 'alias',
|
75
|
+
key: current_context[:key],
|
76
|
+
contextKind: context_to_context_kind(current_context),
|
77
|
+
previousKey: previous_context[:key],
|
78
|
+
previousContextKind: context_to_context_kind(previous_context)
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
69
82
|
def new_custom_event(event_name, user, data, metric_value)
|
70
83
|
e = {
|
71
84
|
kind: 'custom',
|
@@ -74,11 +87,20 @@ module LaunchDarkly
|
|
74
87
|
}
|
75
88
|
e[:data] = data if !data.nil?
|
76
89
|
e[:metricValue] = metric_value if !metric_value.nil?
|
90
|
+
e[:contextKind] = context_to_context_kind(user) if !user.nil? && user[:anonymous]
|
77
91
|
e
|
78
92
|
end
|
79
93
|
|
80
94
|
private
|
81
95
|
|
96
|
+
def context_to_context_kind(user)
|
97
|
+
if !user.nil? && user[:anonymous]
|
98
|
+
return "anonymousUser"
|
99
|
+
else
|
100
|
+
return "user"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
82
104
|
def is_experiment(flag, reason)
|
83
105
|
return false if !reason
|
84
106
|
case reason[:kind]
|
@@ -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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
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 :
|
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 =
|
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
|
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 =
|
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
|
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
|
-
|
207
|
+
Model.deserialize(kind, json_attr)
|
208
208
|
end
|
209
209
|
end
|
210
210
|
|
@@ -55,7 +55,7 @@ module LaunchDarkly
|
|
55
55
|
multi.del(items_key(kind))
|
56
56
|
count = count + items.count
|
57
57
|
items.each do |key, item|
|
58
|
-
multi.hset(items_key(kind), key, item
|
58
|
+
multi.hset(items_key(kind), key, Model.serialize(kind,item))
|
59
59
|
end
|
60
60
|
end
|
61
61
|
multi.set(inited_key, inited_key)
|
@@ -75,8 +75,7 @@ module LaunchDarkly
|
|
75
75
|
with_connection do |redis|
|
76
76
|
hashfs = redis.hgetall(items_key(kind))
|
77
77
|
hashfs.each do |k, json_item|
|
78
|
-
|
79
|
-
fs[k.to_sym] = f
|
78
|
+
fs[k.to_sym] = Model.deserialize(kind, json_item)
|
80
79
|
end
|
81
80
|
end
|
82
81
|
fs
|
@@ -95,7 +94,7 @@ module LaunchDarkly
|
|
95
94
|
before_update_transaction(base_key, key)
|
96
95
|
if old_item.nil? || old_item[:version] < new_item[:version]
|
97
96
|
result = redis.multi do |multi|
|
98
|
-
multi.hset(base_key, key, new_item
|
97
|
+
multi.hset(base_key, key, Model.serialize(kind, new_item))
|
99
98
|
end
|
100
99
|
if result.nil?
|
101
100
|
@logger.debug { "RedisFeatureStore: concurrent modification detected, retrying" }
|
@@ -115,7 +114,7 @@ module LaunchDarkly
|
|
115
114
|
end
|
116
115
|
|
117
116
|
def initialized_internal?
|
118
|
-
with_connection { |redis| redis.exists(inited_key) }
|
117
|
+
with_connection { |redis| redis.exists?(inited_key) }
|
119
118
|
end
|
120
119
|
|
121
120
|
def stop
|
@@ -148,8 +147,7 @@ module LaunchDarkly
|
|
148
147
|
end
|
149
148
|
|
150
149
|
def get_redis(redis, kind, key)
|
151
|
-
|
152
|
-
json_item.nil? ? nil : JSON.parse(json_item, symbolize_names: true)
|
150
|
+
Model.deserialize(kind, redis.hget(items_key(kind), key))
|
153
151
|
end
|
154
152
|
end
|
155
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
|
data/lib/ldclient-rb/ldclient.rb
CHANGED
@@ -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
|
@@ -278,6 +282,23 @@ module LaunchDarkly
|
|
278
282
|
@event_processor.add_event(@event_factory_default.new_custom_event(event_name, user, data, metric_value))
|
279
283
|
end
|
280
284
|
|
285
|
+
#
|
286
|
+
# Associates a new and old user object for analytics purposes via an alias event.
|
287
|
+
#
|
288
|
+
# @param current_context [Hash] The current version of a user.
|
289
|
+
# @param previous_context [Hash] The previous version of a user.
|
290
|
+
# @return [void]
|
291
|
+
#
|
292
|
+
def alias(current_context, previous_context)
|
293
|
+
if !current_context || current_context[:key].nil? || !previous_context || previous_context[:key].nil?
|
294
|
+
@config.logger.warn("Alias called with nil user or nil user key!")
|
295
|
+
return
|
296
|
+
end
|
297
|
+
sanitize_user(current_context)
|
298
|
+
sanitize_user(previous_context)
|
299
|
+
@event_processor.add_event(@event_factory_default.new_alias_event(current_context, previous_context))
|
300
|
+
end
|
301
|
+
|
281
302
|
#
|
282
303
|
# Returns all feature flag values for the given user.
|
283
304
|
#
|
@@ -333,12 +354,13 @@ module LaunchDarkly
|
|
333
354
|
next
|
334
355
|
end
|
335
356
|
begin
|
336
|
-
result = evaluate(f, user, @
|
357
|
+
result = @evaluator.evaluate(f, user, @event_factory_default)
|
337
358
|
state.add_flag(f, result.detail.value, result.detail.variation_index, with_reasons ? result.detail.reason : nil,
|
338
359
|
details_only_if_tracked)
|
339
360
|
rescue => exn
|
340
361
|
Util.log_exception(@config.logger, "Error evaluating flag \"#{k}\" in all_flags_state", exn)
|
341
|
-
state.add_flag(f, nil, nil, with_reasons ?
|
362
|
+
state.add_flag(f, nil, nil, with_reasons ? EvaluationReason::error(EvaluationReason::ERROR_EXCEPTION) : nil,
|
363
|
+
details_only_if_tracked)
|
342
364
|
end
|
343
365
|
end
|
344
366
|
|
@@ -363,12 +385,12 @@ module LaunchDarkly
|
|
363
385
|
return NullUpdateProcessor.new
|
364
386
|
end
|
365
387
|
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
388
|
if config.stream?
|
368
|
-
StreamProcessor.new(sdk_key, config,
|
389
|
+
StreamProcessor.new(sdk_key, config, diagnostic_accumulator)
|
369
390
|
else
|
370
391
|
config.logger.info { "Disabling streaming API" }
|
371
392
|
config.logger.warn { "You should only disable the streaming API if instructed to do so by LaunchDarkly support" }
|
393
|
+
requestor = Requestor.new(sdk_key, config)
|
372
394
|
PollingProcessor.new(config, requestor)
|
373
395
|
end
|
374
396
|
end
|
@@ -376,7 +398,13 @@ module LaunchDarkly
|
|
376
398
|
# @return [EvaluationDetail]
|
377
399
|
def evaluate_internal(key, user, default, event_factory)
|
378
400
|
if @config.offline?
|
379
|
-
return error_result(
|
401
|
+
return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
|
402
|
+
end
|
403
|
+
|
404
|
+
unless user
|
405
|
+
@config.logger.error { "[LDClient] Must specify user" }
|
406
|
+
detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
|
407
|
+
return detail
|
380
408
|
end
|
381
409
|
|
382
410
|
if !initialized?
|
@@ -384,7 +412,7 @@ module LaunchDarkly
|
|
384
412
|
@config.logger.warn { "[LDClient] Client has not finished initializing; using last known values from feature store" }
|
385
413
|
else
|
386
414
|
@config.logger.error { "[LDClient] Client has not finished initializing; feature store unavailable, returning default value" }
|
387
|
-
detail = error_result(
|
415
|
+
detail = Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
|
388
416
|
@event_processor.add_event(event_factory.new_unknown_flag_event(key, user, default, detail.reason))
|
389
417
|
return detail
|
390
418
|
end
|
@@ -394,20 +422,13 @@ module LaunchDarkly
|
|
394
422
|
|
395
423
|
if feature.nil?
|
396
424
|
@config.logger.info { "[LDClient] Unknown feature flag \"#{key}\". Returning default value" }
|
397
|
-
detail = error_result(
|
425
|
+
detail = Evaluator.error_result(EvaluationReason::ERROR_FLAG_NOT_FOUND, default)
|
398
426
|
@event_processor.add_event(event_factory.new_unknown_flag_event(key, user, default, detail.reason))
|
399
427
|
return detail
|
400
428
|
end
|
401
429
|
|
402
|
-
unless user
|
403
|
-
@config.logger.error { "[LDClient] Must specify user" }
|
404
|
-
detail = error_result('USER_NOT_SPECIFIED', default)
|
405
|
-
@event_processor.add_event(event_factory.new_default_event(feature, user, default, detail.reason))
|
406
|
-
return detail
|
407
|
-
end
|
408
|
-
|
409
430
|
begin
|
410
|
-
res = evaluate(feature, user,
|
431
|
+
res = @evaluator.evaluate(feature, user, event_factory)
|
411
432
|
if !res.events.nil?
|
412
433
|
res.events.each do |event|
|
413
434
|
@event_processor.add_event(event)
|
@@ -421,7 +442,7 @@ module LaunchDarkly
|
|
421
442
|
return detail
|
422
443
|
rescue => exn
|
423
444
|
Util.log_exception(@config.logger, "Error evaluating feature flag \"#{key}\"", exn)
|
424
|
-
detail = error_result(
|
445
|
+
detail = Evaluator.error_result(EvaluationReason::ERROR_EXCEPTION, default)
|
425
446
|
@event_processor.add_event(event_factory.new_default_event(feature, user, default, detail.reason))
|
426
447
|
return detail
|
427
448
|
end
|