Packages
statsig_elixir
0.17.1
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
Current section
Files
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