openfeature-go-feature-flag-provider 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '018d25337f3d0990a16a6cf08353de44d09e149a86ee51662849fd0ddffcf15f'
4
+ data.tar.gz: 4676ffdfbd26b433536effd5ac1ee9fc7de7dcd8db684354014cccbe4ce44eed
5
+ SHA512:
6
+ metadata.gz: 0ad10200eaf42b42c939c7b714148612932f18ff624274b9738256ae2ca0b021c66cbe6d895ce406da6d18cd1b6da0ae975ee77a28cefff43f5e4e904b4510d1
7
+ data.tar.gz: d6689dbcb9d1f87201b83b261fc36ca4557977bd27005fe18b2953455717dc1b2bc5cd0f23858fad8d5e338aae417276778370abbe04af633c975ad938f79274
data/.rubocop.yml ADDED
@@ -0,0 +1,5 @@
1
+ inherit_from: ../../shared_config/.rubocop.yml
2
+
3
+ inherit_mode:
4
+ merge:
5
+ - Exclude
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## [0.1.1](https://github.com/open-feature/ruby-sdk-contrib/compare/openfeature-go-feature-flag-provider-v0.1.0...openfeature-go-feature-flag-provider/v0.1.1) (2024-08-13)
4
+
5
+
6
+ ### ✨ New Features
7
+
8
+ * new GO Feature Flag ruby provider ([#38](https://github.com/open-feature/ruby-sdk-contrib/issues/38)) ([a0bbf53](https://github.com/open-feature/ruby-sdk-contrib/commit/a0bbf535da324279b18577f71ffaa05d4f2fdced))
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,114 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ openfeature-go-feature-flag-provider (0.1.1)
5
+ faraday (~> 2.10.1)
6
+ openfeature-sdk (~> 0.3.1)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ addressable (2.8.7)
12
+ public_suffix (>= 2.0.2, < 7.0)
13
+ ast (2.4.2)
14
+ bigdecimal (3.1.8)
15
+ crack (1.0.0)
16
+ bigdecimal
17
+ rexml
18
+ diff-lcs (1.5.1)
19
+ docile (1.4.1)
20
+ faraday (2.10.1)
21
+ faraday-net_http (>= 2.0, < 3.2)
22
+ logger
23
+ faraday-net_http (3.1.1)
24
+ net-http
25
+ hashdiff (1.1.1)
26
+ json (2.7.2)
27
+ language_server-protocol (3.17.0.3)
28
+ lint_roller (1.1.0)
29
+ logger (1.6.0)
30
+ net-http (0.4.1)
31
+ uri
32
+ openfeature-sdk (0.3.1)
33
+ parallel (1.24.0)
34
+ parser (3.3.0.5)
35
+ ast (~> 2.4.1)
36
+ racc
37
+ public_suffix (6.0.1)
38
+ racc (1.7.3)
39
+ rainbow (3.1.1)
40
+ rake (13.2.1)
41
+ regexp_parser (2.9.0)
42
+ rexml (3.3.4)
43
+ strscan
44
+ rspec (3.12.0)
45
+ rspec-core (~> 3.12.0)
46
+ rspec-expectations (~> 3.12.0)
47
+ rspec-mocks (~> 3.12.0)
48
+ rspec-core (3.12.3)
49
+ rspec-support (~> 3.12.0)
50
+ rspec-expectations (3.12.4)
51
+ diff-lcs (>= 1.2.0, < 2.0)
52
+ rspec-support (~> 3.12.0)
53
+ rspec-mocks (3.12.7)
54
+ diff-lcs (>= 1.2.0, < 2.0)
55
+ rspec-support (~> 3.12.0)
56
+ rspec-support (3.12.2)
57
+ rubocop (1.62.1)
58
+ json (~> 2.3)
59
+ language_server-protocol (>= 3.17.0)
60
+ parallel (~> 1.10)
61
+ parser (>= 3.3.0.2)
62
+ rainbow (>= 2.2.2, < 4.0)
63
+ regexp_parser (>= 1.8, < 3.0)
64
+ rexml (>= 3.2.5, < 4.0)
65
+ rubocop-ast (>= 1.31.1, < 2.0)
66
+ ruby-progressbar (~> 1.7)
67
+ unicode-display_width (>= 2.4.0, < 3.0)
68
+ rubocop-ast (1.31.2)
69
+ parser (>= 3.3.0.4)
70
+ rubocop-performance (1.20.2)
71
+ rubocop (>= 1.48.1, < 2.0)
72
+ rubocop-ast (>= 1.30.0, < 2.0)
73
+ ruby-progressbar (1.13.0)
74
+ simplecov (0.22.0)
75
+ docile (~> 1.1)
76
+ simplecov-html (~> 0.11)
77
+ simplecov_json_formatter (~> 0.1)
78
+ simplecov-html (0.12.3)
79
+ simplecov_json_formatter (0.1.4)
80
+ standard (1.35.1)
81
+ language_server-protocol (~> 3.17.0.2)
82
+ lint_roller (~> 1.0)
83
+ rubocop (~> 1.62.0)
84
+ standard-custom (~> 1.0.0)
85
+ standard-performance (~> 1.3)
86
+ standard-custom (1.0.2)
87
+ lint_roller (~> 1.0)
88
+ rubocop (~> 1.50)
89
+ standard-performance (1.3.1)
90
+ lint_roller (~> 1.1)
91
+ rubocop-performance (~> 1.20.2)
92
+ strscan (3.1.0)
93
+ unicode-display_width (2.5.0)
94
+ uri (0.13.0)
95
+ webmock (3.23.1)
96
+ addressable (>= 2.8.0)
97
+ crack (>= 0.3.2)
98
+ hashdiff (>= 0.4.0, < 2.0.0)
99
+
100
+ PLATFORMS
101
+ arm64-darwin-23
102
+ ruby
103
+
104
+ DEPENDENCIES
105
+ openfeature-go-feature-flag-provider!
106
+ rake (~> 13.0)
107
+ rspec (~> 3.12.0)
108
+ rubocop
109
+ simplecov
110
+ standard
111
+ webmock
112
+
113
+ BUNDLED WITH
114
+ 2.5.11
data/README.md ADDED
@@ -0,0 +1,128 @@
1
+ <p align="center">
2
+ <img width="400" src="https://raw.githubusercontent.com/thomaspoignant/go-feature-flag/main/gofeatureflag.svg" alt="go-feature-flag logo" />
3
+
4
+ </p>
5
+
6
+ # GO Feature Flag - OpenFeature Ruby provider
7
+ <p align="center">
8
+ <a href="https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-go-feature-flag-provider"><img src="https://img.shields.io/gem/v/openfeature-go-feature-flag-provider?color=blue&style=flat-square&logo=ruby" alt="gem"></a>
9
+ <a href="https://gofeatureflag.org/"><img src="https://img.shields.io/badge/%F0%9F%93%92-Website-blue" alt="Documentation"></a>
10
+ <a href="https://github.com/thomaspoignant/go-feature-flag/issues"><img src="https://img.shields.io/badge/%E2%9C%8F%EF%B8%8F-issues-red" alt="Issues"></a>
11
+ <a href="https://gofeatureflag.org/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=green" alt="Join us on slack"></a>
12
+ </p>
13
+
14
+ This repository contains the official Ruby OpenFeature provider for accessing your feature flags with [GO Feature Flag](https://gofeatureflag.org).
15
+
16
+ In conjunction with the [OpenFeature SDK](https://openfeature.dev/docs/reference/concepts/provider) you will be able
17
+ to evaluate your feature flags in your Ruby applications.
18
+
19
+ For documentation related to flags management in GO Feature Flag,
20
+ refer to the [GO Feature Flag documentation website](https://gofeatureflag.org/docs).
21
+
22
+ ### Functionalities:
23
+ - Manage the integration of the OpenFeature Ruby SDK and GO Feature Flag relay-proxy.
24
+
25
+ ## Dependency Setup
26
+
27
+ ### Gem Package Manager
28
+
29
+ Add this line to your application's Gemfile:
30
+ ```
31
+ gem 'openfeature-go-feature-flag-provider'
32
+ ```
33
+ And then execute:
34
+ ```
35
+ bundle install
36
+ ```
37
+ Or install it yourself as:
38
+ ```
39
+ gem install openfeature-go-feature-flag-provider
40
+ ```
41
+
42
+ ## Getting started
43
+
44
+ ### Initialize the provider
45
+
46
+ The `OpenFeature::GoFeatureFlag::Provider` needs some options to be created and then set in the OpenFeature SDK.
47
+
48
+ | **Option** | **Description** |
49
+ |------------|---------------------------------------------------------------------------------------------------------------------------------------------|
50
+ | `endpoint` | **(mandatory)** The URL to access to the relay-proxy.<br />*(example: `https://relay.proxy.gofeatureflag.org/`)* |
51
+ | `headers` | A `Hash` object containing the headers to send to the relay-proxy.<br/>*(example to send APIKey: `{"Authorization" => "Bearer my-api-key"}` |
52
+
53
+ The only required option to create a `GoFeatureFlagProvider` is the URL _(`endpoint`)_ to your GO Feature Flag relay-proxy instance.
54
+
55
+ ```ruby
56
+ options = OpenFeature::GoFeatureFlag::Options.new(endpoint: "http://localhost:1031")
57
+ provider = OpenFeature::GoFeatureFlag::Provider.new(options:)
58
+
59
+ evaluation_context = OpenFeature::SDK::EvaluationContext.new(targeting_key: "9b9450f8-ab5c-4dcf-872f-feda3f6ccb16")
60
+
61
+ OpenFeature::SDK.configure do |config|
62
+ config.set_provider(provider)
63
+ end
64
+ client = OpenFeature::SDK.build_client
65
+
66
+ bool_value = client.fetch_boolean_value(
67
+ flag_key: "my-boolean-flag",
68
+ default_value: false,
69
+ evaluation_context:
70
+ )
71
+
72
+ if bool_value
73
+ puts "The flag is enabled"
74
+ else
75
+ puts "The flag is disabled"
76
+ end
77
+ ```
78
+
79
+ The evaluation context is the way for the client to specify contextual data that GO Feature Flag uses to evaluate the feature flags, it allows to define rules on the flag.
80
+
81
+ The `targeting_key` is mandatory for GO Feature Flag to evaluate the feature flag, it could be the id of a user, a session ID or anything you find relevant to use as identifier during the evaluation.
82
+
83
+
84
+ ### Evaluate a feature flag
85
+ The client is used to retrieve values for the current `EvaluationContext`.
86
+ For example, retrieving a boolean value for the flag **"my-flag"**:
87
+
88
+ ```ruby
89
+ client = OpenFeature::SDK.build_client
90
+
91
+ bool_value = client.fetch_boolean_value(
92
+ flag_key: "my-boolean-flag",
93
+ default_value: false,
94
+ evaluation_context: evaluation_context
95
+ )
96
+ ```
97
+
98
+ GO Feature Flag supports different all OpenFeature supported types of feature flags, it means that you can use all the accessor directly
99
+ ```ruby
100
+ # Bool
101
+ client.fetch_boolean_value(flag_key: 'my-flag', default_value: false, evaluation_context:)
102
+
103
+ # String
104
+ client.fetch_string_value(flag_key: 'my-flag', default_value: "default", evaluation_context:)
105
+
106
+ # Number
107
+ client.fetch_number_value(flag_key: 'my-flag', default_value: 0, evaluation_context:)
108
+
109
+ # Object
110
+ client.fetch_object_value(flag_key: 'my-flag', default_value: {"default" => true}, evaluation_context:)
111
+ ```
112
+
113
+ ## Features status
114
+
115
+ | Status | Feature | Description |
116
+ |--------|-----------------|----------------------------------------------------------------------------|
117
+ | ✅ | Flag evaluation | It is possible to evaluate all the type of flags |
118
+ | ❌ | Caching | Mechanism is in place to refresh the cache in case of configuration change |
119
+ | ❌ | Event Streaming | Not supported by the SDK |
120
+ | ❌ | Logging | Not supported by the SDK |
121
+ | ✅ | Flag Metadata | You can retrieve your flag metadata directly in the evaluation details. |
122
+
123
+
124
+ <sub>**Implemented**: ✅ | In-progress: ⚠️ | Not implemented yet: ❌</sub>
125
+
126
+ ## Contributing
127
+ This project welcomes contributions from the community.
128
+ If you're interested in contributing, see the [contributors' guide](https://github.com/thomaspoignant/go-feature-flag/blob/main/CONTRIBUTING.md) for some helpful tips.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[standard spec]
data/bin/rake ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("rake", "rake")
@@ -0,0 +1,76 @@
1
+ # Define a custom error class
2
+ require "open_feature/sdk/provider/error_code"
3
+
4
+ module OpenFeature
5
+ module GoFeatureFlag
6
+ class FlagNotFoundError < StandardError
7
+ attr_reader :response, :error_code, :error_message
8
+
9
+ def initialize(response, flag_key)
10
+ error_message = "Flag not found: #{flag_key}"
11
+ @response = response
12
+ @error_code = SDK::Provider::ErrorCode::FLAG_NOT_FOUND
13
+ @error_message = error_message
14
+ super(error_message)
15
+ end
16
+ end
17
+
18
+ class InternalServerError < StandardError
19
+ attr_reader :response, :error_code, :error_message
20
+
21
+ def initialize(response)
22
+ error_message = "Internal Server Error"
23
+ @response = response
24
+ @error_code = SDK::Provider::ErrorCode::GENERAL
25
+ @error_message = error_message
26
+ super(error_message)
27
+ end
28
+ end
29
+
30
+ class InvalidOptionError < StandardError
31
+ attr_reader :error_code, :error_message
32
+
33
+ def initialize(error_code, error_message)
34
+ @error_code = error_code
35
+ @error_message = error_message
36
+ super(error_message)
37
+ end
38
+ end
39
+
40
+ class UnauthorizedError < StandardError
41
+ attr_reader :response, :error_code, :error_message
42
+
43
+ def initialize(response)
44
+ error_message = "unauthorized"
45
+ @response = response
46
+ @error_code = SDK::Provider::ErrorCode::GENERAL
47
+ @error_message = error_message
48
+ super(error_message)
49
+ end
50
+ end
51
+
52
+ class ParseError < StandardError
53
+ attr_reader :response, :error_code, :error_message
54
+
55
+ def initialize(response)
56
+ error_message = "Parse error"
57
+ @response = response
58
+ @error_code = SDK::Provider::ErrorCode::PARSE_ERROR
59
+ @error_message = error_message
60
+ super(error_message)
61
+ end
62
+ end
63
+
64
+ class RateLimited < StandardError
65
+ attr_reader :response, :error_code, :error_message
66
+
67
+ def initialize(response)
68
+ error_message = response.nil? ? "Rate limited" : "Rate limited: " + response["Retry-After"].to_s
69
+ @response = response
70
+ @error_code = SDK::Provider::ErrorCode::GENERAL
71
+ @error_message = error_message
72
+ super(error_message)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenFeature
4
+ module GoFeatureFlag
5
+ # This class is the entry point for the GoFeatureFlagProvider
6
+ class Provider
7
+ PROVIDER_NAME = "GO Feature Flag Provider"
8
+ attr_reader :metadata, :options
9
+
10
+ def initialize(options: Options.new)
11
+ @metadata = SDK::Provider::ProviderMetadata.new(name: PROVIDER_NAME)
12
+ @options = options
13
+ @goff_api = GoFeatureFlagApi.new(options: options)
14
+ end
15
+
16
+ def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
17
+ evaluate(flag_key: flag_key, default_value: default_value, evaluation_context: evaluation_context, allowed_classes: [TrueClass, FalseClass])
18
+ end
19
+
20
+ def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
21
+ evaluate(flag_key: flag_key, default_value: default_value, evaluation_context: evaluation_context, allowed_classes: [String])
22
+ end
23
+
24
+ def fetch_number_value(flag_key:, default_value:, evaluation_context: nil)
25
+ evaluate(flag_key: flag_key, default_value: default_value, evaluation_context: evaluation_context, allowed_classes: [Integer, Float])
26
+ end
27
+
28
+ def fetch_object_value(flag_key:, default_value:, evaluation_context: nil)
29
+ evaluate(flag_key: flag_key, default_value: default_value, evaluation_context: evaluation_context, allowed_classes: [Array, Hash])
30
+ end
31
+
32
+ private
33
+
34
+ def evaluate(flag_key:, default_value:, allowed_classes:, evaluation_context: nil)
35
+ evaluation_context = OpenFeature::SDK::EvaluationContext.new if evaluation_context.nil?
36
+ validate_parameters(flag_key, evaluation_context)
37
+
38
+ # do a http call to the go feature flag server
39
+ parsed_response = @goff_api.evaluate_ofrep_api(flag_key: flag_key, evaluation_context: evaluation_context)
40
+ parsed_response = OfrepApiResponse unless parsed_response.is_a?(OfrepApiResponse)
41
+
42
+ if parsed_response.has_error?
43
+ return SDK::Provider::ResolutionDetails.new(
44
+ value: default_value,
45
+ error_code: parsed_response.error_code,
46
+ error_message: parsed_response.error_details,
47
+ reason: parsed_response.reason
48
+ )
49
+ end
50
+
51
+ unless allowed_classes.include?(parsed_response.value.class)
52
+ return SDK::Provider::ResolutionDetails.new(
53
+ value: default_value,
54
+ error_code: SDK::Provider::ErrorCode::TYPE_MISMATCH,
55
+ error_message: "flag type #{parsed_response.value.class} does not match allowed types #{allowed_classes}",
56
+ reason: SDK::Provider::Reason::ERROR
57
+ )
58
+ end
59
+
60
+ SDK::Provider::ResolutionDetails.new(
61
+ value: parsed_response.value,
62
+ reason: parsed_response.reason,
63
+ variant: parsed_response.variant,
64
+ flag_metadata: parsed_response.metadata
65
+ )
66
+ rescue UnauthorizedError,
67
+ InvalidOptionError,
68
+ FlagNotFoundError,
69
+ InternalServerError => e
70
+ SDK::Provider::ResolutionDetails.new(
71
+ value: default_value,
72
+ error_code: e.error_code,
73
+ error_message: e.error_message,
74
+ reason: SDK::Provider::Reason::ERROR
75
+ )
76
+ end
77
+
78
+ def validate_parameters(flag_key, evaluation_context)
79
+ if evaluation_context.nil? || evaluation_context.targeting_key.nil? || evaluation_context.targeting_key.empty?
80
+ raise InvalidOptionError.new(SDK::Provider::ErrorCode::INVALID_CONTEXT, "invalid evaluation context provided")
81
+ end
82
+
83
+ if flag_key.nil? || flag_key.empty?
84
+ raise InvalidOptionError.new(SDK::Provider::ErrorCode::GENERAL, "invalid flag key provided")
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open_feature/sdk"
4
+ require "net/http"
5
+ require "json"
6
+ require "faraday"
7
+ require_relative "error/errors"
8
+ require_relative "model/ofrep_api_response"
9
+
10
+ module OpenFeature
11
+ module GoFeatureFlag
12
+ # This class is the entry point for the GoFeatureFlagProvider
13
+ class GoFeatureFlagApi
14
+ attr_reader :options
15
+ def initialize(options: {})
16
+ @options = options
17
+ @faraday_connection = Faraday.new(
18
+ url: @options.endpoint,
19
+ headers: {"Content-Type" => "application/json"}.merge(@options.custom_headers || {})
20
+ )
21
+ end
22
+
23
+ def evaluate_ofrep_api(flag_key:, evaluation_context:)
24
+ unless @retry_after.nil?
25
+ if Time.now < @retry_after
26
+ raise OpenFeature::GoFeatureFlag::RateLimited.new(nil)
27
+ else
28
+ @retry_after = nil
29
+ end
30
+ end
31
+
32
+ evaluation_context = OpenFeature::SDK::EvaluationContext.new if evaluation_context.nil?
33
+ # replace targeting_key by targetingKey
34
+ evaluation_context.fields["targetingKey"] = evaluation_context.targeting_key
35
+ evaluation_context.fields.delete("targeting_key")
36
+
37
+ response = @faraday_connection.post("/ofrep/v1/evaluate/flags/#{flag_key}") do |req|
38
+ req.body = {context: evaluation_context.fields}.to_json
39
+ end
40
+
41
+ case response.status
42
+ when 200
43
+ parse_success_response(response)
44
+ when 400
45
+ parse_error_response(response)
46
+ when 401, 403
47
+ raise OpenFeature::GoFeatureFlag::UnauthorizedError.new(response)
48
+ when 404
49
+ raise OpenFeature::GoFeatureFlag::FlagNotFoundError.new(response, flag_key)
50
+ when 429
51
+ parse_retry_later_header(response)
52
+ raise OpenFeature::GoFeatureFlag::RateLimited.new(response)
53
+ else
54
+ raise OpenFeature::GoFeatureFlag::InternalServerError.new(response)
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def parse_error_response(response)
61
+ required_keys = %w[key error_code]
62
+ parsed = JSON.parse(response.body)
63
+
64
+ missing_keys = required_keys - parsed.keys
65
+ unless missing_keys.empty?
66
+ raise OpenFeature::GoFeatureFlag::ParseError.new(response)
67
+ end
68
+
69
+ OpenFeature::GoFeatureFlag::OfrepApiResponse.new(
70
+ value: nil,
71
+ key: parsed["key"],
72
+ reason: SDK::Provider::Reason::ERROR,
73
+ variant: nil,
74
+ error_code: error_code_mapper(parsed["error_code"]),
75
+ error_details: parsed["error_details"],
76
+ metadata: nil
77
+ )
78
+ end
79
+
80
+ def parse_success_response(response)
81
+ required_keys = %w[key value reason variant]
82
+ parsed = JSON.parse(response.body)
83
+
84
+ missing_keys = required_keys - parsed.keys
85
+ unless missing_keys.empty?
86
+ raise OpenFeature::GoFeatureFlag::ParseError.new(response)
87
+ end
88
+
89
+ OpenFeature::GoFeatureFlag::OfrepApiResponse.new(
90
+ value: parsed["value"],
91
+ key: parsed["key"],
92
+ reason: reason_mapper(parsed["reason"]),
93
+ variant: parsed["variant"],
94
+ error_code: nil,
95
+ error_details: nil,
96
+ metadata: parsed["metadata"]
97
+ )
98
+ end
99
+
100
+ def reason_mapper(reason_str)
101
+ reason_str = reason_str.upcase
102
+ reason_map = {
103
+ "STATIC" => SDK::Provider::Reason::STATIC,
104
+ "DEFAULT" => SDK::Provider::Reason::DEFAULT,
105
+ "TARGETING_MATCH" => SDK::Provider::Reason::TARGETING_MATCH,
106
+ "SPLIT" => SDK::Provider::Reason::SPLIT,
107
+ "CACHED" => SDK::Provider::Reason::CACHED,
108
+ "DISABLED" => SDK::Provider::Reason::DISABLED,
109
+ "UNKNOWN" => SDK::Provider::Reason::UNKNOWN,
110
+ "STALE" => SDK::Provider::Reason::STALE,
111
+ "ERROR" => SDK::Provider::Reason::ERROR
112
+ }
113
+ reason_map[reason_str] || SDK::Provider::Reason::UNKNOWN
114
+ end
115
+
116
+ def error_code_mapper(error_code_str)
117
+ error_code_str = error_code_str.upcase
118
+ error_code_map = {
119
+ "PROVIDER_NOT_READY" => SDK::Provider::ErrorCode::PROVIDER_NOT_READY,
120
+ "FLAG_NOT_FOUND" => SDK::Provider::ErrorCode::FLAG_NOT_FOUND,
121
+ "PARSE_ERROR" => SDK::Provider::ErrorCode::PARSE_ERROR,
122
+ "TYPE_MISMATCH" => SDK::Provider::ErrorCode::TYPE_MISMATCH,
123
+ "TARGETING_KEY_MISSING" => SDK::Provider::ErrorCode::TARGETING_KEY_MISSING,
124
+ "INVALID_CONTEXT" => SDK::Provider::ErrorCode::INVALID_CONTEXT,
125
+ "GENERAL" => SDK::Provider::ErrorCode::GENERAL
126
+ }
127
+ error_code_map[error_code_str] || SDK::Provider::ErrorCode::GENERAL
128
+ end
129
+
130
+ def parse_retry_later_header(response)
131
+ retry_after = response["Retry-After"]
132
+ return nil if retry_after.nil?
133
+
134
+ begin
135
+ @retry_after = if /^\d+$/.match?(retry_after)
136
+ # Retry-After is in seconds
137
+ Time.now + Integer(retry_after)
138
+ else
139
+ # Retry-After is an HTTP-date
140
+ Time.httpdate(retry_after)
141
+ end
142
+ rescue ArgumentError
143
+ # ignore invalid Retry-After header
144
+ nil
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,32 @@
1
+ module OpenFeature
2
+ module GoFeatureFlag
3
+ class OfrepApiResponse
4
+ attr_reader :value, :key, :reason, :variant, :error_code, :error_details, :metadata
5
+
6
+ def initialize(value:, key:, reason:, variant:, error_code:, error_details:, metadata:)
7
+ @value = value
8
+ @key = key
9
+ @reason = reason
10
+ @variant = variant
11
+ @error_code = error_code
12
+ @error_details = error_details
13
+ @metadata = metadata
14
+ end
15
+
16
+ def has_error?
17
+ !@error_code.nil? && !@error_code.empty?
18
+ end
19
+
20
+ def eql?(other)
21
+ return false unless other.is_a?(OpenFeature::GoFeatureFlag::OfrepApiResponse)
22
+ key == other.key &&
23
+ value == other.value &&
24
+ reason == other.reason &&
25
+ variant == other.variant &&
26
+ error_code == other.error_code &&
27
+ error_details == other.error_details &&
28
+ metadata == other.metadata
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module OpenFeature
6
+ module GoFeatureFlag
7
+ # This class is the configuration class for the GoFeatureFlagProvider
8
+ class Options
9
+ attr_accessor :endpoint, :custom_headers
10
+
11
+ def initialize(endpoint: nil, headers: {})
12
+ validate_endpoint(endpoint: endpoint)
13
+ @endpoint = endpoint
14
+ @custom_headers = headers
15
+ end
16
+
17
+ private
18
+
19
+ def validate_endpoint(endpoint: nil)
20
+ return if endpoint.nil?
21
+
22
+ uri = URI.parse(endpoint)
23
+ raise ArgumentError, "Invalid URL for endpoint: #{endpoint}" unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
24
+ rescue URI::InvalidURIError
25
+ raise ArgumentError, "Invalid URL for endpoint: #{endpoint}"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ module OpenFeature
2
+ module GoFeatureFlag
3
+ GO_FEATURE_FLAG_PROVIDER_VERSION = "0.1.1"
4
+ end
5
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/openfeature/go-feature-flag/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "openfeature-go-feature-flag-provider"
7
+ spec.version = OpenFeature::GoFeatureFlag::GO_FEATURE_FLAG_PROVIDER_VERSION
8
+ spec.authors = ["Thomas Poignant"]
9
+ spec.email = ["contact@gofeatureflag.org"]
10
+
11
+ spec.summary = "The GO Feature Flag provider for the OpenFeature Ruby SDK"
12
+ spec.description = "The GO Feature Flag provider for the OpenFeature Ruby SDK"
13
+ spec.homepage = "https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-go-feature-flag-provider"
14
+ spec.license = "Apache-2.0"
15
+ spec.required_ruby_version = ">= 3.1"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-go-feature-flag-provider"
19
+ spec.metadata["changelog_uri"] = "https://github.com/open-feature/ruby-sdk-contrib/blob/main/providers/openfeature-go-feature-flag-provider/CHANGELOG.md"
20
+ spec.metadata["bug_tracker_uri"] = "https://github.com/thomaspoignant/go-feature-flag/issues/new/choose"
21
+ spec.metadata["documentation_uri"] = "https://gofeatureflag.org/docs"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_runtime_dependency "openfeature-sdk", "~> 0.3.1"
33
+ spec.add_runtime_dependency "faraday", "~> 2.10.1"
34
+
35
+ spec.add_development_dependency "rake", "~> 13.0"
36
+ spec.add_development_dependency "rspec", "~> 3.12.0"
37
+ spec.add_development_dependency "standard"
38
+ spec.add_development_dependency "rubocop"
39
+ spec.add_development_dependency "simplecov"
40
+ spec.add_development_dependency "webmock"
41
+ end
metadata ADDED
@@ -0,0 +1,174 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: openfeature-go-feature-flag-provider
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Poignant
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-08-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: openfeature-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.3.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.10.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.10.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.12.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.12.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: standard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: The GO Feature Flag provider for the OpenFeature Ruby SDK
126
+ email:
127
+ - contact@gofeatureflag.org
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".rubocop.yml"
133
+ - CHANGELOG.md
134
+ - Gemfile
135
+ - Gemfile.lock
136
+ - README.md
137
+ - Rakefile
138
+ - bin/rake
139
+ - lib/openfeature/go-feature-flag/error/errors.rb
140
+ - lib/openfeature/go-feature-flag/go_feature_flag_provider.rb
141
+ - lib/openfeature/go-feature-flag/goff_api.rb
142
+ - lib/openfeature/go-feature-flag/model/ofrep_api_response.rb
143
+ - lib/openfeature/go-feature-flag/options.rb
144
+ - lib/openfeature/go-feature-flag/version.rb
145
+ - openfeature-go-feature-flag-provider.gemspec
146
+ homepage: https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-go-feature-flag-provider
147
+ licenses:
148
+ - Apache-2.0
149
+ metadata:
150
+ homepage_uri: https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-go-feature-flag-provider
151
+ source_code_uri: https://github.com/open-feature/ruby-sdk-contrib/tree/main/providers/openfeature-go-feature-flag-provider
152
+ changelog_uri: https://github.com/open-feature/ruby-sdk-contrib/blob/main/providers/openfeature-go-feature-flag-provider/CHANGELOG.md
153
+ bug_tracker_uri: https://github.com/thomaspoignant/go-feature-flag/issues/new/choose
154
+ documentation_uri: https://gofeatureflag.org/docs
155
+ post_install_message:
156
+ rdoc_options: []
157
+ require_paths:
158
+ - lib
159
+ required_ruby_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: '3.1'
164
+ required_rubygems_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: '0'
169
+ requirements: []
170
+ rubygems_version: 3.5.11
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: The GO Feature Flag provider for the OpenFeature Ruby SDK
174
+ test_files: []