launchdarkly-openfeature-server-sdk 0.2.0 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cde8b4698ae233e35dbfb0374fe5d01f86cad889a5dd7cdb5d501bf460a08e59
4
- data.tar.gz: 800e91c816324cd7fdd1c00476fee09e1b70eb03cfaa7d1064e748c4584e7139
3
+ metadata.gz: a049cefbbcbd5e85ec11ecb64d78c6e2a0c838bf702f266dad3e0aed9895b8d0
4
+ data.tar.gz: a3ff14970d3b4cfcd990e0d902c6c080bfbec96965fbb306800c93a405654a32
5
5
  SHA512:
6
- metadata.gz: bc87f538233fc26f07dfe75f064186d5780cf4bc75ddbc176b433ddd926da733d1d2369f9801f627370fd7c45e3427742bfe6a1fff1be5b47de6a6f13e15af95
7
- data.tar.gz: b4ff462356adf44223d27c2aeabd6ccc60516390d137b87b5b9cbb726ca8790cb1ca9616db93854b715843685dcecec4c44ca9f2f4f1aa81698dd3eab4633886
6
+ metadata.gz: d6faf775bd94dfcf35f9b5542b793e61d59b06dd74d7217ca1eaeac06cae58f2c7acf287dd352bd344535a8b82176177b7edea7478440dcdf306211c75b1b0fd
7
+ data.tar.gz: 583e9d48d1ed7ac46cf22bed6f8ecb7f20d369ceedd0d2bbb3163878f2695dfc251a919218fc2e8b472a14b4b75a6819360789f0d6b69b4453d219f4f4f90ddb
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.2.0"
2
+ ".": "0.3.0"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.0](https://github.com/launchdarkly/openfeature-ruby-server/compare/0.2.0...0.3.0) (2026-08-25)
4
+
5
+
6
+ ### Features
7
+
8
+ * Emit provider events from LaunchDarkly client status ([#29](https://github.com/launchdarkly/openfeature-ruby-server/issues/29)) ([1b53b0c](https://github.com/launchdarkly/openfeature-ruby-server/commit/1b53b0c08165efdc29e2504e56b0be651bd3f6e7))
9
+ * Populate OpenFeature flag metadata from the evaluation reason ([#31](https://github.com/launchdarkly/openfeature-ruby-server/issues/31)) ([e6ef78a](https://github.com/launchdarkly/openfeature-ruby-server/commit/e6ef78aafc480de7b61b5ed00d41c6adafe532f3))
10
+ * Report provider name and version to LaunchDarkly ([#30](https://github.com/launchdarkly/openfeature-ruby-server/issues/30)) ([c5dd94d](https://github.com/launchdarkly/openfeature-ruby-server/commit/c5dd94dd09a70f7d10f4dcd5a907f9e1db43a046))
11
+ * Support OpenFeature tracking ([#27](https://github.com/launchdarkly/openfeature-ruby-server/issues/27)) ([709f52d](https://github.com/launchdarkly/openfeature-ruby-server/commit/709f52d73ffaf5144f45eec92101abc77f4a93f5))
12
+ * Support provider initialization and shutdown ([#28](https://github.com/launchdarkly/openfeature-ruby-server/issues/28)) ([27df803](https://github.com/launchdarkly/openfeature-ruby-server/commit/27df80355abac7e3ed18a3549da5f178f1a47234))
13
+
3
14
  ## [0.2.0](https://github.com/launchdarkly/openfeature-ruby-server/compare/0.1.0...0.2.0) (2026-04-02)
4
15
 
5
16
 
data/PROVENANCE.md CHANGED
@@ -9,7 +9,7 @@ To verify build provenance attestations, we recommend using the [GitHub CLI `att
9
9
  <!-- x-release-please-start-version -->
10
10
  ```
11
11
  # Set the version of the SDK to verify
12
- SDK_VERSION=0.2.0
12
+ SDK_VERSION=0.3.0
13
13
  ```
14
14
  <!-- x-release-please-end -->
15
15
 
data/README.md CHANGED
@@ -26,6 +26,27 @@ This provider is designed primarily for use in multi-user systems such as web se
26
26
 
27
27
  This version of the LaunchDarkly provider works with Ruby 3.4 and above.
28
28
 
29
+ ## Feature matrix
30
+
31
+ This matrix mirrors the [feature matrix of the OpenFeature SDK for Ruby](https://github.com/open-feature/ruby-sdk#-features) and describes what this provider supports. Rows which are not supported state whether the limitation comes from the OpenFeature Ruby SDK or from the provider.
32
+
33
+ | Status | Feature | Notes |
34
+ |--------|---------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
35
+ | ✅ | Providers | Evaluates boolean, string, integer, float, number, and object flags through the LaunchDarkly Ruby SDK. |
36
+ | ✅ | Targeting | The `EvaluationContext` is converted to a LaunchDarkly single or multi-context. See [OpenFeature Specific Considerations](#openfeature-specific-considerations). |
37
+ | ✅ | Hooks | Hooks are registered on the OpenFeature API and client; the provider requires no additional support and its results are visible to hooks, including [flag metadata](#flag-metadata). |
38
+ | ✅ | Logging | The provider logs through the logger of the `LaunchDarkly::Config` it is given. |
39
+ | ✅ | Domains | Domains bind clients to providers in the OpenFeature SDK; a separate provider instance may be registered per domain. |
40
+ | ✅ | Eventing | LaunchDarkly data source status changes are emitted as `PROVIDER_READY`, `PROVIDER_STALE`, and `PROVIDER_ERROR`. Flag changes are emitted as `PROVIDER_CONFIGURATION_CHANGED` with the changed flag key. |
41
+ | ✅ | Initialization | `init` reports whether the LaunchDarkly client became ready within the configured wait time; a failure results in the `ERROR` state so that cached or fallback flag data is still evaluated. |
42
+ | ✅ | Shutdown | `shutdown` closes the LaunchDarkly client. A closed client cannot be restarted, so a new provider instance is required afterward. |
43
+ | ✅ | Tracking | `track` sends a LaunchDarkly custom event for the evaluation context, with the tracking event value and remaining details attached. |
44
+ | ✅ | Transaction Context Propagation | Provided by the OpenFeature SDK, which merges the transaction context into the evaluation context before the provider is called; no provider support is required. |
45
+ | ✅ | Extending | This provider is itself an extension of the OpenFeature SDK. The underlying LaunchDarkly client is available through `provider.client` for functionality with no OpenFeature equivalent. |
46
+ | ✅ | Flag metadata | LaunchDarkly evaluation reason details are returned as OpenFeature flag metadata. See [Flag Metadata](#flag-metadata). |
47
+
48
+ <sub>Supported: ✅ | Partially supported: ⚠️ | Not supported: ❌</sub>
49
+
29
50
  ## Getting started
30
51
 
31
52
  ### Requisites
@@ -147,6 +168,24 @@ attributes = {
147
168
  context = EvaluationContext(**attributes)
148
169
  ```
149
170
 
171
+ ### Flag Metadata
172
+
173
+ Evaluations include flag metadata for the LaunchDarkly specific parts of the evaluation result which have no OpenFeature equivalent. Each entry is absent when it does not apply to the evaluation.
174
+
175
+ | Key | Type | Description |
176
+ |---------------------|---------|--------------------------------------------------------------------|
177
+ | `variationIndex` | integer | The index of the returned variation. Absent for default values. |
178
+ | `inExperiment` | boolean | Present, and `true`, when the evaluation was part of an experiment. |
179
+ | `ruleIndex` | integer | The index of the rule that matched. |
180
+ | `ruleId` | string | The identifier of the rule that matched. |
181
+ | `prerequisiteKey` | string | The key of the prerequisite flag that failed. |
182
+ | `bigSegmentsStatus` | string | The status of the Big Segments query made during the evaluation. |
183
+
184
+ ```ruby
185
+ details = client.fetch_boolean_details(flag_key: "my-flag", default_value: false)
186
+ in_experiment = details.flag_metadata.fetch("inExperiment", false)
187
+ ```
188
+
150
189
  ## Learn more
151
190
 
152
191
  Check out our [documentation](http://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [complete reference guide for this SDK](https://docs.launchdarkly.com/sdk/server-side/ruby).
data/SECURITY.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Reporting and Fixing Security Issues
2
2
 
3
- Please report all security issues to the LaunchDarkly security team by submitting a bug bounty report to our [HackerOne program](https://hackerone.com/launchdarkly?type=team). LaunchDarkly will triage and address all valid security issues following the response targets defined in our program policy. Valid security issues may be eligible for a bounty.
3
+ **Do not open Issues or Pull Requests for security issues.**
4
+ This will make potential issues publicly visible before LaunchDarkly's Security Team can address them, which could lead to a compromise of the platform and negatively impact our customers.
4
5
 
5
- Please do not open issues or pull requests for security issues. This makes the problem immediately visible to everyone, including potentially malicious actors.
6
+ Security issues must be reported through our [Bug Bounty program](https://bugcrowd.com/engagements/launchdarkly-mbb-og), following the program policy, for triage and remediation by the LaunchDarkly Security Team. Valid security issues may be eligible for a bounty.
7
+
8
+ Please do not attempt to directly contact members of LaunchDarkly staff.
@@ -28,8 +28,8 @@ Gem::Specification.new do |spec|
28
28
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
29
  spec.require_paths = ["lib"]
30
30
 
31
- spec.add_runtime_dependency "launchdarkly-server-sdk", "~> 8.4"
32
- spec.add_runtime_dependency "openfeature-sdk", "~> 0.6.0"
31
+ spec.add_runtime_dependency "launchdarkly-server-sdk", "~> 8.15"
32
+ spec.add_runtime_dependency "openfeature-sdk", "~> 0.6.1"
33
33
 
34
34
  # For more information and examples about making a new gem, check out our
35
35
  # guide at: https://bundler.io/guides/creating_gem.html
@@ -7,6 +7,16 @@ module LaunchDarkly
7
7
  module OpenFeature
8
8
  module Impl
9
9
  class ResolutionDetailsConverter
10
+ VARIATION_INDEX_KEY = 'variationIndex'
11
+ IN_EXPERIMENT_KEY = 'inExperiment'
12
+ RULE_INDEX_KEY = 'ruleIndex'
13
+ RULE_ID_KEY = 'ruleId'
14
+ PREREQUISITE_KEY_KEY = 'prerequisiteKey'
15
+ BIG_SEGMENTS_STATUS_KEY = 'bigSegmentsStatus'
16
+
17
+ private_constant :VARIATION_INDEX_KEY, :IN_EXPERIMENT_KEY, :RULE_INDEX_KEY, :RULE_ID_KEY,
18
+ :PREREQUISITE_KEY_KEY, :BIG_SEGMENTS_STATUS_KEY
19
+
10
20
  #
11
21
  # @param detail [LaunchDarkly::EvaluationDetail]
12
22
  #
@@ -35,10 +45,30 @@ module LaunchDarkly
35
45
  error_code: openfeature_error_code,
36
46
  error_message: nil,
37
47
  reason: openfeature_reason,
38
- variant: openfeature_variant
48
+ variant: openfeature_variant,
49
+ flag_metadata: flag_metadata(reason, is_default ? nil : variation_index)
39
50
  )
40
51
  end
41
52
 
53
+ #
54
+ # @param reason [LaunchDarkly::EvaluationReason]
55
+ # @param variation_index [Integer, nil]
56
+ #
57
+ # @return [Hash]
58
+ #
59
+ private def flag_metadata(reason, variation_index)
60
+ metadata = {}
61
+
62
+ metadata[VARIATION_INDEX_KEY] = variation_index unless variation_index.nil?
63
+ metadata[IN_EXPERIMENT_KEY] = true if reason.in_experiment
64
+ metadata[RULE_INDEX_KEY] = reason.rule_index unless reason.rule_index.nil?
65
+ metadata[RULE_ID_KEY] = reason.rule_id unless reason.rule_id.nil?
66
+ metadata[PREREQUISITE_KEY_KEY] = reason.prerequisite_key unless reason.prerequisite_key.nil?
67
+ metadata[BIG_SEGMENTS_STATUS_KEY] = reason.big_segments_status.to_s unless reason.big_segments_status.nil?
68
+
69
+ metadata
70
+ end
71
+
42
72
  #
43
73
  # @param kind [Symbol]
44
74
  #
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ldclient-rb'
4
+ require 'open_feature/sdk'
5
+
6
+ module LaunchDarkly
7
+ module OpenFeature
8
+ module Impl
9
+ #
10
+ # Translates LaunchDarkly data source status changes into OpenFeature provider events.
11
+ #
12
+ class DataSourceStatusListener
13
+ #
14
+ # @param provider [LaunchDarkly::OpenFeature::Provider]
15
+ #
16
+ def initialize(provider)
17
+ @provider = provider
18
+ end
19
+
20
+ #
21
+ # @param status [LaunchDarkly::Interfaces::DataSource::Status]
22
+ #
23
+ # @return [void]
24
+ #
25
+ def update(status)
26
+ case status.state
27
+ when ::LaunchDarkly::Interfaces::DataSource::Status::VALID
28
+ @provider.emit_event(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY)
29
+ when ::LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED
30
+ @provider.emit_event(
31
+ ::OpenFeature::SDK::ProviderEvent::PROVIDER_STALE,
32
+ message: message(status, "the data source has been interrupted")
33
+ )
34
+ when ::LaunchDarkly::Interfaces::DataSource::Status::OFF
35
+ @provider.emit_event(
36
+ ::OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR,
37
+ error_code: ::OpenFeature::SDK::Provider::ErrorCode::GENERAL,
38
+ message: message(status, "the data source has been permanently shut down")
39
+ )
40
+ end
41
+ end
42
+
43
+ #
44
+ # @param status [LaunchDarkly::Interfaces::DataSource::Status]
45
+ # @param fallback [String]
46
+ #
47
+ # @return [String]
48
+ #
49
+ private def message(status, fallback)
50
+ error = status.last_error
51
+ return fallback if error.nil?
52
+
53
+ "#{fallback}: #{error.kind} #{error.status_code} #{error.message}".strip
54
+ end
55
+ end
56
+
57
+ #
58
+ # Translates LaunchDarkly flag change events into OpenFeature configuration changed events.
59
+ #
60
+ class FlagChangeListener
61
+ #
62
+ # @param provider [LaunchDarkly::OpenFeature::Provider]
63
+ #
64
+ def initialize(provider)
65
+ @provider = provider
66
+ end
67
+
68
+ #
69
+ # @param flag_change [LaunchDarkly::Interfaces::FlagChange]
70
+ #
71
+ # @return [void]
72
+ #
73
+ def update(flag_change)
74
+ @provider.emit_event(
75
+ ::OpenFeature::SDK::ProviderEvent::PROVIDER_CONFIGURATION_CHANGED,
76
+ flags_changed: [flag_change.key]
77
+ )
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -6,6 +6,8 @@ require 'open_feature/sdk'
6
6
  module LaunchDarkly
7
7
  module OpenFeature
8
8
  class Provider
9
+ include ::OpenFeature::SDK::Provider::EventEmitter
10
+
9
11
  #
10
12
  # Retrieve metadata information describing this provider.
11
13
  #
@@ -25,18 +27,50 @@ module LaunchDarkly
25
27
  NUMERIC_TYPES = %i[integer float number].freeze
26
28
  private_constant :NUMERIC_TYPES
27
29
 
30
+ WRAPPER_NAME = "open-feature-ruby-server"
31
+ private_constant :WRAPPER_NAME
32
+
28
33
  #
29
34
  # @param sdk_key [String]
30
35
  # @param config [LaunchDarkly::Config]
31
36
  # @param wait_for_seconds [Float]
32
37
  #
33
38
  def initialize(sdk_key, config = LaunchDarkly::Config.default, wait_for_seconds = 5)
34
- @client = LaunchDarkly::LDClient.new(sdk_key, config, wait_for_seconds)
39
+ @client = LaunchDarkly::LDClient.new(sdk_key, config.with_wrapper_information(WRAPPER_NAME, VERSION), wait_for_seconds)
35
40
 
41
+ @logger = config.logger
36
42
  @context_converter = Impl::EvaluationContextConverter.new(config.logger)
37
43
  @details_converter = Impl::ResolutionDetailsConverter.new
38
44
 
39
45
  @metadata = ::OpenFeature::SDK::Provider::ProviderMetadata.new(name: "launchdarkly-openfeature-server").freeze
46
+
47
+ @client.data_source_status_provider.add_listener(Impl::DataSourceStatusListener.new(self))
48
+ @client.flag_tracker.add_listener(Impl::FlagChangeListener.new(self))
49
+ end
50
+
51
+ #
52
+ # Called by the OpenFeature SDK when this provider is set. The LaunchDarkly client has already been given the
53
+ # opportunity to initialize, so this only reports whether that succeeded.
54
+ #
55
+ # @param _evaluation_context [::OpenFeature::SDK::EvaluationContext, nil]
56
+ #
57
+ # @return [void]
58
+ #
59
+ def init(_evaluation_context = nil)
60
+ return if @client.initialized?
61
+
62
+ state = @client.data_source_status_provider.status.state
63
+ raise "the LaunchDarkly client was unable to initialize; the data source state is #{state}"
64
+ end
65
+
66
+ #
67
+ # Called by the OpenFeature SDK when this provider is replaced or the SDK is shut down. The LaunchDarkly client
68
+ # cannot be restarted, so a new provider instance is required afterward.
69
+ #
70
+ # @return [void]
71
+ #
72
+ def shutdown
73
+ @client.close
40
74
  end
41
75
 
42
76
  def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
@@ -63,6 +97,32 @@ module LaunchDarkly
63
97
  resolve_value(:object, flag_key, default_value, evaluation_context)
64
98
  end
65
99
 
100
+ #
101
+ # Track a custom event, which can be used as a metric for an experiment.
102
+ #
103
+ # @param tracking_event_name [String]
104
+ # @param evaluation_context [::OpenFeature::SDK::EvaluationContext, nil]
105
+ # @param tracking_event_details [::OpenFeature::SDK::TrackingEventDetails, nil]
106
+ #
107
+ # @return [void]
108
+ #
109
+ def track(tracking_event_name, evaluation_context: nil, tracking_event_details: nil)
110
+ if evaluation_context.nil?
111
+ @logger.warn(
112
+ "The track method was called without an evaluation context. No event will be sent to LaunchDarkly, " \
113
+ "because a context is required to associate the event with."
114
+ )
115
+ return
116
+ end
117
+
118
+ ld_context = @context_converter.to_ld_context(evaluation_context)
119
+
120
+ data = tracking_event_details&.fields
121
+ data = nil if data.nil? || data.empty?
122
+
123
+ @client.track(tracking_event_name, ld_context, data, tracking_event_details&.value)
124
+ end
125
+
66
126
  #
67
127
  # @param flag_type [Symbol]
68
128
  # @param flag_key [String]
@@ -2,6 +2,6 @@
2
2
 
3
3
  module LaunchDarkly
4
4
  module OpenFeature
5
- VERSION = "0.2.0" # x-release-please-version
5
+ VERSION = "0.3.0" # x-release-please-version
6
6
  end
7
7
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "ldclient-openfeature/impl/context_converter"
4
4
  require_relative "ldclient-openfeature/impl/details_converter"
5
+ require_relative "ldclient-openfeature/impl/event_listeners"
5
6
  require_relative "ldclient-openfeature/provider"
6
7
  require_relative "ldclient-openfeature/version"
7
8
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: launchdarkly-openfeature-server-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LaunchDarkly
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '8.4'
18
+ version: '8.15'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '8.4'
25
+ version: '8.15'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: openfeature-sdk
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: 0.6.0
32
+ version: 0.6.1
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 0.6.0
39
+ version: 0.6.1
40
40
  description: A LaunchDarkly provider for use with the OpenFeature SDK
41
41
  email:
42
42
  - team@launchdarkly.com
@@ -63,6 +63,7 @@ files:
63
63
  - lib/ldclient-openfeature/impl.rb
64
64
  - lib/ldclient-openfeature/impl/context_converter.rb
65
65
  - lib/ldclient-openfeature/impl/details_converter.rb
66
+ - lib/ldclient-openfeature/impl/event_listeners.rb
66
67
  - lib/ldclient-openfeature/provider.rb
67
68
  - lib/ldclient-openfeature/version.rb
68
69
  - release-please-config.json