Packages
launchdarkly_server_sdk
2.1.2
3.11.0
3.10.1
3.9.0
3.8.1
3.8.0
3.7.2
3.7.1
3.7.0
3.6.0
3.5.0
3.4.0
3.3.1
3.3.0
3.2.0
3.1.0
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
2.1.2
2.1.1
2.1.0
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.6.0
1.5.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.3
1.1.2
1.1.1
retired
1.1.0
retired
1.0.1
retired
1.0.0
retired
1.0.0-beta4
retired
1.0.0-beta3
retired
1.0.0-beta2
retired
LaunchDarkly SDK for Erlang
Current section
Files
Jump to
Current section
Files
src/ldclient_storage_map_server.erl
%%-------------------------------------------------------------------
%% @doc `ldclient_storage_map_server' module
%% @private
%% This is a simple in-memory implementation of an storage server using Erlang
%% map.
%% @end
%%-------------------------------------------------------------------
-module(ldclient_storage_map_server).
-behaviour(gen_server).
%% Supervision
-export([start_link/2, init/1]).
%% Behavior callbacks
-export([code_change/3, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
%% API
-export([create/2]).
-export([empty/2]).
-export([get/3]).
-export([all/2]).
-export([upsert/3]).
-export([upsert_clean/3]).
-export([delete/3]).
%% Types
-type state() :: #{
data => map(),
tag => atom()
}.
%%===================================================================
%% Supervision
%%===================================================================
start_link(WorkerRegName, Tag) ->
error_logger:info_msg("Starting map server with name ~p", [WorkerRegName]),
gen_server:start_link({local, WorkerRegName}, ?MODULE, [Tag], []).
init([Tag]) ->
true = ldclient_update_processor_state:set_storage_initialized_state(Tag, true),
{ok, #{data => #{}, tag => Tag}}.
%%===================================================================
%% API
%%===================================================================
%% @doc Create a bucket
%%
%% @end
-spec create(ServerRef :: atom(), Bucket :: atom()) ->
ok |
{error, already_exists, string()}.
create(ServerRef, Bucket) when is_atom(Bucket) ->
gen_server:call(ServerRef, {create, Bucket}).
%% @doc Empty a bucket
%%
%% @end
-spec empty(ServerRef :: atom(), Bucket :: atom()) ->
ok |
{error, bucket_not_found, string()}.
empty(ServerRef, Bucket) when is_atom(Bucket) ->
gen_server:call(ServerRef, {empty, Bucket}).
%% @doc Get an item from the bucket by its key
%%
%% @end
-spec get(ServerRef :: atom(), Bucket :: atom(), Key :: binary()) ->
[{Key :: binary(), Value :: any()}] |
{error, bucket_not_found, string()}.
get(ServerRef, Bucket, Key) when is_atom(Bucket), is_binary(Key) ->
gen_server:call(ServerRef, {get, Bucket, Key}).
%% @doc List all items in a bucket
%%
%% @end
-spec all(ServerRef :: atom(), Bucket :: atom()) ->
[{Key :: binary(), Value :: any()}] |
{error, bucket_not_found, string()}.
all(ServerRef, Bucket) when is_atom(Bucket) ->
gen_server:call(ServerRef, {all, Bucket}).
%% @doc Upsert item key value pairs in an existing bucket
%%
%% @end
-spec upsert(ServerRef :: atom(), Bucket :: atom(), Items :: #{Key :: binary() => Value :: any()}) ->
ok |
{error, bucket_not_found, string()}.
upsert(ServerRef, Bucket, Items) when is_atom(Bucket), is_map(Items) ->
ok = gen_server:call(ServerRef, {upsert, Bucket, Items}).
%% @doc Perform an atomic empty and upsert
%%
%% @end
-spec upsert_clean(ServerRef :: atom(), Bucket :: atom(), Items :: #{Key :: binary() => Value :: any()}) ->
ok |
{error, bucket_not_found, string()}.
upsert_clean(ServerRef, Bucket, Items) when is_atom(Bucket), is_map(Items) ->
ok = gen_server:call(ServerRef, {upsert_clean, Bucket, Items}).
%% @doc Delete an item from the bucket by its key
%%
%% @end
-spec delete(ServerRef :: atom(), Bucket :: atom(), Key :: binary()) ->
ok |
{error, bucket_not_found, string()}.
delete(ServerRef, Bucket, Key) ->
ok = gen_server:call(ServerRef, {delete, Bucket, Key}).
%%===================================================================
%% Behavior callbacks
%%===================================================================
-type from() :: {pid(), term()}.
-spec handle_call(term(), from(), state()) -> {reply, term(), state()}.
handle_call({create, Bucket}, _From, #{data := Data} = State) ->
case create_bucket(Bucket, Data) of
{error, already_exists, Error} ->
{reply, {error, already_exists, Error}, State};
{ok, NewData} ->
{reply, ok, maps:update(data, NewData, State)}
end;
handle_call({empty, Bucket}, _From, #{data := Data} = State) ->
case empty_bucket(Bucket, Data) of
{error, bucket_not_found, Error} ->
{reply, {error, bucket_not_found, Error}, State};
{ok, NewData} ->
{reply, ok, maps:update(data, NewData, State)}
end;
handle_call({get, Bucket, Key}, _From, #{data := Data} = State) ->
{reply, lookup_key(Key, Bucket, Data), State};
handle_call({all, Bucket}, _From, #{data := Data} = State) ->
{reply, all_items(Bucket, Data), State};
handle_call({upsert, Bucket, Items}, _From, #{data := Data} = State) ->
case upsert_items(Items, Bucket, Data) of
{error, bucket_not_found, Error} ->
{reply, {error, bucket_not_found, Error}, State};
{ok, NewData} ->
{reply, ok, maps:update(data, NewData, State)}
end;
handle_call({upsert_clean, Bucket, Items}, _From, #{data := Data} = State) ->
case upsert_clean_items(Items, Bucket, Data) of
{error, bucket_not_found, Error} ->
{reply, {error, bucket_not_found, Error}, State};
{ok, NewData} ->
{reply, ok, maps:update(data, NewData, State)}
end;
handle_call({delete, Bucket, Key}, _From, #{data := Data} = State) ->
{reply, delete_key(Key, Bucket, Data), State}.
handle_cast(_Msg, State) -> {noreply, State}.
handle_info(_Msg, State) -> {noreply, State}.
terminate(_Reason, #{tag := Tag} = _State) ->
true = ldclient_update_processor_state:set_storage_initialized_state(Tag, reload),
ok.
code_change(_OldVersion, State, _Extra) -> {ok, State}.
%%===================================================================
%% Internal functions
%%===================================================================
%% @doc Check whether bucket already exists
%% @private
%%
%% @end
-spec bucket_exists(Bucket :: atom(), Data :: map()) -> boolean().
bucket_exists(Bucket, Data) when is_atom(Bucket) ->
maps:is_key(Bucket, Data).
%% @doc Create a bucket
%% @private
%%
%% If bucket with the same name already exists an error will be returned.
%% @end
-spec create_bucket(Bucket :: atom(), Data :: map()) ->
{ok, NewData :: map()} |
{error, already_exists, string()}.
create_bucket(Bucket, Data) when is_atom(Bucket) ->
case bucket_exists(Bucket, Data) of
false ->
{ok, maps:put(Bucket, #{}, Data)};
_ ->
{error, already_exists, "Map " ++ atom_to_list(Bucket) ++ " already exists."}
end.
%% @doc Empty a bucket
%% @private
%%
%% If no such bucket exists, error will be returned.
%% @end
-spec empty_bucket(Bucket :: atom(), Data :: map()) ->
{ok, NewData :: map()} |
{error, bucket_not_found, string()}.
empty_bucket(Bucket, Data) when is_atom(Bucket) ->
case bucket_exists(Bucket, Data) of
false ->
{error, bucket_not_found, "Map " ++ atom_to_list(Bucket) ++ " does not exist."};
_ ->
{ok, maps:update(Bucket, #{}, Data)}
end.
%% @doc List all items in a bucket
%% @private
%%
%% Returns all items stored in the bucket.
%% @end
-spec all_items(Bucket :: atom(), Data :: map()) ->
[tuple()] |
{error, bucket_not_found, string()}.
all_items(Bucket, Data) when is_atom(Bucket) ->
case bucket_exists(Bucket, Data) of
false ->
{error, bucket_not_found, "Map " ++ atom_to_list(Bucket) ++ " does not exist."};
_ ->
maps:to_list(maps:get(Bucket, Data))
end.
%% @doc Look up an item by key
%% @private
%%
%% Search for an item by its key.
%% @end
-spec lookup_key(Key :: binary(), Bucket :: atom(), Data :: map()) ->
[tuple()] |
{error, bucket_not_found, string()}.
lookup_key(Key, Bucket, Data) when is_atom(Bucket), is_binary(Key) ->
case bucket_exists(Bucket, Data) of
false ->
{error, bucket_not_found, "Map " ++ atom_to_list(Bucket) ++ " does not exist."};
_ ->
BucketMap = maps:get(Bucket, Data),
try maps:get(Key, BucketMap) of
Value -> [{Key, Value}]
catch
error:{badkey, Key} -> []
end
end.
%% @doc Upsert key value pairs in bucket
%% @private
%%
%% @end
-spec upsert_items(Items :: #{Key :: binary() => Value :: any()}, Bucket :: atom(), Data :: map()) ->
{ok, NewData :: map()} |
{error, bucket_not_found, string()}.
upsert_items(Items, Bucket, Data) when is_map(Items), is_atom(Bucket) ->
case bucket_exists(Bucket, Data) of
false ->
{error, bucket_not_found, "Map " ++ atom_to_list(Bucket) ++ " does not exist."};
_ ->
BucketMap = maps:get(Bucket, Data),
NewBucketData = maps:merge(BucketMap, Items),
{ok, maps:update(Bucket, NewBucketData, Data)}
end.
%% @doc Empty bucket and upsert key value pairs
%% @private
%%
%% @end
-spec upsert_clean_items(Items :: #{Key :: binary() => Value :: any()}, Bucket :: atom(), Data :: map()) ->
{ok, NewData :: map()} |
{error, bucket_not_found, string()}.
upsert_clean_items(Items, Bucket, Data) when is_map(Items), is_atom(Bucket) ->
case bucket_exists(Bucket, Data) of
false ->
{error, bucket_not_found, "Map " ++ atom_to_list(Bucket) ++ " does not exist."};
_ ->
{ok, maps:update(Bucket, Items, Data)}
end.
%% @doc Delete an item by key
%% @private
%%
%% Delete an item by its key.
%% @end
-spec delete_key(Key :: binary(), Bucket :: atom(), Data :: map()) ->
ok |
{error, bucket_not_found, string()}.
delete_key(Key, Bucket, Data) when is_atom(Bucket), is_binary(Key) ->
case bucket_exists(Bucket, Data) of
false ->
{error, bucket_not_found, "Map " ++ atom_to_list(Bucket) ++ " does not exist."};
_ ->
BucketMap = maps:get(Bucket, Data),
{ok, maps:remove(Key, BucketMap)}
end.