openfeature-go-feature-flag-provider 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/Gemfile.lock +1 -1
- data/lib/openfeature/go-feature-flag/client/common.rb +127 -0
- data/lib/openfeature/go-feature-flag/client/http_api.rb +50 -0
- data/lib/openfeature/go-feature-flag/client/unix_api.rb +45 -0
- data/lib/openfeature/go-feature-flag/go_feature_flag_provider.rb +16 -1
- data/lib/openfeature/go-feature-flag/internal/http_unix.rb +31 -0
- data/lib/openfeature/go-feature-flag/options.rb +14 -6
- data/lib/openfeature/go-feature-flag/version.rb +1 -1
- metadata +6 -3
- data/lib/openfeature/go-feature-flag/goff_api.rb +0 -153
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0f77c6e14ebd429606e0f754c034e740fa5b201d33a2e060417c06d63ff1200c
|
|
4
|
+
data.tar.gz: c3066c5d408e51eec86d06e288fc461934767815d244a95ad758363e97b9bcbd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f70df57dbf0442f9058aad26a15cb3908022dafb87af3d7b195cb66d82866898579da0f0a771c3ffd793bbe283b796a693912b3ab25e41674ccc86367f023cf1
|
|
7
|
+
data.tar.gz: 44d1b3d4c43eee840b6d6c81f7f665df065a2e3af135d9fd43cece06fa567244991bb9781bdd64c6cdeb34e66a14a044993a3adfd8ff5d42a52785299d27e51b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.7](https://github.com/open-feature/ruby-sdk-contrib/compare/openfeature-go-feature-flag-provider/v0.1.6...openfeature-go-feature-flag-provider/v0.1.7) (2025-11-19)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ✨ New Features
|
|
7
|
+
|
|
8
|
+
* **GoFeatureFlag:** unix client ([#66](https://github.com/open-feature/ruby-sdk-contrib/issues/66)) ([3f68ce8](https://github.com/open-feature/ruby-sdk-contrib/commit/3f68ce8691646779755affb2c86edf51ae21b40b))
|
|
9
|
+
|
|
3
10
|
## [0.1.6](https://github.com/open-feature/ruby-sdk-contrib/compare/openfeature-go-feature-flag-provider/v0.1.5...openfeature-go-feature-flag-provider/v0.1.6) (2025-11-11)
|
|
4
11
|
|
|
5
12
|
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "open_feature/sdk"
|
|
6
|
+
require_relative "../error/errors"
|
|
7
|
+
require_relative "../model/ofrep_api_response"
|
|
8
|
+
|
|
9
|
+
module OpenFeature
|
|
10
|
+
module GoFeatureFlag
|
|
11
|
+
module Client
|
|
12
|
+
class Common
|
|
13
|
+
def initialize(endpoint: nil, custom_headers: nil)
|
|
14
|
+
raise "This should be overwritten by implementations"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def evaluate_ofrep_api(flag_key:, evaluation_context:)
|
|
18
|
+
raise "This should be overwritten by implementations"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def headers
|
|
24
|
+
{"Content-Type" => "application/json"}.merge(@custom_headers || {})
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def check_retry_after
|
|
28
|
+
unless @retry_after.nil?
|
|
29
|
+
if Time.now < @retry_after
|
|
30
|
+
raise OpenFeature::GoFeatureFlag::RateLimited.new(nil)
|
|
31
|
+
else
|
|
32
|
+
@retry_after = nil
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def parse_error_response(response)
|
|
38
|
+
required_keys = %w[key error_code]
|
|
39
|
+
parsed = JSON.parse(response.body)
|
|
40
|
+
|
|
41
|
+
missing_keys = required_keys - parsed.keys
|
|
42
|
+
unless missing_keys.empty?
|
|
43
|
+
raise OpenFeature::GoFeatureFlag::ParseError.new(response)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
OpenFeature::GoFeatureFlag::OfrepApiResponse.new(
|
|
47
|
+
value: nil,
|
|
48
|
+
key: parsed["key"],
|
|
49
|
+
reason: SDK::Provider::Reason::ERROR,
|
|
50
|
+
variant: nil,
|
|
51
|
+
error_code: error_code_mapper(parsed["error_code"]),
|
|
52
|
+
error_details: parsed["error_details"],
|
|
53
|
+
metadata: nil
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def parse_success_response(response)
|
|
58
|
+
required_keys = %w[key value reason variant]
|
|
59
|
+
parsed = JSON.parse(response.body)
|
|
60
|
+
|
|
61
|
+
missing_keys = required_keys - parsed.keys
|
|
62
|
+
unless missing_keys.empty?
|
|
63
|
+
raise OpenFeature::GoFeatureFlag::ParseError.new(response)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
OpenFeature::GoFeatureFlag::OfrepApiResponse.new(
|
|
67
|
+
value: parsed["value"],
|
|
68
|
+
key: parsed["key"],
|
|
69
|
+
reason: reason_mapper(parsed["reason"]),
|
|
70
|
+
variant: parsed["variant"],
|
|
71
|
+
error_code: nil,
|
|
72
|
+
error_details: nil,
|
|
73
|
+
metadata: parsed["metadata"]
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def reason_mapper(reason_str)
|
|
78
|
+
reason_str = reason_str.upcase
|
|
79
|
+
reason_map = {
|
|
80
|
+
"STATIC" => SDK::Provider::Reason::STATIC,
|
|
81
|
+
"DEFAULT" => SDK::Provider::Reason::DEFAULT,
|
|
82
|
+
"TARGETING_MATCH" => SDK::Provider::Reason::TARGETING_MATCH,
|
|
83
|
+
"SPLIT" => SDK::Provider::Reason::SPLIT,
|
|
84
|
+
"CACHED" => SDK::Provider::Reason::CACHED,
|
|
85
|
+
"DISABLED" => SDK::Provider::Reason::DISABLED,
|
|
86
|
+
"UNKNOWN" => SDK::Provider::Reason::UNKNOWN,
|
|
87
|
+
"STALE" => SDK::Provider::Reason::STALE,
|
|
88
|
+
"ERROR" => SDK::Provider::Reason::ERROR
|
|
89
|
+
}
|
|
90
|
+
reason_map[reason_str] || SDK::Provider::Reason::UNKNOWN
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def error_code_mapper(error_code_str)
|
|
94
|
+
error_code_str = error_code_str.upcase
|
|
95
|
+
error_code_map = {
|
|
96
|
+
"PROVIDER_NOT_READY" => SDK::Provider::ErrorCode::PROVIDER_NOT_READY,
|
|
97
|
+
"FLAG_NOT_FOUND" => SDK::Provider::ErrorCode::FLAG_NOT_FOUND,
|
|
98
|
+
"PARSE_ERROR" => SDK::Provider::ErrorCode::PARSE_ERROR,
|
|
99
|
+
"TYPE_MISMATCH" => SDK::Provider::ErrorCode::TYPE_MISMATCH,
|
|
100
|
+
"TARGETING_KEY_MISSING" => SDK::Provider::ErrorCode::TARGETING_KEY_MISSING,
|
|
101
|
+
"INVALID_CONTEXT" => SDK::Provider::ErrorCode::INVALID_CONTEXT,
|
|
102
|
+
"GENERAL" => SDK::Provider::ErrorCode::GENERAL
|
|
103
|
+
}
|
|
104
|
+
error_code_map[error_code_str] || SDK::Provider::ErrorCode::GENERAL
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def parse_retry_later_header(response)
|
|
108
|
+
retry_after = response["Retry-After"]
|
|
109
|
+
return nil if retry_after.nil?
|
|
110
|
+
|
|
111
|
+
begin
|
|
112
|
+
@retry_after = if /^\d+$/.match?(retry_after)
|
|
113
|
+
# Retry-After is in seconds
|
|
114
|
+
Time.now + Integer(retry_after)
|
|
115
|
+
else
|
|
116
|
+
# Retry-After is an HTTP-date
|
|
117
|
+
Time.httpdate(retry_after)
|
|
118
|
+
end
|
|
119
|
+
rescue ArgumentError
|
|
120
|
+
# ignore invalid Retry-After header
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "common"
|
|
4
|
+
require "faraday/net_http_persistent"
|
|
5
|
+
|
|
6
|
+
module OpenFeature
|
|
7
|
+
module GoFeatureFlag
|
|
8
|
+
module Client
|
|
9
|
+
class HttpApi < Common
|
|
10
|
+
def initialize(endpoint: nil, custom_headers: nil, instrumentation: nil)
|
|
11
|
+
@custom_headers = custom_headers
|
|
12
|
+
@faraday_connection = Faraday.new(url: endpoint, headers: headers) do |f|
|
|
13
|
+
f.request :instrumentation, instrumentation if instrumentation
|
|
14
|
+
f.adapter :net_http_persistent do |http|
|
|
15
|
+
http.idle_timeout = 30
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def evaluate_ofrep_api(flag_key:, evaluation_context:)
|
|
21
|
+
check_retry_after
|
|
22
|
+
evaluation_context = OpenFeature::SDK::EvaluationContext.new if evaluation_context.nil?
|
|
23
|
+
# replace targeting_key by targetingKey
|
|
24
|
+
evaluation_context.fields["targetingKey"] = evaluation_context.targeting_key
|
|
25
|
+
evaluation_context.fields.delete("targeting_key")
|
|
26
|
+
|
|
27
|
+
response = @faraday_connection.post("/ofrep/v1/evaluate/flags/#{flag_key}") do |req|
|
|
28
|
+
req.body = {context: evaluation_context.fields}.to_json
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
case response.status
|
|
32
|
+
when 200
|
|
33
|
+
parse_success_response(response)
|
|
34
|
+
when 400
|
|
35
|
+
parse_error_response(response)
|
|
36
|
+
when 401, 403
|
|
37
|
+
raise OpenFeature::GoFeatureFlag::UnauthorizedError.new(response)
|
|
38
|
+
when 404
|
|
39
|
+
raise OpenFeature::GoFeatureFlag::FlagNotFoundError.new(response, flag_key)
|
|
40
|
+
when 429
|
|
41
|
+
parse_retry_later_header(response)
|
|
42
|
+
raise OpenFeature::GoFeatureFlag::RateLimited.new(response)
|
|
43
|
+
else
|
|
44
|
+
raise OpenFeature::GoFeatureFlag::InternalServerError.new(response)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "common"
|
|
4
|
+
require_relative "../internal/http_unix"
|
|
5
|
+
|
|
6
|
+
module OpenFeature
|
|
7
|
+
module GoFeatureFlag
|
|
8
|
+
module Client
|
|
9
|
+
class UnixApi < Common
|
|
10
|
+
attr_accessor :socket
|
|
11
|
+
|
|
12
|
+
def initialize(endpoint: nil, custom_headers: nil)
|
|
13
|
+
@custom_headers = custom_headers
|
|
14
|
+
@socket = HttpUnix.new(endpoint)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def evaluate_ofrep_api(flag_key:, evaluation_context:)
|
|
18
|
+
check_retry_after
|
|
19
|
+
evaluation_context = OpenFeature::SDK::EvaluationContext.new if evaluation_context.nil?
|
|
20
|
+
# replace targeting_key by targetingKey
|
|
21
|
+
evaluation_context.fields["targetingKey"] = evaluation_context.targeting_key
|
|
22
|
+
evaluation_context.fields.delete("targeting_key")
|
|
23
|
+
|
|
24
|
+
response = @socket.post("/ofrep/v1/evaluate/flags/#{flag_key}", {context: evaluation_context.fields}, headers)
|
|
25
|
+
|
|
26
|
+
case response.code
|
|
27
|
+
when "200"
|
|
28
|
+
parse_success_response(response)
|
|
29
|
+
when "400"
|
|
30
|
+
parse_error_response(response)
|
|
31
|
+
when "401", "403"
|
|
32
|
+
raise OpenFeature::GoFeatureFlag::UnauthorizedError.new(response)
|
|
33
|
+
when "404"
|
|
34
|
+
raise OpenFeature::GoFeatureFlag::FlagNotFoundError.new(response, flag_key)
|
|
35
|
+
when "429"
|
|
36
|
+
parse_retry_later_header(response)
|
|
37
|
+
raise OpenFeature::GoFeatureFlag::RateLimited.new(response)
|
|
38
|
+
else
|
|
39
|
+
raise OpenFeature::GoFeatureFlag::InternalServerError.new(response)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "openfeature/go-feature-flag/client/http_api"
|
|
4
|
+
require "openfeature/go-feature-flag/client/unix_api"
|
|
5
|
+
|
|
3
6
|
module OpenFeature
|
|
4
7
|
module GoFeatureFlag
|
|
5
8
|
# This class is the entry point for the GoFeatureFlagProvider
|
|
6
9
|
class Provider
|
|
7
10
|
PROVIDER_NAME = "GO Feature Flag Provider"
|
|
11
|
+
Client = OpenFeature::GoFeatureFlag::Client
|
|
8
12
|
attr_reader :metadata, :options
|
|
9
13
|
|
|
10
14
|
def initialize(options: Options.new)
|
|
11
15
|
@metadata = SDK::Provider::ProviderMetadata.new(name: PROVIDER_NAME)
|
|
12
16
|
@options = options
|
|
13
|
-
@goff_api =
|
|
17
|
+
@goff_api = build_client(options)
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
|
|
@@ -95,6 +99,17 @@ module OpenFeature
|
|
|
95
99
|
raise InvalidOptionError.new(SDK::Provider::ErrorCode::GENERAL, "invalid flag key provided")
|
|
96
100
|
end
|
|
97
101
|
end
|
|
102
|
+
|
|
103
|
+
def build_client(options)
|
|
104
|
+
case options.type
|
|
105
|
+
when "http"
|
|
106
|
+
Client::HttpApi.new(endpoint: options.endpoint, custom_headers: options.custom_headers, instrumentation: options.instrumentation)
|
|
107
|
+
when "unix"
|
|
108
|
+
Client::UnixApi.new(endpoint: options.endpoint, custom_headers: options.custom_headers)
|
|
109
|
+
else
|
|
110
|
+
raise InvalidOptionError.new(SDK::Provider::ErrorCode::GENERAL, "Invalid client type: '#{options.type}'. Supported types are: 'http', 'unix'")
|
|
111
|
+
end
|
|
112
|
+
end
|
|
98
113
|
end
|
|
99
114
|
end
|
|
100
115
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
|
|
3
|
+
class HttpUnix < Net::HTTP
|
|
4
|
+
BufferedIO = ::Net::BufferedIO
|
|
5
|
+
UNIX_REGEXP = %r{^unix://}i
|
|
6
|
+
|
|
7
|
+
def initialize(address, port = nil)
|
|
8
|
+
super(address, port)
|
|
9
|
+
@socket_type = "unix"
|
|
10
|
+
@socket_path = address.sub(UNIX_REGEXP, "")
|
|
11
|
+
|
|
12
|
+
@host = "localhost"
|
|
13
|
+
@port = 1031
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def connect
|
|
17
|
+
s = UNIXSocket.open(@socket_path)
|
|
18
|
+
@socket = BufferedIO.new(s,
|
|
19
|
+
read_timeout: @read_timeout,
|
|
20
|
+
continue_timeout: @continue_timeout,
|
|
21
|
+
debug_output: @debug_output)
|
|
22
|
+
on_connect
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def post(url, body, headers)
|
|
26
|
+
request = Net::HTTP::Post.new(url, headers)
|
|
27
|
+
request["host"] = "localhost" # required to form correct HTTP request
|
|
28
|
+
request.body = body.to_json
|
|
29
|
+
request(request)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -6,11 +6,12 @@ module OpenFeature
|
|
|
6
6
|
module GoFeatureFlag
|
|
7
7
|
# This class is the configuration class for the GoFeatureFlagProvider
|
|
8
8
|
class Options
|
|
9
|
-
attr_accessor :endpoint, :custom_headers, :exporter_metadata, :instrumentation
|
|
9
|
+
attr_accessor :endpoint, :custom_headers, :exporter_metadata, :instrumentation, :type
|
|
10
10
|
|
|
11
|
-
def initialize(endpoint: nil, headers: {}, exporter_metadata: {}, instrumentation: nil)
|
|
12
|
-
validate_endpoint(endpoint
|
|
11
|
+
def initialize(endpoint: nil, headers: {}, exporter_metadata: {}, instrumentation: nil, type: "http")
|
|
12
|
+
validate_endpoint(endpoint, type)
|
|
13
13
|
validate_instrumentation(instrumentation: instrumentation)
|
|
14
|
+
@type = type
|
|
14
15
|
@endpoint = endpoint
|
|
15
16
|
@custom_headers = headers
|
|
16
17
|
@exporter_metadata = exporter_metadata
|
|
@@ -19,11 +20,18 @@ module OpenFeature
|
|
|
19
20
|
|
|
20
21
|
private
|
|
21
22
|
|
|
22
|
-
def validate_endpoint(endpoint
|
|
23
|
+
def validate_endpoint(endpoint, type)
|
|
23
24
|
return if endpoint.nil?
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
case type
|
|
27
|
+
when "http"
|
|
28
|
+
uri = URI.parse(endpoint)
|
|
29
|
+
raise ArgumentError, "Invalid URL for endpoint: #{endpoint}" unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
|
30
|
+
when "unix"
|
|
31
|
+
raise ArgumentError, "File not found: #{endpoint}" unless File.exist?(endpoint)
|
|
32
|
+
else
|
|
33
|
+
raise ArgumentError, "Invalid Type: #{type}"
|
|
34
|
+
end
|
|
27
35
|
rescue URI::InvalidURIError
|
|
28
36
|
raise ArgumentError, "Invalid URL for endpoint: #{endpoint}"
|
|
29
37
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openfeature-go-feature-flag-provider
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Thomas Poignant
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-11-
|
|
11
|
+
date: 2025-11-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: openfeature-sdk
|
|
@@ -136,9 +136,12 @@ files:
|
|
|
136
136
|
- README.md
|
|
137
137
|
- Rakefile
|
|
138
138
|
- bin/rake
|
|
139
|
+
- lib/openfeature/go-feature-flag/client/common.rb
|
|
140
|
+
- lib/openfeature/go-feature-flag/client/http_api.rb
|
|
141
|
+
- lib/openfeature/go-feature-flag/client/unix_api.rb
|
|
139
142
|
- lib/openfeature/go-feature-flag/error/errors.rb
|
|
140
143
|
- lib/openfeature/go-feature-flag/go_feature_flag_provider.rb
|
|
141
|
-
- lib/openfeature/go-feature-flag/
|
|
144
|
+
- lib/openfeature/go-feature-flag/internal/http_unix.rb
|
|
142
145
|
- lib/openfeature/go-feature-flag/model/ofrep_api_response.rb
|
|
143
146
|
- lib/openfeature/go-feature-flag/options.rb
|
|
144
147
|
- lib/openfeature/go-feature-flag/version.rb
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "open_feature/sdk"
|
|
4
|
-
require "net/http"
|
|
5
|
-
require "json"
|
|
6
|
-
require "faraday/net_http_persistent"
|
|
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
|
-
def initialize(endpoint: nil, custom_headers: nil, instrumentation: nil)
|
|
15
|
-
@faraday_connection = Faraday.new(url: endpoint, headers: headers(custom_headers)) do |f|
|
|
16
|
-
f.request :instrumentation, instrumentation if instrumentation
|
|
17
|
-
f.adapter :net_http_persistent do |http|
|
|
18
|
-
http.idle_timeout = 30
|
|
19
|
-
end
|
|
20
|
-
end
|
|
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 headers(custom_headers)
|
|
61
|
-
{"Content-Type" => "application/json"}.merge(custom_headers || {})
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def parse_error_response(response)
|
|
65
|
-
required_keys = %w[key error_code]
|
|
66
|
-
parsed = JSON.parse(response.body)
|
|
67
|
-
|
|
68
|
-
missing_keys = required_keys - parsed.keys
|
|
69
|
-
unless missing_keys.empty?
|
|
70
|
-
raise OpenFeature::GoFeatureFlag::ParseError.new(response)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
OpenFeature::GoFeatureFlag::OfrepApiResponse.new(
|
|
74
|
-
value: nil,
|
|
75
|
-
key: parsed["key"],
|
|
76
|
-
reason: SDK::Provider::Reason::ERROR,
|
|
77
|
-
variant: nil,
|
|
78
|
-
error_code: error_code_mapper(parsed["error_code"]),
|
|
79
|
-
error_details: parsed["error_details"],
|
|
80
|
-
metadata: nil
|
|
81
|
-
)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def parse_success_response(response)
|
|
85
|
-
required_keys = %w[key value reason variant]
|
|
86
|
-
parsed = JSON.parse(response.body)
|
|
87
|
-
|
|
88
|
-
missing_keys = required_keys - parsed.keys
|
|
89
|
-
unless missing_keys.empty?
|
|
90
|
-
raise OpenFeature::GoFeatureFlag::ParseError.new(response)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
OpenFeature::GoFeatureFlag::OfrepApiResponse.new(
|
|
94
|
-
value: parsed["value"],
|
|
95
|
-
key: parsed["key"],
|
|
96
|
-
reason: reason_mapper(parsed["reason"]),
|
|
97
|
-
variant: parsed["variant"],
|
|
98
|
-
error_code: nil,
|
|
99
|
-
error_details: nil,
|
|
100
|
-
metadata: parsed["metadata"]
|
|
101
|
-
)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def reason_mapper(reason_str)
|
|
105
|
-
reason_str = reason_str.upcase
|
|
106
|
-
reason_map = {
|
|
107
|
-
"STATIC" => SDK::Provider::Reason::STATIC,
|
|
108
|
-
"DEFAULT" => SDK::Provider::Reason::DEFAULT,
|
|
109
|
-
"TARGETING_MATCH" => SDK::Provider::Reason::TARGETING_MATCH,
|
|
110
|
-
"SPLIT" => SDK::Provider::Reason::SPLIT,
|
|
111
|
-
"CACHED" => SDK::Provider::Reason::CACHED,
|
|
112
|
-
"DISABLED" => SDK::Provider::Reason::DISABLED,
|
|
113
|
-
"UNKNOWN" => SDK::Provider::Reason::UNKNOWN,
|
|
114
|
-
"STALE" => SDK::Provider::Reason::STALE,
|
|
115
|
-
"ERROR" => SDK::Provider::Reason::ERROR
|
|
116
|
-
}
|
|
117
|
-
reason_map[reason_str] || SDK::Provider::Reason::UNKNOWN
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def error_code_mapper(error_code_str)
|
|
121
|
-
error_code_str = error_code_str.upcase
|
|
122
|
-
error_code_map = {
|
|
123
|
-
"PROVIDER_NOT_READY" => SDK::Provider::ErrorCode::PROVIDER_NOT_READY,
|
|
124
|
-
"FLAG_NOT_FOUND" => SDK::Provider::ErrorCode::FLAG_NOT_FOUND,
|
|
125
|
-
"PARSE_ERROR" => SDK::Provider::ErrorCode::PARSE_ERROR,
|
|
126
|
-
"TYPE_MISMATCH" => SDK::Provider::ErrorCode::TYPE_MISMATCH,
|
|
127
|
-
"TARGETING_KEY_MISSING" => SDK::Provider::ErrorCode::TARGETING_KEY_MISSING,
|
|
128
|
-
"INVALID_CONTEXT" => SDK::Provider::ErrorCode::INVALID_CONTEXT,
|
|
129
|
-
"GENERAL" => SDK::Provider::ErrorCode::GENERAL
|
|
130
|
-
}
|
|
131
|
-
error_code_map[error_code_str] || SDK::Provider::ErrorCode::GENERAL
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def parse_retry_later_header(response)
|
|
135
|
-
retry_after = response["Retry-After"]
|
|
136
|
-
return nil if retry_after.nil?
|
|
137
|
-
|
|
138
|
-
begin
|
|
139
|
-
@retry_after = if /^\d+$/.match?(retry_after)
|
|
140
|
-
# Retry-After is in seconds
|
|
141
|
-
Time.now + Integer(retry_after)
|
|
142
|
-
else
|
|
143
|
-
# Retry-After is an HTTP-date
|
|
144
|
-
Time.httpdate(retry_after)
|
|
145
|
-
end
|
|
146
|
-
rescue ArgumentError
|
|
147
|
-
# ignore invalid Retry-After header
|
|
148
|
-
nil
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
end
|