featureflip 2.5.0 → 2.5.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 +4 -4
- data/lib/featureflip/evaluation/condition_evaluator.rb +12 -1
- data/lib/featureflip/shared_core.rb +41 -10
- data/lib/featureflip/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a41a0e3edb8d4325e4a0784930973f07ad8339e7b25b127f076bc7cfade0d948
|
|
4
|
+
data.tar.gz: 6ceb86141220f142710e6a0937af742270c61961ff87f583a2d727bab1c6ee65
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 013af1bacae587f2dd832bd7004ac4a94a45e8d635c64204d8ea892d4e62c80112d07856856d097f03393f4bbf7c3aef7d54c378672db9c61623d9053b8e3e3b
|
|
7
|
+
data.tar.gz: 822537c99bfdd30d8f2f4b16b9b8592d2cd23fdaccaf2cea0333fccdf68f0998877bf124da0060ec63abc0fb347f6506ad10f0fc235d8f22291de50a68f20a33
|
|
@@ -32,6 +32,14 @@ module Featureflip
|
|
|
32
32
|
targets = condition.values.map(&:to_s)
|
|
33
33
|
|
|
34
34
|
result = evaluate_operator(condition.operator, str_value, targets)
|
|
35
|
+
|
|
36
|
+
# Issue #2262: an unrecognised operator fails CLOSED. `!nil` is `true`
|
|
37
|
+
# in Ruby, so without this guard a negated unknown operator would match
|
|
38
|
+
# every user and roll the flag out to 100% of traffic. The realistic
|
|
39
|
+
# trigger is a new operator shipped server-side reaching an SDK pinned
|
|
40
|
+
# to an older version.
|
|
41
|
+
return false if result.nil?
|
|
42
|
+
|
|
35
43
|
condition.negate ? !result : result
|
|
36
44
|
end
|
|
37
45
|
|
|
@@ -150,7 +158,10 @@ module Featureflip
|
|
|
150
158
|
when "SemverLessThanOrEqual"
|
|
151
159
|
targets.any? { |t| compare_semver(value, t, :<=) }
|
|
152
160
|
else
|
|
153
|
-
false
|
|
161
|
+
# Unrecognised operator. `nil` — NOT `false` — so the caller can tell
|
|
162
|
+
# "cannot evaluate" apart from "evaluated, did not match"; only the
|
|
163
|
+
# latter may be inverted by `negate` (#2262).
|
|
164
|
+
nil
|
|
154
165
|
end
|
|
155
166
|
end
|
|
156
167
|
|
|
@@ -241,25 +241,29 @@ module Featureflip
|
|
|
241
241
|
return unless @event_processor
|
|
242
242
|
|
|
243
243
|
context = normalize_context(context)
|
|
244
|
-
@event_processor.queue_event({
|
|
244
|
+
@event_processor.queue_event(compact_event({
|
|
245
245
|
type: "Custom",
|
|
246
246
|
flagKey: event_key,
|
|
247
|
-
userId: context
|
|
248
|
-
metadata: metadata
|
|
247
|
+
userId: resolve_event_user_id(context),
|
|
248
|
+
metadata: metadata,
|
|
249
249
|
timestamp: Time.now.utc.iso8601
|
|
250
|
-
})
|
|
250
|
+
}))
|
|
251
251
|
end
|
|
252
252
|
|
|
253
253
|
def identify(context)
|
|
254
254
|
return unless @event_processor
|
|
255
255
|
|
|
256
256
|
context = normalize_context(context)
|
|
257
|
-
|
|
257
|
+
# Strip both identity spellings so the id is carried once, at the top
|
|
258
|
+
# level, and not duplicated inside the attribute bag.
|
|
259
|
+
attributes = context.reject { |k, _| IDENTITY_KEYS.include?(k) }
|
|
260
|
+
@event_processor.queue_event(compact_event({
|
|
258
261
|
type: "Identify",
|
|
259
262
|
flagKey: "$identify",
|
|
260
|
-
userId: context
|
|
263
|
+
userId: resolve_event_user_id(context),
|
|
264
|
+
metadata: attributes,
|
|
261
265
|
timestamp: Time.now.utc.iso8601
|
|
262
|
-
})
|
|
266
|
+
}))
|
|
263
267
|
end
|
|
264
268
|
|
|
265
269
|
def flush
|
|
@@ -417,16 +421,43 @@ module Featureflip
|
|
|
417
421
|
context.transform_keys(&:to_s)
|
|
418
422
|
end
|
|
419
423
|
|
|
424
|
+
# Both spellings of the identity are accepted on an event context; the
|
|
425
|
+
# canonical +user_id+ wins when a caller supplies both. The evaluator
|
|
426
|
+
# already aliases these for bucketing, so events have to as well or an
|
|
427
|
+
# alias caller gets every event attributed to nil.
|
|
428
|
+
IDENTITY_KEYS = %w[user_id userId].freeze
|
|
429
|
+
|
|
430
|
+
def resolve_event_user_id(context)
|
|
431
|
+
raw = context["user_id"]
|
|
432
|
+
raw = context["userId"] if raw.nil?
|
|
433
|
+
raw&.to_s
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# +userId+ and +metadata+ are optional on the wire. Omit them rather than
|
|
437
|
+
# sending nulls or empty bags, matching the other SDKs.
|
|
438
|
+
#
|
|
439
|
+
# +metadata+ is caller-supplied and untyped, so the emptiness check is
|
|
440
|
+
# guarded: a caller passing a non-collection must not take a NoMethodError
|
|
441
|
+
# into their request path just to record an analytics event.
|
|
442
|
+
def compact_event(event)
|
|
443
|
+
event.delete(:userId) if event[:userId].nil?
|
|
444
|
+
metadata = event[:metadata]
|
|
445
|
+
if metadata.nil? || (metadata.respond_to?(:empty?) && metadata.empty?)
|
|
446
|
+
event.delete(:metadata)
|
|
447
|
+
end
|
|
448
|
+
event
|
|
449
|
+
end
|
|
450
|
+
|
|
420
451
|
def record_evaluation(key, context, variation_key)
|
|
421
452
|
return unless @event_processor
|
|
422
453
|
|
|
423
|
-
@event_processor.queue_event({
|
|
454
|
+
@event_processor.queue_event(compact_event({
|
|
424
455
|
type: "Evaluation",
|
|
425
456
|
flagKey: key,
|
|
426
|
-
userId: context
|
|
457
|
+
userId: resolve_event_user_id(context),
|
|
427
458
|
variation: variation_key,
|
|
428
459
|
timestamp: Time.now.utc.iso8601
|
|
429
|
-
})
|
|
460
|
+
}))
|
|
430
461
|
end
|
|
431
462
|
|
|
432
463
|
# Fire the registered evaluation inspectors. Called once per variation call
|
data/lib/featureflip/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: featureflip
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.5.
|
|
4
|
+
version: 2.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Featureflip
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: logger
|