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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d728557cdb56574a22377e85047ec649a801532f9cea3574ae66ccba703e320e
4
- data.tar.gz: 5054b15ad5ad1b48fde5c6fb174698c701bd8fa38b6054cc8afd6ff015e196ce
3
+ metadata.gz: a484fa27dd7cc53210f2af73984c93d9109205f797172203aea42c44c4722569
4
+ data.tar.gz: 79a64ed42c9497e8ce8ca0f59d4dc3c21e52e68ba5e24b3b76680767d2a4787b
5
5
  SHA512:
6
- metadata.gz: 8871e715209c606026d37367ec46522c3bbcc7925633f9f511bad51179f141924cb4b7e123978212e1a1697050072d0bfacff0463e53cf02510012cb28531db1
7
- data.tar.gz: c3536488d775b49aeefa375f72f3c6b88a993fb4b6464a19e8c5ae73bde75222c310bc820e563f94039509bd6dbbc698ddb04c33cd89de6beba2e7c1ac29dd56
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
- log_debug("Definitions revision matches existing (ETag or Last-Modified)")
193
- store_revision_headers(response_etag, response_lm)
194
- FetchResult.new(definitions: nil, cache_outcome: :hit)
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.each do |entry|
115
+ matching_entry = list.find do |entry|
116
116
  from = entry[:from].to_f
117
117
  to = entry[:to].to_f
118
- matched = to >= 100.0 ? pct >= from : (pct >= from && pct < to)
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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Toggly
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.1"
5
5
  end
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, "Toggly not configured. Call Toggly.configure first." unless @client
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, "Toggly not configured. Call Toggly.configure first." unless @client
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, "Toggly not configured. Call Toggly.configure first." unless @client
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.0
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-09-24 00:00:00.000000000 Z
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.