Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,20 @@ def headers
{"Content-Type" => "application/json"}.merge(@custom_headers || {})
end

def evaluation_request(evaluation_context)
ctx = evaluation_context || OpenFeature::SDK::EvaluationContext.new
fields = ctx.fields.dup
# replace targeting_key by targetingKey without mutating original fields
fields["targetingKey"] = ctx.targeting_key
fields.delete("targeting_key")

{context: fields}
end

def check_retry_after
unless @retry_after.nil?
lock = (@retry_lock ||= Mutex.new)
lock.synchronize do
return if @retry_after.nil?
if Time.now < @retry_after
raise OpenFeature::GoFeatureFlag::RateLimited.new(nil)
else
Expand Down Expand Up @@ -109,12 +121,18 @@ def parse_retry_later_header(response)
return nil if retry_after.nil?

begin
@retry_after = if /^\d+$/.match?(retry_after)
# Retry-After is in seconds
Time.now + Integer(retry_after)
else
# Retry-After is an HTTP-date
Time.httpdate(retry_after)
next_retry_time =
if /^\d+$/.match?(retry_after)
# Retry-After is in seconds
Time.now + Integer(retry_after)
else
# Retry-After is an HTTP-date
Time.httpdate(retry_after)
end
# Protect updates and never shorten an existing backoff window
lock = (@retry_lock ||= Mutex.new)
lock.synchronize do
@retry_after = [@retry_after, next_retry_time].compact.max
end
rescue ArgumentError
# ignore invalid Retry-After header
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,10 @@ def initialize(endpoint: nil, custom_headers: nil, instrumentation: nil)

def evaluate_ofrep_api(flag_key:, evaluation_context:)
check_retry_after
evaluation_context = OpenFeature::SDK::EvaluationContext.new if evaluation_context.nil?
# replace targeting_key by targetingKey
evaluation_context.fields["targetingKey"] = evaluation_context.targeting_key
evaluation_context.fields.delete("targeting_key")
request = evaluation_request(evaluation_context)

response = @faraday_connection.post("/ofrep/v1/evaluate/flags/#{flag_key}") do |req|
req.body = {context: evaluation_context.fields}.to_json
req.body = request.to_json
end

case response.status
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,17 @@ module OpenFeature
module GoFeatureFlag
module Client
class UnixApi < Common
attr_accessor :socket

def initialize(endpoint: nil, custom_headers: nil)
def initialize(endpoint: nil, custom_headers: nil, unix_socket_client_factory: nil)
@custom_headers = custom_headers
@socket = HttpUnix.new(endpoint)
@endpoint = endpoint
@unix_socket_client_factory = unix_socket_client_factory || ->(ep) { HttpUnix.new(ep) }
end

def evaluate_ofrep_api(flag_key:, evaluation_context:)
check_retry_after
evaluation_context = OpenFeature::SDK::EvaluationContext.new if evaluation_context.nil?
# replace targeting_key by targetingKey
evaluation_context.fields["targetingKey"] = evaluation_context.targeting_key
evaluation_context.fields.delete("targeting_key")

response = @socket.post("/ofrep/v1/evaluate/flags/#{flag_key}", {context: evaluation_context.fields}, headers)
request = evaluation_request(evaluation_context)
response = thread_local_socket.post("/ofrep/v1/evaluate/flags/#{flag_key}", request, headers)

case response.code
when "200"
Expand All @@ -39,6 +35,13 @@ def evaluate_ofrep_api(flag_key:, evaluation_context:)
raise OpenFeature::GoFeatureFlag::InternalServerError.new(response)
end
end

private

def thread_local_socket
key = "openfeature_goff_unix_socket_#{object_id}"
Thread.current[key] ||= @unix_socket_client_factory.call(@endpoint)
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

RSpec.describe OpenFeature::GoFeatureFlag::Client::UnixApi do
subject(:unix_api) do
described_class.new(endpoint: "/tmp/http.sock")
described_class.new(endpoint: "/tmp/http.sock", unix_socket_client_factory: unix_socket_client_factory)
end
let(:unix_socket_client) { instance_double(HttpUnix) }
let(:unix_socket_client_factory) { ->(_endpoint) { unix_socket_client } }

let(:default_evaluation_context) do
OpenFeature::SDK::EvaluationContext.new(
Expand All @@ -20,7 +22,7 @@
it "should raise an error if rate limited" do
allow(response).to receive(:code).and_return("429")
allow(response).to receive(:[]).with("Retry-After").and_return(nil)
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

expect {
unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
Expand All @@ -29,7 +31,7 @@

it "should raise an error if not authorized (401)" do
allow(response).to receive(:code).and_return("401")
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

expect {
unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
Expand All @@ -38,7 +40,7 @@

it "should raise an error if not authorized (403)" do
allow(response).to receive(:code).and_return("403")
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

expect {
unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
Expand All @@ -47,7 +49,7 @@

it "should raise an error if flag not found (404)" do
allow(response).to receive(:code).and_return("404")
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

expect {
unix_api.evaluate_ofrep_api(flag_key: "does-not-exists", evaluation_context: default_evaluation_context)
Expand All @@ -56,7 +58,7 @@

it "should raise an error if unknown http code (500)" do
allow(response).to receive(:code).and_return("500")
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

expect {
unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
Expand All @@ -71,7 +73,7 @@
}
allow(response).to receive(:code).and_return("400")
allow(response).to receive(:body).and_return(body.to_json)
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

got = unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
want = OpenFeature::GoFeatureFlag::OfrepApiResponse.new(
Expand All @@ -96,7 +98,7 @@
}
allow(response).to receive(:code).and_return("200")
allow(response).to receive(:body).and_return(body.to_json)
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

got = unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
want = OpenFeature::GoFeatureFlag::OfrepApiResponse.new(
Expand All @@ -120,7 +122,7 @@
}
allow(response).to receive(:code).and_return("200")
allow(response).to receive(:body).and_return(body.to_json)
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

expect {
unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
Expand All @@ -136,7 +138,7 @@
}
allow(response).to receive(:code).and_return("200")
allow(response).to receive(:body).and_return(body.to_json)
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

expect {
unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
Expand All @@ -152,7 +154,7 @@
}
allow(response).to receive(:code).and_return("200")
allow(response).to receive(:body).and_return(body.to_json)
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

expect {
unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
Expand All @@ -168,7 +170,7 @@
}
allow(response).to receive(:code).and_return("200")
allow(response).to receive(:body).and_return(body.to_json)
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

expect {
unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
Expand All @@ -181,7 +183,7 @@
}
allow(response).to receive(:code).and_return("400")
allow(response).to receive(:body).and_return(body.to_json)
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

expect {
unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
Expand All @@ -192,7 +194,7 @@
body = {key: "double_key"}
allow(response).to receive(:code).and_return("400")
allow(response).to receive(:body).and_return(body.to_json)
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

expect {
unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
Expand All @@ -202,7 +204,7 @@
it "should not be able to call the API again if rate-limited (with retry-after int)" do
allow(response).to receive(:code).and_return("429")
allow(response).to receive(:[]).with("Retry-After").and_return("10")
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

expect {
unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
Expand All @@ -224,7 +226,7 @@
allow(response).to receive(:code).and_return("429", "200")
allow(response).to receive(:[]).with("Retry-After").and_return("1")
allow(response).to receive(:body).and_return(body.to_json)
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

expect {
unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
Expand All @@ -240,7 +242,7 @@
it "should not be able to call the API again if rate-limited (with retry-after date)" do
allow(response).to receive(:code).and_return("429")
allow(response).to receive(:[]).with("Retry-After").and_return((Time.now + 1).httpdate)
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

expect {
unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
Expand All @@ -262,7 +264,7 @@
allow(response).to receive(:code).and_return("429", "200")
allow(response).to receive(:[]).with("Retry-After").and_return((Time.now + 1).httpdate)
allow(response).to receive(:body).and_return(body.to_json)
allow(unix_api.socket).to receive(:post).and_return(response)
allow(unix_socket_client).to receive(:post).and_return(response)

expect {
unix_api.evaluate_ofrep_api(flag_key: "double_key", evaluation_context: default_evaluation_context)
Expand Down