|
1 | 1 | defmodule ElixirProvider do
|
2 |
| - alias OpenFeature.EvaluationDetails |
3 |
| - alias ElixirProvider.ResponseFlagEvaluation |
4 |
| - alias ElixirProvider.GoFeatureFlagMetadata |
5 |
| - alias ElixirProvider.ContextTransformer |
6 |
| - alias ElixirProvider.RequestFlagEvaluation |
| 2 | + @behaviour OpenFeature.Provider |
| 3 | + |
| 4 | + alias OpenFeature.ResolutionDetails |
7 | 5 | alias ElixirProvider.GoFeatureFlagOptions
|
8 |
| - alias ElixirProvider.Types |
| 6 | + alias ElixirProvider.HttpClient |
| 7 | + alias ElixirProvider.DataCollectorHook |
9 | 8 | alias ElixirProvider.CacheController
|
| 9 | + alias ElixirProvider.ResponseFlagEvaluation |
10 | 10 | alias ElixirProvider.GoFWebSocketClient
|
11 |
| - alias ElixirProvider.HttpClient |
| 11 | + alias ElixirProvider.RequestFlagEvaluation |
| 12 | + alias ElixirProvider.ContextTransformer |
| 13 | + alias ElixirProvider.GofEvaluationContext |
12 | 14 |
|
13 | 15 | @moduledoc """
|
14 |
| - The provider for GO Feature Flag, managing HTTP requests, caching, and flag evaluation. |
| 16 | + The GO Feature Flag provider for OpenFeature, managing HTTP requests, caching, and flag evaluation. |
15 | 17 | """
|
16 | 18 |
|
17 | 19 | defstruct [
|
18 | 20 | :options,
|
19 |
| - :_http_client, |
20 |
| - _data_collector_hook: nil, |
21 |
| - _ws: nil, |
| 21 | + :http_client, |
| 22 | + :data_collector_hook, |
| 23 | + :ws, |
| 24 | + :domain |
22 | 25 | ]
|
23 | 26 |
|
24 | 27 | @type t :: %__MODULE__{
|
25 |
| - options: GoFeatureFlagOptions.t(), |
26 |
| - _http_client: HttpClient.t(), |
27 |
| - _data_collector_hook: any(), |
28 |
| - _ws: GoFWebSocketClient.t(), |
29 |
| - } |
| 28 | + options: GoFeatureFlagOptions.t(), |
| 29 | + http_client: HttpClient.t(), |
| 30 | + data_collector_hook: DataCollectorHook.t() | nil, |
| 31 | + ws: GoFWebSocketClient.t(), |
| 32 | + domain: String.t() |
| 33 | + } |
| 34 | + |
| 35 | + @impl true |
| 36 | + def initialize(%__MODULE__{} = provider, domain, _context) do |
| 37 | + {:ok, http_client} = HttpClient.start_http_connection(provider.options) |
| 38 | + CacheController.start_link(provider.options) |
| 39 | + {:ok, data_collector_hook} = DataCollectorHook.start_link(provider.options, http_client) |
| 40 | + {:ok, ws} = GoFWebSocketClient.start_link(provider.options.endpoint) |
| 41 | + |
| 42 | + updated_provider = %__MODULE__{ |
| 43 | + provider |
| 44 | + | domain: domain, |
| 45 | + http_client: http_client, |
| 46 | + data_collector_hook: data_collector_hook, |
| 47 | + ws: ws |
| 48 | + } |
| 49 | + |
| 50 | + {:ok, updated_provider} |
| 51 | + end |
| 52 | + |
| 53 | + @impl true |
| 54 | + def shutdown(%__MODULE__{ws: ws} = provider) do |
| 55 | + Process.exit(ws, :normal) |
| 56 | + CacheController.clear() |
| 57 | + if provider.data_collector_hook, do: DataCollectorHook.shutdown(provider.data_collector_hook) |
| 58 | + :ok |
| 59 | + end |
| 60 | + |
| 61 | + @impl true |
| 62 | + def resolve_boolean_value(provider, key, default, context) do |
| 63 | + generic_resolve(provider, :boolean, key, default, context) |
| 64 | + end |
| 65 | + |
| 66 | + @impl true |
| 67 | + def resolve_string_value(provider, key, default, context) do |
| 68 | + generic_resolve(provider, :string, key, default, context) |
| 69 | + end |
| 70 | + |
| 71 | + @impl true |
| 72 | + def resolve_number_value(provider, key, default, context) do |
| 73 | + generic_resolve(provider, :number, key, default, context) |
| 74 | + end |
| 75 | + |
| 76 | + @impl true |
| 77 | + def resolve_map_value(provider, key, default, context) do |
| 78 | + generic_resolve(provider, :map, key, default, context) |
| 79 | + end |
| 80 | + |
| 81 | + defp generic_resolve(provider, type, flag_key, default_value, context) do |
| 82 | + {:ok, goff_context} = ContextTransformer.transform_context(context) |
| 83 | + goff_request = %RequestFlagEvaluation{user: goff_context, default_value: default_value} |
| 84 | + eval_context_hash = GofEvaluationContext.hash(goff_context) |
| 85 | + |
| 86 | + response_body = |
| 87 | + case CacheController.get(flag_key, eval_context_hash) do |
| 88 | + {:ok, cached_response} -> |
| 89 | + cached_response |
| 90 | + |
| 91 | + :miss -> |
| 92 | + # Fetch from HTTP if cache miss |
| 93 | + case HttpClient.post(provider.http_client, "/v1/feature/#{flag_key}/eval", goff_request) do |
| 94 | + {:ok, response} -> handle_response(flag_key, eval_context_hash, response) |
| 95 | + {:error, reason} -> {:error, {:unexpected_error, reason}} |
| 96 | + end |
| 97 | + end |
| 98 | + |
| 99 | + handle_flag_resolution(response_body, type, flag_key, default_value) |
| 100 | + end |
| 101 | + |
| 102 | + defp handle_response(flag_key, eval_context_hash, response) do |
| 103 | + # Build the flag evaluation struct directly from the response map |
| 104 | + flag_eval = ResponseFlagEvaluation.decode(response) |
| 105 | + |
| 106 | + # Cache the response if it's marked as cacheable |
| 107 | + if flag_eval.cacheable do |
| 108 | + CacheController.set(flag_key, eval_context_hash, response) |
| 109 | + end |
| 110 | + |
| 111 | + {:ok, flag_eval} |
| 112 | + end |
| 113 | + |
| 114 | + defp handle_flag_resolution(response, type, flag_key, _default_value) do |
| 115 | + case response do |
| 116 | + {:ok, %ResponseFlagEvaluation{value: value, reason: reason}} -> |
| 117 | + case {type, value} do |
| 118 | + {:boolean, val} when is_boolean(val) -> |
| 119 | + {:ok, %ResolutionDetails{value: val, reason: reason}} |
| 120 | + |
| 121 | + {:string, val} when is_binary(val) -> |
| 122 | + {:ok, %ResolutionDetails{value: val, reason: reason}} |
| 123 | + |
| 124 | + {:number, val} when is_number(val) -> |
| 125 | + {:ok, %ResolutionDetails{value: val, reason: reason}} |
| 126 | + |
| 127 | + {:map, val} when is_map(val) -> |
| 128 | + {:ok, %ResolutionDetails{value: val, reason: reason}} |
| 129 | + |
| 130 | + _ -> |
| 131 | + {:error, {:variant_not_found, "Expected #{type} but got #{inspect(value)} for flag #{flag_key}"}} |
| 132 | + end |
30 | 133 |
|
| 134 | + _ -> |
| 135 | + {:error, {:flag_not_found, "Flag #{flag_key} not found"}} |
| 136 | + end |
| 137 | + end |
31 | 138 |
|
32 | 139 | end
|
0 commit comments