diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml
index b66c448..03aeaa4 100644
--- a/.github/workflows/ruby.yml
+++ b/.github/workflows/ruby.yml
@@ -85,4 +85,26 @@ jobs:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
working-directory: ./providers/openfeature-go-feature-flag-provider
- name: Lint and test
- run: bin/rake
\ No newline at end of file
+ run: bin/rake
+
+ test_flipt_provider:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./providers/openfeature-flipt-provider
+ strategy:
+ matrix:
+ ruby-version:
+ - "3.3"
+ - "3.2"
+ - "3.1"
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: ${{ matrix.ruby-version }}
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
+ working-directory: ./providers/openfeature-flipt-provider
+ - name: Lint and test
+ run: bin/rake
diff --git a/providers/openfeature-flipt-provider/.gitignore b/providers/openfeature-flipt-provider/.gitignore
new file mode 100644
index 0000000..b04a8c8
--- /dev/null
+++ b/providers/openfeature-flipt-provider/.gitignore
@@ -0,0 +1,11 @@
+/.bundle/
+/.yardoc
+/_yardoc/
+/coverage/
+/doc/
+/pkg/
+/spec/reports/
+/tmp/
+
+# rspec failure tracking
+.rspec_status
diff --git a/providers/openfeature-flipt-provider/.rspec b/providers/openfeature-flipt-provider/.rspec
new file mode 100644
index 0000000..44b132b
--- /dev/null
+++ b/providers/openfeature-flipt-provider/.rspec
@@ -0,0 +1,4 @@
+-I lib
+--format documentation
+--color
+--require spec_helper
diff --git a/providers/openfeature-flipt-provider/.rubocop.yml b/providers/openfeature-flipt-provider/.rubocop.yml
new file mode 100644
index 0000000..feec135
--- /dev/null
+++ b/providers/openfeature-flipt-provider/.rubocop.yml
@@ -0,0 +1,5 @@
+inherit_from: ../../shared_config/.rubocop.yml
+
+inherit_mode:
+ merge:
+ - Exclude
diff --git a/providers/openfeature-flipt-provider/.ruby-version b/providers/openfeature-flipt-provider/.ruby-version
new file mode 100644
index 0000000..fa7adc7
--- /dev/null
+++ b/providers/openfeature-flipt-provider/.ruby-version
@@ -0,0 +1 @@
+3.3.5
diff --git a/providers/openfeature-flipt-provider/CHANGELOG.md b/providers/openfeature-flipt-provider/CHANGELOG.md
new file mode 100644
index 0000000..e69de29
diff --git a/providers/openfeature-flipt-provider/Gemfile b/providers/openfeature-flipt-provider/Gemfile
new file mode 100644
index 0000000..3db0240
--- /dev/null
+++ b/providers/openfeature-flipt-provider/Gemfile
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+source "https://rubygems.org"
+
+# Specify your gem's dependencies in openfeature-flipt-provider.gemspec
+gemspec
diff --git a/providers/openfeature-flipt-provider/Gemfile.lock b/providers/openfeature-flipt-provider/Gemfile.lock
new file mode 100644
index 0000000..c28b351
--- /dev/null
+++ b/providers/openfeature-flipt-provider/Gemfile.lock
@@ -0,0 +1,108 @@
+PATH
+ remote: .
+ specs:
+ openfeature-flipt-provider (0.1.0)
+ ffi (~> 1.17)
+ flipt_client (~> 0.10.0)
+ openfeature-sdk (~> 0.4.0)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ ast (2.4.3)
+ diff-lcs (1.6.1)
+ ffi (1.17.1)
+ ffi (1.17.1-aarch64-linux-gnu)
+ ffi (1.17.1-aarch64-linux-musl)
+ ffi (1.17.1-arm-linux-gnu)
+ ffi (1.17.1-arm-linux-musl)
+ ffi (1.17.1-arm64-darwin)
+ ffi (1.17.1-x86-linux-gnu)
+ ffi (1.17.1-x86-linux-musl)
+ ffi (1.17.1-x86_64-darwin)
+ ffi (1.17.1-x86_64-linux-gnu)
+ ffi (1.17.1-x86_64-linux-musl)
+ flipt_client (0.10.0)
+ json (2.10.2)
+ language_server-protocol (3.17.0.4)
+ lint_roller (1.1.0)
+ openfeature-sdk (0.4.0)
+ parallel (1.26.3)
+ parser (3.3.7.3)
+ ast (~> 2.4.1)
+ racc
+ prism (1.4.0)
+ racc (1.8.1)
+ rainbow (3.1.1)
+ rake (13.2.1)
+ regexp_parser (2.10.0)
+ rspec (3.12.0)
+ rspec-core (~> 3.12.0)
+ rspec-expectations (~> 3.12.0)
+ rspec-mocks (~> 3.12.0)
+ rspec-core (3.12.3)
+ rspec-support (~> 3.12.0)
+ rspec-expectations (3.12.4)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.12.0)
+ rspec-mocks (3.12.7)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.12.0)
+ rspec-support (3.12.2)
+ rubocop (1.73.2)
+ json (~> 2.3)
+ language_server-protocol (~> 3.17.0.2)
+ lint_roller (~> 1.1.0)
+ parallel (~> 1.10)
+ parser (>= 3.3.0.2)
+ rainbow (>= 2.2.2, < 4.0)
+ regexp_parser (>= 2.9.3, < 3.0)
+ rubocop-ast (>= 1.38.0, < 2.0)
+ ruby-progressbar (~> 1.7)
+ unicode-display_width (>= 2.4.0, < 4.0)
+ rubocop-ast (1.43.0)
+ parser (>= 3.3.7.2)
+ prism (~> 1.4)
+ rubocop-performance (1.24.0)
+ lint_roller (~> 1.1)
+ rubocop (>= 1.72.1, < 2.0)
+ rubocop-ast (>= 1.38.0, < 2.0)
+ ruby-progressbar (1.13.0)
+ standard (1.47.0)
+ language_server-protocol (~> 3.17.0.2)
+ lint_roller (~> 1.0)
+ rubocop (~> 1.73.0)
+ standard-custom (~> 1.0.0)
+ standard-performance (~> 1.7)
+ standard-custom (1.0.2)
+ lint_roller (~> 1.0)
+ rubocop (~> 1.50)
+ standard-performance (1.7.0)
+ lint_roller (~> 1.1)
+ rubocop-performance (~> 1.24.0)
+ unicode-display_width (3.1.4)
+ unicode-emoji (~> 4.0, >= 4.0.4)
+ unicode-emoji (4.0.4)
+
+PLATFORMS
+ aarch64-linux-gnu
+ aarch64-linux-musl
+ arm-linux-gnu
+ arm-linux-musl
+ arm64-darwin
+ ruby
+ x86-linux-gnu
+ x86-linux-musl
+ x86_64-darwin
+ x86_64-linux-gnu
+ x86_64-linux-musl
+
+DEPENDENCIES
+ openfeature-flipt-provider!
+ rake (~> 13.0)
+ rspec (~> 3.12.0)
+ rubocop
+ standard (>= 1.35.1)
+
+BUNDLED WITH
+ 2.5.16
diff --git a/providers/openfeature-flipt-provider/README.md b/providers/openfeature-flipt-provider/README.md
new file mode 100644
index 0000000..007450e
--- /dev/null
+++ b/providers/openfeature-flipt-provider/README.md
@@ -0,0 +1,57 @@
+
+
+
+
+# Flipt - OpenFeature Ruby provider
+
+This repository contains the Ruby provider for [Flipt](https://www.flipt.io/), a feature flagging and experimentation platform.
+
+In conjunction with the [OpenFeature SDK](https://openfeature.dev/docs/reference/concepts/provider/) you can use this provider to integrate Flipt into your Ruby application.
+
+For documentation on how to use Flipt, please refer to the [Flipt documentation](https://docs.flipt.io/).
+
+## Installation
+Add this line to your application's Gemfile:
+
+```ruby
+gem 'openfeature-flipt-provider'
+```
+
+And then execute:
+```bash
+$ bundle
+```
+
+## Usage
+To use the Flipt provider, you need to create an instance of the provider and pass it to the OpenFeature SDK.
+
+```ruby
+require "open_feature/sdk"
+require "openfeature/flipt/provider"
+
+OpenFeature::SDK.configure do |config|
+ config.set_provider(
+ OpenFeature::Flipt::Provider.new(
+ namespace: "flipt-namespace",
+ options: {
+ url: "https://url-to-flipt-server",
+ update_interval: 60,
+ authentication: "token"
+ }
+ )
+ )
+end
+client = OpenFeature::SDK.build_client
+
+# Check if a feature is enabled
+if client.fetch_boolean_value(flag_key: "featureEnabled", default_value: false)
+ puts "Feature is enabled"
+else
+ puts "Feature is disabled"
+end
+```
+
+For a complete list of configuration options, such as authentication and error strategies, refer to the [Flipt Client Ruby SDK documentation](https://github.com/flipt-io/flipt-client-sdks/tree/main/flipt-client-ruby#constructor-arguments).
+
+## Contributing
+https://github.com/open-feature/ruby-sdk-contrib
diff --git a/providers/openfeature-flipt-provider/Rakefile b/providers/openfeature-flipt-provider/Rakefile
new file mode 100644
index 0000000..85f5f4d
--- /dev/null
+++ b/providers/openfeature-flipt-provider/Rakefile
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require "bundler/gem_tasks"
+require "rspec/core/rake_task"
+
+RSpec::Core::RakeTask.new(:spec)
+
+require "standard/rake"
+
+task default: %i[standard spec]
diff --git a/providers/openfeature-flipt-provider/bin/console b/providers/openfeature-flipt-provider/bin/console
new file mode 100755
index 0000000..05989c4
--- /dev/null
+++ b/providers/openfeature-flipt-provider/bin/console
@@ -0,0 +1,11 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+require "bundler/setup"
+require "openfeature/flipt/provider"
+
+# You can add fixtures and/or initialization code here to make experimenting
+# with your gem easier. You can also use a different console, if you like.
+
+require "irb"
+IRB.start(__FILE__)
diff --git a/providers/openfeature-flipt-provider/bin/rake b/providers/openfeature-flipt-provider/bin/rake
new file mode 100755
index 0000000..4eb7d7b
--- /dev/null
+++ b/providers/openfeature-flipt-provider/bin/rake
@@ -0,0 +1,27 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+#
+# This file was generated by Bundler.
+#
+# The application 'rake' is installed as part of a gem, and
+# this file is here to facilitate running it.
+#
+
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
+
+bundle_binstub = File.expand_path("bundle", __dir__)
+
+if File.file?(bundle_binstub)
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
+ load(bundle_binstub)
+ else
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
+Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
+ end
+end
+
+require "rubygems"
+require "bundler/setup"
+
+load Gem.bin_path("rake", "rake")
diff --git a/providers/openfeature-flipt-provider/bin/setup b/providers/openfeature-flipt-provider/bin/setup
new file mode 100755
index 0000000..dce67d8
--- /dev/null
+++ b/providers/openfeature-flipt-provider/bin/setup
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+set -euo pipefail
+IFS=$'\n\t'
+set -vx
+
+bundle install
+
+# Do any other automated setup that you need to do here
diff --git a/providers/openfeature-flipt-provider/lib/openfeature/flipt/provider.rb b/providers/openfeature-flipt-provider/lib/openfeature/flipt/provider.rb
new file mode 100644
index 0000000..bdc787e
--- /dev/null
+++ b/providers/openfeature-flipt-provider/lib/openfeature/flipt/provider.rb
@@ -0,0 +1,167 @@
+# frozen_string_literal: true
+
+require "flipt_client"
+require "open_feature/sdk"
+
+module OpenFeature
+ module Flipt
+ class Provider
+ PROVIDER_NAME = "Flipt Provider"
+
+ # @param namespace [String] Namespace to use when fetching flags.
+ # @param options [Hash] Options to pass to the Flipt client.
+ def initialize(namespace: "default", options: {})
+ @client = ::Flipt::EvaluationClient.new(namespace, options)
+ end
+
+ def metadata
+ @_metadata ||= SDK::Provider::ProviderMetadata.new(name: PROVIDER_NAME).freeze
+ end
+
+ def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
+ fetch_value(
+ flag_key: flag_key,
+ default_value: default_value,
+ evaluation_context: evaluation_context,
+ evaluation_method: :evaluate_boolean,
+ result_key: "enabled"
+ )
+ end
+
+ def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
+ fetch_value(
+ flag_key: flag_key,
+ default_value: default_value,
+ evaluation_context: evaluation_context,
+ evaluation_method: :evaluate_variant,
+ result_key: "variant_key"
+ )
+ end
+
+ def fetch_number_value(flag_key:, default_value:, evaluation_context: nil)
+ fetch_numeric_value(flag_key:, default_value:, evaluation_context:, allowed_types: [Numeric])
+ end
+
+ def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil)
+ fetch_numeric_value(flag_key:, default_value:, evaluation_context:, allowed_types: [Integer])
+ end
+
+ def fetch_float_value(flag_key:, default_value:, evaluation_context: nil)
+ fetch_numeric_value(flag_key:, default_value:, evaluation_context:, allowed_types: [Float])
+ end
+
+ def fetch_object_value(flag_key:, default_value:, evaluation_context: nil)
+ result = fetch_value(
+ flag_key: flag_key,
+ default_value: default_value,
+ evaluation_context: evaluation_context,
+ evaluation_method: :evaluate_variant,
+ result_key: "variant_key"
+ )
+
+ unless result.value.is_a?(Hash)
+ begin
+ result.value = JSON.parse(result.value)
+ rescue JSON::ParserError, TypeError
+ return OpenFeature::SDK::Provider::ResolutionDetails.new(
+ value: default_value,
+ error_message: "Could not parse '#{result.value}' as JSON",
+ reason: OpenFeature::SDK::Provider::Reason::ERROR
+ )
+ end
+ end
+ result
+ end
+
+ private
+
+ def fetch_value(flag_key:, default_value:, evaluation_context:, evaluation_method:, result_key:)
+ transformed_eval_context = transform_context(evaluation_context)
+
+ begin
+ response = @client.send(evaluation_method, {
+ flag_key: flag_key,
+ entity_id: evaluation_context&.fetch("targeting_key", nil) || "default",
+ context: transformed_eval_context
+ })
+
+ if %w[FLAG_DISABLED_EVALUATION_REASON].include?(response["result"]["reason"])
+ OpenFeature::SDK::Provider::ResolutionDetails.new(
+ value: default_value,
+ reason: OpenFeature::SDK::Provider::Reason::DISABLED
+ )
+ elsif %w[DEFAULT_EVALUATION_REASON MATCH_EVALUATION_REASON].include?(response["result"]["reason"])
+ OpenFeature::SDK::Provider::ResolutionDetails.new(
+ value: response["result"][result_key],
+ reason: OpenFeature::SDK::Provider::Reason::TARGETING_MATCH
+ )
+ elsif %w[UNKNOWN_EVALUATION_REASON].include?(response["result"]["reason"])
+ OpenFeature::SDK::Provider::ResolutionDetails.new(
+ value: default_value,
+ reason: OpenFeature::SDK::Provider::Reason::UNKNOWN
+ )
+ else
+ OpenFeature::SDK::Provider::ResolutionDetails.new(
+ value: default_value,
+ reason: OpenFeature::SDK::Provider::Reason::DEFAULT
+ )
+ end
+ rescue => e
+ OpenFeature::SDK::Provider::ResolutionDetails.new(
+ value: default_value,
+ error_message: e.message,
+ reason: OpenFeature::SDK::Provider::Reason::ERROR
+ )
+ end
+ end
+
+ def fetch_numeric_value(flag_key:, default_value:, evaluation_context:, allowed_types:)
+ result = fetch_value(
+ flag_key: flag_key,
+ default_value: default_value,
+ evaluation_context: evaluation_context,
+ evaluation_method: :evaluate_variant,
+ result_key: "variant_key"
+ )
+
+ unless result.value.is_a?(Numeric)
+ begin
+ parsed_value = Float(result.value)
+ # Only convert to integer if it's a whole number and allowed_types is [Integer]
+ result.value = if allowed_types == [Integer] && parsed_value.to_i == parsed_value
+ parsed_value.to_i
+ else
+ parsed_value
+ end
+ rescue ArgumentError, TypeError
+ return OpenFeature::SDK::Provider::ResolutionDetails.new(
+ value: default_value,
+ error_message: "Could not convert '#{result.value}' to #{allowed_types.first.name.downcase}",
+ reason: OpenFeature::SDK::Provider::Reason::ERROR
+ )
+ end
+ end
+
+ unless allowed_types.any? { |type| result.value.is_a?(type) }
+ return OpenFeature::SDK::Provider::ResolutionDetails.new(
+ value: default_value,
+ error_message: "Value '#{result.value}' is not a #{allowed_types.first.name.downcase}",
+ reason: OpenFeature::SDK::Provider::Reason::ERROR
+ )
+ end
+
+ result
+ end
+
+ def transform_context(context)
+ eval_context = {}
+ context&.each do |key, value|
+ next if key == "targeting_key"
+
+ eval_context[key] = value.to_s
+ end
+ eval_context
+ end
+ end
+ end
+end
diff --git a/providers/openfeature-flipt-provider/lib/openfeature/flipt/version.rb b/providers/openfeature-flipt-provider/lib/openfeature/flipt/version.rb
new file mode 100644
index 0000000..759d0a8
--- /dev/null
+++ b/providers/openfeature-flipt-provider/lib/openfeature/flipt/version.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module OpenFeature
+ module Flipt
+ VERSION = "0.1.0"
+ end
+end
diff --git a/providers/openfeature-flipt-provider/openfeature-flipt-provider.gemspec b/providers/openfeature-flipt-provider/openfeature-flipt-provider.gemspec
new file mode 100644
index 0000000..e111e14
--- /dev/null
+++ b/providers/openfeature-flipt-provider/openfeature-flipt-provider.gemspec
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require_relative "lib/openfeature/flipt/version"
+
+Gem::Specification.new do |spec|
+ spec.name = "openfeature-flipt-provider"
+ spec.version = OpenFeature::Flipt::VERSION
+ spec.authors = ["Firdaus Al Ghifari"]
+ spec.email = ["firdaus.alghifari@gmail.com"]
+
+ spec.summary = "OpenFeature Flipt Provider for Ruby"
+ spec.description = "OpenFeature Flipt Provider for Ruby"
+ spec.homepage = "https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-flipt-provider"
+ spec.license = "Apache-2.0"
+ spec.required_ruby_version = ">= 3.1"
+
+ spec.metadata["homepage_uri"] = spec.homepage
+ spec.metadata["source_code_uri"] = "https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-flipt-provider"
+ spec.metadata["changelog_uri"] = "https://github.com/open-feature/ruby-sdk-contrib/blob/main/providers/openfeature-flipt-provider/CHANGELOG.md"
+ spec.metadata["bug_tracker_uri"] = "https://github.com/open-feature/ruby-sdk-contrib/issues"
+ spec.metadata["documentation_uri"] = "https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-flipt-provider/README.md"
+
+ # Specify which files should be added to the gem when it is released.
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
+ end
+ spec.bindir = "exe"
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
+ spec.require_paths = ["lib"]
+
+ spec.add_dependency "ffi", "~> 1.17"
+ spec.add_dependency "openfeature-sdk", "~> 0.4.0"
+ spec.add_dependency "flipt_client", "~> 0.10.0"
+
+ spec.add_development_dependency "rake", "~> 13.0"
+ spec.add_development_dependency "rspec", "~> 3.12.0"
+ spec.add_development_dependency "standard", ">= 1.35.1"
+ spec.add_development_dependency "rubocop"
+end
diff --git a/providers/openfeature-flipt-provider/spec/openfeature/flipt/provider_spec.rb b/providers/openfeature-flipt-provider/spec/openfeature/flipt/provider_spec.rb
new file mode 100644
index 0000000..4ca3234
--- /dev/null
+++ b/providers/openfeature-flipt-provider/spec/openfeature/flipt/provider_spec.rb
@@ -0,0 +1,245 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe OpenFeature::Flipt::Provider do
+ let(:provider) { described_class.new(namespace: "test-namespace") }
+ let(:client_stub) { double(::Flipt::EvaluationClient) }
+ let(:evaluation_context) { {"targeting_key" => "user123", "some_key" => "some_value"} }
+
+ before do
+ allow(::Flipt::EvaluationClient).to receive(:new).with("test-namespace", {}).and_return(client_stub)
+ end
+
+ context "2.1 - Feature Provider Interface" do
+ describe "#metadata" do
+ it "returns a name field which identifies the provider implementation" do
+ expect(provider.metadata.name).to eq("Flipt Provider")
+ end
+ end
+ end
+
+ context "2.2 - Flag Value Resolution" do
+ describe "#fetch_boolean_value" do
+ it "returns the correct resolution details for a matching evaluation" do
+ response = {
+ "status" => "success",
+ "result" => {"enabled" => true, "reason" => "MATCH_EVALUATION_REASON"}
+ }
+ allow(client_stub).to receive(:evaluate_boolean).and_return(response)
+
+ result = provider.fetch_boolean_value(flag_key: "test_flag", default_value: false, evaluation_context: evaluation_context)
+ expect(result.value).to eq(true)
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::TARGETING_MATCH)
+ end
+
+ it "returns the default value for an unknown evaluation reason" do
+ response = {
+ "status" => "success",
+ "result" => {"reason" => "UNKNOWN_EVALUATION_REASON"}
+ }
+ allow(client_stub).to receive(:evaluate_boolean).and_return(response)
+
+ result = provider.fetch_boolean_value(flag_key: "test_flag", default_value: false, evaluation_context: evaluation_context)
+ expect(result.value).to eq(false)
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::UNKNOWN)
+ end
+
+ it "returns the default value for a flag disabled evaluation reason" do
+ response = {
+ "status" => "success",
+ "result" => {"reason" => "FLAG_DISABLED_EVALUATION_REASON"}
+ }
+ allow(client_stub).to receive(:evaluate_boolean).and_return(response)
+
+ result = provider.fetch_boolean_value(flag_key: "test_flag", default_value: false, evaluation_context: evaluation_context)
+ expect(result.value).to eq(false)
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::DISABLED)
+ end
+
+ it "returns the default value for an empty result" do
+ response = {
+ "status" => "failed",
+ "result" => {}
+ }
+ allow(client_stub).to receive(:evaluate_boolean).and_return(response)
+
+ result = provider.fetch_boolean_value(flag_key: "test_flag", default_value: false, evaluation_context: evaluation_context)
+ expect(result.value).to eq(false)
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::DEFAULT)
+ end
+
+ it "returns the default value and error message on exception" do
+ allow(client_stub).to receive(:evaluate_boolean).and_raise(StandardError.new("Some error"))
+
+ result = provider.fetch_boolean_value(flag_key: "test_flag", default_value: false, evaluation_context: evaluation_context)
+ expect(result.value).to eq(false)
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR)
+ expect(result.error_message).to eq("Some error")
+ end
+ end
+
+ describe "#fetch_string_value" do
+ it "returns the correct resolution details for a matching evaluation" do
+ response = {
+ "status" => "success",
+ "result" => {"variant_key" => "variant1", "reason" => "MATCH_EVALUATION_REASON"}
+ }
+ allow(client_stub).to receive(:evaluate_variant).and_return(response)
+
+ result = provider.fetch_string_value(flag_key: "test_flag", default_value: "default", evaluation_context: evaluation_context)
+ expect(result.value).to eq("variant1")
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::TARGETING_MATCH)
+ end
+
+ it "returns the default value for an unknown evaluation reason" do
+ response = {
+ "status" => "success",
+ "result" => {"reason" => "UNKNOWN_EVALUATION_REASON"}
+ }
+ allow(client_stub).to receive(:evaluate_variant).and_return(response)
+
+ result = provider.fetch_string_value(flag_key: "test_flag", default_value: "default", evaluation_context: evaluation_context)
+ expect(result.value).to eq("default")
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::UNKNOWN)
+ end
+
+ it "returns the default value for a flag disabled evaluation reason" do
+ response = {
+ "status" => "success",
+ "result" => {"reason" => "FLAG_DISABLED_EVALUATION_REASON"}
+ }
+ allow(client_stub).to receive(:evaluate_variant).and_return(response)
+
+ result = provider.fetch_string_value(flag_key: "test_flag", default_value: "default", evaluation_context: evaluation_context)
+ expect(result.value).to eq("default")
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::DISABLED)
+ end
+
+ it "returns the default value and error message on exception" do
+ allow(client_stub).to receive(:evaluate_variant).and_raise(StandardError.new("Some error"))
+
+ result = provider.fetch_string_value(flag_key: "test_flag", default_value: "default", evaluation_context: evaluation_context)
+ expect(result.value).to eq("default")
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR)
+ expect(result.error_message).to eq("Some error")
+ end
+ end
+
+ describe "#fetch_number_value" do
+ it "returns the correct numeric value for a matching evaluation" do
+ response = {
+ "status" => "success",
+ "result" => {"variant_key" => "42.5", "reason" => "MATCH_EVALUATION_REASON"}
+ }
+ allow(client_stub).to receive(:evaluate_variant).and_return(response)
+
+ result = provider.fetch_number_value(flag_key: "test_flag", default_value: 0, evaluation_context: evaluation_context)
+ expect(result.value).to eq(42.5)
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::TARGETING_MATCH)
+ end
+
+ it "returns error when value cannot be converted to number" do
+ response = {
+ "status" => "success",
+ "result" => {"variant_key" => "not_a_number", "reason" => "MATCH_EVALUATION_REASON"}
+ }
+ allow(client_stub).to receive(:evaluate_variant).and_return(response)
+
+ result = provider.fetch_number_value(flag_key: "test_flag", default_value: 0, evaluation_context: evaluation_context)
+ expect(result.value).to eq(0)
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR)
+ end
+ end
+
+ describe "#fetch_integer_value" do
+ it "returns the correct integer value for a matching evaluation" do
+ response = {
+ "status" => "success",
+ "result" => {"variant_key" => "42", "reason" => "MATCH_EVALUATION_REASON"}
+ }
+ allow(client_stub).to receive(:evaluate_variant).and_return(response)
+
+ result = provider.fetch_integer_value(flag_key: "test_flag", default_value: 0, evaluation_context: evaluation_context)
+ expect(result.value).to eq(42)
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::TARGETING_MATCH)
+ end
+
+ it "returns error when value cannot be converted to integer" do
+ response = {
+ "status" => "success",
+ "result" => {"variant_key" => "42.5", "reason" => "MATCH_EVALUATION_REASON"}
+ }
+ allow(client_stub).to receive(:evaluate_variant).and_return(response)
+
+ result = provider.fetch_integer_value(flag_key: "test_flag", default_value: 0, evaluation_context: evaluation_context)
+ expect(result.value).to eq(0)
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR)
+ end
+ end
+
+ describe "#fetch_float_value" do
+ it "returns the correct float value for a matching evaluation" do
+ response = {
+ "status" => "success",
+ "result" => {"variant_key" => "42.5", "reason" => "MATCH_EVALUATION_REASON"}
+ }
+ allow(client_stub).to receive(:evaluate_variant).and_return(response)
+
+ result = provider.fetch_float_value(flag_key: "test_flag", default_value: 0.0, evaluation_context: evaluation_context)
+ expect(result.value).to eq(42.5)
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::TARGETING_MATCH)
+ end
+
+ it "returns error when value cannot be converted to float" do
+ response = {
+ "status" => "success",
+ "result" => {"variant_key" => "not_a_float", "reason" => "MATCH_EVALUATION_REASON"}
+ }
+ allow(client_stub).to receive(:evaluate_variant).and_return(response)
+
+ result = provider.fetch_float_value(flag_key: "test_flag", default_value: 0.0, evaluation_context: evaluation_context)
+ expect(result.value).to eq(0.0)
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR)
+ end
+ end
+
+ describe "#fetch_object_value" do
+ it "returns the correct object value for a matching evaluation" do
+ response = {
+ "status" => "success",
+ "result" => {"variant_key" => '{"key": "value"}', "reason" => "MATCH_EVALUATION_REASON"}
+ }
+ allow(client_stub).to receive(:evaluate_variant).and_return(response)
+
+ result = provider.fetch_object_value(flag_key: "test_flag", default_value: {}, evaluation_context: evaluation_context)
+ expect(result.value).to eq({"key" => "value"})
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::TARGETING_MATCH)
+ end
+
+ it "returns error when value cannot be parsed as JSON" do
+ response = {
+ "status" => "success",
+ "result" => {"variant_key" => "invalid_json", "reason" => "MATCH_EVALUATION_REASON"}
+ }
+ allow(client_stub).to receive(:evaluate_variant).and_return(response)
+
+ result = provider.fetch_object_value(flag_key: "test_flag", default_value: {}, evaluation_context: evaluation_context)
+ expect(result.value).to eq({})
+ expect(result.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR)
+ end
+ end
+ end
+
+ describe "#transform_context" do
+ it "transforms the context correctly" do
+ result = provider.send(:transform_context, evaluation_context)
+ expect(result).to eq({"some_key" => "some_value"})
+ end
+
+ it "returns an empty hash if context is nil" do
+ result = provider.send(:transform_context, nil)
+ expect(result).to eq({})
+ end
+ end
+end
diff --git a/providers/openfeature-flipt-provider/spec/spec_helper.rb b/providers/openfeature-flipt-provider/spec/spec_helper.rb
new file mode 100644
index 0000000..5eeee39
--- /dev/null
+++ b/providers/openfeature-flipt-provider/spec/spec_helper.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require "bundler/setup"
+require "open_feature/sdk"
+require "openfeature/flipt/provider"
+
+RSpec.configure do |config|
+ config.expect_with :rspec do |expectations|
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
+ end
+
+ config.mock_with :rspec do |mocks|
+ mocks.verify_partial_doubles = true
+ end
+
+ config.shared_context_metadata_behavior = :apply_to_host_groups
+ config.filter_run_when_matching :focus
+ config.example_status_persistence_file_path = "spec/examples.txt"
+ config.disable_monkey_patching!
+ config.warnings = true
+ config.order = :random
+ Kernel.srand config.seed
+end
diff --git a/ruby-sdk-contrib.code-workspace b/ruby-sdk-contrib.code-workspace
index 2669c9a..31970d5 100644
--- a/ruby-sdk-contrib.code-workspace
+++ b/ruby-sdk-contrib.code-workspace
@@ -8,6 +8,9 @@
},
{
"path": "providers/openfeature-go-feature-flag-provider"
+ },
+ {
+ "path": "providers/openfeature-flipt-provider"
}
]
}