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
68 changes: 47 additions & 21 deletions lib/open_feature/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ defmodule OpenFeature.Client do
try do
handler.(%{provider: client.provider.name, domain: client.domain})
rescue
e -> Logger.error("Error running event handler. Error: #{e}")
e -> Logger.error("Error running event handler. Error: #{inspect(e)}")
end
end

Expand Down Expand Up @@ -260,23 +260,17 @@ defmodule OpenFeature.Client do
try do
before_hooks(all_hooks, hook_context, opts)

short_circuit_if_not_ready(client)

evaluation_details = evaluation_details(client, type, key, default, merged_context)

after_hooks(all_hooks, hook_context, evaluation_details, opts)

evaluation_details
rescue
e ->
Logger.error("An error happened. #{inspect(e)}")
error_hooks(all_hooks, hook_context, e, opts)

%EvaluationDetails{
key: key,
value: default,
reason: :error,
error_code: :general,
error_message: Exception.message(e)
}
handle_evaluate_error(e, key, default)
after
all_hooks
|> Enum.reverse()
Expand All @@ -290,6 +284,14 @@ defmodule OpenFeature.Client do
|> Map.fetch!(:value)
end

defp short_circuit_if_not_ready(%__MODULE__{provider: provider}) do
case Map.get(provider, :state) do
:not_ready -> raise "provider_not_ready"
:fatal -> raise "provider_fatal"
_state -> :ok
end
end

defp evaluation_details(client, type, key, default, context) do
details =
client
Expand All @@ -316,24 +318,22 @@ defmodule OpenFeature.Client do
error_message: "flag not found"
}

{:error, :variant_not_found, _variant} ->
{:error, :variant_not_found, variant} ->
%EvaluationDetails{
key: key,
value: default,
reason: :error,
error_code: :general,
error_message: "variant not found"
error_message: "variant not found, variant: #{inspect(variant)}"
}

{:error, :unexpected_error, error} ->
Logger.error("Unexpected error happened while resolving value. Error: #{error}")

%EvaluationDetails{
key: key,
value: default,
reason: :error,
error_code: :general,
error_message: "unexpected error"
error_message: "unexpected error, error: #{inspect(error)}"
}
end
end
Expand Down Expand Up @@ -367,9 +367,7 @@ defmodule OpenFeature.Client do
try do
error_hook.(hook_context, error, hook_hints)
rescue
e ->
Logger.error("Unhandled error during 'error' hook: #{inspect(e)}")
Logger.error(inspect(__STACKTRACE__))
_e -> :ok
end
end)
end
Expand All @@ -385,10 +383,38 @@ defmodule OpenFeature.Client do
try do
finally.(hook_context, hook_hints)
rescue
e ->
Logger.error("Unhandled error during 'finally' hook: #{inspect(e)}")
Logger.error(inspect(__STACKTRACE__))
_e -> :ok
end
end)
end

defp handle_evaluate_error(%RuntimeError{message: "provider_not_ready"}, key, default) do
%EvaluationDetails{
key: key,
value: default,
reason: :error,
error_code: :provider_not_ready,
error_message: "provider not ready"
}
end

defp handle_evaluate_error(%RuntimeError{message: "provider_fatal"}, key, default) do
%EvaluationDetails{
key: key,
value: default,
reason: :error,
error_code: :provider_fatal,
error_message: "provider fatal"
}
end

defp handle_evaluate_error(e, key, default) do
%EvaluationDetails{
key: key,
value: default,
reason: :error,
error_code: :general,
error_message: Exception.message(e)
}
end
end
2 changes: 1 addition & 1 deletion lib/open_feature/types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ defmodule OpenFeature.Types do
| :general
@type reason :: :static | :default | :targeting_match | :split | :cached | :disabled | :unknown | :stale | :error
@type flag_metadata :: %{binary => boolean | binary | number}
@type provider_status :: :not_ready | :ready | :stale
@type provider_status :: :not_ready | :ready | :error | :stale | :fatal
@type event_type :: :ready | :error | :configuration_changed | :stale
@type event_details :: %{:provider => binary, optional(:domain) => binary, optional(binary | atom) => any}
@type event_handler :: (event_details -> any())
Expand Down
2 changes: 1 addition & 1 deletion test/integration/value_resolution_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ defmodule Integration.ValueResolutionTest do
variant: nil,
reason: :error,
error_code: :general,
error_message: "variant not found"
error_message: "variant not found, variant: \"variant2\""
} =
Client.get_string_details(client, "target_key", "default", context: %{variant: "variant2"})
end
Expand Down
40 changes: 37 additions & 3 deletions test/unit/open_feature/client_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ defmodule OpenFeature.ClientTest do
{:ok, client: client}
end

describe("Public API") do
describe "Public API" do
test "set_context/2 sets the context", %{client: client} do
context = %{user: "test_user"}
updated_client = Client.set_context(client, context)
Expand Down Expand Up @@ -439,7 +439,7 @@ defmodule OpenFeature.ClientTest do
value: ^default,
reason: :error,
error_code: :general,
error_message: "variant not found"
error_message: "variant not found, variant: \"variant\""
} = Client.get_boolean_details(client, key, default)
end

Expand All @@ -456,7 +456,41 @@ defmodule OpenFeature.ClientTest do
value: ^default,
reason: :error,
error_code: :general,
error_message: "unexpected error"
error_message: "unexpected error, error: :error"
} = Client.get_boolean_details(client, key, default)
end

test "returns evaluation details with default value if provider is not ready", %{client: client} do
key = "key"
default = true

client = put_in(client.provider.state, :not_ready)

reject(Provider, :resolve_value, 5)

assert %EvaluationDetails{
key: ^key,
value: ^default,
reason: :error,
error_code: :provider_not_ready,
error_message: "provider not ready"
} = Client.get_boolean_details(client, key, default)
end

test "returns evaluation details with default value if provider is fatal", %{client: client} do
key = "key"
default = true

client = put_in(client.provider.state, :fatal)

reject(Provider, :resolve_value, 5)

assert %EvaluationDetails{
key: ^key,
value: ^default,
reason: :error,
error_code: :provider_fatal,
error_message: "provider fatal"
} = Client.get_boolean_details(client, key, default)
end
end
Expand Down