launchdarkly-openfeature-server-sdk 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1120d07e2dfc8caa553538ee9e81a28c0d8e49086251f5b267f6b7a81713beb8
4
- data.tar.gz: aae128996f603273d9ca9eb4ea1dba80a5b14f67f027cf20840b87abb2973aca
3
+ metadata.gz: 1246acd88a6e54ba166c00106b8262d5fdb0bd7a8e0ae68b5bf066eae7d883c7
4
+ data.tar.gz: 62e9efe0c482e5bb4650d5fe9079fe702cb156ecbd5d1fa8b7932a483efcb0a9
5
5
  SHA512:
6
- metadata.gz: 7fe4363d6defdd32fa8ee9c1cd829d59d8945f7af4a2bd43532da96580d50a2d6068a2d116f954c418e89ba4acaef5fc793f44d84ca0374f98fd139941238fd1
7
- data.tar.gz: 4e22e388a089408a98fab7ffffb299df6669749a5a2e6e0fc5db4e4b8c5f079ff7612709ca3d525df6f5b772a27357ff737287c77433a07da84275c8fe9cb25e
6
+ metadata.gz: 1c403999bb9c8a6bb5a8428ad40ab226174fa5fbee201331e2ca412d84bf1626e73af7fa53f4b4edf6a8e31ee78789e5403ca2749a441520e04c0b752e051936
7
+ data.tar.gz: ee6272c867a1a1c6f2f1e490b7873e63da1211197035bdd44f4e33013ebea898e26e6489ebfb89ad5e947f90ba222e8f0a6b0842cafc34e64e494953ba132fcc
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.0.0"
2
+ ".": "0.1.0"
3
3
  }
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2024-08-05)
4
+
5
+
6
+ ### Features
7
+
8
+ * Add initial implementation of provider ([#1](https://github.com/launchdarkly/openfeature-ruby-server/issues/1)) ([7550e14](https://github.com/launchdarkly/openfeature-ruby-server/commit/7550e14d94f6a70b0087ba9801dfbfe93c1b374d))
9
+
10
+ ## Changelog
data/CODEOWNERS ADDED
@@ -0,0 +1,2 @@
1
+ # Repository Maintainers
2
+ * @launchdarkly/team-sdk-ruby
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,58 @@
1
+ # Contributing to the LaunchDarkly OpenFeature provider for the Server-side SDK for Ruby
2
+
3
+ LaunchDarkly has published an [SDK contributor's guide](https://docs.launchdarkly.com/sdk/concepts/contributors-guide) that provides a detailed explanation of how our SDKs work. See below for additional information on how to contribute to this provider.
4
+
5
+ ## Submitting bug reports and feature requests
6
+
7
+ The LaunchDarkly SDK team monitors the [issue tracker](https://github.com/launchdarkly/openfeature-ruby-server-sdk/issues) in the provider repository. Bug reports and feature requests specific to this provider should be filed in this issue tracker. The SDK team will respond to all newly filed issues within two business days.
8
+
9
+ ## Submitting pull requests
10
+
11
+ We encourage pull requests and other contributions from the community. Before submitting pull requests, ensure that all temporary or unintended code is removed. Don't worry about adding reviewers to the pull request; the LaunchDarkly SDK team will add themselves. The SDK team will acknowledge all pull requests within two business days.
12
+
13
+ ## Build instructions
14
+
15
+ ### Prerequisites
16
+
17
+ This SDK is built with [Bundler](https://bundler.io/). To install Bundler, run `gem install bundler`. You might need `sudo` to execute the command successfully.
18
+
19
+ To install the runtime dependencies:
20
+
21
+ ```
22
+ bundle install
23
+ ```
24
+
25
+ ### Testing
26
+
27
+ To run all unit tests:
28
+
29
+ ```
30
+ bundle exec rspec spec
31
+ ```
32
+
33
+ ### Building documentation
34
+
35
+ Documentation is built automatically with YARD for each release. To build the documentation locally:
36
+
37
+ ```
38
+ cd docs
39
+ make
40
+ ```
41
+
42
+ The output will appear in `docs/build/html`.
43
+
44
+ ## Code organization
45
+
46
+ The SDK's namespacing convention is as follows:
47
+
48
+ * `LaunchDarkly::OpenFeature`: This namespace contains the most commonly used classes and methods in the SDK, such as `Provider`.
49
+
50
+ A special case is the namespace `LaunchDarkly::OpenFeature::Impl`, and any namespaces within it. Everything under `Impl` is considered a private implementation detail: all files there are excluded from the generated documentation, and are considered subject to change at any time and not supported for direct use by application developers. We do this because Ruby's scope/visibility system is somewhat limited compared to other languages: a method can be `private` or `protected` within a class, but there is no way to make it visible to other classes in the SDK yet invisible to code outside of the SDK, and there is similarly no way to hide a class.
51
+
52
+ So, if there is a class whose existence is entirely an implementation detail, it should be in `Impl`. Similarly, classes that are _not_ in `Impl` must not expose any public members that are not meant to be part of the supported public API. This is important because of our guarantee of backward compatibility for all public APIs within a major version: we want to be able to change our implementation details to suit the needs of the code, without worrying about breaking a customer's code. Due to how the language works, we can't actually prevent an application developer from referencing those classes in their code, but this convention makes it clear that such use is discouraged and unsupported.
53
+
54
+ ## Documenting types and methods
55
+
56
+ All classes and public methods outside of `LaunchDarkly::OpenFeature::Impl` should have documentation comments. These are used to build the API documentation that is published at https://launchdarkly.github.io/openfeature-ruby-server-sdk/ and https://www.rubydoc.info/gems/launchdarkly-openfeature-server-sdk. The documentation generator is YARD; see https://yardoc.org/ for the comment format it uses.
57
+
58
+ Please try to make the style and terminology in documentation comments consistent with other documentation comments in the SDK. Also, if a class or method is being added that has an equivalent in other SDKs, and if we have described it in a consistent away in those other SDKs, please reuse the text whenever possible (with adjustments for anything language-specific) rather than writing new text.
data/Gemfile CHANGED
@@ -2,9 +2,14 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- # Specify your gem's dependencies in openfeature-ruby-server.gemspec
5
+ # Specify your gem's dependencies in launchdarkly-openfeature-server-sdk.gemspec
6
6
  gemspec
7
7
 
8
8
  gem "rake", "~> 13.0"
9
9
 
10
10
  gem "rspec", "~> 3.0"
11
+
12
+ gem "rubocop", "~> 1.21"
13
+ gem "rubocop-performance", "~> 1.15"
14
+ gem "rubocop-rake", "~> 0.6"
15
+ gem "rubocop-rspec", "~> 2.27"
data/PROVENANCE.md ADDED
@@ -0,0 +1,43 @@
1
+ ## Verifying SDK build provenance with the SLSA framework
2
+
3
+ LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages.
4
+
5
+ As part of [SLSA requirements for level 3 compliance](https://slsa.dev/spec/v1.0/requirements), LaunchDarkly publishes provenance about our SDK package builds using [GitHub's generic SLSA3 provenance generator](https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/generic/README.md#generation-of-slsa3-provenance-for-arbitrary-projects) for distribution alongside our packages. These attestations are available for download from the GitHub release page for the release version under Assets > `multiple-provenance.intoto.jsonl`.
6
+
7
+ To verify SLSA provenance attestations, we recommend using [slsa-verifier](https://github.com/slsa-framework/slsa-verifier). Example usage for verifying SDK packages is included below:
8
+
9
+ <!-- x-release-please-start-version -->
10
+ ```
11
+ # Set the version of the SDK to verify
12
+ SDK_VERSION=0.1.0
13
+ ```
14
+ <!-- x-release-please-end -->
15
+
16
+ ```
17
+ # Download gem
18
+ $ gem fetch launchdarkly-openfeature-server-sdk -v $SDK_VERSION
19
+
20
+ # Download provenance from Github release
21
+ $ curl --location -O \
22
+ https://github.com/launchdarkly/openfeature-ruby-server/releases/download/${SDK_VERSION}/launchdarkly-openfeature-server-sdk-${SDK_VERSION}.gem.intoto.jsonl
23
+
24
+ # Run slsa-verifier to verify provenance against package artifacts
25
+ $ slsa-verifier verify-artifact \
26
+ --provenance-path launchdarkly-openfeature-server-sdk-${SDK_VERSION}.gem.intoto.jsonl \
27
+ --source-uri github.com/launchdarkly/openfeature-ruby-server \
28
+ launchdarkly-openfeature-server-sdk-${SDK_VERSION}.gem
29
+ ```
30
+
31
+ Below is a sample of expected output.
32
+
33
+ ```
34
+ Verified signature against tlog entry index 78214752 at URL: https://rekor.sigstore.dev/api/v1/log/entries/24296fb24b8ad77ab941c118ef7e0b2d656b962a0d670c6ac91cfa37d07b7b121ae560b00a978ecf
35
+ Verified build using builder "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v1.7.0" at commit f43b3ad834103fdc282652efbfe4963e8dfa737b
36
+ Verifying artifact launchdarkly-server-sdk-8.3.0.gem: PASSED
37
+
38
+ PASSED: Verified SLSA provenance
39
+ ```
40
+
41
+ Alternatively, to verify the provenance manually, the SLSA framework specifies [recommendations for verifying build artifacts](https://slsa.dev/spec/v1.0/verifying-artifacts) in their documentation.
42
+
43
+ **Note:** These instructions do not apply when building our SDKs from source.
data/SECURITY.md ADDED
@@ -0,0 +1,5 @@
1
+ # Reporting and Fixing Security Issues
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.
4
+
5
+ Please do not open issues or pull requests for security issues. This makes the problem immediately visible to everyone, including potentially malicious actors.
data/docs/Makefile ADDED
@@ -0,0 +1,26 @@
1
+ ifeq ($(LD_RELEASE_VERSION),)
2
+ TITLE=LaunchDarkly Ruby OTEL Library
3
+ else
4
+ TITLE=LaunchDarkly Ruby OTEL Library ($(LD_RELEASE_VERSION))
5
+ endif
6
+
7
+ .PHONY: dependencies html
8
+
9
+ html: dependencies
10
+ rm -rf ./build
11
+ cd .. && yard doc \
12
+ -o docs/build/html \
13
+ --title "$(TITLE)" \
14
+ --no-private \
15
+ --markup markdown \
16
+ --embed-mixins \
17
+ -r docs/index.md \
18
+ lib/*.rb \
19
+ lib/**/*.rb \
20
+ lib/**/**/*.rb \
21
+ lib/**/**/**/*.rb
22
+ rm -f build/html/frames.html
23
+
24
+ dependencies:
25
+ gem install --conservative yard
26
+ gem install --conservative redcarpet # provides Markdown formatting
data/docs/index.md ADDED
@@ -0,0 +1,7 @@
1
+ # LaunchDarkly OpenFeature Server-Side Provider
2
+
3
+ This generated API documentation lists all types and methods in the SDK.
4
+
5
+ The API documentation for the most recent release is hosted on [GitHub Pages](https://launchdarkly.github.io/openfeature-ruby-server). API documentation for current and past releases is hosted on [RubyDoc.info](https://www.rubydoc.info/gems/launchdarkly-openfeature-server-sdk).
6
+
7
+ Source code and readme: [GitHub](https://github.com/launchdarkly/openfeature-ruby-server)
@@ -27,4 +27,10 @@ Gem::Specification.new do |spec|
27
27
  spec.bindir = "exe"
28
28
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
29
  spec.require_paths = ["lib"]
30
+
31
+ spec.add_runtime_dependency "launchdarkly-server-sdk", "~> 8.4.0"
32
+ spec.add_runtime_dependency "openfeature-sdk", "~> 0.4.0"
33
+
34
+ # For more information and examples about making a new gem, check out our
35
+ # guide at: https://bundler.io/guides/creating_gem.html
30
36
  end
@@ -0,0 +1,147 @@
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
+ class EvaluationContextConverter
10
+ #
11
+ # @param logger [Logger]
12
+ #
13
+ def initialize(logger)
14
+ @logger = logger
15
+ end
16
+
17
+ #
18
+ # Create an LDContext from an EvaluationContext.
19
+ #
20
+ # A context will always be created, but the created context may be invalid. Log messages will be written to
21
+ # indicate the source of the problem.
22
+ #
23
+ # @param context [OpenFeature::SDK::EvaluationContext]
24
+ #
25
+ # @return [LaunchDarkly::LDContext]
26
+ #
27
+ def to_ld_context(context)
28
+ kind = context.field('kind')
29
+
30
+ return build_multi_context(context) if kind == "multi"
31
+
32
+ unless kind.nil? || kind.is_a?(String)
33
+ @logger.warn("'kind' was set to a non-string value; defaulting to user")
34
+ kind = 'user'
35
+ end
36
+
37
+ targeting_key = context.targeting_key
38
+ key = context.field('key')
39
+ targeting_key = get_targeting_key(targeting_key, key)
40
+
41
+ kind ||= 'user'
42
+ build_single_context(context.fields, kind, targeting_key)
43
+ end
44
+
45
+ #
46
+ # @param targeting_key [String, nil]
47
+ # @param key [any]
48
+ #
49
+ # @return [String]
50
+ #
51
+ private def get_targeting_key(targeting_key, key)
52
+ # The targeting key may be set but empty. So we want to treat an empty string as a not defined one. Later it
53
+ # could become null, so we will need to check that.
54
+ if !targeting_key.nil? && targeting_key != "" && key.is_a?(String)
55
+ # There is both a targeting key and a key. It will work, but probably is not intentional.
56
+ @logger.warn("EvaluationContext contained both a 'key' and 'targeting_key'.")
57
+ end
58
+
59
+ @logger.warn("A non-string 'key' attribute was provided.") unless key.nil? || key.is_a?(String)
60
+
61
+ targeting_key ||= key unless key.nil? || !key.is_a?(String)
62
+
63
+ if targeting_key.nil? || targeting_key == "" || !targeting_key.is_a?(String)
64
+ @logger.error("The EvaluationContext must contain either a 'targeting_key' or a 'key' and the type must be a string.")
65
+ end
66
+
67
+ targeting_key || ""
68
+ end
69
+
70
+ #
71
+ # @param context [OpenFeature::SDK::EvaluationContext]
72
+ #
73
+ # @return [LaunchDarkly::LDContext]
74
+ #
75
+ private def build_multi_context(context)
76
+ contexts = []
77
+
78
+ context.fields.each do |kind, attributes|
79
+ next if kind == 'kind'
80
+
81
+ unless attributes.is_a?(Hash)
82
+ @logger.warn("Top level attributes in a multi-kind context should be dictionaries")
83
+ next
84
+ end
85
+
86
+ key = attributes.fetch(:key, nil)
87
+ targeting_key = attributes.fetch(:targeting_key, nil)
88
+
89
+ next unless targeting_key.nil? || targeting_key.is_a?(String)
90
+
91
+ targeting_key = get_targeting_key(targeting_key, key)
92
+ single_context = build_single_context(attributes, kind, targeting_key)
93
+
94
+ contexts << single_context
95
+ end
96
+
97
+ LaunchDarkly::LDContext.create_multi(contexts)
98
+ end
99
+
100
+ #
101
+ # @param attributes [Hash]
102
+ # @param kind [String]
103
+ # @param key [String]
104
+ #
105
+ # @return [LaunchDarkly::LDContext]
106
+ #
107
+ private def build_single_context(attributes, kind, key)
108
+ context = { kind: kind, key: key }
109
+
110
+ attributes.each do |k, v|
111
+ next if %w[key targeting_key kind].include? k
112
+
113
+ if k == 'name' && v.is_a?(String)
114
+ context[:name] = v
115
+ elsif k == 'name'
116
+ @logger.error("The attribute 'name' must be a string")
117
+ next
118
+ elsif k == 'anonymous' && [true, false].include?(v)
119
+ context[:anonymous] = v
120
+ elsif k == 'anonymous'
121
+ @logger.error("The attribute 'anonymous' must be a boolean")
122
+ next
123
+ elsif k == 'privateAttributes' && v.is_a?(Array)
124
+ private_attributes = []
125
+ v.each do |private_attribute|
126
+ unless private_attribute.is_a?(String)
127
+ @logger.error("'privateAttributes' must be an array of only string values")
128
+ next
129
+ end
130
+
131
+ private_attributes << private_attribute
132
+ end
133
+
134
+ context[:_meta] = { privateAttributes: private_attributes } unless private_attributes.empty?
135
+ elsif k == 'privateAttributes'
136
+ @logger.error("The attribute 'privateAttributes' must be an array")
137
+ else
138
+ context[k.to_sym] = v
139
+ end
140
+ end
141
+
142
+ LaunchDarkly::LDContext.create(context)
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,86 @@
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
+ class ResolutionDetailsConverter
10
+ #
11
+ # @param detail [LaunchDarkly::EvaluationDetail]
12
+ #
13
+ # @return [OpenFeature::SDK::ResolutionDetails]
14
+ #
15
+ def to_resolution_details(detail)
16
+ value = detail.value
17
+ is_default = detail.variation_index.nil?
18
+ variation_index = detail.variation_index
19
+
20
+ reason = detail.reason
21
+ reason_kind = reason.kind
22
+
23
+ openfeature_reason = kind_to_reason(reason_kind)
24
+
25
+ openfeature_error_code = nil
26
+ if reason_kind == LaunchDarkly::EvaluationReason::ERROR
27
+ openfeature_error_code = error_kind_to_code(reason.error_kind)
28
+ end
29
+
30
+ openfeature_variant = nil
31
+ openfeature_variant = variation_index.to_s unless is_default
32
+
33
+ ::OpenFeature::SDK::Provider::ResolutionDetails.new(
34
+ value: value,
35
+ error_code: openfeature_error_code,
36
+ error_message: nil,
37
+ reason: openfeature_reason,
38
+ variant: openfeature_variant
39
+ )
40
+ end
41
+
42
+ #
43
+ # @param kind [Symbol]
44
+ #
45
+ # @return [String]
46
+ #
47
+ private def kind_to_reason(kind)
48
+ case kind
49
+ when LaunchDarkly::EvaluationReason::OFF
50
+ ::OpenFeature::SDK::Provider::Reason::DISABLED
51
+ when LaunchDarkly::EvaluationReason::TARGET_MATCH
52
+ ::OpenFeature::SDK::Provider::Reason::TARGETING_MATCH
53
+ when LaunchDarkly::EvaluationReason::ERROR
54
+ ::OpenFeature::SDK::Provider::Reason::ERROR
55
+ else
56
+ # NOTE: FALLTHROUGH, RULE_MATCH, PREREQUISITE_FAILED intentionally
57
+ kind.to_s
58
+ end
59
+ end
60
+
61
+ #
62
+ # @param error_kind [Symbol]
63
+ #
64
+ # @return [String]
65
+ #
66
+ private def error_kind_to_code(error_kind)
67
+ return ::OpenFeature::SDK::Provider::ErrorCode::GENERAL if error_kind.nil?
68
+
69
+ case error_kind
70
+ when LaunchDarkly::EvaluationReason::ERROR_CLIENT_NOT_READY
71
+ ::OpenFeature::SDK::Provider::ErrorCode::PROVIDER_NOT_READY
72
+ when LaunchDarkly::EvaluationReason::ERROR_FLAG_NOT_FOUND
73
+ ::OpenFeature::SDK::Provider::ErrorCode::FLAG_NOT_FOUND
74
+ when LaunchDarkly::EvaluationReason::ERROR_MALFORMED_FLAG
75
+ ::OpenFeature::SDK::Provider::ErrorCode::PARSE_ERROR
76
+ when LaunchDarkly::EvaluationReason::ERROR_USER_NOT_SPECIFIED
77
+ ::OpenFeature::SDK::Provider::ErrorCode::TARGETING_KEY_MISSING
78
+ else
79
+ # NOTE: EXCEPTION_ERROR intentionally omitted
80
+ ::OpenFeature::SDK::Provider::ErrorCode::GENERAL
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,13 @@
1
+ module LaunchDarkly
2
+ module OpenFeature
3
+ #
4
+ # Internal implementation classes. Everything in this module should be considered unsupported
5
+ # and subject to change.
6
+ #
7
+ # @private
8
+ #
9
+ module Impl
10
+ # code is in ldclient-openfeature/impl/
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ldclient-rb'
4
+ require 'open_feature/sdk'
5
+
6
+ module LaunchDarkly
7
+ module OpenFeature
8
+ class Provider
9
+ #
10
+ # Retrieve metadata information describing this provider.
11
+ #
12
+ # @return [::OpenFeature::SDK::Provider::ProviderMetadata]
13
+ #
14
+ attr_reader :metadata
15
+
16
+ #
17
+ # Access the underlying LaunchDarky client instance backing this provider.
18
+ #
19
+ # This is useful for accessing additional functionality not exposed by the provider.
20
+ #
21
+ # @return [LaunchDarkly::LDClient]
22
+ #
23
+ attr_reader :client
24
+
25
+ NUMERIC_TYPES = %i[integer float number].freeze
26
+ private_constant :NUMERIC_TYPES
27
+
28
+ #
29
+ # @param sdk_key [String]
30
+ # @param config [LaunchDarkly::Config]
31
+ # @param wait_for_seconds [Float]
32
+ #
33
+ def initialize(sdk_key, config = LaunchDarkly::Config.default, wait_for_seconds = 5)
34
+ @client = LaunchDarkly::LDClient.new(sdk_key, config, wait_for_seconds)
35
+
36
+ @context_converter = Impl::EvaluationContextConverter.new(config.logger)
37
+ @details_converter = Impl::ResolutionDetailsConverter.new
38
+
39
+ @metadata = ::OpenFeature::SDK::Provider::ProviderMetadata.new(name: "launchdarkly-openfeature-server").freeze
40
+ end
41
+
42
+ def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
43
+ resolve_value(:boolean, flag_key, default_value, evaluation_context)
44
+ end
45
+
46
+ def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
47
+ resolve_value(:string, flag_key, default_value, evaluation_context)
48
+ end
49
+
50
+ def fetch_number_value(flag_key:, default_value:, evaluation_context: nil)
51
+ resolve_value(:number, flag_key, default_value, evaluation_context)
52
+ end
53
+
54
+ def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil)
55
+ resolve_value(:integer, flag_key, default_value, evaluation_context)
56
+ end
57
+
58
+ def fetch_float_value(flag_key:, default_value:, evaluation_context: nil)
59
+ resolve_value(:float, flag_key, default_value, evaluation_context)
60
+ end
61
+
62
+ def fetch_object_value(flag_key:, default_value:, evaluation_context: nil)
63
+ resolve_value(:object, flag_key, default_value, evaluation_context)
64
+ end
65
+
66
+ #
67
+ # @param flag_type [Symbol]
68
+ # @param flag_key [String]
69
+ # @param default_value [any]
70
+ # @param evaluation_context [::OpenFeature::SDK::EvaluationContext, nil]
71
+ #
72
+ # @return [::OpenFeature::SDK::Provider::ResolutionDetails]
73
+ #
74
+ private def resolve_value(flag_type, flag_key, default_value, evaluation_context)
75
+ if evaluation_context.nil?
76
+ return ::OpenFeature::SDK::Provider::ResolutionDetails.new(
77
+ value: default_value,
78
+ reason: ::OpenFeature::SDK::Provider::Reason::ERROR,
79
+ error_code: ::OpenFeature::SDK::Provider::ErrorCode::TARGETING_KEY_MISSING
80
+ )
81
+ end
82
+
83
+ ld_context = @context_converter.to_ld_context(evaluation_context)
84
+ evaluation_detail = @client.variation_detail(flag_key, ld_context, default_value)
85
+
86
+ if flag_type == :boolean && ![true, false].include?(evaluation_detail.value)
87
+ return mismatched_type_details(default_value)
88
+ elsif flag_type == :string && !evaluation_detail.value.is_a?(String)
89
+ return mismatched_type_details(default_value)
90
+ elsif NUMERIC_TYPES.include?(flag_type) && !evaluation_detail.value.is_a?(Numeric)
91
+ return mismatched_type_details(default_value)
92
+ elsif flag_type == :object && !evaluation_detail.value.is_a?(Hash) && !evaluation_detail.value.is_a?(Array)
93
+ return mismatched_type_details(default_value)
94
+ end
95
+
96
+ if flag_type == :integer
97
+ evaluation_detail = LaunchDarkly::EvaluationDetail.new(
98
+ evaluation_detail.value.to_i,
99
+ evaluation_detail.variation_index,
100
+ evaluation_detail.reason
101
+ )
102
+ elsif flag_type == :float
103
+ evaluation_detail = LaunchDarkly::EvaluationDetail.new(
104
+ evaluation_detail.value.to_f,
105
+ evaluation_detail.variation_index,
106
+ evaluation_detail.reason
107
+ )
108
+ end
109
+
110
+ @details_converter.to_resolution_details(evaluation_detail)
111
+ end
112
+
113
+ #
114
+ # @param default_value [any]
115
+ #
116
+ # @return [::OpenFeature::SDK::Provider::ResolutionDetails]
117
+ #
118
+ private def mismatched_type_details(default_value)
119
+ ::OpenFeature::SDK::Provider::ResolutionDetails.new(
120
+ value: default_value,
121
+ reason: ::OpenFeature::SDK::Provider::Reason::ERROR,
122
+ error_code: ::OpenFeature::SDK::Provider::ErrorCode::TYPE_MISMATCH
123
+ )
124
+ end
125
+ end
126
+ end
127
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module LaunchDarkly
4
4
  module OpenFeature
5
- VERSION = "0.0.0"
5
+ VERSION = "0.1.0" # x-release-please-version
6
6
  end
7
7
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "ldclient-openfeature/impl/context_converter"
4
+ require_relative "ldclient-openfeature/impl/details_converter"
5
+ require_relative "ldclient-openfeature/provider"
3
6
  require_relative "ldclient-openfeature/version"
4
7
 
5
8
  require "logger"
@@ -0,0 +1,12 @@
1
+ {
2
+ "packages": {
3
+ ".": {
4
+ "release-type": "ruby",
5
+ "bump-minor-pre-major": true,
6
+ "versioning": "default",
7
+ "include-component-in-tag": false,
8
+ "include-v-in-tag": false,
9
+ "extra-files": ["PROVENANCE.md", "lib/ldclient-openfeature/version.rb"]
10
+ }
11
+ }
12
+ }
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: launchdarkly-openfeature-server-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LaunchDarkly
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-19 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-08-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: launchdarkly-server-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 8.4.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 8.4.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: openfeature-sdk
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.4.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.4.0
13
41
  description: A LaunchDarkly provider for use with the OpenFeature SDK
14
42
  email:
15
43
  - team@launchdarkly.com
@@ -20,20 +48,32 @@ files:
20
48
  - ".release-please-manifest.json"
21
49
  - ".rspec"
22
50
  - ".rubocop.yml"
51
+ - CHANGELOG.md
52
+ - CODEOWNERS
53
+ - CONTRIBUTING.md
23
54
  - Gemfile
24
55
  - LICENSE.txt
56
+ - PROVENANCE.md
25
57
  - README.md
26
58
  - Rakefile
59
+ - SECURITY.md
60
+ - docs/Makefile
61
+ - docs/index.md
27
62
  - launchdarkly-openfeature-server-sdk.gemspec
28
63
  - lib/ldclient-openfeature.rb
64
+ - lib/ldclient-openfeature/impl.rb
65
+ - lib/ldclient-openfeature/impl/context_converter.rb
66
+ - lib/ldclient-openfeature/impl/details_converter.rb
67
+ - lib/ldclient-openfeature/provider.rb
29
68
  - lib/ldclient-openfeature/version.rb
69
+ - release-please-config.json
30
70
  homepage: https://github.com/launchdarkly/openfeature-ruby-server
31
71
  licenses: []
32
72
  metadata:
33
73
  homepage_uri: https://github.com/launchdarkly/openfeature-ruby-server
34
74
  source_code_uri: https://github.com/launchdarkly/openfeature-ruby-server
35
75
  changelog_uri: https://github.com/launchdarkly/openfeature-ruby-server/blob/main/CHANGELOG.md
36
- post_install_message:
76
+ post_install_message:
37
77
  rdoc_options: []
38
78
  require_paths:
39
79
  - lib
@@ -48,8 +88,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
48
88
  - !ruby/object:Gem::Version
49
89
  version: '0'
50
90
  requirements: []
51
- rubygems_version: 3.3.26
52
- signing_key:
91
+ rubygems_version: 3.3.27
92
+ signing_key:
53
93
  specification_version: 4
54
94
  summary: LaunchDarkly OpenFeature Server SDK
55
95
  test_files: []