diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1fc38b2..e989337 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: BUNDLE_GEMFILE: Gemfile steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} @@ -33,7 +33,7 @@ jobs: run: bundle exec rspec - name: Upload coverage to Codecov if: ${{ strategy.job-index == 0 }} # only run codecov on first run - uses: codecov/codecov-action@v4.3.0 + uses: codecov/codecov-action@v4.3.1 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true @@ -43,7 +43,7 @@ jobs: name: Standard runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 - uses: ruby/setup-ruby@v1 with: ruby-version: 3.2 diff --git a/.github/workflows/pr-title-lint.yml b/.github/workflows/pr-title-lint.yml index b357fc4..ed9d292 100644 --- a/.github/workflows/pr-title-lint.yml +++ b/.github/workflows/pr-title-lint.yml @@ -1,4 +1,4 @@ -name: "Lint PR" +name: 'Lint PR' on: pull_request_target: @@ -12,6 +12,29 @@ jobs: name: Validate PR title runs-on: ubuntu-latest steps: - - uses: amannn/action-semantic-pull-request@e9fabac35e210fea40ca5b14c0da95a099eff26f # v5 + - uses: amannn/action-semantic-pull-request@v5 + id: lint_pr_title env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: marocchino/sticky-pull-request-comment@v2 + # When the previous steps fails, the workflow would stop. By adding this + # condition you can continue the execution with the populated error message. + if: always() && (steps.lint_pr_title.outputs.error_message != null) + with: + header: pr-title-lint-error + message: | + Hey there and thank you for opening this pull request! 👋🏼 + + We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted. + Details: + + ``` + ${{ steps.lint_pr_title.outputs.error_message }} + ``` + # Delete a previous comment when the issue has been resolved + - if: ${{ steps.lint_pr_title.outputs.error_message == null }} + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: pr-title-lint-error + delete: true diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 3f0b4dc..3b3da3d 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -29,7 +29,7 @@ jobs: steps: # The logic below handles the npm publication: - name: Checkout Repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 with: ref: ${{ needs.release-please.outputs.release_tag_name }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6b7b74c..cce9240 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.3.0" + ".": "0.3.1" } \ No newline at end of file diff --git a/.ruby-version b/.ruby-version index 15a2799..bea438e 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.0 +3.3.1 diff --git a/.tool-versions b/.tool-versions index 3294aed..51f617d 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -ruby 3.3.0 +ruby 3.3.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d5c788..ba35114 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.3.1](https://github.com/open-feature/ruby-sdk/compare/v0.3.0...v0.3.1) (2024-04-22) + + +### Features + +* add integer and float specific resolver methods ([#124](https://github.com/open-feature/ruby-sdk/issues/124)) ([eea9d17](https://github.com/open-feature/ruby-sdk/commit/eea9d17e5892064cec9d81bb0ef452e7e1761764)) + ## [0.3.0](https://github.com/open-feature/ruby-sdk/compare/v0.2.1...v0.3.0) (2024-04-05) diff --git a/Gemfile.lock b/Gemfile.lock index 6935c95..bfd2372 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - openfeature-sdk (0.3.0) + openfeature-sdk (0.3.1) GEM remote: https://rubygems.org/ diff --git a/lib/open_feature/sdk/client.rb b/lib/open_feature/sdk/client.rb index 9d8ca92..8f3749d 100644 --- a/lib/open_feature/sdk/client.rb +++ b/lib/open_feature/sdk/client.rb @@ -5,7 +5,7 @@ module SDK # TODO: Write documentation # class Client - RESULT_TYPE = %i[boolean string number object].freeze + RESULT_TYPE = %i[boolean string number integer float object].freeze SUFFIXES = %i[value details].freeze attr_reader :metadata, :evaluation_context diff --git a/lib/open_feature/sdk/provider/error_code.rb b/lib/open_feature/sdk/provider/error_code.rb index 6e10dbd..e133e2a 100644 --- a/lib/open_feature/sdk/provider/error_code.rb +++ b/lib/open_feature/sdk/provider/error_code.rb @@ -2,13 +2,14 @@ module OpenFeature module SDK module Provider module ErrorCode - PROVIDER_NOT_READY = "Provider Not Ready" - FLAG_NOT_FOUND = "Flag Not Found" - PARSE_ERROR = "Parse Error" - TYPE_MISMATCH = "Type Mismatch" - TARGETING_KEY_MISSING = "Targeting Key Missing" - INVALID_CONTEXT = "Invalid Context" - GENERAL = "General" + PROVIDER_NOT_READY = "PROVIDER_NOT_READY" + FLAG_NOT_FOUND = "FLAG_NOT_FOUND" + PARSE_ERROR = "PARSE_ERROR" + TYPE_MISMATCH = "TYPE_MISMATCH" + TARGETING_KEY_MISSING = "TARGETING_KEY_MISSING" + INVALID_CONTEXT = "INVALID_CONTEXT" + PROVIDER_FATAL = "PROVIDER_FATAL" + GENERAL = "GENERAL" end end end diff --git a/lib/open_feature/sdk/provider/in_memory_provider.rb b/lib/open_feature/sdk/provider/in_memory_provider.rb index 5a49fab..78703a4 100644 --- a/lib/open_feature/sdk/provider/in_memory_provider.rb +++ b/lib/open_feature/sdk/provider/in_memory_provider.rb @@ -34,7 +34,15 @@ def fetch_string_value(flag_key:, default_value:, evaluation_context: nil) end def fetch_number_value(flag_key:, default_value:, evaluation_context: nil) - fetch_value(allowed_classes: [Integer, Float], flag_key:, default_value:, evaluation_context:) + fetch_value(allowed_classes: [Numeric], flag_key:, default_value:, evaluation_context:) + end + + def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil) + fetch_value(allowed_classes: [Integer], flag_key:, default_value:, evaluation_context:) + end + + def fetch_float_value(flag_key:, default_value:, evaluation_context: nil) + fetch_value(allowed_classes: [Float], flag_key:, default_value:, evaluation_context:) end def fetch_object_value(flag_key:, default_value:, evaluation_context: nil) @@ -52,7 +60,7 @@ def fetch_value(allowed_classes:, flag_key:, default_value:, evaluation_context: return ResolutionDetails.new(value: default_value, error_code: ErrorCode::FLAG_NOT_FOUND, reason: Reason::ERROR) end - if allowed_classes.include?(value.class) + if allowed_classes.any? { |klass| value.is_a?(klass) } ResolutionDetails.new(value:, reason: Reason::STATIC) else ResolutionDetails.new(value: default_value, error_code: ErrorCode::TYPE_MISMATCH, reason: Reason::ERROR) diff --git a/lib/open_feature/sdk/provider/no_op_provider.rb b/lib/open_feature/sdk/provider/no_op_provider.rb index 358bd22..ab18959 100644 --- a/lib/open_feature/sdk/provider/no_op_provider.rb +++ b/lib/open_feature/sdk/provider/no_op_provider.rb @@ -44,6 +44,14 @@ def fetch_number_value(flag_key:, default_value:, evaluation_context: nil) no_op(default_value) end + def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil) + no_op(default_value) + end + + def fetch_float_value(flag_key:, default_value:, evaluation_context: nil) + no_op(default_value) + end + def fetch_object_value(flag_key:, default_value:, evaluation_context: nil) no_op(default_value) end diff --git a/lib/open_feature/sdk/provider/reason.rb b/lib/open_feature/sdk/provider/reason.rb index 15e459a..48d9be6 100644 --- a/lib/open_feature/sdk/provider/reason.rb +++ b/lib/open_feature/sdk/provider/reason.rb @@ -2,15 +2,15 @@ module OpenFeature module SDK module Provider module Reason - STATIC = "Static" - DEFAULT = "Default" - TARGETING_MATCH = "Targeting Match" - SPLIT = "Split" - CACHED = "Cached" - DISABLED = "Disabled" - UNKNOWN = "Unknown" - STALE = "Stale" - ERROR = "Error" + STATIC = "STATIC" + DEFAULT = "DEFAULT" + TARGETING_MATCH = "TARGETING_MATCH" + SPLIT = "SPLIT" + CACHED = "CACHED" + DISABLED = "DISABLED" + UNKNOWN = "UNKNOWN" + STALE = "STALE" + ERROR = "ERROR" end end end diff --git a/lib/open_feature/sdk/version.rb b/lib/open_feature/sdk/version.rb index 34c7c22..5e0d0cb 100644 --- a/lib/open_feature/sdk/version.rb +++ b/lib/open_feature/sdk/version.rb @@ -2,6 +2,6 @@ module OpenFeature module SDK - VERSION = "0.3.0" + VERSION = "0.3.1" end end diff --git a/spec/open_feature/sdk/client_spec.rb b/spec/open_feature/sdk/client_spec.rb index 828402f..051ee4c 100644 --- a/spec/open_feature/sdk/client_spec.rb +++ b/spec/open_feature/sdk/client_spec.rb @@ -64,13 +64,21 @@ expect(client).to respond_to(:fetch_number_value) end - context "Condition 1.3.2 - The implementation language differentiates between floating-point numbers and integers." do + it do + expect(client.fetch_number_value(flag_key: flag_key, default_value: 4)).is_a?(Integer) + end + + it do + expect(client.fetch_number_value(flag_key: flag_key, default_value: 95.5)).is_a?(Float) + end + + context "Condition 1.3.3 - The implementation language differentiates between floating-point numbers and integers." do it do - expect(client.fetch_number_value(flag_key: flag_key, default_value: 4)).is_a?(Integer) + expect(client.fetch_integer_value(flag_key: flag_key, default_value: 4)).is_a?(Integer) end it do - expect(client.fetch_number_value(flag_key: flag_key, default_value: 95.5)).is_a?(Float) + expect(client.fetch_float_value(flag_key: flag_key, default_value: 95.5)).is_a?(Float) end end end diff --git a/spec/open_feature/sdk/provider/in_memory_provider_spec.rb b/spec/open_feature/sdk/provider/in_memory_provider_spec.rb index 6f96d80..e449883 100644 --- a/spec/open_feature/sdk/provider/in_memory_provider_spec.rb +++ b/spec/open_feature/sdk/provider/in_memory_provider_spec.rb @@ -6,7 +6,8 @@ { "bool" => true, "str" => "testing", - "num" => 1, + "int" => 1, + "float" => 1.0, "struct" => {"more" => "config"} } ) @@ -103,12 +104,18 @@ describe "#fetch_number_value" do context "when flag is found" do context "when type matches" do - it "returns value as static" do - fetched = provider.fetch_number_value(flag_key: "num", default_value: 0) + it "returns int as static" do + fetched = provider.fetch_number_value(flag_key: "int", default_value: 0) expect(fetched.value).to eq(1) expect(fetched.reason).to eq(OpenFeature::SDK::Provider::Reason::STATIC) end + it "returns float as static" do + fetched = provider.fetch_number_value(flag_key: "float", default_value: 0.0) + + expect(fetched.value).to eq(1.0) + expect(fetched.reason).to eq(OpenFeature::SDK::Provider::Reason::STATIC) + end end context "when type does not match" do @@ -133,6 +140,72 @@ end end + describe "#fetch_integer_value" do + context "when flag is found" do + context "when type matches" do + it "returns value as static" do + fetched = provider.fetch_integer_value(flag_key: "int", default_value: 0) + + expect(fetched.value).to eq(1) + expect(fetched.reason).to eq(OpenFeature::SDK::Provider::Reason::STATIC) + end + end + + context "when type does not match" do + it "returns default as type mismatch" do + fetched = provider.fetch_integer_value(flag_key: "float", default_value: 0) + + expect(fetched.value).to eq(0) + expect(fetched.error_code).to eq(OpenFeature::SDK::Provider::ErrorCode::TYPE_MISMATCH) + expect(fetched.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR) + end + end + end + + context "when flag is not found" do + it "returns default as flag not found" do + fetched = provider.fetch_number_value(flag_key: "not here", default_value: 0) + + expect(fetched.value).to eq(0) + expect(fetched.error_code).to eq(OpenFeature::SDK::Provider::ErrorCode::FLAG_NOT_FOUND) + expect(fetched.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR) + end + end + end + + describe "#fetch_integer_value" do + context "when flag is found" do + context "when type matches" do + it "returns value as static" do + fetched = provider.fetch_float_value(flag_key: "float", default_value: 0.0) + + expect(fetched.value).to eq(1.0) + expect(fetched.reason).to eq(OpenFeature::SDK::Provider::Reason::STATIC) + end + end + + context "when type does not match" do + it "returns default as type mismatch" do + fetched = provider.fetch_float_value(flag_key: "int", default_value: 0.0) + + expect(fetched.value).to eq(0) + expect(fetched.error_code).to eq(OpenFeature::SDK::Provider::ErrorCode::TYPE_MISMATCH) + expect(fetched.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR) + end + end + end + + context "when flag is not found" do + it "returns default as flag not found" do + fetched = provider.fetch_number_value(flag_key: "not here", default_value: 0) + + expect(fetched.value).to eq(0) + expect(fetched.error_code).to eq(OpenFeature::SDK::Provider::ErrorCode::FLAG_NOT_FOUND) + expect(fetched.reason).to eq(OpenFeature::SDK::Provider::Reason::ERROR) + end + end + end + describe "#fetch_object_value" do context "when flag is found" do context "when type matches" do @@ -146,7 +219,7 @@ context "when type does not match" do it "returns default as type mismatch" do - fetched = provider.fetch_object_value(flag_key: "num", default_value: {}) + fetched = provider.fetch_object_value(flag_key: "int", default_value: {}) expect(fetched.value).to eq({}) expect(fetched.error_code).to eq(OpenFeature::SDK::Provider::ErrorCode::TYPE_MISMATCH)