Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
681d898
chore(main): release openfeature-meta_provider 0.0.5 (#50)
openfeaturebot Mar 3, 2025
665500f
feat: Add Flipt provider gem with basic implementation (#51)
falghi Apr 14, 2025
d225d36
chore: add Flipt to Release Please (#52)
beeme1mr Apr 14, 2025
40dcc77
chore: correct release please config for flipt
beeme1mr Apr 14, 2025
456bcf0
fix: Update Gemfile.lock on flipt provider (#55)
falghi Apr 15, 2025
7e78ac1
chore(main): release openfeature-flipt-provider 0.0.2 (#54)
openfeaturebot Apr 16, 2025
5bc1e2b
feat: connection persistance (#59)
augustinasrce Jul 30, 2025
cdf68cf
chore(main): release openfeature-go-feature-flag-provider 0.1.4 (#60)
openfeaturebot Jul 30, 2025
54ebfd1
feat(goff): add Faraday instrumentation (#62)
avelicka Nov 4, 2025
1c841df
chore(main): release openfeature-go-feature-flag-provider 0.1.5 (#63)
openfeaturebot Nov 4, 2025
7337653
fix(openfeature-go-feature-flag-provider): fallback to sdkDefault if …
doriandekoning Nov 11, 2025
9124151
chore(main): release openfeature-go-feature-flag-provider 0.1.6 (#65)
openfeaturebot Nov 11, 2025
0d347da
feat: implemented-flagsmith-provider
Zaimwa9 Nov 18, 2025
133fe3a
feat: improved-flag-not-found-handlings
Zaimwa9 Nov 18, 2025
7011ead
feat: docs
Zaimwa9 Nov 18, 2025
784932f
feat: removed-cached-reason-from-doc-as-not-used
Zaimwa9 Nov 18, 2025
638f513
feat: fixed-number-parsing
Zaimwa9 Nov 21, 2025
ce92eb5
chore: update copyright to OpenFeature Maintainers (#67)
jonathannorris Nov 18, 2025
6f193e9
feat(GoFeatureFlag): unix client (#66)
augustinasrce Nov 19, 2025
60f3cc0
chore(main): release openfeature-go-feature-flag-provider 0.1.7 (#69)
openfeaturebot Nov 19, 2025
5d9da39
Update providers/openfeature-flagsmith-provider/README.md
Zaimwa9 Nov 21, 2025
1d43025
feat: included-message-in-error
Zaimwa9 Nov 21, 2025
d776bd4
feat: added-ruby-maintainers-as-codeowners (#71)
Zaimwa9 Nov 25, 2025
421584f
chore: add component owners
toddbaert Nov 26, 2025
b6e085f
feat: added-as-component-owner
Zaimwa9 Nov 26, 2025
5ecf073
feat: implemented-flagsmith-provider
Zaimwa9 Nov 18, 2025
a8e23cf
feat: improved-flag-not-found-handlings
Zaimwa9 Nov 18, 2025
427a805
feat: fixed-conflicts
Zaimwa9 Nov 26, 2025
783f62d
feat: split-evaluate-into-boolean-and-value-methods
Zaimwa9 Nov 26, 2025
b5f4e15
feat: moved-require-flagsmith-to-top-level
Zaimwa9 Nov 26, 2025
771e552
feat: specify-targeting-key-requirement-in-readme
Zaimwa9 Nov 27, 2025
1356c88
feat: handle-nil-evaluation-context-case
Zaimwa9 Nov 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/component_owners.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ components:
- thomaspoignant
providers/openfeature-meta_provider:
- maxveldink
providers/openfeature-flagsmith-provider:
- zaimwa9

ignored-authors:
- renovate-bot
3 changes: 2 additions & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"providers/openfeature-flagd-provider": "0.1.2",
"providers/openfeature-flipt-provider": "0.0.2",
"providers/openfeature-meta_provider": "0.0.5",
"providers/openfeature-go-feature-flag-provider": "0.1.7"
"providers/openfeature-go-feature-flag-provider": "0.1.7",
"providers/openfeature-flagsmith-provider": "0.1.0"
}
316 changes: 316 additions & 0 deletions providers/openfeature-flagsmith-provider/.context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
# Flagsmith OpenFeature Provider - Context

**Started:** 2025-11-17
**Full Design Doc:** `./FLAGSMITH_PROVIDER_DESIGN.md`

---

## Quick Facts

**What we're building:** OpenFeature provider for Flagsmith Ruby SDK
**Target Flagsmith version:** Upcoming release (TBD)

---

## Key Design Decisions ✅

| Decision | Choice | Rationale |
|----------|--------|-----------|
| Evaluation mode | Remote (default) | Simpler, no polling. Local available via config |
| No targeting_key | Fall back to environment flags | `get_environment_flags()` vs `get_identity_flags()` |
| Analytics | Opt-in (disabled default) | Privacy-first approach |
| Default values | Use OpenFeature's default_value | Match other providers, no custom handler |
| Configuration | Options object pattern | Clear validation, like GO Feature Flag |

---

## Architecture Map

### Directory Structure
```
openfeature-flagsmith-provider/
├── lib/openfeature/flagsmith/
│ ├── provider.rb # Main provider class
│ ├── configuration.rb # Options/config with validation
│ ├── error/errors.rb # Custom exceptions → ErrorCode mapping
│ └── version.rb # VERSION constant
├── spec/ # RSpec tests with WebMock
├── openfeature-flagsmith-provider.gemspec
└── README.md
```

### Key Classes

**Configuration** (lib/openfeature/flagsmith/configuration.rb):
```ruby
OpenFeature::Flagsmith::Configuration.new(
environment_key: "required",
api_url: "https://edge.api.flagsmith.com/api/v1/",
enable_local_evaluation: false,
request_timeout_seconds: 10,
enable_analytics: false
)
```

**Provider** (lib/openfeature/flagsmith/provider.rb):
```ruby
class Provider
attr_reader :metadata

# Required methods:
def fetch_boolean_value(flag_key:, default_value:, evaluation_context:)
def fetch_string_value(flag_key:, default_value:, evaluation_context:)
def fetch_number_value(flag_key:, default_value:, evaluation_context:)
def fetch_integer_value(flag_key:, default_value:, evaluation_context:)
def fetch_float_value(flag_key:, default_value:, evaluation_context:)
def fetch_object_value(flag_key:, default_value:, evaluation_context:)
end
```

---

## Critical Mappings

### 1. Context → Identity/Traits
```ruby
# OpenFeature → Flagsmith
evaluation_context.targeting_key → identifier (for get_identity_flags)
evaluation_context.fields → traits (as keyword args)

# If no targeting_key → use get_environment_flags()
```

### 2. Flag Types
| Flagsmith | OpenFeature Method | Implementation |
|-----------|-------------------|----------------|
| `is_feature_enabled()` → Boolean | `fetch_boolean_value` | Direct mapping |
| `get_feature_value()` → String | `fetch_string_value` | Direct return |
| `get_feature_value()` → Number | `fetch_number_value` | Parse & validate type |
| `get_feature_value()` → JSON | `fetch_object_value` | `JSON.parse()` |

### 3. Reason Mapping
| Situation | OpenFeature Reason |
|-----------|-------------------|
| Flag evaluated with identity | `TARGETING_MATCH` |
| Flag evaluated at environment level | `STATIC` |
| Flag not found | `DEFAULT` |
| Flag exists but disabled | `DISABLED` |
| Error occurred | `ERROR` |

### 4. Error Handling
```ruby
# Pattern: Always return ResolutionDetails with default_value on error
SDK::Provider::ResolutionDetails.new(
value: default_value,
error_code: <appropriate ErrorCode>,
error_message: "description",
reason: SDK::Provider::Reason::ERROR
)
```

**ErrorCode mapping:**
- Flag not found → `FLAG_NOT_FOUND`
- Wrong type → `TYPE_MISMATCH`
- Network error → `PROVIDER_NOT_READY` or `GENERAL`
- Invalid context → `INVALID_CONTEXT`

---

## Flagsmith SDK API Reference

### Initialization
```ruby
require "flagsmith"
client = Flagsmith::Client.new(
environment_key: "key",
api_url: "url",
enable_local_evaluation: false,
request_timeout_seconds: 10,
enable_analytics: false
)
```

### Evaluation Methods
```ruby
# Environment-level (no user)
flags = client.get_environment_flags()
flags.is_feature_enabled('flag_key') # → Boolean
flags.get_feature_value('flag_key') # → String/value

# Identity-specific (with user context)
flags = client.get_identity_flags('user@example.com', trait1: 'value', age: 30)
flags.is_feature_enabled('flag_key')
flags.get_feature_value('flag_key')
```

---

## OpenFeature Provider Contract

### Required Interface
```ruby
# Metadata
attr_reader :metadata # ProviderMetadata.new(name: "Flagsmith Provider")

# Lifecycle (optional)
def init # Optional initialization
def shutdown # Optional cleanup

# Evaluation methods (required)
def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
def fetch_number_value(flag_key:, default_value:, evaluation_context: nil)
def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil)
def fetch_float_value(flag_key:, default_value:, evaluation_context: nil)
def fetch_object_value(flag_key:, default_value:, evaluation_context: nil)
```

### Return Type
```ruby
OpenFeature::SDK::Provider::ResolutionDetails.new(
value: <evaluated_value>, # Required
reason: <Reason constant>, # Required
variant: "variant_key", # Optional
flag_metadata: {key: "value"}, # Optional
error_code: <ErrorCode constant>, # If error
error_message: "details" # If error
)
```

---

## Code Patterns to Follow

### Type Validation Pattern
```ruby
def evaluate(flag_key:, default_value:, allowed_classes:, evaluation_context:)
# ... get value from Flagsmith ...

unless allowed_classes.include?(value.class)
return SDK::Provider::ResolutionDetails.new(
value: default_value,
error_code: SDK::Provider::ErrorCode::TYPE_MISMATCH,
error_message: "Expected #{allowed_classes}, got #{value.class}",
reason: SDK::Provider::Reason::ERROR
)
end

# return success ResolutionDetails
end
```

### Error Rescue Pattern
```ruby
begin
# Flagsmith evaluation
rescue SomeError => e
return SDK::Provider::ResolutionDetails.new(
value: default_value,
error_code: SDK::Provider::ErrorCode::GENERAL,
error_message: e.message,
reason: SDK::Provider::Reason::ERROR
)
end
```

---

## Dependencies

### Runtime
```ruby
spec.add_runtime_dependency "openfeature-sdk", "~> 0.3.1"
spec.add_runtime_dependency "flagsmith", "~> <VERSION_TBD>"
```

### Development
```ruby
spec.add_development_dependency "rake", "~> 13.0"
spec.add_development_dependency "rspec", "~> 3.12.0"
spec.add_development_dependency "webmock", "~> 3.0" # Mock Flagsmith calls
spec.add_development_dependency "standard"
spec.add_development_dependency "rubocop"
spec.add_development_dependency "simplecov"
```

---

## Testing Strategy

### Mock Flagsmith Responses
```ruby
# Use WebMock to stub Flagsmith API calls
# OR stub Flagsmith::Client methods directly

allow(flagsmith_client).to receive(:get_identity_flags).and_return(mock_flags)
allow(mock_flags).to receive(:is_feature_enabled).and_return(true)
allow(mock_flags).to receive(:get_feature_value).and_return("value")
```

### Test Coverage Areas
- ✅ Metadata verification
- ✅ Configuration validation
- ✅ Each flag type evaluation (boolean, string, number, integer, float, object)
- ✅ Type mismatches
- ✅ Missing flags (return default)
- ✅ Error handling
- ✅ Context mapping (with/without targeting_key)
- ✅ Environment vs Identity evaluation

---

## Implementation Checklist

- [ ] Directory structure created
- [ ] Gemspec with dependencies
- [ ] Configuration class with validation
- [ ] Provider skeleton with metadata
- [ ] Context → Identity/Traits mapping helper
- [ ] `fetch_boolean_value` implementation
- [ ] `fetch_string_value` implementation
- [ ] `fetch_number_value` implementation
- [ ] `fetch_integer_value` implementation
- [ ] `fetch_float_value` implementation
- [ ] `fetch_object_value` implementation
- [ ] Error handling and custom exceptions
- [ ] Type validation
- [ ] RSpec test suite
- [ ] README with examples
- [ ] Release configuration

---

## Open Items / Notes

- **Flagsmith version**: Waiting for upcoming release - update gemspec when available
- **Variant support**: Flagsmith doesn't have explicit variants - TBD how to handle
- **Flag metadata**: Flagsmith has limited metadata - may need to extract from traits/response

---

## Quick Commands

```bash
# Run tests
bundle exec rspec

# Run linter
bundle exec rubocop

# Install locally for testing
gem build openfeature-flagsmith-provider.gemspec
gem install ./openfeature-flagsmith-provider-<version>.gem

# Test with real Flagsmith
# (add example script in spec/manual_test.rb)
```

---

## References

- Full design doc: `../FLAGSMITH_PROVIDER_DESIGN.md`
- Flagsmith docs: https://docs.flagsmith.com/clients/server-side
- OpenFeature spec: https://openfeature.dev/specification/
- GO Feature Flag provider: `../openfeature-go-feature-flag-provider/`
- flagd provider: `../openfeature-flagd-provider/`
11 changes: 11 additions & 0 deletions providers/openfeature-flagsmith-provider/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/

# rspec failure tracking
.rspec_status
4 changes: 4 additions & 0 deletions providers/openfeature-flagsmith-provider/.rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-I lib
--format documentation
--color
--require spec_helper
5 changes: 5 additions & 0 deletions providers/openfeature-flagsmith-provider/.rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
inherit_from: ../../shared_config/.rubocop.yml

inherit_mode:
merge:
- Exclude
1 change: 1 addition & 0 deletions providers/openfeature-flagsmith-provider/.ruby-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.1.4
15 changes: 15 additions & 0 deletions providers/openfeature-flagsmith-provider/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- Initial implementation of Flagsmith OpenFeature provider
- Support for all OpenFeature flag types (boolean, string, number, integer, float, object)
- Remote and local evaluation modes
- Environment-level and identity-specific flag evaluation
- Comprehensive error handling and type validation
Loading