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 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
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,5 @@
1
+ inherit_from: ../../shared_config/.rubocop.yml
2
+
3
+ inherit_mode:
4
+ merge:
5
+ - Exclude
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
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
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
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[standard spec]
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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenFeature
4
+ module OFREP
5
+ VERSION = "0.1.2"
6
+ end
7
+ 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: []