ldclient-rb 5.5.4 → 5.5.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 59ce5212d9da56a89b81f777b4167917a888dd81
4
- data.tar.gz: f6bae254bfd984bf899629c7a12a42771742b49a
3
+ metadata.gz: '07628a76356f0beddcd2faffb3a50af652d51fcb'
4
+ data.tar.gz: e0f6cedf0d5c6b6c3496e4a234307fa9bd904b4e
5
5
  SHA512:
6
- metadata.gz: f53b38fe2d082a621b728ba98479468923ddf5cd05a6bb0ddf05632210524d149dde39041055c5294fc6186e7fe908b175612926c8c6c107eb22213db79d3b2f
7
- data.tar.gz: fa22bf113cca0bc31a91cba3d218063a246b47209ea55482962a6ff6f7c8db18625d560388d62e1ef06b8244ee9847dec0ae5f41d9b5d16528a1639d9529d168
6
+ metadata.gz: 978e957445f2669f9c59f3e4a1af9bc7b603d272e6defea501735be9a8db2024148a2570d7c5e81dfc70d51115cf08f76447fa855c3d6be1912c94a6b5c58264
7
+ data.tar.gz: 768fc857cefdb5764bbecd35378320c99770058fc176facc5cbc47e8921ccdc80b5a64f699a83974cefde3c47e4a3225824f4149ebc08c900696635433f94775
@@ -2,6 +2,10 @@
2
2
 
3
3
  All notable changes to the LaunchDarkly Ruby SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).
4
4
 
5
+ ## [5.5.5] - 2019-03-28
6
+ ### Fixed:
7
+ - Setting user attributes to non-string values when a string was expected would cause analytics events not to be processed. Also, in the case of the `secondary` attribute, this could cause evaluations to fail for a flag with a percentage rollout. The SDK will now convert attribute values to strings as needed. ([#131](https://github.com/launchdarkly/ruby-client/issues/131))
8
+
5
9
  ## [5.5.4] - 2019-03-29
6
10
  ### Fixed:
7
11
  - Fixed a missing `require` that could sometimes cause a `NameError` to be thrown when starting the client, depending on what other gems were installed. This bug was introduced in version 5.5.3. ([#129](https://github.com/launchdarkly/ruby-client/issues/129))
@@ -189,6 +189,10 @@ module LaunchDarkly
189
189
  # Used internally to hold an evaluation result and the events that were generated from prerequisites.
190
190
  EvalResult = Struct.new(:detail, :events)
191
191
 
192
+ USER_ATTRS_TO_STRINGIFY_FOR_EVALUATION = [ :key, :secondary ]
193
+ # Currently we are not stringifying the rest of the built-in attributes prior to evaluation, only for events.
194
+ # This is because it could affect evaluation results for existing users (ch35206).
195
+
192
196
  def error_result(errorKind, value = nil)
193
197
  EvaluationDetail.new(value, nil, { kind: 'ERROR', errorKind: errorKind })
194
198
  end
@@ -200,8 +204,10 @@ module LaunchDarkly
200
204
  return EvalResult.new(error_result('USER_NOT_SPECIFIED'), [])
201
205
  end
202
206
 
207
+ sanitized_user = Util.stringify_attrs(user, USER_ATTRS_TO_STRINGIFY_FOR_EVALUATION)
208
+
203
209
  events = []
204
- detail = eval_internal(flag, user, store, events, logger)
210
+ detail = eval_internal(flag, sanitized_user, store, events, logger)
205
211
  return EvalResult.new(detail, events)
206
212
  end
207
213
 
@@ -7,9 +7,12 @@ require "time"
7
7
  module LaunchDarkly
8
8
  MAX_FLUSH_WORKERS = 5
9
9
  CURRENT_SCHEMA_VERSION = 3
10
+ USER_ATTRS_TO_STRINGIFY_FOR_EVENTS = [ :key, :secondary, :ip, :country, :email, :firstName, :lastName,
11
+ :avatar, :name ]
10
12
 
11
13
  private_constant :MAX_FLUSH_WORKERS
12
14
  private_constant :CURRENT_SCHEMA_VERSION
15
+ private_constant :USER_ATTRS_TO_STRINGIFY_FOR_EVENTS
13
16
 
14
17
  # @private
15
18
  class NullEventProcessor
@@ -219,7 +222,7 @@ module LaunchDarkly
219
222
  if user.nil? || !user.has_key?(:key)
220
223
  true
221
224
  else
222
- @user_keys.add(user[:key])
225
+ @user_keys.add(user[:key].to_s)
223
226
  end
224
227
  end
225
228
 
@@ -371,6 +374,11 @@ module LaunchDarkly
371
374
 
372
375
  private
373
376
 
377
+ def process_user(event)
378
+ filtered = @user_filter.transform_user_props(event[:user])
379
+ Util.stringify_attrs(filtered, USER_ATTRS_TO_STRINGIFY_FOR_EVENTS)
380
+ end
381
+
374
382
  def make_output_event(event)
375
383
  case event[:kind]
376
384
  when "feature"
@@ -386,7 +394,7 @@ module LaunchDarkly
386
394
  out[:version] = event[:version] if event.has_key?(:version)
387
395
  out[:prereqOf] = event[:prereqOf] if event.has_key?(:prereqOf)
388
396
  if @inline_users || is_debug
389
- out[:user] = @user_filter.transform_user_props(event[:user])
397
+ out[:user] = process_user(event)
390
398
  else
391
399
  out[:userKey] = event[:user].nil? ? nil : event[:user][:key]
392
400
  end
@@ -396,8 +404,8 @@ module LaunchDarkly
396
404
  {
397
405
  kind: "identify",
398
406
  creationDate: event[:creationDate],
399
- key: event[:user].nil? ? nil : event[:user][:key],
400
- user: @user_filter.transform_user_props(event[:user])
407
+ key: event[:user].nil? ? nil : event[:user][:key].to_s,
408
+ user: process_user(event)
401
409
  }
402
410
  when "custom"
403
411
  out = {
@@ -407,7 +415,7 @@ module LaunchDarkly
407
415
  }
408
416
  out[:data] = event[:data] if event.has_key?(:data)
409
417
  if @inline_users
410
- out[:user] = @user_filter.transform_user_props(event[:user])
418
+ out[:user] = process_user(event)
411
419
  else
412
420
  out[:userKey] = event[:user].nil? ? nil : event[:user][:key]
413
421
  end
@@ -416,7 +424,7 @@ module LaunchDarkly
416
424
  {
417
425
  kind: "index",
418
426
  creationDate: event[:creationDate],
419
- user: @user_filter.transform_user_props(event[:user])
427
+ user: process_user(event)
420
428
  }
421
429
  else
422
430
  event
@@ -215,7 +215,6 @@ module LaunchDarkly
215
215
  @config.logger.warn("Identify called with nil user or nil user key!")
216
216
  return
217
217
  end
218
- sanitize_user(user)
219
218
  @event_processor.add_event(kind: "identify", key: user[:key], user: user)
220
219
  end
221
220
 
@@ -237,7 +236,6 @@ module LaunchDarkly
237
236
  @config.logger.warn("Track called with nil user or nil user key!")
238
237
  return
239
238
  end
240
- sanitize_user(user)
241
239
  @event_processor.add_event(kind: "custom", key: event_name, user: user, data: data)
242
240
  end
243
241
 
@@ -280,8 +278,6 @@ module LaunchDarkly
280
278
  return FeatureFlagsState.new(false)
281
279
  end
282
280
 
283
- sanitize_user(user)
284
-
285
281
  begin
286
282
  features = @store.all(FEATURES)
287
283
  rescue => exn
@@ -353,7 +349,6 @@ module LaunchDarkly
353
349
  end
354
350
  end
355
351
 
356
- sanitize_user(user) if !user.nil?
357
352
  feature = @store.get(FEATURES, key)
358
353
 
359
354
  if feature.nil?
@@ -367,12 +362,12 @@ module LaunchDarkly
367
362
  unless user
368
363
  @config.logger.error { "[LDClient] Must specify user" }
369
364
  detail = error_result('USER_NOT_SPECIFIED', default)
370
- @event_processor.add_event(make_feature_event(feature, user, detail, default, include_reasons_in_events))
365
+ @event_processor.add_event(make_feature_event(feature, nil, detail, default, include_reasons_in_events))
371
366
  return detail
372
367
  end
373
368
 
374
369
  begin
375
- res = evaluate(feature, user, @store, @config.logger)
370
+ res = evaluate(feature, user, @store, @config.logger) # note, evaluate will do its own sanitization
376
371
  if !res.events.nil?
377
372
  res.events.each do |event|
378
373
  @event_processor.add_event(event)
@@ -392,12 +387,6 @@ module LaunchDarkly
392
387
  end
393
388
  end
394
389
 
395
- def sanitize_user(user)
396
- if user[:key]
397
- user[:key] = user[:key].to_s
398
- end
399
- end
400
-
401
390
  def make_feature_event(flag, user, detail, default, with_reasons)
402
391
  {
403
392
  kind: "feature",
@@ -4,6 +4,21 @@ require "uri"
4
4
  module LaunchDarkly
5
5
  # @private
6
6
  module Util
7
+ def self.stringify_attrs(hash, attrs)
8
+ return hash if hash.nil?
9
+ ret = hash
10
+ changed = false
11
+ attrs.each do |attr|
12
+ value = hash[attr]
13
+ if !value.nil? && !value.is_a?(String)
14
+ ret = hash.clone if !changed
15
+ ret[attr] = value.to_s
16
+ changed = true
17
+ end
18
+ end
19
+ ret
20
+ end
21
+
7
22
  def self.new_http_client(uri_s, config)
8
23
  uri = URI(uri_s)
9
24
  client = Net::HTTP.new(uri.hostname, uri.port)
@@ -1,3 +1,3 @@
1
1
  module LaunchDarkly
2
- VERSION = "5.5.4"
2
+ VERSION = "5.5.5"
3
3
  end
@@ -359,6 +359,25 @@ describe LaunchDarkly::Evaluation do
359
359
  expect(result.detail).to eq(detail)
360
360
  expect(result.events).to eq([])
361
361
  end
362
+
363
+ it "coerces user key to a string for evaluation" do
364
+ clause = { attribute: 'key', op: 'in', values: ['999'] }
365
+ flag = boolean_flag_with_clauses([clause])
366
+ user = { key: 999 }
367
+ result = evaluate(flag, user, features, logger)
368
+ expect(result.detail.value).to eq(true)
369
+ end
370
+
371
+ it "coerces secondary key to a string for evaluation" do
372
+ # We can't really verify that the rollout calculation works correctly, but we can at least
373
+ # make sure it doesn't error out if there's a non-string secondary value (ch35189)
374
+ rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }],
375
+ rollout: { salt: '', variations: [ { weight: 100000, variation: 1 } ] } }
376
+ flag = boolean_flag_with_rules([rule])
377
+ user = { key: "userkey", secondary: 999 }
378
+ result = evaluate(flag, user, features, logger)
379
+ expect(result.detail.reason).to eq({ kind: 'RULE_MATCH', ruleIndex: 0, ruleId: 'ruleid'})
380
+ end
362
381
  end
363
382
 
364
383
  describe "clause" do
@@ -9,6 +9,10 @@ describe LaunchDarkly::EventProcessor do
9
9
  let(:hc) { FakeHttpClient.new }
10
10
  let(:user) { { key: "userkey", name: "Red" } }
11
11
  let(:filtered_user) { { key: "userkey", privateAttrs: [ "name" ] } }
12
+ let(:numeric_user) { { key: 1, secondary: 2, ip: 3, country: 4, email: 5, firstName: 6, lastName: 7,
13
+ avatar: 8, name: 9, anonymous: false, custom: { age: 99 } } }
14
+ let(:stringified_numeric_user) { { key: '1', secondary: '2', ip: '3', country: '4', email: '5', firstName: '6',
15
+ lastName: '7', avatar: '8', name: '9', anonymous: false, custom: { age: 99 } } }
12
16
 
13
17
  after(:each) do
14
18
  if !@ep.nil?
@@ -40,6 +44,21 @@ describe LaunchDarkly::EventProcessor do
40
44
  })
41
45
  end
42
46
 
47
+ it "stringifies built-in user attributes in identify event" do
48
+ @ep = subject.new("sdk_key", default_config, hc)
49
+ flag = { key: "flagkey", version: 11 }
50
+ e = { kind: "identify", key: numeric_user[:key], user: numeric_user }
51
+ @ep.add_event(e)
52
+
53
+ output = flush_and_get_events
54
+ expect(output).to contain_exactly(
55
+ kind: "identify",
56
+ key: numeric_user[:key].to_s,
57
+ creationDate: e[:creationDate],
58
+ user: stringified_numeric_user
59
+ )
60
+ end
61
+
43
62
  it "queues individual feature event with index event" do
44
63
  @ep = subject.new("sdk_key", default_config, hc)
45
64
  flag = { key: "flagkey", version: 11 }
@@ -75,6 +94,23 @@ describe LaunchDarkly::EventProcessor do
75
94
  )
76
95
  end
77
96
 
97
+ it "stringifies built-in user attributes in index event" do
98
+ @ep = subject.new("sdk_key", default_config, hc)
99
+ flag = { key: "flagkey", version: 11 }
100
+ fe = {
101
+ kind: "feature", key: "flagkey", version: 11, user: numeric_user,
102
+ variation: 1, value: "value", trackEvents: true
103
+ }
104
+ @ep.add_event(fe)
105
+
106
+ output = flush_and_get_events
107
+ expect(output).to contain_exactly(
108
+ eq(index_event(fe, stringified_numeric_user)),
109
+ eq(feature_event(fe, flag, false, nil)),
110
+ include(:kind => "summary")
111
+ )
112
+ end
113
+
78
114
  it "can include inline user in feature event" do
79
115
  config = LaunchDarkly::Config.new(inline_users_in_events: true)
80
116
  @ep = subject.new("sdk_key", config, hc)
@@ -92,6 +128,23 @@ describe LaunchDarkly::EventProcessor do
92
128
  )
93
129
  end
94
130
 
131
+ it "stringifies built-in user attributes in feature event" do
132
+ config = LaunchDarkly::Config.new(inline_users_in_events: true)
133
+ @ep = subject.new("sdk_key", config, hc)
134
+ flag = { key: "flagkey", version: 11 }
135
+ fe = {
136
+ kind: "feature", key: "flagkey", version: 11, user: numeric_user,
137
+ variation: 1, value: "value", trackEvents: true
138
+ }
139
+ @ep.add_event(fe)
140
+
141
+ output = flush_and_get_events
142
+ expect(output).to contain_exactly(
143
+ eq(feature_event(fe, flag, false, stringified_numeric_user)),
144
+ include(:kind => "summary")
145
+ )
146
+ end
147
+
95
148
  it "filters user in feature event" do
96
149
  config = LaunchDarkly::Config.new(all_attributes_private: true, inline_users_in_events: true)
97
150
  @ep = subject.new("sdk_key", config, hc)
@@ -323,6 +376,18 @@ describe LaunchDarkly::EventProcessor do
323
376
  )
324
377
  end
325
378
 
379
+ it "stringifies built-in user attributes in custom event" do
380
+ config = LaunchDarkly::Config.new(inline_users_in_events: true)
381
+ @ep = subject.new("sdk_key", config, hc)
382
+ e = { kind: "custom", key: "eventkey", user: numeric_user }
383
+ @ep.add_event(e)
384
+
385
+ output = flush_and_get_events
386
+ expect(output).to contain_exactly(
387
+ eq(custom_event(e, stringified_numeric_user))
388
+ )
389
+ end
390
+
326
391
  it "does a final flush when shutting down" do
327
392
  @ep = subject.new("sdk_key", default_config, hc)
328
393
  e = { kind: "identify", key: user[:key], user: user }
@@ -25,22 +25,6 @@ describe LaunchDarkly::LDClient do
25
25
  }
26
26
  }
27
27
  end
28
- let(:numeric_key_user) do
29
- {
30
- key: 33,
31
- custom: {
32
- groups: [ "microsoft", "google" ]
33
- }
34
- }
35
- end
36
- let(:sanitized_numeric_key_user) do
37
- {
38
- key: "33",
39
- custom: {
40
- groups: [ "microsoft", "google" ]
41
- }
42
- }
43
- end
44
28
  let(:user_without_key) do
45
29
  { name: "Keyless Joe" }
46
30
  end
@@ -354,11 +338,6 @@ describe LaunchDarkly::LDClient do
354
338
  client.track("custom_event_name", user, 42)
355
339
  end
356
340
 
357
- it "sanitizes the user in the event" do
358
- expect(event_processor).to receive(:add_event).with(hash_including(user: sanitized_numeric_key_user))
359
- client.track("custom_event_name", numeric_key_user, nil)
360
- end
361
-
362
341
  it "does not send an event, and logs a warning, if user is nil" do
363
342
  expect(event_processor).not_to receive(:add_event)
364
343
  expect(logger).to receive(:warn)
@@ -378,11 +357,6 @@ describe LaunchDarkly::LDClient do
378
357
  client.identify(user)
379
358
  end
380
359
 
381
- it "sanitizes the user in the event" do
382
- expect(event_processor).to receive(:add_event).with(hash_including(user: sanitized_numeric_key_user))
383
- client.identify(numeric_key_user)
384
- end
385
-
386
360
  it "does not send an event, and logs a warning, if user is nil" do
387
361
  expect(event_processor).not_to receive(:add_event)
388
362
  expect(logger).to receive(:warn)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ldclient-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.5.4
4
+ version: 5.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - LaunchDarkly
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-21 00:00:00.000000000 Z
11
+ date: 2019-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-dynamodb