Packages

statsig_elixir

0.18.0
0.20.3-beta.2607250300 0.20.3-beta.2607240300 0.20.2 0.20.2-rc.2607201629 0.20.2-beta.2607230301 0.20.2-beta.2607220301 0.20.2-beta.2607180300 0.20.2-beta.2607170300 0.20.1 0.20.1-rc.2607140134 0.20.1-rc.2607132133 0.20.1-beta.2607150300 0.20.0 0.19.9-rc.2607090505 0.19.9-rc.2607010924 0.19.9-beta.2607110300 0.19.9-beta.2607100302 0.19.9-beta.2607090302 0.19.9-beta.2607080301 0.19.9-beta.2607020303 0.19.9-beta.2607010304 0.19.9-beta.2606260304 0.19.8 0.19.7-rc.2606232103 0.19.7-beta.2606240303 0.19.7-beta.2606190306 0.19.7-beta.2606180304 0.19.6 0.19.6-rc.2606161624 0.19.6-rc.2606151903 0.19.6-beta.2606170305 0.19.6-beta.2606100304 0.19.5 0.19.5-rc.2606050349 0.19.5-beta.2606060303 0.19.4 0.19.4-rc.2605212136 0.19.3 0.19.3-beta.2604211739 0.19.2 0.19.2-rc.2604140138 0.19.2-beta.2604150312 0.19.1 0.19.1-rc.2604121707 0.19.1-beta.2604130314 0.19.1-beta.2604110309 0.19.1-beta.2604100313 0.19.0 0.18.2-rc.2604080018 0.18.2-beta.2604080312 0.18.1 0.18.1-rc.2604070138 0.18.1-beta.2604072116 0.18.0 0.17.3-rc.2603272156 0.17.3-beta.2603290312 0.17.3-beta.2603280308 0.17.3-beta.2603250310 0.17.2 0.17.2-rc.2603240138 0.17.2-beta.2603241852 0.17.1 0.17.1-rc.2603201903 0.17.1-rc.2603192323 0.17.1-beta.2603210301 0.17.1-beta.2603200305 0.17.1-beta.2603181954 0.17.0 0.16.6-rc.2603180529 0.16.6-rc.2603170138 0.16.6-beta.2603181807 0.16.5 0.16.5-rc.2603131810 0.16.5-beta.2603130304 0.16.4 0.16.4-rc.2603092140 0.16.4-beta.2603110302 0.16.4-beta.2603060304 0.16.4-beta.2603051742 0.16.4-beta.2603040814 0.16.4-beta.2603040303 0.16.3 0.16.3-rc.2602271954 0.16.3-rc.2602270324 0.16.3-rc.2602240138 0.16.3-beta.2603040747 0.16.3-beta.2603021916 0.16.3-beta.2602280254 0.16.3-beta.2602262056 0.16.3-beta.2602250307 0.16.3-beta.2602240529 0.16.2 0.16.2-rc.2602202152 0.16.2-rc.2602200042 0.16.2-beta.2602210300 0.16.2-beta.2602200305 0.16.1 0.16.1-rc.2602180406 0.16.1-beta.2602190307 0.16.0 0.15.2-rc.2602172254 0.15.2-rc.2602140111 0.15.2-rc.2602130016 0.15.2-rc.2602100139 0.15.2-rc.2602030138 0.15.2-beta.2602180309 0.15.2-beta.2602150309 0.15.2-beta.2602140304 0.15.2-beta.2602130310 0.15.2-beta.2602122245 0.15.2-beta.2602122035 0.15.2-beta.2602120040 0.15.2-beta.2602110310 0.15.2-beta.2602050305 0.15.2-beta.2602040304 0.15.2-beta.2602031515 0.15.1 0.15.1-rc.2601270136 0.15.1-beta.2601310133 0.15.1-beta.2601301825 0.15.1-beta.2601300156 0.15.1-beta.2601292313 0.15.1-beta.2601280249 0.15.0 0.14.2-rc.2601230113 0.14.2-rc.2601200136 0.14.2-rc.2601130135 0.14.2-rc.2601062017 0.14.2-rc.2512230135 0.14.2-beta.2601262335 0.14.2-beta.2601240243 0.14.2-beta.2601230247 0.14.2-beta.2601220251 0.14.2-beta.2601210246 0.14.2-beta.2601170240 0.14.2-beta.2601160247 0.14.2-beta.2601140250 0.14.2-beta.2601130116 0.14.2-beta.2601100241 0.14.2-beta.2601070246 0.14.2-beta.2512310244 0.14.2-beta.2512240241 0.14.1 0.14.1-rc.2512190311 0.14.1-rc.2512162207 0.14.1-rc.2512160223 0.14.1-beta.2512200239 0.14.1-beta.2512190242 0.14.1-beta.2512170241 0.14.0 0.13.1-beta.2512140246 0.13.1-beta.2512130201 0.13.1-beta.2512120242 0.13.1-beta.2512110242 0.13.1-beta.2512110007 0.13.1-beta.2512102250 0.13.1-beta.2512102139 0.13.1-beta.2512100241 0.13.1-beta.2512051931 0.13.1-beta.2512050240 0.12.1 0.12.1-rc.2511121816 0.12.1-rc.2511110014 0.12.1-beta.2511110238 0.12.0 0.11.2-rc.2511040014 0.11.2-beta.2511050238 0.11.2-beta.2511032240 0.11.2-beta.2511032212 0.11.2-beta.2511030239 0.11.2-beta.2510310237 0.11.1 0.11.1-rc.2510292219 0.11.1-rc.2510280013 0.11.1-beta.2510300237 0.11.1-beta.2510290239 0.11.0 0.10.3-rc.2510210409 0.10.3-rc.2510210014 0.10.3-rc.6 0.10.3-beta.2510250234 0.10.3-beta.2510240234 0.10.3-beta.2510230235 0.10.3-beta.2510220237 0.10.2 0.10.2-rc.2510141845 0.10.2-beta.2510180231 0.10.2-beta.2510172041 0.10.2-beta.2510170235 0.10.2-beta.2510160235 0.10.2-beta.2510152259 0.10.2-beta.2510152125 0.10.2-beta.2510151849 0.10.2-beta.2510150236 0.10.2-beta.2510142155 0.10.2-beta.2510142023 0.10.1 0.10.0 0.10.0-rc.2510090432 0.10.0-rc.2510070136 0.10.0-beta.2510120235 0.10.0-beta.2510102126 0.10.0-beta.2510100234 0.10.0-beta.2510090234 0.10.0-beta.2510072144 0.9.6-beta.2510020232 0.9.5-rc.2510030411 0.9.5-rc.2510020138 0.9.5-beta.2510010238 0.9.4-rc.2509300113 0.9.4-beta.2509252256 0.9.3 0.9.3-beta.2509250234 0.9.3-beta.2509240232 0.9.3-beta.2509231844 0.9.2-rc.2509221315 0.9.2-beta.2509190233 0.9.2-beta.2509180233 0.9.2-beta.2509180231 0.9.2-beta.2509170231 0.9.1 0.9.1-rc.2509190115 0.9.1-rc.2509181925 0.9.1-rc.2509171909 0.9.0-rc.1 0.8.9-beta.2509161804 0.8.9-beta.2509160231 0.8.8-beta.2509130225 0.8.8-beta.2509112152 0.8.8-beta.2509112049 0.8.8-beta.2509110233 0.8.7 0.8.7-rc.2509102057 0.8.7-rc.2509102017 0.8.7-rc.2509100003 0.8.7-rc.2509092342 0.8.7-beta.2509092208 0.8.6 0.8.6-rc.2509082013 0.8.6-beta.2509040231 0.8.6-beta.2509020235 0.8.5 0.8.5-beta.2508300230 0.8.4 0.8.3 0.8.3-beta.2508280233 0.8.2 0.8.2-rc.2508282224 0.8.1 0.8.1-rc.2508271913 0.8.1-rc.2508262341 0.8.1-rc.2508251314 0.8.0 0.7.4-rc.2508222145 0.7.4-rc.2508220107 0.7.4-rc.2508220104 0.7.4-rc.2508220101 0.7.4-rc.2508220057 0.7.4-rc.2508220049 0.7.4-rc.2508220046 0.7.4-rc.2508220041 0.7.4-rc.2508220033 0.7.4-rc.2508220032 0.7.4-rc.2508220027 0.7.4-rc.2508220022 0.7.4-rc.2508220017 0.7.4-rc.2508220010 0.7.4-rc.2508220006 0.7.4-rc.2508220000 0.7.4-rc.2508212357 0.7.4-rc.2508212349 0.7.4-rc.2508202203 0.7.4-rc.2508202034 0.7.4-rc.2508191829 0.7.4-beta.2508220235 0.7.4-beta.2508210235 0.7.4-beta.2508200235 0.7.4-beta.2508190236 0.7.4-beta.2508160236 0.7.4-beta.2508150240 0.0.6-beta.7 0.0.6-beta.6 0.0.2 retired 0.0.1 retired

A performant elixir SDK for Statsig feature gates and experiments using Rustler

Current section

Files

Jump to
statsig_elixir lib statsig data_store.ex
Raw

lib/statsig/data_store.ex

defmodule Statsig.DataStore.Reference do
@moduledoc false
@enforce_keys [:pid]
defstruct [:pid]
@type t :: %__MODULE__{pid: pid()}
end
defmodule Statsig.DataStore.Response do
@moduledoc """
Response struct returned from `handle_get/2`.
"""
defstruct result: nil, time: nil
@type t :: %__MODULE__{result: nil | String.t(), time: nil | non_neg_integer()}
end
defmodule Statsig.DataStore do
@moduledoc """
Behaviour and helper APIs for implementing a custom Statsig data store in Elixir.
A data store implementation is a module that implements the callbacks in this behaviour.
Use `start_link/3` to launch the bridge process and pass the returned
`%Statsig.DataStore.Reference{}` into `Statsig.Options`.
"""
alias Statsig.DataStore.Reference
alias Statsig.DataStore.Server
@typedoc "Opaque state returned from user callbacks."
@type state :: term()
@callback init(init_arg :: term()) :: {:ok, state()} | {:error, term()}
@callback handle_initialize(state()) :: {:ok, state()} | {:error, term()}
@callback handle_shutdown(state()) :: {:ok, state()} | {:error, term()}
@callback handle_get(String.t(), state()) ::
{:ok, %Statsig.DataStore.Response{}, state()} | {:error, term()}
@callback handle_set(String.t(), String.t(), non_neg_integer() | nil, state()) ::
{:ok, state()} | {:error, term()}
@callback handle_support_polling_updates_for(String.t(), state()) ::
{:ok, boolean(), state()} | {:error, term()}
@optional_callbacks handle_initialize: 1,
handle_shutdown: 1,
handle_set: 4,
handle_support_polling_updates_for: 2
@doc """
Starts a bridge process for the provided implementation module.
Returns `{:ok, %Statsig.DataStore.Reference{}}` which can be assigned to
`%Statsig.Options{data_store: reference}`.
"""
@spec start_link(module(), term(), Keyword.t()) ::
{:ok, Reference.t()} | {:error, term()}
def start_link(module, init_arg \\ nil, opts \\ []) do
case Server.start_link(module, init_arg, opts) do
{:ok, pid} -> {:ok, %Reference{pid: pid}}
other -> other
end
end
@doc """
Stops the bridge process for the given reference.
"""
@spec stop(Reference.t(), term(), non_neg_integer()) :: :ok
def stop(%Reference{pid: pid}, reason \\ :normal, timeout \\ 5_000) do
GenServer.stop(pid, reason, timeout)
end
end
defmodule Statsig.DataStore.Server do
@moduledoc false
use GenServer
require Logger
alias Statsig.DataStore.Response
@request_tag :statsig_data_store_request
@default_bindings Statsig.NativeBindings
@spec start_link(module(), term(), Keyword.t()) :: GenServer.on_start()
def start_link(module, init_arg, opts) do
{bindings_module, gen_server_opts} =
Keyword.put_new(opts, :bindings_module, @default_bindings)
|> Keyword.pop(:bindings_module)
GenServer.start_link(
__MODULE__,
%{module: module, module_state: nil, bindings_module: bindings_module, init_arg: init_arg},
gen_server_opts
)
end
@impl true
def init(%{module: module, init_arg: init_arg} = state) do
case safe_apply(module, :init, [init_arg]) do
{:ok, module_state} ->
{:ok,
state
|> Map.put(:module_state, module_state)
|> Map.delete(:init_arg)}
{:error, reason} ->
{:stop, reason}
other ->
{:stop, {:bad_return, {module, :init, other}}}
end
end
@impl true
def handle_info(
{@request_tag, request_ref, request_type, payload},
%{module: module} = state
) do
case dispatch(request_type, payload, state) do
{:ok, reply_payload, new_module_state} ->
send_reply(state.bindings_module, request_ref, reply_payload)
{:noreply, %{state | module_state: new_module_state}}
{:error, reason} ->
send_error(state.bindings_module, request_ref, reason)
{:noreply, state}
:unknown ->
send_error(
state.bindings_module,
request_ref,
"Unknown data store request #{inspect(request_type)}"
)
{:noreply, state}
end
end
@impl true
def handle_info(message, state) do
Logger.debug("Statsig.DataStore.Server received unexpected message: #{inspect(message)}")
{:noreply, state}
end
defp dispatch(:initialize, _payload, state) do
case call_without_value(state.module, :handle_initialize, 1, [state.module_state]) do
{:ok, new_state} -> {:ok, :ok, new_state}
:noop -> {:ok, :ok, state.module_state}
{:error, reason} -> {:error, reason}
end
end
defp dispatch(:shutdown, _payload, state) do
case call_without_value(state.module, :handle_shutdown, 1, [state.module_state]) do
{:ok, new_state} -> {:ok, :ok, new_state}
:noop -> {:ok, :ok, state.module_state}
{:error, reason} -> {:error, reason}
end
end
defp dispatch(:get, key, state) do
case maybe_call(state.module, :handle_get, 2, [key, state.module_state], :missing) do
:missing ->
{:error, "handle_get/2 is not implemented for #{inspect(state.module)}"}
{:ok, %Response{} = response, new_state} ->
{:ok, response, new_state}
{:ok, %Response{} = response} ->
{:ok, response, state.module_state}
{:error, reason} ->
{:error, reason}
other ->
{:error, {:bad_return, {state.module, :handle_get, other}}}
end
end
defp dispatch(:set, {key, value, time}, state) do
case call_without_value(
state.module,
:handle_set,
4,
[key, value, time, state.module_state]
) do
{:ok, new_state} -> {:ok, :ok, new_state}
:noop -> {:ok, :ok, state.module_state}
{:error, reason} -> {:error, reason}
end
end
defp dispatch(:support_polling_updates_for, path, state) do
default = {:ok, false, state.module_state}
case maybe_call(
state.module,
:handle_support_polling_updates_for,
2,
[path, state.module_state],
default
) do
{:ok, bool, new_state} when is_boolean(bool) ->
{:ok, bool, new_state}
{:ok, bool} when is_boolean(bool) ->
{:ok, bool, state.module_state}
{:ok, other, _new_state} ->
{:error, {:invalid_return, other}}
{:error, reason} ->
{:error, reason}
other ->
{:error, {:bad_return, {state.module, :handle_support_polling_updates_for, other}}}
end
end
defp dispatch(_type, _payload, _state), do: :unknown
defp call_without_value(module, fun, arity, args) do
case maybe_call(module, fun, arity, args, :ok) do
{:ok, new_state} -> {:ok, new_state}
{:error, reason} -> {:error, reason}
:ok -> :noop
other -> {:error, {:bad_return, {module, fun, other}}}
end
end
defp maybe_call(module, fun, arity, args, default) do
if function_exported?(module, fun, arity) do
safe_apply(module, fun, args)
else
default
end
end
defp safe_apply(module, fun, args) do
try do
apply(module, fun, args)
rescue
exception -> {:error, Exception.message(exception)}
catch
kind, reason -> {:error, {kind, reason}}
end
end
defp send_reply({module, context}, request_ref, payload) do
_ = module.data_store_reply(context, request_ref, payload)
end
defp send_reply(module, request_ref, payload) do
_ = module.data_store_reply(request_ref, payload)
end
defp send_error({module, context}, request_ref, reason) do
reason = format_reason(reason)
_ = module.data_store_reply_error(context, request_ref, reason)
end
defp send_error(module, request_ref, reason) do
reason = format_reason(reason)
_ = module.data_store_reply_error(request_ref, reason)
end
defp format_reason(reason) when is_binary(reason), do: reason
defp format_reason({:bad_return, {module, fun, value}}),
do: "Invalid return from #{inspect(module)}.#{fun}: #{inspect(value)}"
defp format_reason(reason), do: inspect(reason)
end