openfeature-sdk 0.6.5 → 0.6.7

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: 70a393d81225d8b8b546f985dffee74d8b12451e1b9c18d348e6ac3d6a7781e6
4
- data.tar.gz: 960f7f7ba3e704965b1dcb1d38c800d162874a9abf423ce0dc09695e8e7b7372
3
+ metadata.gz: 93f85fdb9eb64b634a3267d0987415f3e4b9033a9d864e80478dfde83074fa7d
4
+ data.tar.gz: 14e94971fdae29a56d31db9d8ee44716b805b6783e0d381ac04e6348168ccf67
5
5
  SHA512:
6
- metadata.gz: e166c0c2698e277129734166ec0e0ed33d38329e090fa2760f720d653df93037e599ce83ba14eea803c99bb3be5b7c301e2e522dc99cc47f6299ce41699be451
7
- data.tar.gz: f74b128a0a376b5f934940226d5a96906b21500373afbce98d52267580a69fab59cc5b0b043f90546bfbf7ed3393ae5033d510e80c50f5abc57a77f10d5a93c7
6
+ metadata.gz: c1e726bae4617d91ed82f676270bb51b7355e8694fac68a76af5f8ea046ed60c33d4c31b0a716827ad61edffe2849c3eb444c614975ac8a09707dcf620286c03
7
+ data.tar.gz: 47d740d86cac2c493058c0f35aa6c2489d89f0502e8df3eb9ba850585777934478fe2dcf500b7eac9b71c96cfb2c857bc6026e9d1214e7876bc385788fcf6f18
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.6.5"
2
+ ".": "0.6.7"
3
3
  }
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 4.0.2
1
+ 4.0.7
data/.tool-versions CHANGED
@@ -1 +1 @@
1
- ruby 4.0.2
1
+ ruby 4.0.7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.7](https://github.com/open-feature/ruby-sdk/compare/v0.6.6...v0.6.7) (2026-10-23)
4
+
5
+
6
+ ### Features
7
+
8
+ * pass the bound domain to provider init and support domain-scoped providers ([#345](https://github.com/open-feature/ruby-sdk/issues/345)) ([f3cf6b7](https://github.com/open-feature/ruby-sdk/commit/f3cf6b76c06297aa4c0a2a95ea1055f816269e3b))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * return defaults instead of raising from client calls ([#346](https://github.com/open-feature/ruby-sdk/issues/346)) ([481f21c](https://github.com/open-feature/ruby-sdk/commit/481f21cfe70365de92c6ece3eb92c40117a7e1c5))
14
+
15
+ ## [0.6.6](https://github.com/open-feature/ruby-sdk/compare/v0.6.5...v0.6.6) (2026-10-22)
16
+
17
+
18
+ ### Features
19
+
20
+ * add PROVIDER_RECONCILING and PROVIDER_CONTEXT_CHANGED events ([#338](https://github.com/open-feature/ruby-sdk/issues/338)) ([2b93aa4](https://github.com/open-feature/ruby-sdk/commit/2b93aa4daf7087a818d0b758c7978e7ad3c1c589))
21
+ * support variant flags in InMemoryProvider ([#340](https://github.com/open-feature/ruby-sdk/issues/340)) ([1e4b168](https://github.com/open-feature/ruby-sdk/commit/1e4b168373e39522c4884e34383730d48772b95f))
22
+
23
+
24
+ ### Bug Fixes
25
+
26
+ * carry provider error codes through hooks ([#339](https://github.com/open-feature/ruby-sdk/issues/339)) ([b9e586c](https://github.com/open-feature/ruby-sdk/commit/b9e586c518a4b8e85facbb90e123c347b0db33e5))
27
+
3
28
  ## [0.6.5](https://github.com/open-feature/ruby-sdk/compare/v0.6.4...v0.6.5) (2026-03-18)
4
29
 
5
30
 
data/CLAUDE.md CHANGED
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4
4
 
5
5
  ## Overview
6
6
 
7
- This is the official OpenFeature SDK for Ruby — an implementation of the [OpenFeature specification](https://openfeature.dev) providing a vendor-agnostic API for feature flag evaluation. Published as the `openfeature-sdk` gem. Requires Ruby >= 3.1.
7
+ This is the official OpenFeature SDK for Ruby — an implementation of the [OpenFeature specification](https://openfeature.dev) providing a vendor-agnostic API for feature flag evaluation. Published as the `openfeature-sdk` gem. Requires Ruby >= 3.4.
8
8
 
9
9
  ## Commands
10
10
 
data/Gemfile CHANGED
@@ -12,13 +12,13 @@ group :development, :test do
12
12
  gem "rspec", "~> 3.13.0"
13
13
  gem "standard"
14
14
  gem "standard-performance"
15
- gem "simplecov", "~> 0.22.0"
16
- gem "simplecov-cobertura", "~> 3.0"
17
- gem "steep", "~> 1.9"
15
+ gem "simplecov", "~> 1.3.0"
16
+ gem "simplecov-cobertura", "~> 4.0"
17
+ gem "steep", "~> 2.0"
18
18
  gem "timecop", "~> 0.9.10"
19
19
  end
20
20
 
21
21
  group :test do
22
- gem "cucumber", "~> 10.0"
22
+ gem "cucumber", "~> 11.0"
23
23
  gem "logger"
24
24
  end
data/Gemfile.lock CHANGED
@@ -1,116 +1,92 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- openfeature-sdk (0.6.5)
4
+ openfeature-sdk (0.6.7)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- activesupport (8.1.2)
10
- base64
11
- bigdecimal
12
- concurrent-ruby (~> 1.0, >= 1.3.1)
13
- connection_pool (>= 2.2.5)
14
- drb
15
- i18n (>= 1.6, < 2)
16
- json
17
- logger (>= 1.4.2)
18
- minitest (>= 5.1)
19
- securerandom (>= 0.3)
20
- tzinfo (~> 2.0, >= 2.0.5)
21
- uri (>= 0.13.1)
22
9
  ast (2.4.3)
23
10
  base64 (0.3.0)
24
- bigdecimal (4.0.1)
11
+ bigdecimal (4.1.3)
25
12
  builder (3.3.0)
26
- concurrent-ruby (1.3.6)
27
- connection_pool (3.0.2)
28
- csv (3.3.5)
29
- cucumber (10.2.0)
13
+ concurrent-ruby (1.3.8)
14
+ csv (3.3.6)
15
+ cucumber (11.1.1)
30
16
  base64 (~> 0.2)
31
17
  builder (~> 3.2)
32
18
  cucumber-ci-environment (> 9, < 12)
33
- cucumber-core (> 15, < 17)
19
+ cucumber-core (>= 16.2.0, < 17)
34
20
  cucumber-cucumber-expressions (> 17, < 20)
35
- cucumber-html-formatter (> 21, < 23)
21
+ cucumber-html-formatter (> 21, < 24)
36
22
  diff-lcs (~> 1.5)
37
23
  logger (~> 1.6)
38
24
  mini_mime (~> 1.1)
39
25
  multi_test (~> 1.1)
40
- sys-uname (~> 1.3)
26
+ sys-uname (~> 1.5)
41
27
  cucumber-ci-environment (11.0.0)
42
28
  cucumber-core (16.2.0)
43
29
  cucumber-gherkin (> 36, < 40)
44
30
  cucumber-messages (> 31, < 33)
45
31
  cucumber-tag-expressions (> 6, < 9)
46
- cucumber-cucumber-expressions (19.0.0)
32
+ cucumber-cucumber-expressions (19.0.1)
47
33
  bigdecimal
48
- cucumber-gherkin (39.0.0)
34
+ cucumber-gherkin (39.1.0)
49
35
  cucumber-messages (>= 31, < 33)
50
- cucumber-html-formatter (22.3.0)
36
+ cucumber-html-formatter (23.1.0)
51
37
  cucumber-messages (> 23, < 33)
52
- cucumber-messages (32.2.0)
38
+ cucumber-messages (32.3.1)
53
39
  cucumber-tag-expressions (8.1.0)
54
- date (3.5.1)
55
40
  debug (1.11.1)
56
41
  irb (~> 1.10)
57
42
  reline (>= 0.3.8)
58
43
  diff-lcs (1.6.2)
59
- docile (1.4.1)
60
- drb (2.2.3)
61
- erb (6.0.2)
62
- ffi (1.17.3)
63
- fileutils (1.8.0)
64
- i18n (1.14.8)
65
- concurrent-ruby (~> 1.0)
66
- io-console (0.8.2)
67
- irb (1.17.0)
44
+ erb (6.0.7)
45
+ ffi (1.17.4)
46
+ fileutils (1.8.1)
47
+ io-console (0.9.4)
48
+ irb (1.18.0)
68
49
  pp (>= 0.6.0)
69
50
  prism (>= 1.3.0)
70
51
  rdoc (>= 4.0.0)
71
52
  reline (>= 0.4.2)
72
- json (2.18.1)
73
- language_server-protocol (3.17.0.5)
53
+ json (3.1.0)
54
+ language_server-protocol (3.17.0.6)
74
55
  lint_roller (1.1.0)
75
- listen (3.10.0)
56
+ listen (3.10.1)
76
57
  logger
77
58
  rb-fsevent (~> 0.10, >= 0.10.3)
78
59
  rb-inotify (~> 0.9, >= 0.9.10)
79
60
  logger (1.7.0)
80
- markly (0.15.2)
61
+ markly (0.19.0)
81
62
  memoist3 (1.0.0)
82
63
  mini_mime (1.1.5)
83
- minitest (6.0.2)
84
- drb (~> 2.0)
85
- prism (~> 1.5)
86
64
  multi_test (1.1.0)
87
- mutex_m (0.3.0)
88
- parallel (1.27.0)
89
- parser (3.3.10.2)
65
+ parallel (2.3.0)
66
+ parser (3.3.12.0)
90
67
  ast (~> 2.4.1)
91
68
  racc
92
- pp (0.6.3)
69
+ pp (0.6.4)
93
70
  prettyprint
94
- prettyprint (0.2.0)
71
+ prettyprint (0.2.1)
95
72
  prism (1.9.0)
96
- psych (5.3.1)
97
- date
98
- stringio
99
73
  racc (1.8.1)
100
74
  rainbow (3.1.1)
101
- rake (13.3.1)
75
+ rake (13.4.2)
102
76
  rb-fsevent (0.11.2)
103
77
  rb-inotify (0.11.1)
104
78
  ffi (~> 1.0)
105
- rbs (3.10.3)
79
+ rbs (4.2.0)
106
80
  logger
81
+ prism (>= 1.6.0)
107
82
  tsort
108
- rdoc (7.2.0)
83
+ rdoc (8.1.0)
109
84
  erb
110
- psych (>= 4.0.0)
85
+ prism (>= 1.6.0)
86
+ rbs (>= 4.0.0)
111
87
  tsort
112
- regexp_parser (2.11.3)
113
- reline (0.6.3)
88
+ regexp_parser (2.13.1)
89
+ reline (0.7.0)
114
90
  io-console (~> 0.5)
115
91
  rexml (3.4.4)
116
92
  rspec (3.13.2)
@@ -126,18 +102,18 @@ GEM
126
102
  diff-lcs (>= 1.2.0, < 2.0)
127
103
  rspec-support (~> 3.13.0)
128
104
  rspec-support (3.13.7)
129
- rubocop (1.84.2)
130
- json (~> 2.3)
105
+ rubocop (1.91.0)
106
+ json (>= 2.3)
131
107
  language_server-protocol (~> 3.17.0.2)
132
108
  lint_roller (~> 1.1.0)
133
- parallel (~> 1.10)
109
+ parallel (>= 1.10)
134
110
  parser (>= 3.3.0.2)
135
111
  rainbow (>= 2.2.2, < 4.0)
136
112
  regexp_parser (>= 2.9.3, < 3.0)
137
113
  rubocop-ast (>= 1.49.0, < 2.0)
138
114
  ruby-progressbar (~> 1.7)
139
115
  unicode-display_width (>= 2.4.0, < 4.0)
140
- rubocop-ast (1.49.0)
116
+ rubocop-ast (1.50.0)
141
117
  parser (>= 3.3.7.2)
142
118
  prism (~> 1.7)
143
119
  rubocop-performance (1.26.1)
@@ -146,19 +122,14 @@ GEM
146
122
  rubocop-ast (>= 1.47.1, < 2.0)
147
123
  ruby-progressbar (1.13.0)
148
124
  securerandom (0.4.1)
149
- simplecov (0.22.0)
150
- docile (~> 1.1)
151
- simplecov-html (~> 0.11)
152
- simplecov_json_formatter (~> 0.1)
153
- simplecov-cobertura (3.1.0)
125
+ simplecov (1.3.2)
126
+ simplecov-cobertura (4.0.0)
154
127
  rexml
155
- simplecov (~> 0.19)
156
- simplecov-html (0.13.2)
157
- simplecov_json_formatter (0.1.4)
158
- standard (1.54.0)
128
+ simplecov (~> 1.0)
129
+ standard (1.57.0)
159
130
  language_server-protocol (~> 3.17.0.2)
160
131
  lint_roller (~> 1.0)
161
- rubocop (~> 1.84.0)
132
+ rubocop (~> 1.91.0)
162
133
  standard-custom (~> 1.0.0)
163
134
  standard-performance (~> 1.8)
164
135
  standard-custom (1.0.2)
@@ -167,8 +138,7 @@ GEM
167
138
  standard-performance (1.9.0)
168
139
  lint_roller (~> 1.1)
169
140
  rubocop-performance (~> 1.26.0)
170
- steep (1.10.0)
171
- activesupport (>= 5.1)
141
+ steep (2.1.0)
172
142
  concurrent-ruby (>= 1.1.10)
173
143
  csv (>= 3.0.9)
174
144
  fileutils (>= 1.1.0)
@@ -176,29 +146,31 @@ GEM
176
146
  language_server-protocol (>= 3.17.0.4, < 4.0)
177
147
  listen (~> 3.0)
178
148
  logger (>= 1.3.0)
179
- mutex_m (>= 0.3.0)
180
- parser (>= 3.1)
149
+ parser (>= 3.2)
150
+ prism (>= 0.25.0)
181
151
  rainbow (>= 2.2.2, < 4.0)
182
- rbs (~> 3.9)
152
+ rbs (~> 4.2)
183
153
  securerandom (>= 0.1)
184
154
  strscan (>= 1.0.0)
185
155
  terminal-table (>= 2, < 5)
186
156
  uri (>= 0.12.0)
187
- stringio (3.2.0)
188
- strscan (3.1.7)
189
- sys-uname (1.5.0)
157
+ strscan (3.1.8)
158
+ sys-uname (1.5.1)
159
+ ffi (~> 1.1)
160
+ memoist3 (~> 1.0.0)
161
+ sys-uname (1.5.1-universal-mingw32)
190
162
  ffi (~> 1.1)
191
163
  memoist3 (~> 1.0.0)
164
+ win32ole
192
165
  terminal-table (4.0.0)
193
166
  unicode-display_width (>= 1.1.1, < 4)
194
- timecop (0.9.10)
167
+ timecop (0.9.11)
195
168
  tsort (0.2.0)
196
- tzinfo (2.0.6)
197
- concurrent-ruby (~> 1.0)
198
- unicode-display_width (3.2.0)
199
- unicode-emoji (~> 4.1)
200
- unicode-emoji (4.2.0)
201
- uri (1.1.1)
169
+ unicode-display_width (3.3.0)
170
+ unicode-emoji (~> 4.3)
171
+ unicode-emoji (4.3.0)
172
+ uri (1.1.2)
173
+ win32ole (1.9.3)
202
174
 
203
175
  PLATFORMS
204
176
  arm64-darwin-21
@@ -214,19 +186,19 @@ PLATFORMS
214
186
  x86_64-linux
215
187
 
216
188
  DEPENDENCIES
217
- cucumber (~> 10.0)
189
+ cucumber (~> 11.0)
218
190
  debug
219
191
  logger
220
192
  markly
221
193
  openfeature-sdk!
222
194
  rake (~> 13.0)
223
195
  rspec (~> 3.13.0)
224
- simplecov (~> 0.22.0)
225
- simplecov-cobertura (~> 3.0)
196
+ simplecov (~> 1.3.0)
197
+ simplecov-cobertura (~> 4.0)
226
198
  standard
227
199
  standard-performance
228
- steep (~> 1.9)
200
+ steep (~> 2.0)
229
201
  timecop (~> 0.9.10)
230
202
 
231
203
  BUNDLED WITH
232
- 2.3.25
204
+ 4.0.12
data/README.md CHANGED
@@ -17,8 +17,8 @@
17
17
  </a>
18
18
  <!-- x-release-please-start-version -->
19
19
 
20
- <a href="https://github.com/open-feature/ruby-sdk/releases/tag/v0.6.5">
21
- <img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.6.5&color=blue&style=for-the-badge" />
20
+ <a href="https://github.com/open-feature/ruby-sdk/releases/tag/v0.6.7">
21
+ <img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.6.7&color=blue&style=for-the-badge" />
22
22
  </a>
23
23
 
24
24
  <!-- x-release-please-end -->
@@ -70,7 +70,6 @@ gem install openfeature-sdk
70
70
 
71
71
  ```ruby
72
72
  require 'open_feature/sdk'
73
- require 'json' # For JSON.dump
74
73
 
75
74
  # API Initialization and configuration
76
75
 
@@ -79,7 +78,13 @@ OpenFeature::SDK.configure do |config|
79
78
  config.set_provider(OpenFeature::SDK::Provider::InMemoryProvider.new(
80
79
  {
81
80
  "flag1" => true,
82
- "flag2" => 1
81
+ "flag2" => 1,
82
+ # Variant flags report a variant and reason, and can target on the evaluation context
83
+ "flag3" => OpenFeature::SDK::Provider::InMemoryProvider::Flag.new(
84
+ variants: {"on" => true, "off" => false},
85
+ default_variant: "off",
86
+ context_evaluator: ->(ctx) { "on" if ctx&.targeting_key == "beta-user" }
87
+ )
83
88
  }
84
89
  ))
85
90
  end
@@ -91,7 +96,7 @@ client = OpenFeature::SDK.build_client
91
96
  bool_value = client.fetch_boolean_value(flag_key: 'boolean_flag', default_value: false)
92
97
 
93
98
  # a details method is also available for more information about the flag evaluation
94
- # see `ResolutionDetails` for more info
99
+ # see `EvaluationDetails` for more info
95
100
  bool_details = client.fetch_boolean_details(flag_key: 'boolean_flag', default_value: false)
96
101
 
97
102
  # fetching string value feature flag
@@ -158,9 +163,6 @@ rescue OpenFeature::SDK::ProviderInitializationError => e
158
163
  puts "Original error: #{e.original_error}"
159
164
  end
160
165
 
161
- # With custom timeout (default is 30 seconds)
162
- OpenFeature::SDK.set_provider_and_wait(my_provider, timeout: 60)
163
-
164
166
  # Domain-specific provider
165
167
  OpenFeature::SDK.set_provider_and_wait(my_provider, domain: "feature-flags")
166
168
 
@@ -299,14 +301,15 @@ legacy_flag_client = OpenFeature::SDK.build_client(domain: "legacy_flags")
299
301
 
300
302
  Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions.
301
303
  Initialization events (`PROVIDER_READY` on success, `PROVIDER_ERROR` on failure) are dispatched for every provider.
302
- Some providers support additional events, such as `PROVIDER_CONFIGURATION_CHANGED`.
304
+ Some providers emit additional events: `PROVIDER_CONFIGURATION_CHANGED`, `PROVIDER_STALE`, `PROVIDER_RECONCILING` and `PROVIDER_CONTEXT_CHANGED`.
305
+ Handlers receive a hash with the `provider_name` and any details the provider sent, such as `message` or `error_code`.
303
306
 
304
307
  Please refer to the documentation of the provider you're using to see what events are supported.
305
308
 
306
309
  ```ruby
307
310
  # Register event handlers at the API (global) level
308
311
  ready_handler = ->(event_details) do
309
- puts "Provider #{event_details[:provider].metadata.name} is ready!"
312
+ puts "Provider #{event_details[:provider_name]} is ready!"
310
313
  end
311
314
 
312
315
  OpenFeature::SDK.add_handler(OpenFeature::SDK::ProviderEvent::PROVIDER_READY, ready_handler)
@@ -349,6 +352,9 @@ This should only be called when your application is in the process of shutting d
349
352
  OpenFeature::SDK.shutdown
350
353
  ```
351
354
 
355
+ The SDK also shuts down a replaced provider once no domain uses it.
356
+ Events from shut-down providers are ignored.
357
+
352
358
  Individual providers can implement a `shutdown` method to perform cleanup:
353
359
 
354
360
  ```ruby
@@ -440,13 +446,21 @@ You’ll then need to write the provider by implementing the `Provider` duck.
440
446
 
441
447
  ```ruby
442
448
  class MyProvider
443
- def init
449
+ # Both arguments are optional; declare only the ones you need.
450
+ # `domain` is the domain the provider was bound to, or nil for the default provider.
451
+ def init(evaluation_context = nil, domain: nil)
444
452
  # Perform any initialization steps with flag management system here
445
453
  # Return value is ignored
446
454
  # **Note** The OpenFeature spec defines a lifecycle method called `initialize` to be called when a new provider is set.
447
455
  # To avoid conflicting with the Ruby `initialize` method, this method should be named `init` when creating a provider.
448
456
  end
449
457
 
458
+ # Optional: return true if this provider keeps state for a single domain.
459
+ # The SDK then raises ArgumentError if the same instance is bound to a second domain.
460
+ def domain_scoped?
461
+ false
462
+ end
463
+
450
464
  def shutdown
451
465
  # Perform any shutdown/reclamation steps with flag management system here
452
466
  # Return value is ignored
@@ -454,6 +468,8 @@ class MyProvider
454
468
 
455
469
  def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
456
470
  # Retrieve a boolean value from provider source
471
+ # To signal an error, return ResolutionDetails with an error_code, or raise
472
+ # OpenFeature::SDK::EvaluationError.new("message", error_code: OpenFeature::SDK::Provider::ErrorCode::FLAG_NOT_FOUND)
457
473
  end
458
474
 
459
475
  def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
@@ -7,6 +7,7 @@ require_relative "configuration"
7
7
  require_relative "evaluation_context"
8
8
  require_relative "evaluation_context_builder"
9
9
  require_relative "evaluation_details"
10
+ require_relative "evaluation_error"
10
11
  require_relative "client_metadata"
11
12
  require_relative "hooks"
12
13
  require_relative "client"
@@ -56,12 +56,15 @@ module OpenFeature
56
56
  built_context = build_evaluation_context(evaluation_context)
57
57
 
58
58
  @provider.track(tracking_event_name, evaluation_context: built_context, tracking_event_details: tracking_event_details)
59
+ rescue
60
+ nil
59
61
  end
60
62
 
61
63
  RESULT_TYPE.each do |result_type|
62
64
  SUFFIXES.each do |suffix|
63
65
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
64
66
  def fetch_#{result_type}_#{suffix}(flag_key:, default_value:, evaluation_context: nil, hooks: [], hook_hints: nil)
67
+ validate_default_value_type(:#{result_type}, default_value)
65
68
  evaluation_details = fetch_details(type: :#{result_type}, flag_key:, default_value:, evaluation_context:, invocation_hooks: hooks, hook_hints: hook_hints)
66
69
  #{"evaluation_details.value" if suffix == :value}
67
70
  end
@@ -72,15 +75,13 @@ module OpenFeature
72
75
  private
73
76
 
74
77
  def fetch_details(type:, flag_key:, default_value:, evaluation_context: nil, invocation_hooks: [], hook_hints: nil)
75
- validate_default_value_type(type, default_value)
76
-
77
78
  built_context = build_evaluation_context(evaluation_context)
78
79
 
79
80
  # Assemble ordered hooks: API → Client → Invocation → Provider (spec 4.4.2)
80
81
  provider_hooks = @provider.respond_to?(:hooks) ? Array(@provider.hooks) : []
81
82
  ordered_hooks = [*OpenFeature::SDK.hooks, *@hooks, *invocation_hooks, *provider_hooks]
82
83
 
83
- # Check for short-circuit conditions (spec 1.7.6 + 1.7.7)
84
+ # Short-circuit NOT_READY/FATAL providers (required by evaluation_v2.feature)
84
85
  short_circuit_code = nil
85
86
  if OpenFeature::SDK.configuration.provider_tracked?(@provider)
86
87
  short_circuit_code = short_circuit_error_code(provider_status)
@@ -97,17 +98,7 @@ module OpenFeature
97
98
  return EvaluationDetails.new(flag_key: flag_key, resolution_details: resolution)
98
99
  end
99
100
 
100
- begin
101
- return evaluate_flag(type: type, flag_key: flag_key, default_value: default_value, evaluation_context: built_context)
102
- rescue => e
103
- resolution = Provider::ResolutionDetails.new(
104
- value: default_value,
105
- error_code: Provider::ErrorCode::GENERAL,
106
- reason: Provider::Reason::ERROR,
107
- error_message: e.message
108
- )
109
- return EvaluationDetails.new(flag_key: flag_key, resolution_details: resolution)
110
- end
101
+ return evaluate_flag(type: type, flag_key: flag_key, default_value: default_value, evaluation_context: built_context)
111
102
  end
112
103
 
113
104
  hook_context = Hooks::HookContext.new(
@@ -130,7 +121,6 @@ module OpenFeature
130
121
  executor = Hooks::HookExecutor.new(logger: OpenFeature::SDK.configuration.logger)
131
122
 
132
123
  if short_circuit_code
133
- # Spec 1.7.6 + 1.7.7: short-circuit must still run error hooks and finally hooks
134
124
  resolution = Provider::ResolutionDetails.new(
135
125
  value: default_value,
136
126
  error_code: short_circuit_code,
@@ -144,6 +134,15 @@ module OpenFeature
144
134
  evaluate_flag(type: type, flag_key: flag_key, default_value: default_value, evaluation_context: hctx.evaluation_context)
145
135
  end
146
136
  end
137
+ rescue StandardError, NotImplementedError => e
138
+ # Spec 1.4.10 and 1.4.11: report through the details, never raise or log. NotImplementedError is the abstract-method idiom.
139
+ resolution = Provider::ResolutionDetails.new(
140
+ value: default_value,
141
+ error_code: EvaluationError.code_for(e),
142
+ reason: Provider::Reason::ERROR,
143
+ error_message: e.message
144
+ )
145
+ EvaluationDetails.new(flag_key: flag_key, resolution_details: resolution)
147
146
  end
148
147
 
149
148
  def evaluate_flag(type:, flag_key:, default_value:, evaluation_context:)
@@ -154,9 +153,9 @@ module OpenFeature
154
153
  evaluation_context: evaluation_context
155
154
  )
156
155
 
157
- if TYPE_CLASS_MAP[type].none? { |klass| resolution_details.value.is_a?(klass) }
156
+ if resolution_details.error_code || TYPE_CLASS_MAP[type].none? { |klass| resolution_details.value.is_a?(klass) }
158
157
  resolution_details.value = default_value
159
- resolution_details.error_code = Provider::ErrorCode::TYPE_MISMATCH
158
+ resolution_details.error_code ||= Provider::ErrorCode::TYPE_MISMATCH
160
159
  resolution_details.reason = Provider::Reason::ERROR
161
160
  resolution_details.variant = nil
162
161
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "timeout"
4
3
  require_relative "api"
5
4
  require_relative "provider_initialization_error"
5
+ require_relative "evaluation_error"
6
6
  require_relative "event_dispatcher"
7
7
  require_relative "provider_event"
8
8
  require_relative "provider_state_registry"
@@ -20,9 +20,14 @@ module OpenFeature
20
20
  attr_accessor :evaluation_context, :hooks, :transaction_context_propagator
21
21
  attr_reader :logger
22
22
 
23
+ # One per registration; +done+ is closed once init finishes, so waiters can pop on it
24
+ InitAttempt = Struct.new(:state, :error, :done)
25
+ private_constant :InitAttempt
26
+
23
27
  def initialize
24
28
  @hooks = []
25
29
  @providers = {}
30
+ @init_attempts = {}.compare_by_identity
26
31
  @provider_mutex = Mutex.new
27
32
  @logger = nil
28
33
  @event_dispatcher = EventDispatcher.new(@logger)
@@ -89,11 +94,17 @@ module OpenFeature
89
94
  end
90
95
 
91
96
  def shutdown
92
- providers_to_shutdown = @provider_mutex.synchronize { @providers.values.uniq(&:object_id) }
97
+ # Unbind before shutting down so events that arrive mid-shutdown are dropped
98
+ providers_to_shutdown = @provider_mutex.synchronize do
99
+ bound = @providers.values.uniq(&:object_id)
100
+ @providers = {}
101
+ bound
102
+ end
93
103
 
94
104
  providers_to_shutdown.each do |prov|
95
- # Spec 1.7.9: Set provider state to NOT_READY before shutdown
105
+ # Spec 1.7.6: Set provider state to NOT_READY before shutdown
96
106
  @provider_state_registry.set_initial_state(prov, ProviderState::NOT_READY)
107
+ prov.send(:detach) if prov.is_a?(Provider::EventEmitter)
97
108
  prov.shutdown if prov.respond_to?(:shutdown)
98
109
  rescue => e
99
110
  @logger&.warn("Error shutting down provider #{prov&.class&.name || "unknown"}: #{e.message}")
@@ -112,6 +123,9 @@ module OpenFeature
112
123
  @provider_state_registry.clear
113
124
  @provider_mutex.synchronize do
114
125
  @providers.clear
126
+ # Release anyone waiting on an init that shutdown cut short
127
+ @init_attempts.each_value { |attempt| attempt.done.close }
128
+ @init_attempts.clear
115
129
  end
116
130
  @hooks.clear
117
131
  @evaluation_context = nil
@@ -123,31 +137,41 @@ module OpenFeature
123
137
  context_for_init = @evaluation_context
124
138
 
125
139
  old_provider = nil
140
+ attempt = nil
126
141
  needs_init = false
142
+ init_thread = nil
127
143
  needs_shutdown = false
128
144
 
145
+ # Provider code runs before taking the lock: it may call back into the SDK
146
+ scoped = domain_scoped?(provider)
147
+
129
148
  @provider_mutex.synchronize do
149
+ validate_domain_scoped_binding!(provider, domain, scoped)
150
+
130
151
  old_provider = @providers[domain]
131
152
 
132
153
  new_providers = @providers.dup
133
154
  new_providers[domain] = provider
134
155
  @providers = new_providers
135
156
 
136
- # Spec 1.1.2.2: Only initialize if the provider is not already active
137
- # (i.e., not already bound to another domain)
138
- already_active = @providers.any? { |d, p| d != domain && p.equal?(provider) && @provider_state_registry.tracked?(p) }
139
- needs_init = !already_active
157
+ # Spec 1.1.2.2: initialize once per registration; a failed init is retried on the next set
158
+ attempt = @init_attempts[provider]
159
+ needs_init = attempt.nil? || attempt.state == :failed
140
160
 
141
161
  if needs_init
162
+ attempt = @init_attempts[provider] = InitAttempt.new(:running, nil, Thread::Queue.new)
142
163
  @provider_state_registry.set_initial_state(provider)
143
164
  provider.send(:attach, self) if provider.is_a?(Provider::EventEmitter)
144
165
  end
145
166
 
146
167
  # Spec 1.1.2.3: Only shutdown old provider if it's no longer bound to any domain
147
168
  if old_provider && !old_provider.equal?(provider)
148
- still_bound = @providers.any? { |_, p| p.equal?(old_provider) }
149
- needs_shutdown = !still_bound
150
- @provider_state_registry.remove_provider(old_provider) unless still_bound
169
+ needs_shutdown = !provider_bound?(old_provider)
170
+ if needs_shutdown
171
+ @init_attempts.delete(old_provider)
172
+ @provider_state_registry.remove_provider(old_provider)
173
+ old_provider.send(:detach) if old_provider.is_a?(Provider::EventEmitter)
174
+ end
151
175
  end
152
176
  end
153
177
 
@@ -163,53 +187,74 @@ module OpenFeature
163
187
  # Initialize provider outside the mutex to avoid blocking other operations
164
188
  if needs_init
165
189
  if wait_for_init
166
- init_provider(provider, context_for_init, raise_on_error: true)
190
+ init_provider(provider, context_for_init, domain:, attempt:, raise_on_error: true)
167
191
  else
168
- Thread.new do
169
- init_provider(provider, context_for_init, raise_on_error: false)
192
+ init_thread = Thread.new do
193
+ init_provider(provider, context_for_init, domain:, attempt:, raise_on_error: false)
170
194
  end
171
195
  end
172
196
  elsif wait_for_init
173
- # Provider already active; no init needed but still dispatch READY
197
+ # Already registered: wait for an init still in flight, then report its outcome
198
+ attempt.done.pop
199
+ unless attempt.state == :succeeded
200
+ raise attempt.error || ProviderInitializationError.new(
201
+ "Provider #{provider.class.name} initialization did not complete",
202
+ provider:,
203
+ error_code: Provider::ErrorCode::GENERAL
204
+ )
205
+ end
206
+
174
207
  dispatch_provider_event(provider, ProviderEvent::PROVIDER_READY)
175
208
  end
209
+ ensure
210
+ # Fail an attempt no init took over (e.g. a Timeout hit first), or it would stay running forever
211
+ finish_init_attempt(attempt, :failed) if needs_init && !init_thread
176
212
  end
177
213
 
178
- def init_provider(provider, context, raise_on_error: false)
179
- if provider.respond_to?(:init)
180
- init_method = provider.method(:init)
181
- if init_method.parameters.empty?
182
- provider.init
183
- else
184
- provider.init(context)
185
- end
186
- end
214
+ def init_provider(provider, context, domain:, attempt:, raise_on_error: false)
215
+ call_init(provider, context, domain)
187
216
 
217
+ # Settle the attempt before handlers run, so a handler can register this provider again
218
+ finish_init_attempt(attempt, :succeeded)
188
219
  dispatch_provider_event(provider, ProviderEvent::PROVIDER_READY)
189
220
  rescue => e
190
- # Spec 1.7.8: Propagate error code from provider if available
191
- error_code = if e.respond_to?(:error_code) && e.error_code
192
- e.error_code
193
- else
194
- Provider::ErrorCode::GENERAL
195
- end
221
+ # Spec 1.7.5: PROVIDER_FATAL from init makes the provider FATAL
222
+ error_code = EvaluationError.code_for(e)
223
+ error = ProviderInitializationError.new(
224
+ "Provider #{provider.class.name} initialization failed: #{e.message}",
225
+ provider:,
226
+ error_code: error_code,
227
+ original_error: e
228
+ )
229
+ finish_init_attempt(attempt, :failed, error)
196
230
 
197
231
  dispatch_provider_event(provider, ProviderEvent::PROVIDER_ERROR,
198
232
  error_code: error_code,
199
233
  message: e.message)
200
234
 
201
- if raise_on_error
202
- raise ProviderInitializationError.new(
203
- "Provider #{provider.class.name} initialization failed: #{e.message}",
204
- provider:,
205
- error_code: error_code,
206
- original_error: e
207
- )
235
+ raise error if raise_on_error
236
+ ensure
237
+ # Reached unsettled only when init raised something other than a StandardError
238
+ finish_init_attempt(attempt, :failed)
239
+ end
240
+
241
+ def finish_init_attempt(attempt, state, error = nil)
242
+ @provider_mutex.synchronize do
243
+ return unless attempt.state == :running
244
+
245
+ attempt.state = state
246
+ attempt.error = error
208
247
  end
248
+ attempt.done.close
209
249
  end
210
250
 
211
251
  def dispatch_provider_event(provider, event_type, details = {})
212
- @provider_state_registry.update_state_from_event(provider, event_type, details)
252
+ # Under the replacement lock, so a late event can't re-track a replaced or shut-down provider
253
+ @provider_mutex.synchronize do
254
+ return unless provider_bound?(provider)
255
+
256
+ @provider_state_registry.update_state_from_event(provider, event_type, details)
257
+ end
213
258
 
214
259
  provider_name = extract_provider_name(provider)
215
260
 
@@ -222,10 +267,48 @@ module OpenFeature
222
267
 
223
268
  private
224
269
 
270
+ def provider_bound?(provider)
271
+ @providers.each_value.any? { |p| p.equal?(provider) }
272
+ end
273
+
225
274
  def extract_provider_name(provider)
226
275
  provider.respond_to?(:metadata) ? provider.metadata.name : provider.class.name
227
276
  end
228
277
 
278
+ def domain_scoped?(provider)
279
+ provider.respond_to?(:domain_scoped?) && provider.domain_scoped?
280
+ end
281
+
282
+ def validate_domain_scoped_binding!(provider, domain, scoped)
283
+ return unless scoped && provider_bound?(provider) && !@providers[domain].equal?(provider)
284
+
285
+ raise ArgumentError, "Cannot bind domain-scoped provider to more than one domain"
286
+ end
287
+
288
+ def call_init(provider, context, domain)
289
+ return unless provider.respond_to?(:init)
290
+
291
+ parameters = provider.method(:init).parameters
292
+ takes_domain = init_accepts_domain?(parameters)
293
+ if !takes_domain && domain_scoped?(provider)
294
+ @logger&.warn("Provider #{extract_provider_name(provider)} is domain-scoped but its init doesn't accept domain:")
295
+ end
296
+
297
+ args = init_accepts_evaluation_context?(parameters) ? [context] : []
298
+ provider.init(*args, **(takes_domain ? {domain:} : {}))
299
+ end
300
+
301
+ # Anonymous `**` (from `...`) and delegators' bare `*` can't be trusted to accept `domain:`
302
+ def init_accepts_domain?(parameters)
303
+ parameters.any? do |kind, name|
304
+ ((kind == :key || kind == :keyreq) && name == :domain) || (kind == :keyrest && name && name != :**)
305
+ end
306
+ end
307
+
308
+ def init_accepts_evaluation_context?(parameters)
309
+ parameters.any? { |kind, _| kind == :req || kind == :opt || kind == :rest }
310
+ end
311
+
229
312
  def run_handlers_for_provider(provider, event_type, event_details)
230
313
  # Run global handlers (API-level, no domain filtering)
231
314
  @event_dispatcher.trigger_event(event_type, event_details)
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "provider/error_code"
4
+
5
+ module OpenFeature
6
+ module SDK
7
+ # Raised by a provider to signal an evaluation error with an OpenFeature
8
+ # error code (spec 2.2.7). Any exception responding to +error_code+ is
9
+ # honored the same way.
10
+ class EvaluationError < StandardError
11
+ # @return [String] the OpenFeature error code
12
+ attr_reader :error_code
13
+
14
+ def initialize(message = nil, error_code: Provider::ErrorCode::GENERAL)
15
+ super(message || error_code)
16
+ @error_code = error_code
17
+ end
18
+
19
+ # The error code carried by +exception+, or GENERAL when it has none.
20
+ # Rescues a failing reader so callers inside a rescue block never raise (spec 1.4.10).
21
+ def self.code_for(exception)
22
+ (exception.respond_to?(:error_code) && exception.error_code) || Provider::ErrorCode::GENERAL
23
+ rescue
24
+ Provider::ErrorCode::GENERAL
25
+ end
26
+ end
27
+ end
28
+ end
@@ -18,15 +18,15 @@ module OpenFeature
18
18
  @logger = logger
19
19
  end
20
20
 
21
- # Runs error hooks and finally hooks for a short-circuit evaluation
22
- # (spec 1.7.6 + 1.7.7). Before hooks and after hooks are NOT run.
21
+ # Runs error hooks and finally hooks for a short-circuit evaluation.
22
+ # Before hooks and after hooks are NOT run.
23
23
  #
24
24
  # @param ordered_hooks [Array] hooks in before-order
25
25
  # @param hook_context [HookContext] the hook context
26
26
  # @param hints [Hints] hook hints
27
27
  # @param evaluation_details [EvaluationDetails] the short-circuit result
28
28
  def run_short_circuit(ordered_hooks:, hook_context:, hints:, evaluation_details:)
29
- error = StandardError.new(evaluation_details.error_code)
29
+ error = EvaluationError.new(evaluation_details.error_message, error_code: evaluation_details.error_code)
30
30
  run_error_hooks(ordered_hooks, hook_context, error, hints)
31
31
  ensure
32
32
  run_finally_hooks(ordered_hooks, hook_context, evaluation_details, hints)
@@ -49,7 +49,7 @@ module OpenFeature
49
49
  # Spec 4.3.6: If evaluation resulted in an error (e.g. FLAG_NOT_FOUND,
50
50
  # TYPE_MISMATCH), run error hooks instead of after hooks.
51
51
  if evaluation_details.error_code
52
- error = StandardError.new(evaluation_details.error_message || evaluation_details.error_code)
52
+ error = EvaluationError.new(evaluation_details.error_message, error_code: evaluation_details.error_code)
53
53
  run_error_hooks(ordered_hooks, hook_context, error, hints)
54
54
  else
55
55
  run_after_hooks(ordered_hooks, hook_context, evaluation_details, hints)
@@ -61,7 +61,7 @@ module OpenFeature
61
61
  flag_key: hook_context.flag_key,
62
62
  resolution_details: Provider::ResolutionDetails.new(
63
63
  value: hook_context.default_value,
64
- error_code: Provider::ErrorCode::GENERAL,
64
+ error_code: EvaluationError.code_for(e),
65
65
  reason: Provider::Reason::ERROR,
66
66
  error_message: e.message
67
67
  )
@@ -49,8 +49,7 @@ module OpenFeature
49
49
 
50
50
  def build_error_message(hook_context, exception)
51
51
  parts = base_parts("error", hook_context)
52
- error_code = exception.respond_to?(:error_code) ? exception.error_code : Provider::ErrorCode::GENERAL
53
- parts << "error_code=#{sanitize(error_code)}"
52
+ parts << "error_code=#{sanitize(EvaluationError.code_for(exception))}"
54
53
  parts << "error_message=#{sanitize(exception.message)}"
55
54
  parts << "evaluation_context=#{format_context(hook_context.evaluation_context)}" if @include_evaluation_context
56
55
  parts.join(" ")
@@ -8,6 +8,10 @@ module OpenFeature
8
8
 
9
9
  NAME = "In-memory Provider"
10
10
 
11
+ # A flag with named variants. +context_evaluator+ receives the evaluation
12
+ # context and returns a variant key; anything else falls back to +default_variant+.
13
+ Flag = Struct.new(:variants, :default_variant, :context_evaluator, :flag_metadata, :disabled, keyword_init: true)
14
+
11
15
  attr_reader :metadata
12
16
 
13
17
  def initialize(flags = {})
@@ -70,6 +74,8 @@ module OpenFeature
70
74
  return ResolutionDetails.new(value: default_value, error_code: ErrorCode::FLAG_NOT_FOUND, reason: Reason::ERROR)
71
75
  end
72
76
 
77
+ return resolve_variant_flag(raw_value, default_value, evaluation_context) if raw_value.is_a?(Flag)
78
+
73
79
  if raw_value.respond_to?(:call)
74
80
  value = raw_value.call(evaluation_context)
75
81
  ResolutionDetails.new(value: value, reason: Reason::TARGETING_MATCH)
@@ -78,6 +84,24 @@ module OpenFeature
78
84
  end
79
85
  end
80
86
 
87
+ def resolve_variant_flag(flag, default_value, evaluation_context)
88
+ flag_metadata = flag.flag_metadata
89
+ return ResolutionDetails.new(value: default_value, reason: Reason::DISABLED, flag_metadata:) if flag.disabled
90
+
91
+ variants = flag.variants.to_h
92
+ unless variants.key?(flag.default_variant)
93
+ return ResolutionDetails.new(value: default_value, error_code: ErrorCode::PARSE_ERROR, reason: Reason::ERROR, flag_metadata:)
94
+ end
95
+
96
+ matched = flag.context_evaluator&.call(evaluation_context)
97
+ if variants.key?(matched)
98
+ return ResolutionDetails.new(value: variants[matched], variant: matched, reason: Reason::TARGETING_MATCH, flag_metadata:)
99
+ end
100
+
101
+ reason = flag.context_evaluator ? Reason::DEFAULT : Reason::STATIC
102
+ ResolutionDetails.new(value: variants[flag.default_variant], variant: flag.default_variant, reason:, flag_metadata:)
103
+ end
104
+
81
105
  def emit_provider_changed(flag_keys)
82
106
  return unless configuration_attached?
83
107
 
@@ -13,12 +13,16 @@ module OpenFeature
13
13
  PROVIDER_ERROR = "PROVIDER_ERROR"
14
14
  PROVIDER_CONFIGURATION_CHANGED = "PROVIDER_CONFIGURATION_CHANGED"
15
15
  PROVIDER_STALE = "PROVIDER_STALE"
16
+ PROVIDER_RECONCILING = "PROVIDER_RECONCILING"
17
+ PROVIDER_CONTEXT_CHANGED = "PROVIDER_CONTEXT_CHANGED"
16
18
 
17
19
  ALL_EVENTS = [
18
20
  PROVIDER_READY,
19
21
  PROVIDER_ERROR,
20
22
  PROVIDER_CONFIGURATION_CHANGED,
21
- PROVIDER_STALE
23
+ PROVIDER_STALE,
24
+ PROVIDER_RECONCILING,
25
+ PROVIDER_CONTEXT_CHANGED
22
26
  ].freeze
23
27
  end
24
28
  end
@@ -91,7 +91,7 @@ module OpenFeature
91
91
 
92
92
  def state_from_event(event_type, event_details = nil)
93
93
  case event_type
94
- when ProviderEvent::PROVIDER_READY
94
+ when ProviderEvent::PROVIDER_READY, ProviderEvent::PROVIDER_CONTEXT_CHANGED
95
95
  ProviderState::READY
96
96
  when ProviderEvent::PROVIDER_STALE
97
97
  ProviderState::STALE
@@ -100,7 +100,7 @@ module OpenFeature
100
100
  when ProviderEvent::PROVIDER_CONFIGURATION_CHANGED
101
101
  nil # No state change per OpenFeature spec Requirement 5.3.5
102
102
  else
103
- nil # No state change for unknown events - conservative default
103
+ nil # PROVIDER_RECONCILING (RECONCILING status is static-context only, spec 1.7.2.1) and unknown events
104
104
  end
105
105
  end
106
106
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OpenFeature
4
4
  module SDK
5
- VERSION = "0.6.5"
5
+ VERSION = "0.6.7"
6
6
  end
7
7
  end
data/renovate.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
3
  "extends": [
4
- "config:recommended",
4
+ "github>open-feature/community-tooling",
5
5
  ":automergeStableNonMajor",
6
6
  "security:minimumReleaseAgeNpm"
7
7
  ],
@@ -32,8 +32,15 @@ module OpenFeature
32
32
 
33
33
  def reset: () -> void
34
34
  def set_provider_internal: (untyped provider, domain: String?, wait_for_init: bool) -> void
35
- def init_provider: (untyped provider, EvaluationContext? context, ?raise_on_error: bool) -> void
35
+ def init_provider: (untyped provider, EvaluationContext? context, domain: String?, attempt: untyped, ?raise_on_error: bool) -> void
36
+ def finish_init_attempt: (untyped attempt, Symbol state, ?ProviderInitializationError? error) -> void
36
37
  def dispatch_provider_event: (untyped provider, String event_type, ?Hash[Symbol, untyped] details) -> void
38
+ def provider_bound?: (untyped provider) -> bool
39
+ def domain_scoped?: (untyped provider) -> bool
40
+ def validate_domain_scoped_binding!: (untyped provider, String? domain, bool scoped) -> void
41
+ def call_init: (untyped provider, EvaluationContext? context, String? domain) -> void
42
+ def init_accepts_domain?: (Array[[Symbol, Symbol?]] parameters) -> bool
43
+ def init_accepts_evaluation_context?: (Array[[Symbol, Symbol?]] parameters) -> bool
37
44
  def extract_provider_name: (untyped provider) -> String
38
45
  def run_handlers_for_provider: (untyped provider, String event_type, Hash[Symbol, untyped] event_details) -> void
39
46
  def run_immediate_handler: (String event_type, _ToProc handler, Client? client) -> void
@@ -0,0 +1,11 @@
1
+ module OpenFeature
2
+ module SDK
3
+ class EvaluationError < StandardError
4
+ attr_reader error_code: String
5
+
6
+ def initialize: (?String? message, ?error_code: String) -> void
7
+
8
+ def self.code_for: (Exception exception) -> String
9
+ end
10
+ end
11
+ end
@@ -6,6 +6,16 @@ module OpenFeature
6
6
 
7
7
  NAME: String
8
8
 
9
+ class Flag < Struct[untyped]
10
+ attr_accessor variants: Hash[String, untyped]?
11
+ attr_accessor default_variant: String?
12
+ attr_accessor context_evaluator: (^(EvaluationContext?) -> String?)?
13
+ attr_accessor flag_metadata: Hash[String, untyped]?
14
+ attr_accessor disabled: bool?
15
+
16
+ def initialize: (?variants: Hash[String, untyped]?, ?default_variant: String?, ?context_evaluator: (^(EvaluationContext?) -> String?)?, ?flag_metadata: Hash[String, untyped]?, ?disabled: bool?) -> void
17
+ end
18
+
9
19
  attr_reader metadata: ProviderMetadata
10
20
 
11
21
  def initialize: (?Hash[String, untyped] flags) -> void
@@ -28,6 +38,7 @@ module OpenFeature
28
38
  attr_reader flags: Hash[String, untyped]
29
39
 
30
40
  def fetch_value: (flag_key: String, default_value: untyped, evaluation_context: EvaluationContext?) -> ResolutionDetails
41
+ def resolve_variant_flag: (Flag flag, untyped default_value, EvaluationContext? evaluation_context) -> ResolutionDetails
31
42
  def emit_provider_changed: (Array[String] flag_keys) -> void
32
43
  end
33
44
  end
@@ -11,11 +11,15 @@ module OpenFeature
11
11
  end
12
12
 
13
13
  interface _LifecycleProvider
14
- def init: (?EvaluationContext? evaluation_context) -> void
14
+ def init: (?EvaluationContext? evaluation_context, ?domain: String?) -> void
15
15
  def shutdown: () -> void
16
16
  def metadata: () -> ProviderMetadata
17
17
  end
18
18
 
19
+ interface _DomainScopedProvider
20
+ def domain_scoped?: () -> bool
21
+ end
22
+
19
23
  interface _TrackableProvider
20
24
  def track: (String tracking_event_name, ?evaluation_context: EvaluationContext?, ?tracking_event_details: TrackingEventDetails?) -> void
21
25
  end
@@ -5,6 +5,8 @@ module OpenFeature
5
5
  PROVIDER_ERROR: String
6
6
  PROVIDER_CONFIGURATION_CHANGED: String
7
7
  PROVIDER_STALE: String
8
+ PROVIDER_RECONCILING: String
9
+ PROVIDER_CONTEXT_CHANGED: String
8
10
 
9
11
  ALL_EVENTS: Array[String]
10
12
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openfeature-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.5
4
+ version: 0.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - OpenFeature Authors
@@ -20,7 +20,6 @@ files:
20
20
  - ".release-please-manifest.json"
21
21
  - ".rspec"
22
22
  - ".ruby-version"
23
- - ".simplecov"
24
23
  - ".standard.yml"
25
24
  - ".tool-versions"
26
25
  - ".yardopts"
@@ -48,6 +47,7 @@ files:
48
47
  - lib/open_feature/sdk/evaluation_context.rb
49
48
  - lib/open_feature/sdk/evaluation_context_builder.rb
50
49
  - lib/open_feature/sdk/evaluation_details.rb
50
+ - lib/open_feature/sdk/evaluation_error.rb
51
51
  - lib/open_feature/sdk/event_dispatcher.rb
52
52
  - lib/open_feature/sdk/hooks.rb
53
53
  - lib/open_feature/sdk/hooks/hints.rb
@@ -85,6 +85,7 @@ files:
85
85
  - sig/open_feature/sdk/evaluation_context.rbs
86
86
  - sig/open_feature/sdk/evaluation_context_builder.rbs
87
87
  - sig/open_feature/sdk/evaluation_details.rbs
88
+ - sig/open_feature/sdk/evaluation_error.rbs
88
89
  - sig/open_feature/sdk/event_dispatcher.rbs
89
90
  - sig/open_feature/sdk/hooks/hints.rbs
90
91
  - sig/open_feature/sdk/hooks/hook.rbs
@@ -132,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
133
  - !ruby/object:Gem::Version
133
134
  version: '0'
134
135
  requirements: []
135
- rubygems_version: 4.0.6
136
+ rubygems_version: 4.0.20
136
137
  specification_version: 4
137
138
  summary: OpenFeature SDK for Ruby
138
139
  test_files: []
data/.simplecov DELETED
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "simplecov-cobertura"
4
-
5
- SimpleCov.start do
6
- add_filter "/spec/"
7
- minimum_coverage 90
8
- if ENV["CI"] == "true"
9
- formatter SimpleCov::Formatter::CoberturaFormatter
10
- else
11
- formatter SimpleCov::Formatter::HTMLFormatter
12
- end
13
- end