openfeature-sdk 0.6.5 → 0.6.6
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/.release-please-manifest.json +1 -1
- data/.ruby-version +1 -1
- data/.tool-versions +1 -1
- data/CHANGELOG.md +13 -0
- data/CLAUDE.md +1 -1
- data/Gemfile +4 -4
- data/Gemfile.lock +63 -91
- data/README.md +12 -8
- data/lib/open_feature/sdk/api.rb +1 -0
- data/lib/open_feature/sdk/client.rb +2 -3
- data/lib/open_feature/sdk/configuration.rb +26 -13
- data/lib/open_feature/sdk/evaluation_error.rb +28 -0
- data/lib/open_feature/sdk/hooks/hook_executor.rb +5 -5
- data/lib/open_feature/sdk/hooks/logging_hook.rb +1 -2
- data/lib/open_feature/sdk/provider/in_memory_provider.rb +24 -0
- data/lib/open_feature/sdk/provider_event.rb +5 -1
- data/lib/open_feature/sdk/provider_state_registry.rb +2 -2
- data/lib/open_feature/sdk/version.rb +1 -1
- data/renovate.json +1 -1
- data/sig/open_feature/sdk/configuration.rbs +1 -0
- data/sig/open_feature/sdk/evaluation_error.rbs +11 -0
- data/sig/open_feature/sdk/provider/in_memory_provider.rbs +11 -0
- data/sig/open_feature/sdk/provider_event.rbs +2 -0
- metadata +4 -3
- data/.simplecov +0 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 500c09c5333475a75aeff77cdab714c1360f4c14a84c28f9f16fe3b0dc42cb8f
|
|
4
|
+
data.tar.gz: 4d760e25308fad1ea762e8547260f87dea3474a8861eadd25993cfd2dde55b04
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4bfda5295966ab245426ecaa0d2dbb440b7f273833193906b9fd91fd6ffeb41e5b49e652a9d05dbf6679c08cfc434c4733d34384dfc1d64be995194f3938cdf1
|
|
7
|
+
data.tar.gz: 2359651d7407cdd30e41e15e4d764efc47565e025b2046adbc4942da0c25d62396bd5e52991626d728b675b123aa52bcce733f8ebc71703112ebe77d6704e96e
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.0.
|
|
1
|
+
4.0.7
|
data/.tool-versions
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
ruby 4.0.
|
|
1
|
+
ruby 4.0.7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.6](https://github.com/open-feature/ruby-sdk/compare/v0.6.5...v0.6.6) (2026-10-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* 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))
|
|
9
|
+
* 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))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* 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))
|
|
15
|
+
|
|
3
16
|
## [0.6.5](https://github.com/open-feature/ruby-sdk/compare/v0.6.4...v0.6.5) (2026-03-18)
|
|
4
17
|
|
|
5
18
|
|
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.
|
|
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", "~>
|
|
16
|
-
gem "simplecov-cobertura", "~>
|
|
17
|
-
gem "steep", "~>
|
|
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", "~>
|
|
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.
|
|
4
|
+
openfeature-sdk (0.6.6)
|
|
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.
|
|
11
|
+
bigdecimal (4.1.3)
|
|
25
12
|
builder (3.3.0)
|
|
26
|
-
concurrent-ruby (1.3.
|
|
27
|
-
|
|
28
|
-
|
|
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 (
|
|
19
|
+
cucumber-core (>= 16.2.0, < 17)
|
|
34
20
|
cucumber-cucumber-expressions (> 17, < 20)
|
|
35
|
-
cucumber-html-formatter (> 21, <
|
|
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.
|
|
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.
|
|
32
|
+
cucumber-cucumber-expressions (19.0.1)
|
|
47
33
|
bigdecimal
|
|
48
|
-
cucumber-gherkin (39.
|
|
34
|
+
cucumber-gherkin (39.1.0)
|
|
49
35
|
cucumber-messages (>= 31, < 33)
|
|
50
|
-
cucumber-html-formatter (
|
|
36
|
+
cucumber-html-formatter (23.1.0)
|
|
51
37
|
cucumber-messages (> 23, < 33)
|
|
52
|
-
cucumber-messages (32.
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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 (
|
|
73
|
-
language_server-protocol (3.17.0.
|
|
53
|
+
json (3.1.0)
|
|
54
|
+
language_server-protocol (3.17.0.6)
|
|
74
55
|
lint_roller (1.1.0)
|
|
75
|
-
listen (3.10.
|
|
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.
|
|
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
|
-
|
|
88
|
-
|
|
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.
|
|
69
|
+
pp (0.6.4)
|
|
93
70
|
prettyprint
|
|
94
|
-
prettyprint (0.2.
|
|
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.
|
|
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 (
|
|
79
|
+
rbs (4.2.0)
|
|
106
80
|
logger
|
|
81
|
+
prism (>= 1.6.0)
|
|
107
82
|
tsort
|
|
108
|
-
rdoc (
|
|
83
|
+
rdoc (8.1.0)
|
|
109
84
|
erb
|
|
110
|
-
|
|
85
|
+
prism (>= 1.6.0)
|
|
86
|
+
rbs (>= 4.0.0)
|
|
111
87
|
tsort
|
|
112
|
-
regexp_parser (2.
|
|
113
|
-
reline (0.
|
|
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.
|
|
130
|
-
json (
|
|
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 (
|
|
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.
|
|
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 (
|
|
150
|
-
|
|
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
|
|
156
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
180
|
-
|
|
149
|
+
parser (>= 3.2)
|
|
150
|
+
prism (>= 0.25.0)
|
|
181
151
|
rainbow (>= 2.2.2, < 4.0)
|
|
182
|
-
rbs (~>
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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.
|
|
167
|
+
timecop (0.9.11)
|
|
195
168
|
tsort (0.2.0)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
unicode-
|
|
199
|
-
|
|
200
|
-
|
|
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 (~>
|
|
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 (~>
|
|
225
|
-
simplecov-cobertura (~>
|
|
196
|
+
simplecov (~> 1.3.0)
|
|
197
|
+
simplecov-cobertura (~> 4.0)
|
|
226
198
|
standard
|
|
227
199
|
standard-performance
|
|
228
|
-
steep (~>
|
|
200
|
+
steep (~> 2.0)
|
|
229
201
|
timecop (~> 0.9.10)
|
|
230
202
|
|
|
231
203
|
BUNDLED WITH
|
|
232
|
-
|
|
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.
|
|
21
|
-
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.6.
|
|
20
|
+
<a href="https://github.com/open-feature/ruby-sdk/releases/tag/v0.6.6">
|
|
21
|
+
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v0.6.6&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 `
|
|
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
|
|
|
@@ -454,6 +456,8 @@ class MyProvider
|
|
|
454
456
|
|
|
455
457
|
def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
|
|
456
458
|
# Retrieve a boolean value from provider source
|
|
459
|
+
# To signal an error, return ResolutionDetails with an error_code, or raise
|
|
460
|
+
# OpenFeature::SDK::EvaluationError.new("message", error_code: OpenFeature::SDK::Provider::ErrorCode::FLAG_NOT_FOUND)
|
|
457
461
|
end
|
|
458
462
|
|
|
459
463
|
def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
|
data/lib/open_feature/sdk/api.rb
CHANGED
|
@@ -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"
|
|
@@ -80,7 +80,7 @@ module OpenFeature
|
|
|
80
80
|
provider_hooks = @provider.respond_to?(:hooks) ? Array(@provider.hooks) : []
|
|
81
81
|
ordered_hooks = [*OpenFeature::SDK.hooks, *@hooks, *invocation_hooks, *provider_hooks]
|
|
82
82
|
|
|
83
|
-
#
|
|
83
|
+
# Short-circuit NOT_READY/FATAL providers (required by evaluation_v2.feature)
|
|
84
84
|
short_circuit_code = nil
|
|
85
85
|
if OpenFeature::SDK.configuration.provider_tracked?(@provider)
|
|
86
86
|
short_circuit_code = short_circuit_error_code(provider_status)
|
|
@@ -102,7 +102,7 @@ module OpenFeature
|
|
|
102
102
|
rescue => e
|
|
103
103
|
resolution = Provider::ResolutionDetails.new(
|
|
104
104
|
value: default_value,
|
|
105
|
-
error_code:
|
|
105
|
+
error_code: EvaluationError.code_for(e),
|
|
106
106
|
reason: Provider::Reason::ERROR,
|
|
107
107
|
error_message: e.message
|
|
108
108
|
)
|
|
@@ -130,7 +130,6 @@ module OpenFeature
|
|
|
130
130
|
executor = Hooks::HookExecutor.new(logger: OpenFeature::SDK.configuration.logger)
|
|
131
131
|
|
|
132
132
|
if short_circuit_code
|
|
133
|
-
# Spec 1.7.6 + 1.7.7: short-circuit must still run error hooks and finally hooks
|
|
134
133
|
resolution = Provider::ResolutionDetails.new(
|
|
135
134
|
value: default_value,
|
|
136
135
|
error_code: short_circuit_code,
|
|
@@ -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"
|
|
@@ -89,11 +89,17 @@ module OpenFeature
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def shutdown
|
|
92
|
-
|
|
92
|
+
# Unbind before shutting down so events that arrive mid-shutdown are dropped
|
|
93
|
+
providers_to_shutdown = @provider_mutex.synchronize do
|
|
94
|
+
bound = @providers.values.uniq(&:object_id)
|
|
95
|
+
@providers = {}
|
|
96
|
+
bound
|
|
97
|
+
end
|
|
93
98
|
|
|
94
99
|
providers_to_shutdown.each do |prov|
|
|
95
|
-
# Spec 1.7.
|
|
100
|
+
# Spec 1.7.6: Set provider state to NOT_READY before shutdown
|
|
96
101
|
@provider_state_registry.set_initial_state(prov, ProviderState::NOT_READY)
|
|
102
|
+
prov.send(:detach) if prov.is_a?(Provider::EventEmitter)
|
|
97
103
|
prov.shutdown if prov.respond_to?(:shutdown)
|
|
98
104
|
rescue => e
|
|
99
105
|
@logger&.warn("Error shutting down provider #{prov&.class&.name || "unknown"}: #{e.message}")
|
|
@@ -145,9 +151,11 @@ module OpenFeature
|
|
|
145
151
|
|
|
146
152
|
# Spec 1.1.2.3: Only shutdown old provider if it's no longer bound to any domain
|
|
147
153
|
if old_provider && !old_provider.equal?(provider)
|
|
148
|
-
|
|
149
|
-
needs_shutdown
|
|
150
|
-
|
|
154
|
+
needs_shutdown = !provider_bound?(old_provider)
|
|
155
|
+
if needs_shutdown
|
|
156
|
+
@provider_state_registry.remove_provider(old_provider)
|
|
157
|
+
old_provider.send(:detach) if old_provider.is_a?(Provider::EventEmitter)
|
|
158
|
+
end
|
|
151
159
|
end
|
|
152
160
|
end
|
|
153
161
|
|
|
@@ -187,12 +195,8 @@ module OpenFeature
|
|
|
187
195
|
|
|
188
196
|
dispatch_provider_event(provider, ProviderEvent::PROVIDER_READY)
|
|
189
197
|
rescue => e
|
|
190
|
-
# Spec 1.7.
|
|
191
|
-
error_code =
|
|
192
|
-
e.error_code
|
|
193
|
-
else
|
|
194
|
-
Provider::ErrorCode::GENERAL
|
|
195
|
-
end
|
|
198
|
+
# Spec 1.7.5: PROVIDER_FATAL from init makes the provider FATAL
|
|
199
|
+
error_code = EvaluationError.code_for(e)
|
|
196
200
|
|
|
197
201
|
dispatch_provider_event(provider, ProviderEvent::PROVIDER_ERROR,
|
|
198
202
|
error_code: error_code,
|
|
@@ -209,7 +213,12 @@ module OpenFeature
|
|
|
209
213
|
end
|
|
210
214
|
|
|
211
215
|
def dispatch_provider_event(provider, event_type, details = {})
|
|
212
|
-
|
|
216
|
+
# Under the replacement lock, so a late event can't re-track a replaced or shut-down provider
|
|
217
|
+
@provider_mutex.synchronize do
|
|
218
|
+
return unless provider_bound?(provider)
|
|
219
|
+
|
|
220
|
+
@provider_state_registry.update_state_from_event(provider, event_type, details)
|
|
221
|
+
end
|
|
213
222
|
|
|
214
223
|
provider_name = extract_provider_name(provider)
|
|
215
224
|
|
|
@@ -222,6 +231,10 @@ module OpenFeature
|
|
|
222
231
|
|
|
223
232
|
private
|
|
224
233
|
|
|
234
|
+
def provider_bound?(provider)
|
|
235
|
+
@providers.each_value.any? { |p| p.equal?(provider) }
|
|
236
|
+
end
|
|
237
|
+
|
|
225
238
|
def extract_provider_name(provider)
|
|
226
239
|
provider.respond_to?(:metadata) ? provider.metadata.name : provider.class.name
|
|
227
240
|
end
|
|
@@ -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
|
-
#
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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 #
|
|
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
|
|
data/renovate.json
CHANGED
|
@@ -34,6 +34,7 @@ module OpenFeature
|
|
|
34
34
|
def set_provider_internal: (untyped provider, domain: String?, wait_for_init: bool) -> void
|
|
35
35
|
def init_provider: (untyped provider, EvaluationContext? context, ?raise_on_error: bool) -> void
|
|
36
36
|
def dispatch_provider_event: (untyped provider, String event_type, ?Hash[Symbol, untyped] details) -> void
|
|
37
|
+
def provider_bound?: (untyped provider) -> bool
|
|
37
38
|
def extract_provider_name: (untyped provider) -> String
|
|
38
39
|
def run_handlers_for_provider: (untyped provider, String event_type, Hash[Symbol, untyped] event_details) -> void
|
|
39
40
|
def run_immediate_handler: (String event_type, _ToProc handler, Client? client) -> void
|
|
@@ -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
|
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.
|
|
4
|
+
version: 0.6.6
|
|
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.
|
|
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
|