openfeature-ofrep-provider 0.1.2
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 +7 -0
- data/.rspec +2 -0
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +18 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +174 -0
- data/README.md +247 -0
- data/Rakefile +10 -0
- data/bin/rake +27 -0
- data/lib/openfeature/ofrep/provider/client.rb +171 -0
- data/lib/openfeature/ofrep/provider/configuration.rb +29 -0
- data/lib/openfeature/ofrep/provider/errors.rb +78 -0
- data/lib/openfeature/ofrep/provider/response.rb +34 -0
- data/lib/openfeature/ofrep/provider/version.rb +7 -0
- data/lib/openfeature/ofrep/provider.rb +107 -0
- data/openfeature-ofrep-provider.gemspec +43 -0
- metadata +188 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 78e2ea5f8ba1403a5bb51f2354bb097905ea42dc693240244e8352decf539f65
|
|
4
|
+
data.tar.gz: e9f74b87097be94c5333cd54eb64614db1a504e54b11e12750367f34664dcd5a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0051e88d937bc94024faa6e4e95129fdd59daecf036fb7740f355a8bdf53da7c8dcfbfea06f17b5022a19ad300b8409561764615f242c789a35de9704fb9ecbb
|
|
7
|
+
data.tar.gz: e67b311a80efaca9ec882bd95b3afaafb4a77f88594bde3268a04bf925bb2748987eb096f9b1624b314b4d4f72db7d87c2688ff0a09b757fe8d6e763025de8f6
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.2](https://github.com/open-feature/ruby-sdk-contrib/compare/openfeature-ofrep-provider/v0.1.1...openfeature-ofrep-provider/v0.1.2) (2026-03-18)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### 🐛 Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **ofrep:** fix flaky rate limit test with date-based Retry-After ([#118](https://github.com/open-feature/ruby-sdk-contrib/issues/118)) ([0ecdf00](https://github.com/open-feature/ruby-sdk-contrib/commit/0ecdf003a9c1b3b2c5e4fcf853ccc3614355df94))
|
|
9
|
+
|
|
10
|
+
## [0.1.1](https://github.com/open-feature/ruby-sdk-contrib/compare/openfeature-ofrep-provider-v0.1.0...openfeature-ofrep-provider/v0.1.1) (2026-03-09)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### 🧹 Chore
|
|
14
|
+
|
|
15
|
+
* add conformance tests, SimpleCov, version standardization, and README improvements ([#95](https://github.com/open-feature/ruby-sdk-contrib/issues/95)) ([1c430a9](https://github.com/open-feature/ruby-sdk-contrib/commit/1c430a92041a7841ac01a2642a66376b8259acd2))
|
|
16
|
+
* improve repository maturity and contributor experience ([#91](https://github.com/open-feature/ruby-sdk-contrib/issues/91)) ([7e28025](https://github.com/open-feature/ruby-sdk-contrib/commit/7e280257e6f543bc46537f43aeac302be003b47b))
|
|
17
|
+
|
|
18
|
+
## Changelog
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
openfeature-ofrep-provider (0.1.2)
|
|
5
|
+
faraday-net_http_persistent (~> 2.3)
|
|
6
|
+
openfeature-sdk (~> 0.3.1)
|
|
7
|
+
|
|
8
|
+
GEM
|
|
9
|
+
remote: https://rubygems.org/
|
|
10
|
+
specs:
|
|
11
|
+
addressable (2.8.9)
|
|
12
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
13
|
+
ast (2.4.3)
|
|
14
|
+
bigdecimal (4.0.1)
|
|
15
|
+
connection_pool (3.0.2)
|
|
16
|
+
crack (1.0.1)
|
|
17
|
+
bigdecimal
|
|
18
|
+
rexml
|
|
19
|
+
diff-lcs (1.6.2)
|
|
20
|
+
docile (1.4.1)
|
|
21
|
+
faraday (2.14.1)
|
|
22
|
+
faraday-net_http (>= 2.0, < 3.5)
|
|
23
|
+
json
|
|
24
|
+
logger
|
|
25
|
+
faraday-net_http (3.4.2)
|
|
26
|
+
net-http (~> 0.5)
|
|
27
|
+
faraday-net_http_persistent (2.3.1)
|
|
28
|
+
faraday (~> 2.5)
|
|
29
|
+
net-http-persistent (>= 4.0.4, < 5)
|
|
30
|
+
hashdiff (1.2.1)
|
|
31
|
+
json (2.19.0)
|
|
32
|
+
language_server-protocol (3.17.0.5)
|
|
33
|
+
lint_roller (1.1.0)
|
|
34
|
+
logger (1.7.0)
|
|
35
|
+
net-http (0.9.1)
|
|
36
|
+
uri (>= 0.11.1)
|
|
37
|
+
net-http-persistent (4.0.8)
|
|
38
|
+
connection_pool (>= 2.2.4, < 4)
|
|
39
|
+
openfeature-sdk (0.3.1)
|
|
40
|
+
parallel (1.27.0)
|
|
41
|
+
parser (3.3.10.2)
|
|
42
|
+
ast (~> 2.4.1)
|
|
43
|
+
racc
|
|
44
|
+
prism (1.9.0)
|
|
45
|
+
public_suffix (7.0.5)
|
|
46
|
+
racc (1.8.1)
|
|
47
|
+
rainbow (3.1.1)
|
|
48
|
+
rake (13.3.1)
|
|
49
|
+
regexp_parser (2.11.3)
|
|
50
|
+
rexml (3.4.4)
|
|
51
|
+
rspec (3.13.2)
|
|
52
|
+
rspec-core (~> 3.13.0)
|
|
53
|
+
rspec-expectations (~> 3.13.0)
|
|
54
|
+
rspec-mocks (~> 3.13.0)
|
|
55
|
+
rspec-core (3.13.6)
|
|
56
|
+
rspec-support (~> 3.13.0)
|
|
57
|
+
rspec-expectations (3.13.5)
|
|
58
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
59
|
+
rspec-support (~> 3.13.0)
|
|
60
|
+
rspec-mocks (3.13.8)
|
|
61
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
62
|
+
rspec-support (~> 3.13.0)
|
|
63
|
+
rspec-support (3.13.7)
|
|
64
|
+
rubocop (1.84.2)
|
|
65
|
+
json (~> 2.3)
|
|
66
|
+
language_server-protocol (~> 3.17.0.2)
|
|
67
|
+
lint_roller (~> 1.1.0)
|
|
68
|
+
parallel (~> 1.10)
|
|
69
|
+
parser (>= 3.3.0.2)
|
|
70
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
71
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
72
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
73
|
+
ruby-progressbar (~> 1.7)
|
|
74
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
75
|
+
rubocop-ast (1.49.0)
|
|
76
|
+
parser (>= 3.3.7.2)
|
|
77
|
+
prism (~> 1.7)
|
|
78
|
+
rubocop-performance (1.26.1)
|
|
79
|
+
lint_roller (~> 1.1)
|
|
80
|
+
rubocop (>= 1.75.0, < 2.0)
|
|
81
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
82
|
+
ruby-progressbar (1.13.0)
|
|
83
|
+
simplecov (0.22.0)
|
|
84
|
+
docile (~> 1.1)
|
|
85
|
+
simplecov-html (~> 0.11)
|
|
86
|
+
simplecov_json_formatter (~> 0.1)
|
|
87
|
+
simplecov-html (0.13.2)
|
|
88
|
+
simplecov_json_formatter (0.1.4)
|
|
89
|
+
standard (1.54.0)
|
|
90
|
+
language_server-protocol (~> 3.17.0.2)
|
|
91
|
+
lint_roller (~> 1.0)
|
|
92
|
+
rubocop (~> 1.84.0)
|
|
93
|
+
standard-custom (~> 1.0.0)
|
|
94
|
+
standard-performance (~> 1.8)
|
|
95
|
+
standard-custom (1.0.2)
|
|
96
|
+
lint_roller (~> 1.0)
|
|
97
|
+
rubocop (~> 1.50)
|
|
98
|
+
standard-performance (1.9.0)
|
|
99
|
+
lint_roller (~> 1.1)
|
|
100
|
+
rubocop-performance (~> 1.26.0)
|
|
101
|
+
unicode-display_width (3.2.0)
|
|
102
|
+
unicode-emoji (~> 4.1)
|
|
103
|
+
unicode-emoji (4.2.0)
|
|
104
|
+
uri (1.1.1)
|
|
105
|
+
webmock (3.26.1)
|
|
106
|
+
addressable (>= 2.8.0)
|
|
107
|
+
crack (>= 0.3.2)
|
|
108
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
|
109
|
+
|
|
110
|
+
PLATFORMS
|
|
111
|
+
arm64-darwin-24
|
|
112
|
+
ruby
|
|
113
|
+
|
|
114
|
+
DEPENDENCIES
|
|
115
|
+
openfeature-ofrep-provider!
|
|
116
|
+
rake (~> 13.0)
|
|
117
|
+
rspec (~> 3.13.0)
|
|
118
|
+
rubocop
|
|
119
|
+
simplecov (~> 0.22)
|
|
120
|
+
standard (>= 1.35.1)
|
|
121
|
+
standard-performance
|
|
122
|
+
webmock
|
|
123
|
+
|
|
124
|
+
CHECKSUMS
|
|
125
|
+
addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485
|
|
126
|
+
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
|
127
|
+
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
|
|
128
|
+
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
|
|
129
|
+
crack (1.0.1) sha256=ff4a10390cd31d66440b7524eb1841874db86201d5b70032028553130b6d4c7e
|
|
130
|
+
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
|
131
|
+
docile (1.4.1) sha256=96159be799bfa73cdb721b840e9802126e4e03dfc26863db73647204c727f21e
|
|
132
|
+
faraday (2.14.1) sha256=a43cceedc1e39d188f4d2cdd360a8aaa6a11da0c407052e426ba8d3fb42ef61c
|
|
133
|
+
faraday-net_http (3.4.2) sha256=f147758260d3526939bf57ecf911682f94926a3666502e24c69992765875906c
|
|
134
|
+
faraday-net_http_persistent (2.3.1) sha256=23ffba37d6a27807a10f033d01918ec958aa73fa6ff0fccfbcd5ce2d2e68fca3
|
|
135
|
+
hashdiff (1.2.1) sha256=9c079dbc513dfc8833ab59c0c2d8f230fa28499cc5efb4b8dd276cf931457cd1
|
|
136
|
+
json (2.19.0) sha256=bc5202f083618b3af7aba3184146ec9d820f8f6de261838b577173475e499d9a
|
|
137
|
+
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
|
|
138
|
+
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
139
|
+
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
|
140
|
+
net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
|
|
141
|
+
net-http-persistent (4.0.8) sha256=ef3de8319d691537b329053fae3a33195f8b070bbbfae8bf1a58c796081960e6
|
|
142
|
+
openfeature-ofrep-provider (0.1.2)
|
|
143
|
+
openfeature-sdk (0.3.1) sha256=17a930f52c66ee76a5d20f5bb68cfadbb8d86a4db5b1f99caab8c60ab577f9e5
|
|
144
|
+
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
|
|
145
|
+
parser (3.3.10.2) sha256=6f60c84aa4bdcedb6d1a2434b738fe8a8136807b6adc8f7f53b97da9bc4e9357
|
|
146
|
+
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
|
|
147
|
+
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
|
|
148
|
+
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
|
149
|
+
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
|
150
|
+
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
|
|
151
|
+
regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4
|
|
152
|
+
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
|
|
153
|
+
rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
|
|
154
|
+
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
|
|
155
|
+
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
|
|
156
|
+
rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
|
|
157
|
+
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
|
|
158
|
+
rubocop (1.84.2) sha256=5692cea54168f3dc8cb79a6fe95c5424b7ea893c707ad7a4307b0585e88dbf5f
|
|
159
|
+
rubocop-ast (1.49.0) sha256=49c3676d3123a0923d333e20c6c2dbaaae2d2287b475273fddee0c61da9f71fd
|
|
160
|
+
rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
|
|
161
|
+
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
|
|
162
|
+
simplecov (0.22.0) sha256=fe2622c7834ff23b98066bb0a854284b2729a569ac659f82621fc22ef36213a5
|
|
163
|
+
simplecov-html (0.13.2) sha256=bd0b8e54e7c2d7685927e8d6286466359b6f16b18cb0df47b508e8d73c777246
|
|
164
|
+
simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428
|
|
165
|
+
standard (1.54.0) sha256=7a4b08f83d9893083c8f03bc486f0feeb6a84d48233b40829c03ef4767ea0100
|
|
166
|
+
standard-custom (1.0.2) sha256=424adc84179a074f1a2a309bb9cf7cd6bfdb2b6541f20c6bf9436c0ba22a652b
|
|
167
|
+
standard-performance (1.9.0) sha256=49483d31be448292951d80e5e67cdcb576c2502103c7b40aec6f1b6e9c88e3f2
|
|
168
|
+
unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
|
|
169
|
+
unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
|
|
170
|
+
uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
|
|
171
|
+
webmock (3.26.1) sha256=4f696fb57c90a827c20aadb2d4f9058bbff10f7f043bd0d4c3f58791143b1cd7
|
|
172
|
+
|
|
173
|
+
BUNDLED WITH
|
|
174
|
+
4.0.6
|
data/README.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# OpenFeature OFREP Provider for Ruby
|
|
2
|
+
|
|
3
|
+
An [OpenFeature](https://openfeature.dev/) provider for [OFREP](https://openfeature.dev/docs/reference/technologies/remote-evaluation-protocol) (OpenFeature Remote Evaluation Protocol) compliant feature flag servers.
|
|
4
|
+
|
|
5
|
+
OFREP is a vendor-neutral protocol that allows any compliant server to be used with this provider, including [flagd](https://flagd.dev/), [GO Feature Flag](https://gofeatureflag.org/), and others.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
| Status | Feature | Description |
|
|
10
|
+
|--------|---------|-------------|
|
|
11
|
+
| ✅ | Flag Evaluation | Support for all OpenFeature flag types |
|
|
12
|
+
| ✅ | Boolean Flags | Evaluate boolean feature flags |
|
|
13
|
+
| ✅ | String Flags | Evaluate string feature flags |
|
|
14
|
+
| ✅ | Number Flags | Evaluate numeric feature flags (integer, float) |
|
|
15
|
+
| ✅ | Object Flags | Evaluate JSON object/array flags |
|
|
16
|
+
| ✅ | Evaluation Context | Support for targeting key and custom attributes |
|
|
17
|
+
| ✅ | Custom Headers | Configurable HTTP headers for authentication |
|
|
18
|
+
| ✅ | Error Handling | Comprehensive error handling with typed errors |
|
|
19
|
+
| ✅ | Type Validation | Strict type checking for flag values |
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- Ruby >= 3.4
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Add this line to your application's Gemfile:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
gem 'openfeature-ofrep-provider'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
And then execute:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bundle install
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or install it yourself as:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
gem install openfeature-ofrep-provider
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
### Basic Setup
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
require "open_feature/sdk"
|
|
51
|
+
require "openfeature/ofrep/provider"
|
|
52
|
+
|
|
53
|
+
# Configure the OFREP provider
|
|
54
|
+
configuration = OpenFeature::OFREP::Configuration.new(
|
|
55
|
+
base_url: "http://localhost:8080",
|
|
56
|
+
headers: { "Authorization" => "Bearer my-token" },
|
|
57
|
+
timeout: 10
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
provider = OpenFeature::OFREP::Provider.new(configuration: configuration)
|
|
61
|
+
|
|
62
|
+
# Set the provider in OpenFeature
|
|
63
|
+
OpenFeature::SDK.configure do |config|
|
|
64
|
+
config.set_provider(provider)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Get a client
|
|
68
|
+
client = OpenFeature::SDK.build_client
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Evaluating Flags
|
|
72
|
+
|
|
73
|
+
#### Boolean Flags
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
evaluation_context = OpenFeature::SDK::EvaluationContext.new(
|
|
77
|
+
targeting_key: "user_123"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
enabled = client.fetch_boolean_value(
|
|
81
|
+
flag_key: "new_feature",
|
|
82
|
+
default_value: false,
|
|
83
|
+
evaluation_context: evaluation_context
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### String Flags
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
theme = client.fetch_string_value(
|
|
91
|
+
flag_key: "theme",
|
|
92
|
+
default_value: "light",
|
|
93
|
+
evaluation_context: evaluation_context
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### Number Flags
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
max_items = client.fetch_integer_value(
|
|
101
|
+
flag_key: "max_items",
|
|
102
|
+
default_value: 10,
|
|
103
|
+
evaluation_context: evaluation_context
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
rate_limit = client.fetch_float_value(
|
|
107
|
+
flag_key: "rate_limit",
|
|
108
|
+
default_value: 1.5,
|
|
109
|
+
evaluation_context: evaluation_context
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### Object Flags
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
config = client.fetch_object_value(
|
|
117
|
+
flag_key: "app_config",
|
|
118
|
+
default_value: { "timeout" => 30 },
|
|
119
|
+
evaluation_context: evaluation_context
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Configuration Options
|
|
124
|
+
|
|
125
|
+
The `Configuration` class accepts the following parameters:
|
|
126
|
+
|
|
127
|
+
| Option | Type | Default | Required | Description |
|
|
128
|
+
|--------|------|---------|----------|-------------|
|
|
129
|
+
| `base_url` | String | - | **Yes** | Base URL of the OFREP-compliant server (must be a valid HTTP/HTTPS URL) |
|
|
130
|
+
| `headers` | Hash | `{}` | No | Custom HTTP headers (e.g., for authentication) |
|
|
131
|
+
| `timeout` | Integer | `10` | No | HTTP request timeout in seconds |
|
|
132
|
+
|
|
133
|
+
### Configuration Examples
|
|
134
|
+
|
|
135
|
+
#### Minimal Configuration
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
configuration = OpenFeature::OFREP::Configuration.new(
|
|
139
|
+
base_url: "http://localhost:8080"
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### With Authentication
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
configuration = OpenFeature::OFREP::Configuration.new(
|
|
147
|
+
base_url: "https://flags.example.com",
|
|
148
|
+
headers: { "Authorization" => "Bearer your-api-key" }
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### Custom Timeout
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
configuration = OpenFeature::OFREP::Configuration.new(
|
|
156
|
+
base_url: "https://flags.example.com",
|
|
157
|
+
headers: { "Authorization" => "Bearer your-api-key" },
|
|
158
|
+
timeout: 30
|
|
159
|
+
)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Evaluation Context
|
|
163
|
+
|
|
164
|
+
The OFREP provider requires a `targeting_key` in the evaluation context. If the `targeting_key` is missing or empty, an `INVALID_CONTEXT` error is returned.
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
evaluation_context = OpenFeature::SDK::EvaluationContext.new(
|
|
168
|
+
targeting_key: "user_123",
|
|
169
|
+
email: "user@example.com",
|
|
170
|
+
plan: "premium"
|
|
171
|
+
)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Error Handling
|
|
175
|
+
|
|
176
|
+
The provider handles errors gracefully and returns the default value with appropriate error codes:
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
details = client.fetch_boolean_details(
|
|
180
|
+
flag_key: "unknown_flag",
|
|
181
|
+
default_value: false,
|
|
182
|
+
evaluation_context: evaluation_context
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
puts details.value # => false (default value)
|
|
186
|
+
puts details.error_code # => FLAG_NOT_FOUND
|
|
187
|
+
puts details.error_message # => "Flag not found: unknown_flag"
|
|
188
|
+
puts details.reason # => ERROR
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Error Codes
|
|
192
|
+
|
|
193
|
+
| Error Code | Description |
|
|
194
|
+
|------------|-------------|
|
|
195
|
+
| `FLAG_NOT_FOUND` | The requested flag does not exist on the server |
|
|
196
|
+
| `TYPE_MISMATCH` | The flag value type does not match the requested type |
|
|
197
|
+
| `PARSE_ERROR` | Failed to parse the server response |
|
|
198
|
+
| `INVALID_CONTEXT` | The evaluation context is invalid (e.g., missing `targeting_key`) |
|
|
199
|
+
| `GENERAL` | A general error occurred (unauthorized, internal server error, invalid flag key, or rate limited) |
|
|
200
|
+
|
|
201
|
+
## Development
|
|
202
|
+
|
|
203
|
+
### Running Tests
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
bundle install
|
|
207
|
+
bundle exec rspec
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Running Linter
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
bundle exec rubocop
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Building the Gem
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
gem build openfeature-ofrep-provider.gemspec
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Contributing
|
|
223
|
+
|
|
224
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
225
|
+
|
|
226
|
+
1. Fork the repository
|
|
227
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
228
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
229
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
230
|
+
5. Open a Pull Request
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
Apache 2.0 - See [LICENSE](LICENSE) for more information.
|
|
235
|
+
|
|
236
|
+
## Links
|
|
237
|
+
|
|
238
|
+
- [OFREP Specification](https://openfeature.dev/docs/reference/technologies/remote-evaluation-protocol)
|
|
239
|
+
- [OpenFeature Documentation](https://openfeature.dev/)
|
|
240
|
+
- [OpenFeature Ruby SDK](https://github.com/open-feature/ruby-sdk)
|
|
241
|
+
- [Ruby SDK Contrib Repository](https://github.com/open-feature/ruby-sdk-contrib)
|
|
242
|
+
|
|
243
|
+
## Support
|
|
244
|
+
|
|
245
|
+
For issues related to:
|
|
246
|
+
- **This provider**: [GitHub Issues](https://github.com/open-feature/ruby-sdk-contrib/issues)
|
|
247
|
+
- **OpenFeature**: [OpenFeature Community](https://openfeature.dev/community/)
|
data/Rakefile
ADDED
data/bin/rake
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
|
12
|
+
|
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
|
14
|
+
|
|
15
|
+
if File.file?(bundle_binstub)
|
|
16
|
+
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
|
17
|
+
load(bundle_binstub)
|
|
18
|
+
else
|
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
require "rubygems"
|
|
25
|
+
require "bundler/setup"
|
|
26
|
+
|
|
27
|
+
load Gem.bin_path("rake", "rake")
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
require "json"
|
|
5
|
+
require "open_feature/sdk"
|
|
6
|
+
require "faraday/net_http_persistent"
|
|
7
|
+
require_relative "errors"
|
|
8
|
+
require_relative "response"
|
|
9
|
+
|
|
10
|
+
module OpenFeature
|
|
11
|
+
module OFREP
|
|
12
|
+
class Client
|
|
13
|
+
REASON_MAP = {
|
|
14
|
+
"STATIC" => SDK::Provider::Reason::STATIC,
|
|
15
|
+
"DEFAULT" => SDK::Provider::Reason::DEFAULT,
|
|
16
|
+
"TARGETING_MATCH" => SDK::Provider::Reason::TARGETING_MATCH,
|
|
17
|
+
"SPLIT" => SDK::Provider::Reason::SPLIT,
|
|
18
|
+
"CACHED" => SDK::Provider::Reason::CACHED,
|
|
19
|
+
"DISABLED" => SDK::Provider::Reason::DISABLED,
|
|
20
|
+
"UNKNOWN" => SDK::Provider::Reason::UNKNOWN,
|
|
21
|
+
"STALE" => SDK::Provider::Reason::STALE,
|
|
22
|
+
"ERROR" => SDK::Provider::Reason::ERROR
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
ERROR_CODE_MAP = {
|
|
26
|
+
"PROVIDER_NOT_READY" => SDK::Provider::ErrorCode::PROVIDER_NOT_READY,
|
|
27
|
+
"FLAG_NOT_FOUND" => SDK::Provider::ErrorCode::FLAG_NOT_FOUND,
|
|
28
|
+
"PARSE_ERROR" => SDK::Provider::ErrorCode::PARSE_ERROR,
|
|
29
|
+
"TYPE_MISMATCH" => SDK::Provider::ErrorCode::TYPE_MISMATCH,
|
|
30
|
+
"TARGETING_KEY_MISSING" => SDK::Provider::ErrorCode::TARGETING_KEY_MISSING,
|
|
31
|
+
"INVALID_CONTEXT" => SDK::Provider::ErrorCode::INVALID_CONTEXT,
|
|
32
|
+
"GENERAL" => SDK::Provider::ErrorCode::GENERAL
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
def initialize(configuration:)
|
|
36
|
+
@configuration = configuration
|
|
37
|
+
@retry_lock = Mutex.new
|
|
38
|
+
request_options = {timeout: configuration.timeout}
|
|
39
|
+
@faraday_connection = Faraday.new(
|
|
40
|
+
url: configuration.base_url,
|
|
41
|
+
headers: build_headers,
|
|
42
|
+
request: request_options
|
|
43
|
+
) do |f|
|
|
44
|
+
f.adapter :net_http_persistent do |http|
|
|
45
|
+
http.idle_timeout = 30
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def evaluate(flag_key:, evaluation_context:)
|
|
51
|
+
check_retry_after
|
|
52
|
+
request = evaluation_request(evaluation_context)
|
|
53
|
+
|
|
54
|
+
response = @faraday_connection.post("/ofrep/v1/evaluate/flags/#{CGI.escape(flag_key)}") do |req|
|
|
55
|
+
req.body = request.to_json
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
case response.status
|
|
59
|
+
when 200
|
|
60
|
+
parse_success_response(response)
|
|
61
|
+
when 400
|
|
62
|
+
parse_error_response(response)
|
|
63
|
+
when 401, 403
|
|
64
|
+
raise OpenFeature::OFREP::UnauthorizedError.new(response)
|
|
65
|
+
when 404
|
|
66
|
+
raise OpenFeature::OFREP::FlagNotFoundError.new(response, flag_key)
|
|
67
|
+
when 429
|
|
68
|
+
parse_retry_later_header(response)
|
|
69
|
+
raise OpenFeature::OFREP::RateLimited.new(response)
|
|
70
|
+
else
|
|
71
|
+
raise OpenFeature::OFREP::InternalServerError.new(response)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def build_headers
|
|
78
|
+
{"Content-Type" => "application/json"}.merge(@configuration.headers || {})
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def evaluation_request(evaluation_context)
|
|
82
|
+
ctx = evaluation_context
|
|
83
|
+
fields = ctx.fields.dup
|
|
84
|
+
fields["targetingKey"] = ctx.targeting_key
|
|
85
|
+
fields.delete("targeting_key")
|
|
86
|
+
|
|
87
|
+
{context: fields}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def check_retry_after
|
|
91
|
+
@retry_lock.synchronize do
|
|
92
|
+
return if @retry_after.nil?
|
|
93
|
+
if Time.now < @retry_after
|
|
94
|
+
raise OpenFeature::OFREP::RateLimited.new(nil)
|
|
95
|
+
else
|
|
96
|
+
@retry_after = nil
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def parse_error_response(response)
|
|
102
|
+
required_keys = %w[key error_code]
|
|
103
|
+
parsed = JSON.parse(response.body)
|
|
104
|
+
|
|
105
|
+
missing_keys = required_keys - parsed.keys
|
|
106
|
+
unless missing_keys.empty?
|
|
107
|
+
raise OpenFeature::OFREP::ParseError.new(response)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
OpenFeature::OFREP::Response.new(
|
|
111
|
+
value: nil,
|
|
112
|
+
key: parsed["key"],
|
|
113
|
+
reason: SDK::Provider::Reason::ERROR,
|
|
114
|
+
variant: nil,
|
|
115
|
+
error_code: error_code_mapper(parsed["error_code"]),
|
|
116
|
+
error_details: parsed["error_details"],
|
|
117
|
+
metadata: nil
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def parse_success_response(response)
|
|
122
|
+
required_keys = %w[key value reason variant]
|
|
123
|
+
parsed = JSON.parse(response.body)
|
|
124
|
+
|
|
125
|
+
missing_keys = required_keys - parsed.keys
|
|
126
|
+
unless missing_keys.empty?
|
|
127
|
+
raise OpenFeature::OFREP::ParseError.new(response)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
OpenFeature::OFREP::Response.new(
|
|
131
|
+
value: parsed["value"],
|
|
132
|
+
key: parsed["key"],
|
|
133
|
+
reason: reason_mapper(parsed["reason"]),
|
|
134
|
+
variant: parsed["variant"],
|
|
135
|
+
error_code: nil,
|
|
136
|
+
error_details: nil,
|
|
137
|
+
metadata: parsed["metadata"]
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def reason_mapper(reason_str)
|
|
142
|
+
return SDK::Provider::Reason::UNKNOWN if reason_str.nil?
|
|
143
|
+
REASON_MAP[reason_str.upcase] || SDK::Provider::Reason::UNKNOWN
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def error_code_mapper(error_code_str)
|
|
147
|
+
return SDK::Provider::ErrorCode::GENERAL if error_code_str.nil?
|
|
148
|
+
ERROR_CODE_MAP[error_code_str.upcase] || SDK::Provider::ErrorCode::GENERAL
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def parse_retry_later_header(response)
|
|
152
|
+
retry_after = response["Retry-After"]
|
|
153
|
+
return nil if retry_after.nil?
|
|
154
|
+
|
|
155
|
+
begin
|
|
156
|
+
next_retry_time =
|
|
157
|
+
if /^\d+$/.match?(retry_after)
|
|
158
|
+
Time.now + Integer(retry_after)
|
|
159
|
+
else
|
|
160
|
+
Time.httpdate(retry_after)
|
|
161
|
+
end
|
|
162
|
+
@retry_lock.synchronize do
|
|
163
|
+
@retry_after = [@retry_after, next_retry_time].compact.max
|
|
164
|
+
end
|
|
165
|
+
rescue ArgumentError
|
|
166
|
+
nil
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module OpenFeature
|
|
6
|
+
module OFREP
|
|
7
|
+
class Configuration
|
|
8
|
+
attr_reader :base_url, :headers, :timeout
|
|
9
|
+
|
|
10
|
+
def initialize(base_url:, headers: {}, timeout: 10)
|
|
11
|
+
validate_base_url(base_url)
|
|
12
|
+
@base_url = base_url
|
|
13
|
+
@headers = headers
|
|
14
|
+
@timeout = timeout
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def validate_base_url(base_url)
|
|
20
|
+
raise ArgumentError, "base_url is required" if base_url.nil? || base_url.empty?
|
|
21
|
+
|
|
22
|
+
uri = URI.parse(base_url)
|
|
23
|
+
raise ArgumentError, "Invalid URL for base_url: #{base_url}" unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
24
|
+
rescue URI::InvalidURIError
|
|
25
|
+
raise ArgumentError, "Invalid URL for base_url: #{base_url}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open_feature/sdk/provider/error_code"
|
|
4
|
+
|
|
5
|
+
module OpenFeature
|
|
6
|
+
module OFREP
|
|
7
|
+
class FlagNotFoundError < StandardError
|
|
8
|
+
attr_reader :response, :error_code, :error_message
|
|
9
|
+
|
|
10
|
+
def initialize(response, flag_key)
|
|
11
|
+
error_message = "Flag not found: #{flag_key}"
|
|
12
|
+
@response = response
|
|
13
|
+
@error_code = SDK::Provider::ErrorCode::FLAG_NOT_FOUND
|
|
14
|
+
@error_message = error_message
|
|
15
|
+
super(error_message)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class InternalServerError < StandardError
|
|
20
|
+
attr_reader :response, :error_code, :error_message
|
|
21
|
+
|
|
22
|
+
def initialize(response)
|
|
23
|
+
error_message = "Internal Server Error"
|
|
24
|
+
@response = response
|
|
25
|
+
@error_code = SDK::Provider::ErrorCode::GENERAL
|
|
26
|
+
@error_message = error_message
|
|
27
|
+
super(error_message)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class InvalidOptionError < StandardError
|
|
32
|
+
attr_reader :error_code, :error_message
|
|
33
|
+
|
|
34
|
+
def initialize(error_code, error_message)
|
|
35
|
+
@error_code = error_code
|
|
36
|
+
@error_message = error_message
|
|
37
|
+
super(error_message)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class UnauthorizedError < StandardError
|
|
42
|
+
attr_reader :response, :error_code, :error_message
|
|
43
|
+
|
|
44
|
+
def initialize(response)
|
|
45
|
+
error_message = "unauthorized"
|
|
46
|
+
@response = response
|
|
47
|
+
@error_code = SDK::Provider::ErrorCode::GENERAL
|
|
48
|
+
@error_message = error_message
|
|
49
|
+
super(error_message)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class ParseError < StandardError
|
|
54
|
+
attr_reader :response, :error_code, :error_message
|
|
55
|
+
|
|
56
|
+
def initialize(response)
|
|
57
|
+
error_message = "Parse error"
|
|
58
|
+
@response = response
|
|
59
|
+
@error_code = SDK::Provider::ErrorCode::PARSE_ERROR
|
|
60
|
+
@error_message = error_message
|
|
61
|
+
super(error_message)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class RateLimited < StandardError
|
|
66
|
+
attr_reader :response, :error_code, :error_message
|
|
67
|
+
|
|
68
|
+
def initialize(response)
|
|
69
|
+
error_message = "Rate limited"
|
|
70
|
+
error_message += ": #{response["Retry-After"]}" if response&.[]("Retry-After")
|
|
71
|
+
@response = response
|
|
72
|
+
@error_code = SDK::Provider::ErrorCode::GENERAL
|
|
73
|
+
@error_message = error_message
|
|
74
|
+
super(error_message)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenFeature
|
|
4
|
+
module OFREP
|
|
5
|
+
class Response
|
|
6
|
+
attr_reader :value, :key, :reason, :variant, :error_code, :error_details, :metadata
|
|
7
|
+
|
|
8
|
+
def initialize(value:, key:, reason:, variant:, error_code:, error_details:, metadata:)
|
|
9
|
+
@value = value
|
|
10
|
+
@key = key
|
|
11
|
+
@reason = reason
|
|
12
|
+
@variant = variant
|
|
13
|
+
@error_code = error_code
|
|
14
|
+
@error_details = error_details
|
|
15
|
+
@metadata = metadata
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def error?
|
|
19
|
+
!@error_code.nil? && !@error_code.empty?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def eql?(other)
|
|
23
|
+
return false unless other.is_a?(OpenFeature::OFREP::Response)
|
|
24
|
+
key == other.key &&
|
|
25
|
+
value == other.value &&
|
|
26
|
+
reason == other.reason &&
|
|
27
|
+
variant == other.variant &&
|
|
28
|
+
error_code == other.error_code &&
|
|
29
|
+
error_details == other.error_details &&
|
|
30
|
+
metadata == other.metadata
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openfeature/ofrep/provider/configuration"
|
|
4
|
+
require "openfeature/ofrep/provider/client"
|
|
5
|
+
require "openfeature/ofrep/provider/response"
|
|
6
|
+
require "openfeature/ofrep/provider/errors"
|
|
7
|
+
require "openfeature/ofrep/provider/version"
|
|
8
|
+
|
|
9
|
+
module OpenFeature
|
|
10
|
+
module OFREP
|
|
11
|
+
class Provider
|
|
12
|
+
PROVIDER_NAME = "OFREP Provider"
|
|
13
|
+
attr_reader :metadata
|
|
14
|
+
|
|
15
|
+
def initialize(configuration:)
|
|
16
|
+
@metadata = SDK::Provider::ProviderMetadata.new(name: PROVIDER_NAME)
|
|
17
|
+
@configuration = configuration
|
|
18
|
+
@client = Client.new(configuration: configuration)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
|
|
22
|
+
evaluate(flag_key: flag_key, default_value: default_value, evaluation_context: evaluation_context, allowed_classes: [TrueClass, FalseClass])
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
|
|
26
|
+
evaluate(flag_key: flag_key, default_value: default_value, evaluation_context: evaluation_context, allowed_classes: [String])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def fetch_number_value(flag_key:, default_value:, evaluation_context: nil)
|
|
30
|
+
evaluate(flag_key: flag_key, default_value: default_value, evaluation_context: evaluation_context, allowed_classes: [Integer, Float])
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil)
|
|
34
|
+
evaluate(flag_key: flag_key, default_value: default_value, evaluation_context: evaluation_context, allowed_classes: [Integer])
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def fetch_float_value(flag_key:, default_value:, evaluation_context: nil)
|
|
38
|
+
evaluate(flag_key: flag_key, default_value: default_value, evaluation_context: evaluation_context, allowed_classes: [Float])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def fetch_object_value(flag_key:, default_value:, evaluation_context: nil)
|
|
42
|
+
evaluate(flag_key: flag_key, default_value: default_value, evaluation_context: evaluation_context, allowed_classes: [Array, Hash])
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def evaluate(flag_key:, default_value:, allowed_classes:, evaluation_context: nil)
|
|
48
|
+
evaluation_context = OpenFeature::SDK::EvaluationContext.new if evaluation_context.nil?
|
|
49
|
+
validate_parameters(flag_key, evaluation_context)
|
|
50
|
+
|
|
51
|
+
parsed_response = @client.evaluate(flag_key: flag_key, evaluation_context: evaluation_context)
|
|
52
|
+
|
|
53
|
+
if parsed_response.error?
|
|
54
|
+
return SDK::Provider::ResolutionDetails.new(
|
|
55
|
+
value: default_value,
|
|
56
|
+
error_code: parsed_response.error_code,
|
|
57
|
+
error_message: parsed_response.error_details,
|
|
58
|
+
reason: parsed_response.reason
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if parsed_response.reason == SDK::Provider::Reason::DISABLED
|
|
63
|
+
return SDK::Provider::ResolutionDetails.new(
|
|
64
|
+
value: default_value,
|
|
65
|
+
reason: SDK::Provider::Reason::DISABLED
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
unless allowed_classes.include?(parsed_response.value.class)
|
|
70
|
+
return SDK::Provider::ResolutionDetails.new(
|
|
71
|
+
value: default_value,
|
|
72
|
+
error_code: SDK::Provider::ErrorCode::TYPE_MISMATCH,
|
|
73
|
+
error_message: "flag type #{parsed_response.value.class} does not match allowed types #{allowed_classes}",
|
|
74
|
+
reason: SDK::Provider::Reason::ERROR
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
SDK::Provider::ResolutionDetails.new(
|
|
79
|
+
value: parsed_response.value,
|
|
80
|
+
reason: parsed_response.reason,
|
|
81
|
+
variant: parsed_response.variant,
|
|
82
|
+
flag_metadata: parsed_response.metadata
|
|
83
|
+
)
|
|
84
|
+
rescue UnauthorizedError,
|
|
85
|
+
InvalidOptionError,
|
|
86
|
+
FlagNotFoundError,
|
|
87
|
+
InternalServerError => e
|
|
88
|
+
SDK::Provider::ResolutionDetails.new(
|
|
89
|
+
value: default_value,
|
|
90
|
+
error_code: e.error_code,
|
|
91
|
+
error_message: e.error_message,
|
|
92
|
+
reason: SDK::Provider::Reason::ERROR
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def validate_parameters(flag_key, evaluation_context)
|
|
97
|
+
if evaluation_context.targeting_key.nil? || evaluation_context.targeting_key.empty?
|
|
98
|
+
raise InvalidOptionError.new(SDK::Provider::ErrorCode::INVALID_CONTEXT, "invalid evaluation context provided")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if flag_key.nil? || flag_key.empty?
|
|
102
|
+
raise InvalidOptionError.new(SDK::Provider::ErrorCode::GENERAL, "invalid flag key provided")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/openfeature/ofrep/provider/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "openfeature-ofrep-provider"
|
|
7
|
+
spec.version = OpenFeature::OFREP::VERSION
|
|
8
|
+
spec.authors = ["OpenFeature Contributors"]
|
|
9
|
+
spec.email = ["cncf-openfeature-contributors@lists.cncf.io"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "The OFREP provider for the OpenFeature Ruby SDK"
|
|
12
|
+
spec.description = "A vendor-neutral OFREP (OpenFeature Remote Evaluation Protocol) provider for the OpenFeature Ruby SDK"
|
|
13
|
+
spec.homepage = "https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-ofrep-provider"
|
|
14
|
+
spec.license = "Apache-2.0"
|
|
15
|
+
spec.required_ruby_version = ">= 3.4"
|
|
16
|
+
|
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-ofrep-provider"
|
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/open-feature/ruby-sdk-contrib/blob/main/providers/openfeature-ofrep-provider/CHANGELOG.md"
|
|
20
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/open-feature/ruby-sdk-contrib/issues"
|
|
21
|
+
spec.metadata["documentation_uri"] = "https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-ofrep-provider/README.md"
|
|
22
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
23
|
+
|
|
24
|
+
# Specify which files should be added to the gem when it is released.
|
|
25
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
26
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
27
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
28
|
+
end
|
|
29
|
+
spec.bindir = "exe"
|
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
31
|
+
spec.require_paths = ["lib"]
|
|
32
|
+
|
|
33
|
+
spec.add_runtime_dependency "openfeature-sdk", "~> 0.3.1"
|
|
34
|
+
spec.add_runtime_dependency "faraday-net_http_persistent", "~> 2.3"
|
|
35
|
+
|
|
36
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
37
|
+
spec.add_development_dependency "rspec", "~> 3.13.0"
|
|
38
|
+
spec.add_development_dependency "standard", ">= 1.35.1"
|
|
39
|
+
spec.add_development_dependency "rubocop"
|
|
40
|
+
spec.add_development_dependency "standard-performance"
|
|
41
|
+
spec.add_development_dependency "simplecov", "~> 0.22"
|
|
42
|
+
spec.add_development_dependency "webmock"
|
|
43
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: openfeature-ofrep-provider
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- OpenFeature Contributors
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: openfeature-sdk
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.3.1
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.3.1
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: faraday-net_http_persistent
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.3'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.3'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '13.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rspec
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: 3.13.0
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: 3.13.0
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: standard
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: 1.35.1
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: 1.35.1
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rubocop
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: standard-performance
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: simplecov
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0.22'
|
|
117
|
+
type: :development
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0.22'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: webmock
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - ">="
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0'
|
|
131
|
+
type: :development
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - ">="
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '0'
|
|
138
|
+
description: A vendor-neutral OFREP (OpenFeature Remote Evaluation Protocol) provider
|
|
139
|
+
for the OpenFeature Ruby SDK
|
|
140
|
+
email:
|
|
141
|
+
- cncf-openfeature-contributors@lists.cncf.io
|
|
142
|
+
executables: []
|
|
143
|
+
extensions: []
|
|
144
|
+
extra_rdoc_files: []
|
|
145
|
+
files:
|
|
146
|
+
- ".rspec"
|
|
147
|
+
- ".rubocop.yml"
|
|
148
|
+
- CHANGELOG.md
|
|
149
|
+
- Gemfile
|
|
150
|
+
- Gemfile.lock
|
|
151
|
+
- README.md
|
|
152
|
+
- Rakefile
|
|
153
|
+
- bin/rake
|
|
154
|
+
- lib/openfeature/ofrep/provider.rb
|
|
155
|
+
- lib/openfeature/ofrep/provider/client.rb
|
|
156
|
+
- lib/openfeature/ofrep/provider/configuration.rb
|
|
157
|
+
- lib/openfeature/ofrep/provider/errors.rb
|
|
158
|
+
- lib/openfeature/ofrep/provider/response.rb
|
|
159
|
+
- lib/openfeature/ofrep/provider/version.rb
|
|
160
|
+
- openfeature-ofrep-provider.gemspec
|
|
161
|
+
homepage: https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-ofrep-provider
|
|
162
|
+
licenses:
|
|
163
|
+
- Apache-2.0
|
|
164
|
+
metadata:
|
|
165
|
+
homepage_uri: https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-ofrep-provider
|
|
166
|
+
source_code_uri: https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-ofrep-provider
|
|
167
|
+
changelog_uri: https://github.com/open-feature/ruby-sdk-contrib/blob/main/providers/openfeature-ofrep-provider/CHANGELOG.md
|
|
168
|
+
bug_tracker_uri: https://github.com/open-feature/ruby-sdk-contrib/issues
|
|
169
|
+
documentation_uri: https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-ofrep-provider/README.md
|
|
170
|
+
rubygems_mfa_required: 'true'
|
|
171
|
+
rdoc_options: []
|
|
172
|
+
require_paths:
|
|
173
|
+
- lib
|
|
174
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
175
|
+
requirements:
|
|
176
|
+
- - ">="
|
|
177
|
+
- !ruby/object:Gem::Version
|
|
178
|
+
version: '3.4'
|
|
179
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
180
|
+
requirements:
|
|
181
|
+
- - ">="
|
|
182
|
+
- !ruby/object:Gem::Version
|
|
183
|
+
version: '0'
|
|
184
|
+
requirements: []
|
|
185
|
+
rubygems_version: 3.6.9
|
|
186
|
+
specification_version: 4
|
|
187
|
+
summary: The OFREP provider for the OpenFeature Ruby SDK
|
|
188
|
+
test_files: []
|