toggly 1.1.0 → 1.2.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/CHANGELOG.md +17 -0
- data/lib/toggly/client.rb +50 -2
- data/lib/toggly/definitions_provider.rb +5 -3
- data/lib/toggly/variant_allocator.rb +3 -4
- data/lib/toggly/version.rb +1 -1
- data/lib/toggly.rb +7 -5
- 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: a484fa27dd7cc53210f2af73984c93d9109205f797172203aea42c44c4722569
|
|
4
|
+
data.tar.gz: 79a64ed42c9497e8ce8ca0f59d4dc3c21e52e68ba5e24b3b76680767d2a4787b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 07f924fb8addc26c7af35f31f0a023680201f097cfa6b26826c333a2dc3a2086c07ae094ca23413daf176546a886dbd39598914a79c0ae6cd35c8c0aeb5df564
|
|
7
|
+
data.tar.gz: 353559ba9d83853a589d843f01b52cbdb9287074b7be77505784515df99b2505317d54225456b7818c210d050b5cd170abe1a8bc315545dad5f534f9a7537244
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.2.1] - 2026-09-28
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Always apply HTTP 200 definition bodies when the storage revision matches.
|
|
13
|
+
Same-revision responses still count as cache hits for telemetry after the
|
|
14
|
+
body is applied [OPS-1568].
|
|
15
|
+
|
|
16
|
+
## [1.2.0] - 2026-09-24
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Soft-typed `get_variant_value(feature, as: Klass)` (and optional block form).
|
|
21
|
+
Missing assignment or bind failure returns `nil` — never raises solely for
|
|
22
|
+
shape mismatch. Untyped `get_variant_value` (no `as:`) is unchanged
|
|
23
|
+
[OPS-1365].
|
|
24
|
+
|
|
8
25
|
## [1.1.0] - 2026-09-23
|
|
9
26
|
|
|
10
27
|
### Added
|
data/lib/toggly/client.rb
CHANGED
|
@@ -24,6 +24,8 @@ module Toggly
|
|
|
24
24
|
# )
|
|
25
25
|
# client = Toggly::Client.new(config)
|
|
26
26
|
class Client
|
|
27
|
+
UNHANDLED_VARIANT_VALUE = Object.new.freeze
|
|
28
|
+
|
|
27
29
|
include CacheTelemetry
|
|
28
30
|
include SnapshotSupport
|
|
29
31
|
|
|
@@ -166,8 +168,13 @@ module Toggly
|
|
|
166
168
|
# @param feature_key [String, Symbol] The feature key
|
|
167
169
|
# @param context [Context, nil] Optional targeting context (userId + groups)
|
|
168
170
|
# @return [Object, nil]
|
|
169
|
-
def get_variant_value(feature_key, context: nil)
|
|
170
|
-
get_variant(feature_key, context: context)&.configuration_value
|
|
171
|
+
def get_variant_value(feature_key, context: nil, as: nil, &block)
|
|
172
|
+
value = get_variant(feature_key, context: context)&.configuration_value
|
|
173
|
+
return value if as.nil? && !block
|
|
174
|
+
|
|
175
|
+
return nil if value.nil?
|
|
176
|
+
|
|
177
|
+
decode_variant_configuration(value, as, &block)
|
|
171
178
|
end
|
|
172
179
|
|
|
173
180
|
# Default targeting userId used by {#get_variant} / {#get_variant_value}
|
|
@@ -350,6 +357,47 @@ module Toggly
|
|
|
350
357
|
|
|
351
358
|
private
|
|
352
359
|
|
|
360
|
+
def decode_variant_configuration(value, as, &block)
|
|
361
|
+
return block.call(value) if block
|
|
362
|
+
return nil if as.nil?
|
|
363
|
+
|
|
364
|
+
scalar_value = decode_scalar_variant_value(value, as)
|
|
365
|
+
return scalar_value unless scalar_value.equal?(UNHANDLED_VARIANT_VALUE)
|
|
366
|
+
|
|
367
|
+
# is_a? requires a Module; symbols like :boolean are handled above.
|
|
368
|
+
return value if as.is_a?(Module) && value.is_a?(as)
|
|
369
|
+
|
|
370
|
+
decode_variant_object(value, as)
|
|
371
|
+
rescue StandardError
|
|
372
|
+
nil
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Scalars / :boolean before Class#new — String.new({}) would soft-fail to "".
|
|
376
|
+
def decode_scalar_variant_value(value, as)
|
|
377
|
+
return value.is_a?(String) ? value : nil if as == String
|
|
378
|
+
return numeric_variant_value(value, Integer) if as == Integer
|
|
379
|
+
return numeric_variant_value(value, Float) if as == Float
|
|
380
|
+
return [true, false].include?(value) ? value : nil if [TrueClass, FalseClass, :boolean].include?(as)
|
|
381
|
+
|
|
382
|
+
UNHANDLED_VARIANT_VALUE
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def numeric_variant_value(value, type)
|
|
386
|
+
return nil if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
387
|
+
return value if type == Integer && value.is_a?(Integer)
|
|
388
|
+
return value.to_f if type == Float && value.is_a?(Numeric)
|
|
389
|
+
|
|
390
|
+
nil
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def decode_variant_object(value, as)
|
|
394
|
+
return nil unless value.is_a?(Hash)
|
|
395
|
+
return as.new(**value.transform_keys(&:to_sym)) if as.respond_to?(:new)
|
|
396
|
+
return as.json_create(value) if as.respond_to?(:json_create)
|
|
397
|
+
|
|
398
|
+
nil
|
|
399
|
+
end
|
|
400
|
+
|
|
353
401
|
# `definitions` / `definitions-signed` → local rule eval. The sole
|
|
354
402
|
# source of truth for `enabled?`; also carries `variants` / `allocation`
|
|
355
403
|
# for the catalog-local `get_variant` assignment.
|
|
@@ -189,9 +189,11 @@ module Toggly
|
|
|
189
189
|
log_debug("Definitions not modified")
|
|
190
190
|
FetchResult.new(definitions: nil, cache_outcome: :hit)
|
|
191
191
|
when :same_revision
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
# Same storage revision is a telemetry hit, but HTTP 200 still carries
|
|
193
|
+
# an authoritative body — apply it (parity with Go/Rust/Python).
|
|
194
|
+
log_debug("Definitions revision matches existing; applying HTTP 200 body")
|
|
195
|
+
result = handle_new_content(response, response_etag, response_lm)
|
|
196
|
+
FetchResult.new(definitions: result.definitions, cache_outcome: :hit)
|
|
195
197
|
when :new_content
|
|
196
198
|
handle_new_content(response, response_etag, response_lm)
|
|
197
199
|
when :error_status
|
|
@@ -112,13 +112,12 @@ module Toggly
|
|
|
112
112
|
return nil if list.empty?
|
|
113
113
|
|
|
114
114
|
pct = compute_percentile(identity, seed, feature_key, ignore_case)
|
|
115
|
-
list.
|
|
115
|
+
matching_entry = list.find do |entry|
|
|
116
116
|
from = entry[:from].to_f
|
|
117
117
|
to = entry[:to].to_f
|
|
118
|
-
|
|
119
|
-
return entry[:variant] if matched
|
|
118
|
+
to >= 100.0 ? pct >= from : (pct >= from && pct < to)
|
|
120
119
|
end
|
|
121
|
-
nil
|
|
120
|
+
matching_entry&.fetch(:variant, nil)
|
|
122
121
|
end
|
|
123
122
|
|
|
124
123
|
# MF-parity percentile hash.
|
data/lib/toggly/version.rb
CHANGED
data/lib/toggly.rb
CHANGED
|
@@ -71,6 +71,8 @@ require_relative "toggly/client"
|
|
|
71
71
|
# }
|
|
72
72
|
# )
|
|
73
73
|
module Toggly
|
|
74
|
+
NOT_CONFIGURED_ERROR = "Toggly not configured. Call Toggly.configure first."
|
|
75
|
+
|
|
74
76
|
class << self
|
|
75
77
|
# Global client instance
|
|
76
78
|
attr_accessor :client
|
|
@@ -92,7 +94,7 @@ module Toggly
|
|
|
92
94
|
# @param context [Context, nil] Optional evaluation context
|
|
93
95
|
# @return [Boolean]
|
|
94
96
|
def enabled?(feature_key, context: nil)
|
|
95
|
-
raise Error,
|
|
97
|
+
raise Error, NOT_CONFIGURED_ERROR unless @client
|
|
96
98
|
|
|
97
99
|
@client.enabled?(feature_key, context: context)
|
|
98
100
|
end
|
|
@@ -113,7 +115,7 @@ module Toggly
|
|
|
113
115
|
# @param context [Context, nil]
|
|
114
116
|
# @return [VariantResult, nil]
|
|
115
117
|
def get_variant(feature_key, context: nil)
|
|
116
|
-
raise Error,
|
|
118
|
+
raise Error, NOT_CONFIGURED_ERROR unless @client
|
|
117
119
|
|
|
118
120
|
@client.get_variant(feature_key, context: context)
|
|
119
121
|
end
|
|
@@ -123,10 +125,10 @@ module Toggly
|
|
|
123
125
|
# @param feature_key [String, Symbol]
|
|
124
126
|
# @param context [Context, nil]
|
|
125
127
|
# @return [Object, nil]
|
|
126
|
-
def get_variant_value(feature_key, context: nil)
|
|
127
|
-
raise Error,
|
|
128
|
+
def get_variant_value(feature_key, context: nil, as: nil, &)
|
|
129
|
+
raise Error, NOT_CONFIGURED_ERROR unless @client
|
|
128
130
|
|
|
129
|
-
@client.get_variant_value(feature_key, context: context)
|
|
131
|
+
@client.get_variant_value(feature_key, context: context, as: as, &)
|
|
130
132
|
end
|
|
131
133
|
|
|
132
134
|
# Reset the global client (mainly for testing)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: toggly
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1
|
|
4
|
+
version: 1.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ops.ai
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: High-performance Ruby SDK for Toggly feature flag management. Works with
|
|
14
14
|
or without Toggly.io.
|