openfeature-sdk-sorbet 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +9 -9
- data/README.md +16 -1
- data/lib/open_feature/client.rb +4 -4
- data/lib/open_feature/multiple_source_provider.rb +121 -0
- data/lib/open_feature/no_op_provider.rb +2 -2
- data/lib/open_feature/provider.rb +2 -2
- data/lib/open_feature/structure.rb +6 -0
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96650d45be9bb86f0349e27d1ad9d56c748659b3d075b6c2cc6c769fff642571
|
4
|
+
data.tar.gz: 370bd63d0ffb1143eec2de19d3b70e5e0b5033b4dcd0f08117dfbaeee7c1f8cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 915afef5bdae8110b53c4e96dc9494955636753c1170349eb25f432760590fc75004ebd6f8c47bd3359da982b81fbae0c08a7053df4b552fe760f939021bdd00
|
7
|
+
data.tar.gz: 799b9745b26bf0eac7cdf5e54b237d63abe8f2af5e238bcaf554bd368105dc880a7cc643e5565529053baa4a7041cbc61a5c95eafcc56331ad25b6c2527883e9
|
data/.rubocop.yml
CHANGED
@@ -19,16 +19,20 @@ Layout/LineLength:
|
|
19
19
|
|
20
20
|
Lint/UnusedMethodArgument:
|
21
21
|
Exclude:
|
22
|
+
- lib/open_feature/multiple_source_provider.rb
|
22
23
|
- lib/open_feature/no_op_provider.rb
|
23
24
|
|
24
25
|
Metrics/ClassLength:
|
25
26
|
Exclude:
|
26
27
|
- lib/open_feature/client.rb
|
27
28
|
- test/open_feature/client_test.rb
|
29
|
+
- test/open_feature/multiple_source_provider_test.rb
|
28
30
|
|
29
31
|
Metrics/MethodLength:
|
30
32
|
Exclude:
|
31
33
|
- test/open_feature/evaluation_details_test.rb
|
34
|
+
- test/open_feature/multiple_source_provider_test.rb
|
35
|
+
- test/support/test_provider.rb
|
32
36
|
|
33
37
|
Style/AccessorGrouping:
|
34
38
|
Enabled: false
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [0.1.2] - 2023-05-16
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- Introduced `OpenFeature::MultipleSourceProvider` to allow fetching flags from multiple sources.
|
14
|
+
|
15
|
+
## [0.1.1] - 2023-05-15
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
|
19
|
+
- Expanded type of structure resolver to `T.any(T::Array[T.untyped], T::Hash[T.untyped, T.untyped])`
|
20
|
+
|
9
21
|
## [0.1.0] - 2023-05-15
|
10
22
|
|
11
23
|
### Added
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
openfeature-sdk-sorbet (0.1.
|
4
|
+
openfeature-sdk-sorbet (0.1.2)
|
5
5
|
sorbet-runtime (~> 0.5)
|
6
6
|
sorbet-struct-comparable (~> 1.3)
|
7
7
|
zeitwerk (~> 2.6)
|
@@ -56,14 +56,14 @@ GEM
|
|
56
56
|
rubocop-sorbet (0.7.0)
|
57
57
|
rubocop (>= 0.90.0)
|
58
58
|
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.
|
59
|
+
sorbet (0.5.10827)
|
60
|
+
sorbet-static (= 0.5.10827)
|
61
|
+
sorbet-runtime (0.5.10827)
|
62
|
+
sorbet-static (0.5.10827-universal-darwin-22)
|
63
|
+
sorbet-static (0.5.10827-x86_64-linux)
|
64
|
+
sorbet-static-and-runtime (0.5.10827)
|
65
|
+
sorbet (= 0.5.10827)
|
66
|
+
sorbet-runtime (= 0.5.10827)
|
67
67
|
sorbet-struct-comparable (1.3.0)
|
68
68
|
sorbet-runtime (>= 0.5)
|
69
69
|
spoom (1.2.1)
|
data/README.md
CHANGED
@@ -43,12 +43,27 @@ structure_evaluation_details = client.fetch_structure_details(flag_key: "my_stru
|
|
43
43
|
|
44
44
|
### Note on `Structure`
|
45
45
|
|
46
|
-
The OpenFeature specification defines [Structure as a potential return type](https://openfeature.dev/specification/types#structure). This is somewhat ambiguous in Ruby, further complicated by `T::Struct` that we get from Sorbet. For now, the type I've elected here is `T::Hash[T.untyped, T.untyped]` for flexibility but with a little more structure than a YML or JSON parsable string. This decision might change in the future upon further interpretation or new versions of the specification.
|
46
|
+
The OpenFeature specification defines [Structure as a potential return type](https://openfeature.dev/specification/types#structure). This is somewhat ambiguous in Ruby, further complicated by `T::Struct` that we get from Sorbet. For now, the type I've elected here is `T.any(T::Array[T.untyped], T::Hash[T.untyped, T.untyped]` (loosely, either an Array of untyped members or a Hash with untyped keys and untyped values) for flexibility but with a little more structure than a YML or JSON parsable string. This decision might change in the future upon further interpretation or new versions of the specification.
|
47
47
|
|
48
48
|
### Provider Interface
|
49
49
|
|
50
50
|
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).
|
51
51
|
|
52
|
+
This gem also provides `OpenFeature::MultipleSourceProvider` to allow fetching flags from multiple sources. This is especially useful if your existing application has flags spread across bespoke and vendor solutions and you want to unify the evaluation sites. It can be instantiated and configured like so:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
provider = OpenFeature::MultipleSourceProvider.new(
|
56
|
+
providers: [
|
57
|
+
CustomProvider.new,
|
58
|
+
OpenFeature::NoOpProvider.new
|
59
|
+
]
|
60
|
+
)
|
61
|
+
|
62
|
+
OpenFeature.set_provider(provider)
|
63
|
+
```
|
64
|
+
|
65
|
+
#### Implementing Custom Providers
|
66
|
+
|
52
67
|
Thanks to Sorbet interfaces, it's fairly straightforward to implement a new provider. Here is an example for a JSON-based flag format on disk:
|
53
68
|
|
54
69
|
```ruby
|
data/lib/open_feature/client.rb
CHANGED
@@ -145,10 +145,10 @@ module OpenFeature
|
|
145
145
|
sig do
|
146
146
|
params(
|
147
147
|
flag_key: String,
|
148
|
-
default_value:
|
148
|
+
default_value: Structure,
|
149
149
|
context: T.nilable(EvaluationContext),
|
150
150
|
options: T.nilable(EvaluationOptions)
|
151
|
-
).returns(
|
151
|
+
).returns(Structure)
|
152
152
|
end
|
153
153
|
def fetch_structure_value(flag_key:, default_value:, context: nil, options: nil) # rubocop:disable Lint/UnusedMethodArgument
|
154
154
|
provider.resolve_structure_value(flag_key: flag_key, default_value: default_value, context: context).value
|
@@ -159,10 +159,10 @@ module OpenFeature
|
|
159
159
|
sig do
|
160
160
|
params(
|
161
161
|
flag_key: String,
|
162
|
-
default_value:
|
162
|
+
default_value: Structure,
|
163
163
|
context: T.nilable(EvaluationContext),
|
164
164
|
options: T.nilable(EvaluationOptions)
|
165
|
-
).returns(EvaluationDetails[
|
165
|
+
).returns(EvaluationDetails[Structure])
|
166
166
|
end
|
167
167
|
def fetch_structure_details(flag_key:, default_value:, context: nil, options: nil) # rubocop:disable Lint/UnusedMethodArgument
|
168
168
|
details = provider.resolve_structure_value(flag_key: flag_key, default_value: default_value, context: context)
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module OpenFeature
|
5
|
+
# Used to pull from multiple providers.
|
6
|
+
# Order of the providers given to initialize matters.
|
7
|
+
# The providers will be evaluated in that order and the first
|
8
|
+
# non-error result will be used. If all sources return an error
|
9
|
+
# then the default value is used.
|
10
|
+
class MultipleSourceProvider
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
include Provider
|
14
|
+
|
15
|
+
sig { params(providers: T::Array[Provider]).void }
|
16
|
+
def initialize(providers:)
|
17
|
+
@providers = providers
|
18
|
+
end
|
19
|
+
|
20
|
+
sig { override.returns(ProviderMetadata) }
|
21
|
+
def metadata
|
22
|
+
ProviderMetadata.new(name: "Multiple Sources: #{providers.map(&:metadata).map(&:name).join(", ")}")
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { override.returns(T::Array[Hook]) }
|
26
|
+
def hooks
|
27
|
+
providers.flat_map(&:hooks)
|
28
|
+
end
|
29
|
+
|
30
|
+
sig do
|
31
|
+
override
|
32
|
+
.params(
|
33
|
+
flag_key: String,
|
34
|
+
default_value: T::Boolean,
|
35
|
+
context: T.nilable(EvaluationContext)
|
36
|
+
)
|
37
|
+
.returns(ResolutionDetails[T::Boolean])
|
38
|
+
end
|
39
|
+
def resolve_boolean_value(flag_key:, default_value:, context: nil)
|
40
|
+
resolve_from_sources(default_value: default_value) do |provider|
|
41
|
+
provider.resolve_boolean_value(flag_key: flag_key, default_value: default_value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
sig do
|
46
|
+
override
|
47
|
+
.params(
|
48
|
+
flag_key: String,
|
49
|
+
default_value: Numeric,
|
50
|
+
context: T.nilable(EvaluationContext)
|
51
|
+
)
|
52
|
+
.returns(ResolutionDetails[Numeric])
|
53
|
+
end
|
54
|
+
def resolve_number_value(flag_key:, default_value:, context: nil)
|
55
|
+
resolve_from_sources(default_value: default_value) do |provider|
|
56
|
+
provider.resolve_number_value(flag_key: flag_key, default_value: default_value)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
sig do
|
61
|
+
override
|
62
|
+
.params(
|
63
|
+
flag_key: String,
|
64
|
+
default_value: Structure,
|
65
|
+
context: T.nilable(EvaluationContext)
|
66
|
+
)
|
67
|
+
.returns(ResolutionDetails[Structure])
|
68
|
+
end
|
69
|
+
def resolve_structure_value(flag_key:, default_value:, context: nil)
|
70
|
+
resolve_from_sources(default_value: default_value) do |provider|
|
71
|
+
provider.resolve_structure_value(flag_key: flag_key, default_value: default_value)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
sig do
|
76
|
+
override
|
77
|
+
.params(
|
78
|
+
flag_key: String,
|
79
|
+
default_value: String,
|
80
|
+
context: T.nilable(EvaluationContext)
|
81
|
+
)
|
82
|
+
.returns(ResolutionDetails[String])
|
83
|
+
end
|
84
|
+
def resolve_string_value(flag_key:, default_value:, context: nil)
|
85
|
+
resolve_from_sources(default_value: default_value) do |provider|
|
86
|
+
provider.resolve_string_value(flag_key: flag_key, default_value: default_value)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
sig { returns(T::Array[Provider]) }
|
93
|
+
attr_reader :providers
|
94
|
+
|
95
|
+
# rubocop:disable Metrics/MethodLength
|
96
|
+
sig do
|
97
|
+
type_parameters(:U)
|
98
|
+
.params(
|
99
|
+
default_value: T.type_parameter(:U),
|
100
|
+
blk: T.proc.params(arg0: Provider).returns(ResolutionDetails[T.type_parameter(:U)])
|
101
|
+
)
|
102
|
+
.returns(ResolutionDetails[T.type_parameter(:U)])
|
103
|
+
end
|
104
|
+
def resolve_from_sources(default_value:, &blk)
|
105
|
+
successful_details = providers.each do |provider|
|
106
|
+
details = yield(provider)
|
107
|
+
|
108
|
+
break details if details.error_code.nil?
|
109
|
+
rescue StandardError
|
110
|
+
next
|
111
|
+
end
|
112
|
+
|
113
|
+
if successful_details.is_a?(ResolutionDetails)
|
114
|
+
successful_details
|
115
|
+
else
|
116
|
+
ResolutionDetails.new(value: default_value, error_code: ErrorCode::General, reason: "ERROR")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
# rubocop:enable Metrics/MethodLength
|
120
|
+
end
|
121
|
+
end
|
@@ -65,10 +65,10 @@ module OpenFeature
|
|
65
65
|
override
|
66
66
|
.params(
|
67
67
|
flag_key: String,
|
68
|
-
default_value:
|
68
|
+
default_value: Structure,
|
69
69
|
context: T.nilable(EvaluationContext)
|
70
70
|
)
|
71
|
-
.returns(ResolutionDetails[
|
71
|
+
.returns(ResolutionDetails[Structure])
|
72
72
|
end
|
73
73
|
def resolve_structure_value(flag_key:, default_value:, context: nil)
|
74
74
|
ResolutionDetails.new(value: default_value, reason: "DEFAULT")
|
@@ -51,10 +51,10 @@ module OpenFeature
|
|
51
51
|
abstract
|
52
52
|
.params(
|
53
53
|
flag_key: String,
|
54
|
-
default_value:
|
54
|
+
default_value: Structure,
|
55
55
|
context: T.nilable(EvaluationContext)
|
56
56
|
)
|
57
|
-
.returns(ResolutionDetails[
|
57
|
+
.returns(ResolutionDetails[Structure])
|
58
58
|
end
|
59
59
|
def resolve_structure_value(flag_key:, default_value:, context: nil); end
|
60
60
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openfeature-sdk-sorbet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Max VelDink
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-05-
|
11
|
+
date: 2023-05-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sorbet-runtime
|
@@ -52,7 +52,7 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '2.6'
|
55
|
-
description:
|
55
|
+
description:
|
56
56
|
email:
|
57
57
|
- maxveldink@gmail.com
|
58
58
|
executables: []
|
@@ -80,10 +80,12 @@ files:
|
|
80
80
|
- lib/open_feature/evaluation_options.rb
|
81
81
|
- lib/open_feature/flag_metadata.rb
|
82
82
|
- lib/open_feature/hook.rb
|
83
|
+
- lib/open_feature/multiple_source_provider.rb
|
83
84
|
- lib/open_feature/no_op_provider.rb
|
84
85
|
- lib/open_feature/provider.rb
|
85
86
|
- lib/open_feature/provider_metadata.rb
|
86
87
|
- lib/open_feature/resolution_details.rb
|
88
|
+
- lib/open_feature/structure.rb
|
87
89
|
- sorbet/config
|
88
90
|
- sorbet/rbi/annotations/rainbow.rbi
|
89
91
|
- sorbet/rbi/gems/ast@2.4.2.rbi
|
@@ -128,7 +130,7 @@ metadata:
|
|
128
130
|
homepage_uri: https://github.com/maxveldink/openfeature-sdk-sorbet
|
129
131
|
source_code_uri: https://github.com/maxveldink/openfeature-sdk-sorbet
|
130
132
|
changelog_uri: https://github.com/maxveldink/openfeature-sdk-sorbet/blob/main/CHANGELOG.md
|
131
|
-
post_install_message:
|
133
|
+
post_install_message:
|
132
134
|
rdoc_options: []
|
133
135
|
require_paths:
|
134
136
|
- lib
|
@@ -143,8 +145,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
143
145
|
- !ruby/object:Gem::Version
|
144
146
|
version: '0'
|
145
147
|
requirements: []
|
146
|
-
rubygems_version: 3.4.
|
147
|
-
signing_key:
|
148
|
+
rubygems_version: 3.4.10
|
149
|
+
signing_key:
|
148
150
|
specification_version: 4
|
149
151
|
summary: Sorbet-aware implemention of the OpenFeature specification
|
150
152
|
test_files: []
|