openfeature-sdk-sorbet 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -1
- data/.ruby-version +1 -1
- data/.tool-versions +1 -1
- data/CHANGELOG.md +12 -3
- data/Gemfile +1 -1
- data/Gemfile.lock +55 -49
- data/README.md +23 -6
- data/lib/open_feature/client.rb +90 -32
- data/lib/open_feature/client_metadata.rb +1 -0
- data/lib/open_feature/evaluation_context.rb +2 -2
- data/lib/open_feature/evaluation_details.rb +2 -2
- data/lib/open_feature/hook.rb +41 -1
- data/lib/open_feature/hook_context.rb +30 -0
- data/lib/open_feature/hooks.rb +22 -0
- data/lib/open_feature/multiple_source_provider.rb +28 -12
- data/lib/open_feature/no_op_provider.rb +2 -3
- data/lib/open_feature/provider.rb +16 -2
- data/lib/open_feature/provider_status.rb +13 -0
- data/lib/open_feature.rb +9 -2
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/{json@2.6.3.rbi → json@2.7.1.rbi} +80 -60
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
- data/sorbet/rbi/gems/{minitest@5.18.0.rbi → minitest@5.21.2.rbi} +299 -258
- data/sorbet/rbi/gems/{parallel@1.23.0.rbi → parallel@1.24.0.rbi} +8 -1
- data/sorbet/rbi/gems/{parser@3.2.2.1.rbi → parser@3.3.0.5.rbi} +438 -2219
- data/sorbet/rbi/gems/prism@0.19.0.rbi +25199 -0
- data/sorbet/rbi/gems/psych@5.1.2.rbi +1731 -0
- data/sorbet/rbi/gems/racc@1.7.3.rbi +157 -0
- data/sorbet/rbi/gems/{rake@13.0.6.rbi → rake@13.1.0.rbi} +68 -65
- data/sorbet/rbi/gems/{rbi@0.0.16.rbi → rbi@0.1.6.rbi} +628 -755
- data/sorbet/rbi/gems/{regexp_parser@2.8.0.rbi → regexp_parser@2.9.0.rbi} +203 -180
- data/sorbet/rbi/gems/{rexml@3.2.5.rbi → rexml@3.2.6.rbi} +116 -52
- data/sorbet/rbi/gems/{rubocop-ast@1.28.1.rbi → rubocop-ast@1.30.0.rbi} +178 -84
- data/sorbet/rbi/gems/{rubocop-minitest@0.31.0.rbi → rubocop-minitest@0.34.5.rbi} +280 -232
- data/sorbet/rbi/gems/{rubocop-performance@1.17.1.rbi → rubocop-performance@1.20.2.rbi} +397 -172
- data/sorbet/rbi/gems/{rubocop-sorbet@0.7.0.rbi → rubocop-sorbet@0.7.6.rbi} +728 -261
- data/sorbet/rbi/gems/{rubocop@1.51.0.rbi → rubocop@1.60.2.rbi} +4006 -1936
- data/sorbet/rbi/gems/spoom@1.2.1.rbi +17 -56
- data/sorbet/rbi/gems/stringio@3.1.0.rbi +8 -0
- data/sorbet/rbi/gems/{tapioca@0.11.6.rbi → tapioca@0.11.17.rbi} +778 -576
- data/sorbet/rbi/gems/{thor@1.2.2.rbi → thor@1.3.0.rbi} +775 -395
- data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +1 -1
- data/sorbet/rbi/gems/yard@0.9.34.rbi +2 -2
- data/sorbet/rbi/gems/{zeitwerk@2.6.8.rbi → zeitwerk@2.6.12.rbi} +78 -67
- data/sorbet/tapioca/config.yml +2 -2
- data/sorbet/tapioca/require.rb +3 -1
- metadata +36 -31
- data/openfeature-sdk-sorbet.gemspec +0 -35
- data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +0 -1083
- data/sorbet/rbi/gems/irb@1.6.4.rbi +0 -342
- data/sorbet/rbi/gems/unparser@0.6.7.rbi +0 -4524
- /data/sorbet/rbi/gems/{io-console@0.6.0.rbi → io-console@0.7.2.rbi} +0 -0
- /data/sorbet/rbi/gems/{reline@0.3.3.rbi → reline@0.4.2.rbi} +0 -0
- /data/sorbet/rbi/gems/{unicode-display_width@2.4.2.rbi → unicode-display_width@2.5.0.rbi} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c15c80725be63842d38491ff9c0addddcf29d063a191eadb44fe957e059ab520
|
4
|
+
data.tar.gz: 7a263495564db2fee7cb4805d82458ee70951a30ebe15bb470f205c97c32b529
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6cb5f8413afe5a94866d456e841fabc092c09058d0117df0847e33111cd1020b313f45c5ad512200f19e463557369eca2a846f6eb0b28b6e4f8c7fff898ce1e7
|
7
|
+
data.tar.gz: 6279c0be67ad089ee53201d451cf6349cc976e59d016c850a1d74711a47bc890aad6ba5e11ff92ec343c8277555a81834bf238d7a487275db33cd224400272e1
|
data/.rubocop.yml
CHANGED
@@ -10,7 +10,7 @@ require:
|
|
10
10
|
|
11
11
|
AllCops:
|
12
12
|
NewCops: enable
|
13
|
-
TargetRubyVersion:
|
13
|
+
TargetRubyVersion: 3.1
|
14
14
|
Exclude:
|
15
15
|
- sorbet/**/*.rbi
|
16
16
|
|
@@ -25,6 +25,7 @@ Lint/UnusedMethodArgument:
|
|
25
25
|
Metrics/ClassLength:
|
26
26
|
Exclude:
|
27
27
|
- lib/open_feature/client.rb
|
28
|
+
- lib/open_feature/multiple_source_provider.rb
|
28
29
|
- test/open_feature/client_test.rb
|
29
30
|
- test/open_feature/multiple_source_provider_test.rb
|
30
31
|
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.
|
1
|
+
3.3.0
|
data/.tool-versions
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby 3.
|
1
|
+
ruby 3.3.0
|
data/CHANGELOG.md
CHANGED
@@ -6,11 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
-
## [0.
|
9
|
+
## [0.3.0] - 2024-01-24
|
10
10
|
|
11
|
-
###
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- Added `OpenFeature::Provider#status` reader that returns an `OpenFeature::ProviderStatus`.
|
14
|
+
- Added `OpenFeature::Provider#init` as an overridable method on `Provider`s that can perform any initialization work for the given provider. This method accepts the global `EvaluationContext` and has a `void` return type. The default implementation is a no-op.
|
15
|
+
- Added `OpenFeature::Provider#shutdown` as an overridable method on `Provider`s that can perform any cleanup work for the given provider. This method has a `void` return type and the default implementation is a no-op.
|
16
|
+
- Added `OpenFeature.shutdown` to invoke the current provider's `shutdown` method.
|
17
|
+
|
18
|
+
### Changed
|
12
19
|
|
13
|
-
-
|
20
|
+
- *Breaking* Changed minimum supported Ruby version to 3.1.
|
21
|
+
- *Breaking* `OpenFeature::Provider` changed from a module interface to an abstract class to support default method implementations.
|
22
|
+
- *Breaking* `OpenFeature::Provider.initialize` now must accept an `OpenFeature::ProviderStatus`. Any providers may pass this in during initialization. If you need to do additional setup in `Provider.init`, we recommend you pass `OpenFeature::ProviderStatus::NotReady` here.
|
14
23
|
|
15
24
|
## [0.2.0] - 2023-05-17
|
16
25
|
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
openfeature-sdk-sorbet (0.
|
4
|
+
openfeature-sdk-sorbet (0.3.0)
|
5
5
|
sorbet-runtime (~> 0.5)
|
6
6
|
sorbet-struct-comparable (~> 1.3)
|
7
7
|
zeitwerk (~> 2.6)
|
@@ -10,88 +10,94 @@ GEM
|
|
10
10
|
remote: https://rubygems.org/
|
11
11
|
specs:
|
12
12
|
ast (2.4.2)
|
13
|
-
debug (1.
|
14
|
-
irb (
|
15
|
-
reline (>= 0.3.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
reline (>= 0.
|
20
|
-
json (2.
|
21
|
-
|
13
|
+
debug (1.9.1)
|
14
|
+
irb (~> 1.10)
|
15
|
+
reline (>= 0.3.8)
|
16
|
+
io-console (0.7.2)
|
17
|
+
irb (1.11.1)
|
18
|
+
rdoc
|
19
|
+
reline (>= 0.4.2)
|
20
|
+
json (2.7.1)
|
21
|
+
language_server-protocol (3.17.0.3)
|
22
|
+
minitest (5.21.2)
|
22
23
|
netrc (0.11.0)
|
23
|
-
parallel (1.
|
24
|
-
parser (3.
|
24
|
+
parallel (1.24.0)
|
25
|
+
parser (3.3.0.5)
|
25
26
|
ast (~> 2.4.1)
|
27
|
+
racc
|
28
|
+
prism (0.19.0)
|
29
|
+
psych (5.1.2)
|
30
|
+
stringio
|
31
|
+
racc (1.7.3)
|
26
32
|
rainbow (3.1.1)
|
27
|
-
rake (13.0
|
28
|
-
rbi (0.
|
29
|
-
|
30
|
-
parser (>= 2.6.4.0)
|
33
|
+
rake (13.1.0)
|
34
|
+
rbi (0.1.6)
|
35
|
+
prism (>= 0.18.0, < 0.20)
|
31
36
|
sorbet-runtime (>= 0.5.9204)
|
32
|
-
|
33
|
-
|
34
|
-
|
37
|
+
rdoc (6.6.2)
|
38
|
+
psych (>= 4.0.0)
|
39
|
+
regexp_parser (2.9.0)
|
40
|
+
reline (0.4.2)
|
35
41
|
io-console (~> 0.5)
|
36
|
-
rexml (3.2.
|
37
|
-
rubocop (1.
|
42
|
+
rexml (3.2.6)
|
43
|
+
rubocop (1.60.2)
|
38
44
|
json (~> 2.3)
|
45
|
+
language_server-protocol (>= 3.17.0)
|
39
46
|
parallel (~> 1.10)
|
40
|
-
parser (>= 3.
|
47
|
+
parser (>= 3.3.0.2)
|
41
48
|
rainbow (>= 2.2.2, < 4.0)
|
42
49
|
regexp_parser (>= 1.8, < 3.0)
|
43
50
|
rexml (>= 3.2.5, < 4.0)
|
44
|
-
rubocop-ast (>= 1.
|
51
|
+
rubocop-ast (>= 1.30.0, < 2.0)
|
45
52
|
ruby-progressbar (~> 1.7)
|
46
53
|
unicode-display_width (>= 2.4.0, < 3.0)
|
47
|
-
rubocop-ast (1.
|
54
|
+
rubocop-ast (1.30.0)
|
48
55
|
parser (>= 3.2.1.0)
|
49
|
-
rubocop-minitest (0.
|
56
|
+
rubocop-minitest (0.34.5)
|
50
57
|
rubocop (>= 1.39, < 2.0)
|
51
|
-
|
52
|
-
|
53
|
-
rubocop
|
58
|
+
rubocop-ast (>= 1.30.0, < 2.0)
|
59
|
+
rubocop-performance (1.20.2)
|
60
|
+
rubocop (>= 1.48.1, < 2.0)
|
61
|
+
rubocop-ast (>= 1.30.0, < 2.0)
|
54
62
|
rubocop-rake (0.6.0)
|
55
63
|
rubocop (~> 1.0)
|
56
|
-
rubocop-sorbet (0.7.
|
64
|
+
rubocop-sorbet (0.7.6)
|
57
65
|
rubocop (>= 0.90.0)
|
58
66
|
ruby-progressbar (1.13.0)
|
59
|
-
sorbet (0.5.
|
60
|
-
sorbet-static (= 0.5.
|
61
|
-
sorbet-runtime (0.5.
|
62
|
-
sorbet-static (0.5.
|
63
|
-
sorbet-static (0.5.
|
64
|
-
sorbet-static-and-runtime (0.5.
|
65
|
-
sorbet (= 0.5.
|
66
|
-
sorbet-runtime (= 0.5.
|
67
|
+
sorbet (0.5.11218)
|
68
|
+
sorbet-static (= 0.5.11218)
|
69
|
+
sorbet-runtime (0.5.11218)
|
70
|
+
sorbet-static (0.5.11218-universal-darwin)
|
71
|
+
sorbet-static (0.5.11218-x86_64-linux)
|
72
|
+
sorbet-static-and-runtime (0.5.11218)
|
73
|
+
sorbet (= 0.5.11218)
|
74
|
+
sorbet-runtime (= 0.5.11218)
|
67
75
|
sorbet-struct-comparable (1.3.0)
|
68
76
|
sorbet-runtime (>= 0.5)
|
69
77
|
spoom (1.2.1)
|
70
78
|
sorbet (>= 0.5.10187)
|
71
79
|
sorbet-runtime (>= 0.5.9204)
|
72
80
|
thor (>= 0.19.2)
|
73
|
-
|
81
|
+
stringio (3.1.0)
|
82
|
+
tapioca (0.11.17)
|
74
83
|
bundler (>= 2.2.25)
|
75
84
|
netrc (>= 0.11.0)
|
76
85
|
parallel (>= 1.21.0)
|
77
|
-
rbi (
|
78
|
-
sorbet-static-and-runtime (>= 0.5.
|
86
|
+
rbi (>= 0.1.4, < 0.2)
|
87
|
+
sorbet-static-and-runtime (>= 0.5.10820)
|
79
88
|
spoom (~> 1.2.0, >= 1.2.0)
|
80
89
|
thor (>= 1.2.0)
|
81
90
|
yard-sorbet
|
82
|
-
thor (1.
|
83
|
-
unicode-display_width (2.
|
84
|
-
unparser (0.6.7)
|
85
|
-
diff-lcs (~> 1.3)
|
86
|
-
parser (>= 3.2.0)
|
91
|
+
thor (1.3.0)
|
92
|
+
unicode-display_width (2.5.0)
|
87
93
|
yard (0.9.34)
|
88
94
|
yard-sorbet (0.8.1)
|
89
95
|
sorbet-runtime (>= 0.5)
|
90
96
|
yard (>= 0.9)
|
91
|
-
zeitwerk (2.6.
|
97
|
+
zeitwerk (2.6.12)
|
92
98
|
|
93
99
|
PLATFORMS
|
94
|
-
arm64-darwin
|
100
|
+
arm64-darwin
|
95
101
|
x86_64-linux
|
96
102
|
|
97
103
|
DEPENDENCIES
|
@@ -106,8 +112,8 @@ DEPENDENCIES
|
|
106
112
|
rubocop-sorbet
|
107
113
|
sorbet
|
108
114
|
sorbet-runtime
|
109
|
-
spoom
|
115
|
+
spoom (= 1.2.1)
|
110
116
|
tapioca
|
111
117
|
|
112
118
|
BUNDLED WITH
|
113
|
-
2.
|
119
|
+
2.5.5
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
If an organization is not already using Sorbet, you probably don't want to introduce a dependency on `sorbet-runtime`, which this gem does. As such, this will always be a distinct implementation, separate from the official [Ruby SDK](https://github.com/open-feature/ruby-sdk).
|
6
6
|
|
7
7
|
Current OpenFeature specification target version: `0.5.2`
|
8
|
-
Current supported Ruby versions: `
|
8
|
+
Current supported Ruby versions: `3.1.X`, `3.2.X`
|
9
9
|
Support for Evaluation Context and Hooks is not complete.
|
10
10
|
|
11
11
|
## Installation
|
@@ -53,7 +53,7 @@ The OpenFeature specification defines [Structure as a potential return type](htt
|
|
53
53
|
|
54
54
|
We support global evaluation context (set on the `OpenFeature` module), client evaluation context (set on client instances or during client initialization) and invocation evaluation context (passed in during flag evaluation). In compliance with the specification, the invocation context merges into the client context which merges into the global context. Fields in invocation context take precedence over fields in the client context which take precedence over fields in the global context.
|
55
55
|
|
56
|
-
### Provider
|
56
|
+
### Provider Abstract Class
|
57
57
|
|
58
58
|
By default, this implementation sets the provider to the `OpenFeature::NoOpProvider` which always returns the default value. It's up to the individual teams to define their own providers based on their flag source (in the future, I'll release open-source providers based on various, common vendors).
|
59
59
|
|
@@ -72,13 +72,26 @@ OpenFeature.set_provider(provider)
|
|
72
72
|
|
73
73
|
#### Implementing Custom Providers
|
74
74
|
|
75
|
-
Thanks to Sorbet
|
75
|
+
Thanks to Sorbet abstract classes, it's fairly straightforward to implement a new provider. Here is an example for a JSON-based flag format on disk:
|
76
76
|
|
77
77
|
```ruby
|
78
|
-
class JsonFileFlagProvider
|
78
|
+
class JsonFileFlagProvider < OpenFeature::Provider
|
79
79
|
extend T::Sig
|
80
80
|
|
81
|
-
|
81
|
+
sig { void }
|
82
|
+
def initialize
|
83
|
+
super(OpenFeature::ProviderStatus::NotReady)
|
84
|
+
end
|
85
|
+
|
86
|
+
def init(context)
|
87
|
+
@file = File.open(context.file || "flags.json")
|
88
|
+
@status = OpenFeature::ProviderStatus::Ready
|
89
|
+
end
|
90
|
+
|
91
|
+
sig { overridable.void }
|
92
|
+
def shutdown
|
93
|
+
@file.close
|
94
|
+
end
|
82
95
|
|
83
96
|
sig { override.returns(OpenFeature::ProviderMetadata) }
|
84
97
|
def metadata
|
@@ -113,7 +126,11 @@ class JsonFileFlagProvider
|
|
113
126
|
end
|
114
127
|
```
|
115
128
|
|
116
|
-
By
|
129
|
+
By inheriting from the `OpenFeature::Provider` class, Sorbet will indicate what methods it's expecting and what their type signatures should be.
|
130
|
+
|
131
|
+
##### A note on `initialize` versus `init`
|
132
|
+
|
133
|
+
The Ruby `initialize` method is the best place to do any direct construction logic for an object, such as setting configuration values. `init` is called by OpenFeature when setting a provider and is the best place to make any HTTP requests, establish persistent connections, or any other connection logic that could potentially fail. By the end of this method, `@status` must be set to either `Ready` or `Error`.
|
117
134
|
|
118
135
|
## Development
|
119
136
|
|
data/lib/open_feature/client.rb
CHANGED
@@ -25,7 +25,7 @@ module OpenFeature
|
|
25
25
|
end
|
26
26
|
def initialize(provider:, name: nil, evaluation_context: nil, hooks: [])
|
27
27
|
@provider = provider
|
28
|
-
@client_metadata = T.let(ClientMetadata.new(name:
|
28
|
+
@client_metadata = T.let(ClientMetadata.new(name:), ClientMetadata)
|
29
29
|
@evaluation_context = evaluation_context
|
30
30
|
@hooks = hooks
|
31
31
|
end
|
@@ -43,10 +43,12 @@ module OpenFeature
|
|
43
43
|
options: T.nilable(EvaluationOptions)
|
44
44
|
).returns(T::Boolean)
|
45
45
|
end
|
46
|
-
def fetch_boolean_value(flag_key:, default_value:, context: nil, options: nil)
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
def fetch_boolean_value(flag_key:, default_value:, context: nil, options: nil)
|
47
|
+
evaluated_context = build_context_with_before_hooks(flag_key:, default_value:,
|
48
|
+
invocation_context: context, options:,
|
49
|
+
flag_type: "Boolean")
|
50
|
+
provider.resolve_boolean_value(flag_key:, default_value:, context: evaluated_context)
|
51
|
+
.value
|
50
52
|
rescue StandardError
|
51
53
|
default_value
|
52
54
|
end
|
@@ -61,14 +63,14 @@ module OpenFeature
|
|
61
63
|
end
|
62
64
|
def fetch_boolean_details(flag_key:, default_value:, context: nil, options: nil) # rubocop:disable Lint/UnusedMethodArgument
|
63
65
|
details = provider.resolve_boolean_value(
|
64
|
-
flag_key
|
65
|
-
default_value
|
66
|
+
flag_key:,
|
67
|
+
default_value:,
|
66
68
|
context: build_context(context)
|
67
69
|
)
|
68
70
|
|
69
|
-
EvaluationDetails.from_resolution_details(details, flag_key:
|
71
|
+
EvaluationDetails.from_resolution_details(details, flag_key:)
|
70
72
|
rescue StandardError => e
|
71
|
-
EvaluationDetails.from_error(e.message, flag_key
|
73
|
+
EvaluationDetails.from_error(e.message, flag_key:, default_value:)
|
72
74
|
end
|
73
75
|
|
74
76
|
sig do
|
@@ -79,9 +81,12 @@ module OpenFeature
|
|
79
81
|
options: T.nilable(EvaluationOptions)
|
80
82
|
).returns(String)
|
81
83
|
end
|
82
|
-
def fetch_string_value(flag_key:, default_value:, context: nil, options: nil)
|
84
|
+
def fetch_string_value(flag_key:, default_value:, context: nil, options: nil)
|
85
|
+
evaluated_context = build_context_with_before_hooks(flag_key:, default_value:,
|
86
|
+
invocation_context: context, options:,
|
87
|
+
flag_type: "String")
|
83
88
|
provider
|
84
|
-
.resolve_string_value(flag_key
|
89
|
+
.resolve_string_value(flag_key:, default_value:, context: evaluated_context)
|
85
90
|
.value
|
86
91
|
rescue StandardError
|
87
92
|
default_value
|
@@ -97,14 +102,14 @@ module OpenFeature
|
|
97
102
|
end
|
98
103
|
def fetch_string_details(flag_key:, default_value:, context: nil, options: nil) # rubocop:disable Lint/UnusedMethodArgument
|
99
104
|
details = provider.resolve_string_value(
|
100
|
-
flag_key
|
101
|
-
default_value
|
105
|
+
flag_key:,
|
106
|
+
default_value:,
|
102
107
|
context: build_context(context)
|
103
108
|
)
|
104
109
|
|
105
|
-
EvaluationDetails.from_resolution_details(details, flag_key:
|
110
|
+
EvaluationDetails.from_resolution_details(details, flag_key:)
|
106
111
|
rescue StandardError => e
|
107
|
-
EvaluationDetails.from_error(e.message, flag_key
|
112
|
+
EvaluationDetails.from_error(e.message, flag_key:, default_value:)
|
108
113
|
end
|
109
114
|
|
110
115
|
sig do
|
@@ -115,9 +120,12 @@ module OpenFeature
|
|
115
120
|
options: T.nilable(EvaluationOptions)
|
116
121
|
).returns(Numeric)
|
117
122
|
end
|
118
|
-
def fetch_number_value(flag_key:, default_value:, context: nil, options: nil)
|
123
|
+
def fetch_number_value(flag_key:, default_value:, context: nil, options: nil)
|
124
|
+
evaluated_context = build_context_with_before_hooks(flag_key:, default_value:,
|
125
|
+
invocation_context: context, options:,
|
126
|
+
flag_type: "Number")
|
119
127
|
provider
|
120
|
-
.resolve_number_value(flag_key
|
128
|
+
.resolve_number_value(flag_key:, default_value:, context: evaluated_context)
|
121
129
|
.value
|
122
130
|
rescue StandardError
|
123
131
|
default_value
|
@@ -133,14 +141,14 @@ module OpenFeature
|
|
133
141
|
end
|
134
142
|
def fetch_number_details(flag_key:, default_value:, context: nil, options: nil) # rubocop:disable Lint/UnusedMethodArgument
|
135
143
|
details = provider.resolve_number_value(
|
136
|
-
flag_key
|
137
|
-
default_value
|
144
|
+
flag_key:,
|
145
|
+
default_value:,
|
138
146
|
context: build_context(context)
|
139
147
|
)
|
140
148
|
|
141
|
-
EvaluationDetails.from_resolution_details(details, flag_key:
|
149
|
+
EvaluationDetails.from_resolution_details(details, flag_key:)
|
142
150
|
rescue StandardError => e
|
143
|
-
EvaluationDetails.from_error(e.message, flag_key
|
151
|
+
EvaluationDetails.from_error(e.message, flag_key:, default_value:)
|
144
152
|
end
|
145
153
|
|
146
154
|
sig do
|
@@ -151,9 +159,12 @@ module OpenFeature
|
|
151
159
|
options: T.nilable(EvaluationOptions)
|
152
160
|
).returns(Integer)
|
153
161
|
end
|
154
|
-
def fetch_integer_value(flag_key:, default_value:, context: nil, options: nil)
|
162
|
+
def fetch_integer_value(flag_key:, default_value:, context: nil, options: nil)
|
163
|
+
evaluated_context = build_context_with_before_hooks(flag_key:, default_value:,
|
164
|
+
invocation_context: context, options:,
|
165
|
+
flag_type: "Integer")
|
155
166
|
provider
|
156
|
-
.resolve_number_value(flag_key
|
167
|
+
.resolve_number_value(flag_key:, default_value:, context: evaluated_context)
|
157
168
|
.value
|
158
169
|
.to_i
|
159
170
|
rescue StandardError
|
@@ -168,9 +179,12 @@ module OpenFeature
|
|
168
179
|
options: T.nilable(EvaluationOptions)
|
169
180
|
).returns(Float)
|
170
181
|
end
|
171
|
-
def fetch_float_value(flag_key:, default_value:, context: nil, options: nil)
|
182
|
+
def fetch_float_value(flag_key:, default_value:, context: nil, options: nil)
|
183
|
+
evaluated_context = build_context_with_before_hooks(flag_key:, default_value:,
|
184
|
+
invocation_context: context, options:,
|
185
|
+
flag_type: "Float")
|
172
186
|
provider
|
173
|
-
.resolve_number_value(flag_key
|
187
|
+
.resolve_number_value(flag_key:, default_value:, context: evaluated_context)
|
174
188
|
.value
|
175
189
|
.to_f
|
176
190
|
rescue StandardError
|
@@ -185,9 +199,12 @@ module OpenFeature
|
|
185
199
|
options: T.nilable(EvaluationOptions)
|
186
200
|
).returns(Structure)
|
187
201
|
end
|
188
|
-
def fetch_structure_value(flag_key:, default_value:, context: nil, options: nil)
|
202
|
+
def fetch_structure_value(flag_key:, default_value:, context: nil, options: nil)
|
203
|
+
evaluated_context = build_context_with_before_hooks(flag_key:, default_value:,
|
204
|
+
invocation_context: context, options:,
|
205
|
+
flag_type: "Structure")
|
189
206
|
provider
|
190
|
-
.resolve_structure_value(flag_key
|
207
|
+
.resolve_structure_value(flag_key:, default_value:, context: evaluated_context)
|
191
208
|
.value
|
192
209
|
rescue StandardError
|
193
210
|
default_value
|
@@ -203,14 +220,14 @@ module OpenFeature
|
|
203
220
|
end
|
204
221
|
def fetch_structure_details(flag_key:, default_value:, context: nil, options: nil) # rubocop:disable Lint/UnusedMethodArgument
|
205
222
|
details = provider.resolve_structure_value(
|
206
|
-
flag_key
|
207
|
-
default_value
|
223
|
+
flag_key:,
|
224
|
+
default_value:,
|
208
225
|
context: build_context(context)
|
209
226
|
)
|
210
227
|
|
211
|
-
EvaluationDetails.from_resolution_details(details, flag_key:
|
228
|
+
EvaluationDetails.from_resolution_details(details, flag_key:)
|
212
229
|
rescue StandardError => e
|
213
|
-
EvaluationDetails.from_error(e.message, flag_key
|
230
|
+
EvaluationDetails.from_error(e.message, flag_key:, default_value:)
|
214
231
|
end
|
215
232
|
|
216
233
|
private
|
@@ -218,12 +235,53 @@ module OpenFeature
|
|
218
235
|
sig { returns(Provider) }
|
219
236
|
attr_reader :provider
|
220
237
|
|
238
|
+
sig do
|
239
|
+
type_parameters(:U).params(
|
240
|
+
flag_key: String,
|
241
|
+
default_value: T.type_parameter(:U),
|
242
|
+
invocation_context: T.nilable(EvaluationContext),
|
243
|
+
options: T.nilable(EvaluationOptions),
|
244
|
+
flag_type: String
|
245
|
+
).returns(EvaluationContext)
|
246
|
+
end
|
247
|
+
def build_context_with_before_hooks(flag_key:, default_value:, invocation_context:, options:, flag_type:)
|
248
|
+
hook_context = build_hook_context(flag_key:, default_value:,
|
249
|
+
invocation_context:, flag_type:)
|
250
|
+
OpenFeature::Hook::BeforeHook.call(hooks: build_hooks(options), context: hook_context,
|
251
|
+
hints: {})
|
252
|
+
end
|
253
|
+
|
254
|
+
sig { params(options: T.nilable(EvaluationOptions)).returns(Hooks) }
|
255
|
+
def build_hooks(options)
|
256
|
+
Hooks.new(
|
257
|
+
global: OpenFeature.configuration.hooks,
|
258
|
+
client: hooks,
|
259
|
+
invocation: (options ? options.hooks : []),
|
260
|
+
provider: provider.hooks
|
261
|
+
)
|
262
|
+
end
|
263
|
+
|
264
|
+
sig do
|
265
|
+
type_parameters(:U).params(
|
266
|
+
flag_key: String,
|
267
|
+
default_value: T.type_parameter(:U),
|
268
|
+
invocation_context: T.nilable(EvaluationContext),
|
269
|
+
flag_type: String
|
270
|
+
).returns(HookContext[T.type_parameter(:U)])
|
271
|
+
end
|
272
|
+
def build_hook_context(flag_key:, default_value:, invocation_context:, flag_type:)
|
273
|
+
evaluation_context = build_context(invocation_context) || OpenFeature::EvaluationContext.new
|
274
|
+
HookContext.new(flag_key:, default_value:,
|
275
|
+
evaluation_context:, flag_type:,
|
276
|
+
client_metadata:, provider_metadata: provider.metadata)
|
277
|
+
end
|
278
|
+
|
221
279
|
sig { params(invocation_context: T.nilable(EvaluationContext)).returns(T.nilable(EvaluationContext)) }
|
222
280
|
def build_context(invocation_context)
|
223
281
|
EvaluationContextBuilder.new.call(
|
224
282
|
global_context: OpenFeature.configuration.evaluation_context,
|
225
283
|
client_context: evaluation_context,
|
226
|
-
invocation_context:
|
284
|
+
invocation_context:
|
227
285
|
)
|
228
286
|
end
|
229
287
|
end
|
@@ -13,7 +13,7 @@ module OpenFeature
|
|
13
13
|
|
14
14
|
FieldValueType = T.type_alias { T.any(T::Boolean, String, Numeric, DateTime, Structure) }
|
15
15
|
|
16
|
-
const :targeting_key, T.nilable(String)
|
16
|
+
const :targeting_key, T.nilable(String), default: nil
|
17
17
|
const :fields, T::Hash[String, FieldValueType], default: {}
|
18
18
|
|
19
19
|
sig { params(method_name: Symbol).returns(T::Boolean) }
|
@@ -29,7 +29,7 @@ module OpenFeature
|
|
29
29
|
sig { params(key: String, value: FieldValueType).returns(EvaluationContext) }
|
30
30
|
def add_field(key, value)
|
31
31
|
EvaluationContext.new(
|
32
|
-
targeting_key
|
32
|
+
targeting_key:,
|
33
33
|
fields: fields.merge({ key => value })
|
34
34
|
)
|
35
35
|
end
|
@@ -25,7 +25,7 @@ module OpenFeature
|
|
25
25
|
sig { params(details: ResolutionDetails[SelfValue], flag_key: String).returns(EvaluationDetails[SelfValue]) }
|
26
26
|
def from_resolution_details(details, flag_key:)
|
27
27
|
EvaluationDetails.new(
|
28
|
-
flag_key
|
28
|
+
flag_key:,
|
29
29
|
value: details.value,
|
30
30
|
error_code: details.error_code,
|
31
31
|
error_message: details.error_message,
|
@@ -44,7 +44,7 @@ module OpenFeature
|
|
44
44
|
end
|
45
45
|
def from_error(error_message, flag_key:, default_value:)
|
46
46
|
EvaluationDetails.new(
|
47
|
-
flag_key
|
47
|
+
flag_key:,
|
48
48
|
value: default_value,
|
49
49
|
error_code: ErrorCode::General,
|
50
50
|
error_message: "Provider raised error: #{error_message}",
|
data/lib/open_feature/hook.rb
CHANGED
@@ -2,5 +2,45 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module OpenFeature
|
5
|
-
|
5
|
+
# See https://openfeature.dev/specification/sections/hooks
|
6
|
+
# We model Hooks as a simple ADT
|
7
|
+
module Hook
|
8
|
+
extend T::Sig
|
9
|
+
extend T::Helpers
|
10
|
+
interface!
|
11
|
+
sealed!
|
12
|
+
|
13
|
+
# See Requirement 4.3.2 - 4.3.4
|
14
|
+
class BeforeHook
|
15
|
+
include Hook
|
16
|
+
extend T::Sig
|
17
|
+
extend T::Helpers
|
18
|
+
include OpenFeature::Hook
|
19
|
+
abstract!
|
20
|
+
sig do
|
21
|
+
abstract.params(context: OpenFeature::HookContext[T.untyped],
|
22
|
+
hints: T::Hash[String, T.untyped]).returns(OpenFeature::EvaluationContext)
|
23
|
+
end
|
24
|
+
def call(context:, hints:); end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
extend T::Sig
|
28
|
+
|
29
|
+
sig do
|
30
|
+
params(hooks: Hooks, context: OpenFeature::HookContext[T.untyped],
|
31
|
+
hints: T::Hash[String, T.untyped]).returns(OpenFeature::EvaluationContext)
|
32
|
+
end
|
33
|
+
def call(hooks:, context:, hints:)
|
34
|
+
context.evaluation_context.merge(
|
35
|
+
hooks.before.reduce(context.evaluation_context) do |evaluation_context, hook|
|
36
|
+
hook.call(
|
37
|
+
context: context.with_new_evaluation_context(evaluation_context),
|
38
|
+
hints:
|
39
|
+
)
|
40
|
+
end
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
6
46
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module OpenFeature
|
5
|
+
# See https://openfeature.dev/specification/sections/hooks#41-hook-context
|
6
|
+
# See Requirement 4.1.1, 4.1.3, 4.1.4
|
7
|
+
# TODO: Requirement 4.1.2
|
8
|
+
class HookContext < T::Struct
|
9
|
+
extend T::Generic
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
include T::Struct::ActsAsComparable
|
13
|
+
|
14
|
+
Value = type_member
|
15
|
+
const :flag_key, String
|
16
|
+
const :flag_type, String
|
17
|
+
const :evaluation_context, EvaluationContext
|
18
|
+
const :default_value, Value
|
19
|
+
const :client_metadata, ClientMetadata
|
20
|
+
const :provider_metadata, ProviderMetadata
|
21
|
+
|
22
|
+
# Needed as opposed to .with due to https://sorbet.org/docs/tstruct#from_hash-gotchas
|
23
|
+
sig { params(new_context: EvaluationContext).returns(HookContext[Value]) }
|
24
|
+
def with_new_evaluation_context(new_context)
|
25
|
+
OpenFeature::HookContext.new(flag_key:, flag_type:, default_value:,
|
26
|
+
evaluation_context: new_context, client_metadata:,
|
27
|
+
provider_metadata:)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|