toggly 1.2.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: 810c3cb9858347a1cb79212512e006bf2362ee97cd09c7a528eb334c0bf949cc
4
- data.tar.gz: 7f6ef5ce376a28c1101ce65c1d82080c02e6b254487ed0908479c1a622adffeb
3
+ metadata.gz: a484fa27dd7cc53210f2af73984c93d9109205f797172203aea42c44c4722569
4
+ data.tar.gz: 79a64ed42c9497e8ce8ca0f59d4dc3c21e52e68ba5e24b3b76680767d2a4787b
5
5
  SHA512:
6
- metadata.gz: e0c8fbd59c7cfa8baafd73f580da168fe0fe4114e802f0e55bec3c406994e8a4a169b773dbf6e97862f831157d7b011df18f6210cda33740a56a6f515c05fcfc
7
- data.tar.gz: 92d791902d6bf591f9731647caa1963b2c589bf12255afe376b1f1fa9591437d9af10388503b54bbbf06e911afb41fb2b557be60c9564dd09eec423037fcbdab
6
+ metadata.gz: 07f924fb8addc26c7af35f31f0a023680201f097cfa6b26826c333a2dc3a2086c07ae094ca23413daf176546a886dbd39598914a79c0ae6cd35c8c0aeb5df564
7
+ data.tar.gz: 353559ba9d83853a589d843f01b52cbdb9287074b7be77505784515df99b2505317d54225456b7818c210d050b5cd170abe1a8bc315545dad5f534f9a7537244
data/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ 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
+
8
16
  ## [1.2.0] - 2026-09-24
9
17
 
10
18
  ### 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
 
@@ -359,30 +361,43 @@ module Toggly
359
361
  return block.call(value) if block
360
362
  return nil if as.nil?
361
363
 
362
- # Scalars / :boolean before Class#new — String.new({}) would soft-fail to "".
363
- if as == String
364
- return value.is_a?(String) ? value : nil
365
- end
366
- if as == Integer
367
- return value.is_a?(Integer) && !value.is_a?(TrueClass) && !value.is_a?(FalseClass) ? value : nil
368
- end
369
- if as == Float
370
- return value.is_a?(Numeric) && !value.is_a?(TrueClass) && !value.is_a?(FalseClass) ? value.to_f : nil
371
- end
372
- if [TrueClass, FalseClass, :boolean].include?(as)
373
- return [true, false].include?(value) ? value : nil
374
- end
364
+ scalar_value = decode_scalar_variant_value(value, as)
365
+ return scalar_value unless scalar_value.equal?(UNHANDLED_VARIANT_VALUE)
375
366
 
376
367
  # is_a? requires a Module; symbols like :boolean are handled above.
377
368
  return value if as.is_a?(Module) && value.is_a?(as)
378
- return as.new(**value.transform_keys(&:to_sym)) if as.respond_to?(:new) && value.is_a?(Hash)
379
- return as.json_create(value) if as.respond_to?(:json_create) && value.is_a?(Hash)
380
369
 
381
- nil
370
+ decode_variant_object(value, as)
382
371
  rescue StandardError
383
372
  nil
384
373
  end
385
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
+
386
401
  # `definitions` / `definitions-signed` → local rule eval. The sole
387
402
  # source of truth for `enabled?`; also carries `variants` / `allocation`
388
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.2.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
@@ -124,7 +126,7 @@ module Toggly
124
126
  # @param context [Context, nil]
125
127
  # @return [Object, nil]
126
128
  def get_variant_value(feature_key, context: nil, as: nil, &)
127
- raise Error, "Toggly not configured. Call Toggly.configure first." unless @client
129
+ raise Error, NOT_CONFIGURED_ERROR unless @client
128
130
 
129
131
  @client.get_variant_value(feature_key, context: context, as: as, &)
130
132
  end
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.2.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-25 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.