openfeature-flagsmith-provider 0.1.1
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/.context.md +316 -0
- data/.gitignore +11 -0
- data/.rspec +4 -0
- data/.rubocop.yml +5 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +22 -0
- data/FLAGSMITH_PROVIDER_DESIGN.md +393 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +126 -0
- data/README.md +324 -0
- data/Rakefile +6 -0
- data/lib/openfeature/flagsmith/error/errors.rb +78 -0
- data/lib/openfeature/flagsmith/options.rb +82 -0
- data/lib/openfeature/flagsmith/provider.rb +265 -0
- data/lib/openfeature/flagsmith/version.rb +7 -0
- data/openfeature-flagsmith-provider.gemspec +38 -0
- metadata +176 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7194e7bbb567a6f66ad964bde2bfd58544e6091c5da1a721ce076d9ae91b94fc
|
|
4
|
+
data.tar.gz: ece05c570cee2aeffecd0375a7771e6cae9a616f029a9a1d9d54a9d4843a21a7
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a1bc411152743dacbf30ca78fed7820733e72db3e732e967e2e8b4cd80528ddfb0c61f204d7f3f41b3de90386093fbf95e005d9d3bdd4def6d6ca2d4e6c67434
|
|
7
|
+
data.tar.gz: deb6a3d707a7a680b273f292f7976c28ee4f0c826671616e443cd846055d961c9a091f067bc34a069745cf9583608876de223f0eea08df1a34803342a0845768
|
data/.context.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# Flagsmith OpenFeature Provider - Context
|
|
2
|
+
|
|
3
|
+
**Started:** 2025-11-17
|
|
4
|
+
**Full Design Doc:** `./FLAGSMITH_PROVIDER_DESIGN.md`
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Quick Facts
|
|
9
|
+
|
|
10
|
+
**What we're building:** OpenFeature provider for Flagsmith Ruby SDK
|
|
11
|
+
**Target Flagsmith version:** Upcoming release (TBD)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Key Design Decisions ✅
|
|
16
|
+
|
|
17
|
+
| Decision | Choice | Rationale |
|
|
18
|
+
|----------|--------|-----------|
|
|
19
|
+
| Evaluation mode | Remote (default) | Simpler, no polling. Local available via config |
|
|
20
|
+
| No targeting_key | Fall back to environment flags | `get_environment_flags()` vs `get_identity_flags()` |
|
|
21
|
+
| Analytics | Opt-in (disabled default) | Privacy-first approach |
|
|
22
|
+
| Default values | Use OpenFeature's default_value | Match other providers, no custom handler |
|
|
23
|
+
| Configuration | Options object pattern | Clear validation, like GO Feature Flag |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Architecture Map
|
|
28
|
+
|
|
29
|
+
### Directory Structure
|
|
30
|
+
```
|
|
31
|
+
openfeature-flagsmith-provider/
|
|
32
|
+
├── lib/openfeature/flagsmith/
|
|
33
|
+
│ ├── provider.rb # Main provider class
|
|
34
|
+
│ ├── configuration.rb # Options/config with validation
|
|
35
|
+
│ ├── error/errors.rb # Custom exceptions → ErrorCode mapping
|
|
36
|
+
│ └── version.rb # VERSION constant
|
|
37
|
+
├── spec/ # RSpec tests with WebMock
|
|
38
|
+
├── openfeature-flagsmith-provider.gemspec
|
|
39
|
+
└── README.md
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Key Classes
|
|
43
|
+
|
|
44
|
+
**Configuration** (lib/openfeature/flagsmith/configuration.rb):
|
|
45
|
+
```ruby
|
|
46
|
+
OpenFeature::Flagsmith::Configuration.new(
|
|
47
|
+
environment_key: "required",
|
|
48
|
+
api_url: "https://edge.api.flagsmith.com/api/v1/",
|
|
49
|
+
enable_local_evaluation: false,
|
|
50
|
+
request_timeout_seconds: 10,
|
|
51
|
+
enable_analytics: false
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Provider** (lib/openfeature/flagsmith/provider.rb):
|
|
56
|
+
```ruby
|
|
57
|
+
class Provider
|
|
58
|
+
attr_reader :metadata
|
|
59
|
+
|
|
60
|
+
# Required methods:
|
|
61
|
+
def fetch_boolean_value(flag_key:, default_value:, evaluation_context:)
|
|
62
|
+
def fetch_string_value(flag_key:, default_value:, evaluation_context:)
|
|
63
|
+
def fetch_number_value(flag_key:, default_value:, evaluation_context:)
|
|
64
|
+
def fetch_integer_value(flag_key:, default_value:, evaluation_context:)
|
|
65
|
+
def fetch_float_value(flag_key:, default_value:, evaluation_context:)
|
|
66
|
+
def fetch_object_value(flag_key:, default_value:, evaluation_context:)
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Critical Mappings
|
|
73
|
+
|
|
74
|
+
### 1. Context → Identity/Traits
|
|
75
|
+
```ruby
|
|
76
|
+
# OpenFeature → Flagsmith
|
|
77
|
+
evaluation_context.targeting_key → identifier (for get_identity_flags)
|
|
78
|
+
evaluation_context.fields → traits (as keyword args)
|
|
79
|
+
|
|
80
|
+
# If no targeting_key → use get_environment_flags()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 2. Flag Types
|
|
84
|
+
| Flagsmith | OpenFeature Method | Implementation |
|
|
85
|
+
|-----------|-------------------|----------------|
|
|
86
|
+
| `is_feature_enabled()` → Boolean | `fetch_boolean_value` | Direct mapping |
|
|
87
|
+
| `get_feature_value()` → String | `fetch_string_value` | Direct return |
|
|
88
|
+
| `get_feature_value()` → Number | `fetch_number_value` | Parse & validate type |
|
|
89
|
+
| `get_feature_value()` → JSON | `fetch_object_value` | `JSON.parse()` |
|
|
90
|
+
|
|
91
|
+
### 3. Reason Mapping
|
|
92
|
+
| Situation | OpenFeature Reason |
|
|
93
|
+
|-----------|-------------------|
|
|
94
|
+
| Flag evaluated with identity | `TARGETING_MATCH` |
|
|
95
|
+
| Flag evaluated at environment level | `STATIC` |
|
|
96
|
+
| Flag not found | `DEFAULT` |
|
|
97
|
+
| Flag exists but disabled | `DISABLED` |
|
|
98
|
+
| Error occurred | `ERROR` |
|
|
99
|
+
|
|
100
|
+
### 4. Error Handling
|
|
101
|
+
```ruby
|
|
102
|
+
# Pattern: Always return ResolutionDetails with default_value on error
|
|
103
|
+
SDK::Provider::ResolutionDetails.new(
|
|
104
|
+
value: default_value,
|
|
105
|
+
error_code: <appropriate ErrorCode>,
|
|
106
|
+
error_message: "description",
|
|
107
|
+
reason: SDK::Provider::Reason::ERROR
|
|
108
|
+
)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**ErrorCode mapping:**
|
|
112
|
+
- Flag not found → `FLAG_NOT_FOUND`
|
|
113
|
+
- Wrong type → `TYPE_MISMATCH`
|
|
114
|
+
- Network error → `PROVIDER_NOT_READY` or `GENERAL`
|
|
115
|
+
- Invalid context → `INVALID_CONTEXT`
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Flagsmith SDK API Reference
|
|
120
|
+
|
|
121
|
+
### Initialization
|
|
122
|
+
```ruby
|
|
123
|
+
require "flagsmith"
|
|
124
|
+
client = Flagsmith::Client.new(
|
|
125
|
+
environment_key: "key",
|
|
126
|
+
api_url: "url",
|
|
127
|
+
enable_local_evaluation: false,
|
|
128
|
+
request_timeout_seconds: 10,
|
|
129
|
+
enable_analytics: false
|
|
130
|
+
)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Evaluation Methods
|
|
134
|
+
```ruby
|
|
135
|
+
# Environment-level (no user)
|
|
136
|
+
flags = client.get_environment_flags()
|
|
137
|
+
flags.is_feature_enabled('flag_key') # → Boolean
|
|
138
|
+
flags.get_feature_value('flag_key') # → String/value
|
|
139
|
+
|
|
140
|
+
# Identity-specific (with user context)
|
|
141
|
+
flags = client.get_identity_flags('user@example.com', trait1: 'value', age: 30)
|
|
142
|
+
flags.is_feature_enabled('flag_key')
|
|
143
|
+
flags.get_feature_value('flag_key')
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## OpenFeature Provider Contract
|
|
149
|
+
|
|
150
|
+
### Required Interface
|
|
151
|
+
```ruby
|
|
152
|
+
# Metadata
|
|
153
|
+
attr_reader :metadata # ProviderMetadata.new(name: "Flagsmith Provider")
|
|
154
|
+
|
|
155
|
+
# Lifecycle (optional)
|
|
156
|
+
def init # Optional initialization
|
|
157
|
+
def shutdown # Optional cleanup
|
|
158
|
+
|
|
159
|
+
# Evaluation methods (required)
|
|
160
|
+
def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
|
|
161
|
+
def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
|
|
162
|
+
def fetch_number_value(flag_key:, default_value:, evaluation_context: nil)
|
|
163
|
+
def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil)
|
|
164
|
+
def fetch_float_value(flag_key:, default_value:, evaluation_context: nil)
|
|
165
|
+
def fetch_object_value(flag_key:, default_value:, evaluation_context: nil)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Return Type
|
|
169
|
+
```ruby
|
|
170
|
+
OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
171
|
+
value: <evaluated_value>, # Required
|
|
172
|
+
reason: <Reason constant>, # Required
|
|
173
|
+
variant: "variant_key", # Optional
|
|
174
|
+
flag_metadata: {key: "value"}, # Optional
|
|
175
|
+
error_code: <ErrorCode constant>, # If error
|
|
176
|
+
error_message: "details" # If error
|
|
177
|
+
)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Code Patterns to Follow
|
|
183
|
+
|
|
184
|
+
### Type Validation Pattern
|
|
185
|
+
```ruby
|
|
186
|
+
def evaluate(flag_key:, default_value:, allowed_classes:, evaluation_context:)
|
|
187
|
+
# ... get value from Flagsmith ...
|
|
188
|
+
|
|
189
|
+
unless allowed_classes.include?(value.class)
|
|
190
|
+
return SDK::Provider::ResolutionDetails.new(
|
|
191
|
+
value: default_value,
|
|
192
|
+
error_code: SDK::Provider::ErrorCode::TYPE_MISMATCH,
|
|
193
|
+
error_message: "Expected #{allowed_classes}, got #{value.class}",
|
|
194
|
+
reason: SDK::Provider::Reason::ERROR
|
|
195
|
+
)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# return success ResolutionDetails
|
|
199
|
+
end
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Error Rescue Pattern
|
|
203
|
+
```ruby
|
|
204
|
+
begin
|
|
205
|
+
# Flagsmith evaluation
|
|
206
|
+
rescue SomeError => e
|
|
207
|
+
return SDK::Provider::ResolutionDetails.new(
|
|
208
|
+
value: default_value,
|
|
209
|
+
error_code: SDK::Provider::ErrorCode::GENERAL,
|
|
210
|
+
error_message: e.message,
|
|
211
|
+
reason: SDK::Provider::Reason::ERROR
|
|
212
|
+
)
|
|
213
|
+
end
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Dependencies
|
|
219
|
+
|
|
220
|
+
### Runtime
|
|
221
|
+
```ruby
|
|
222
|
+
spec.add_runtime_dependency "openfeature-sdk", "~> 0.3.1"
|
|
223
|
+
spec.add_runtime_dependency "flagsmith", "~> <VERSION_TBD>"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Development
|
|
227
|
+
```ruby
|
|
228
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
229
|
+
spec.add_development_dependency "rspec", "~> 3.12.0"
|
|
230
|
+
spec.add_development_dependency "webmock", "~> 3.0" # Mock Flagsmith calls
|
|
231
|
+
spec.add_development_dependency "standard"
|
|
232
|
+
spec.add_development_dependency "rubocop"
|
|
233
|
+
spec.add_development_dependency "simplecov"
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Testing Strategy
|
|
239
|
+
|
|
240
|
+
### Mock Flagsmith Responses
|
|
241
|
+
```ruby
|
|
242
|
+
# Use WebMock to stub Flagsmith API calls
|
|
243
|
+
# OR stub Flagsmith::Client methods directly
|
|
244
|
+
|
|
245
|
+
allow(flagsmith_client).to receive(:get_identity_flags).and_return(mock_flags)
|
|
246
|
+
allow(mock_flags).to receive(:is_feature_enabled).and_return(true)
|
|
247
|
+
allow(mock_flags).to receive(:get_feature_value).and_return("value")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Test Coverage Areas
|
|
251
|
+
- ✅ Metadata verification
|
|
252
|
+
- ✅ Configuration validation
|
|
253
|
+
- ✅ Each flag type evaluation (boolean, string, number, integer, float, object)
|
|
254
|
+
- ✅ Type mismatches
|
|
255
|
+
- ✅ Missing flags (return default)
|
|
256
|
+
- ✅ Error handling
|
|
257
|
+
- ✅ Context mapping (with/without targeting_key)
|
|
258
|
+
- ✅ Environment vs Identity evaluation
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Implementation Checklist
|
|
263
|
+
|
|
264
|
+
- [ ] Directory structure created
|
|
265
|
+
- [ ] Gemspec with dependencies
|
|
266
|
+
- [ ] Configuration class with validation
|
|
267
|
+
- [ ] Provider skeleton with metadata
|
|
268
|
+
- [ ] Context → Identity/Traits mapping helper
|
|
269
|
+
- [ ] `fetch_boolean_value` implementation
|
|
270
|
+
- [ ] `fetch_string_value` implementation
|
|
271
|
+
- [ ] `fetch_number_value` implementation
|
|
272
|
+
- [ ] `fetch_integer_value` implementation
|
|
273
|
+
- [ ] `fetch_float_value` implementation
|
|
274
|
+
- [ ] `fetch_object_value` implementation
|
|
275
|
+
- [ ] Error handling and custom exceptions
|
|
276
|
+
- [ ] Type validation
|
|
277
|
+
- [ ] RSpec test suite
|
|
278
|
+
- [ ] README with examples
|
|
279
|
+
- [ ] Release configuration
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Open Items / Notes
|
|
284
|
+
|
|
285
|
+
- **Flagsmith version**: Waiting for upcoming release - update gemspec when available
|
|
286
|
+
- **Variant support**: Flagsmith doesn't have explicit variants - TBD how to handle
|
|
287
|
+
- **Flag metadata**: Flagsmith has limited metadata - may need to extract from traits/response
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Quick Commands
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
# Run tests
|
|
295
|
+
bundle exec rspec
|
|
296
|
+
|
|
297
|
+
# Run linter
|
|
298
|
+
bundle exec rubocop
|
|
299
|
+
|
|
300
|
+
# Install locally for testing
|
|
301
|
+
gem build openfeature-flagsmith-provider.gemspec
|
|
302
|
+
gem install ./openfeature-flagsmith-provider-<version>.gem
|
|
303
|
+
|
|
304
|
+
# Test with real Flagsmith
|
|
305
|
+
# (add example script in spec/manual_test.rb)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## References
|
|
311
|
+
|
|
312
|
+
- Full design doc: `../FLAGSMITH_PROVIDER_DESIGN.md`
|
|
313
|
+
- Flagsmith docs: https://docs.flagsmith.com/clients/server-side
|
|
314
|
+
- OpenFeature spec: https://openfeature.dev/specification/
|
|
315
|
+
- GO Feature Flag provider: `../openfeature-go-feature-flag-provider/`
|
|
316
|
+
- flagd provider: `../openfeature-flagd-provider/`
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.1.4
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.1](https://github.com/open-feature/ruby-sdk-contrib/compare/openfeature-flagsmith-provider-v0.1.0...openfeature-flagsmith-provider/v0.1.1) (2025-11-27)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### ✨ New Features
|
|
12
|
+
|
|
13
|
+
* add flagsmith provider ([#68](https://github.com/open-feature/ruby-sdk-contrib/issues/68)) ([9e3216a](https://github.com/open-feature/ruby-sdk-contrib/commit/9e3216ac67d1eeb12f423a4c0615442ac52b2a17))
|
|
14
|
+
|
|
15
|
+
## [Unreleased]
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- Initial implementation of Flagsmith OpenFeature provider
|
|
19
|
+
- Support for all OpenFeature flag types (boolean, string, number, integer, float, object)
|
|
20
|
+
- Remote and local evaluation modes
|
|
21
|
+
- Environment-level and identity-specific flag evaluation
|
|
22
|
+
- Comprehensive error handling and type validation
|