Current section

Files

Jump to
launchdarkly_server_sdk src ldclient.erl
Raw

src/ldclient.erl

%%-------------------------------------------------------------------
%% @doc `ldclient' module
%%
%% Acts as an interface to most common SDK functions: starting and stopping
%% client instances, and evaluating feature flags for contexts.
%%
%% Most use cases only need a single client instance for the lifetime of
%% their application. Consider using multiple instances only if you need to
%% simultaneously access more than one environment. Do not start an instance
%% every time you need to make a variation or other SDK call.
%% @end
%%-------------------------------------------------------------------
-module(ldclient).
%% API
-export([start_instance/1]).
-export([start_instance/2]).
-export([start_instance/3]).
-export([stop_all_instances/0]).
-export([stop_instance/0]).
-export([stop_instance/1]).
-export([is_offline/1]).
-export([initialized/1]).
-export([variation/3]).
-export([variation/4]).
-export([variation_detail/3]).
-export([variation_detail/4]).
-export([all_flags_state/1]).
-export([all_flags_state/2]).
-export([all_flags_state/3]).
-export([identify/1]).
-export([identify/2]).
-export([track/3]).
-export([track/4]).
-export([track_metric/4]).
-export([track_metric/5]).
%% Constants
-define(DEFAULT_INSTANCE_NAME, default).
%%===================================================================
%% API
%%===================================================================
%% @doc Start client with default options
%%
%% The SDK key is required to connect. Default streamer URL, storage backend
%% and instance name `default' will be used.
%% @end
-spec start_instance(SdkKey :: string()) -> ok | {error, atom(), term()}.
start_instance(SdkKey) ->
start_instance(SdkKey, ?DEFAULT_INSTANCE_NAME).
%% @doc Start client with custom name or options
%%
%% When `TagOrOptions' is an atom, the instance is started with that name. When
%% it's a map, it can be used to start with custom options.
%% @end
-spec start_instance(SdkKey :: string(), TagOrOptions :: atom() | map()) ->
ok | {error, atom(), term()}.
start_instance(SdkKey, Tag) when is_list(SdkKey), is_atom(Tag) ->
start_instance(SdkKey, Tag, #{});
start_instance(SdkKey, Options) when is_list(SdkKey), is_map(Options) ->
start_instance(SdkKey, ?DEFAULT_INSTANCE_NAME, Options).
%% @doc Start client with custom name and options
%%
%% Specify both custom client name and options when starting the client.
%% @end
-spec start_instance(SdkKey :: string(), Tag :: atom(), Options :: map()) ->
ok | {error, atom(), term()}.
start_instance(SdkKey, Tag, Options) when is_list(SdkKey), is_atom(Tag), is_map(Options) ->
ldclient_instance:start(Tag, SdkKey, Options).
%% @doc Stop all client instances
%%
%% @end
-spec stop_all_instances() -> ok.
stop_all_instances() ->
ldclient_instance:stop_all().
%% @doc Stop client instance
%%
%% Stops the default client instance.
%% @end
-spec stop_instance() -> ok | {error, not_found, term()}.
stop_instance() ->
stop_instance(?DEFAULT_INSTANCE_NAME).
%% @doc Stop client with the custom name
%%
%% This is useful if a client instance was started with a custom name.
%% @end
-spec stop_instance(Tag :: atom()) -> ok | {error, not_found, term()}.
stop_instance(Tag) when is_atom(Tag) ->
ldclient_instance:stop(Tag).
%% @doc Returns whether the LaunchDarkly client is in offline mode.
%%
%% In some situations, you might want to stop making remote calls to LaunchDarkly and
%% fall back to default values for your feature flags, this tells you whether only default
%% values will be returned.
%% @end
-spec is_offline(Tag :: atom()) -> boolean().
is_offline(Tag) ->
ldclient_config:get_value(Tag, offline).
%% @doc Returns whether the LaunchDarkly client has initialized.
%%
%% If this value is true, it means the client has succeeded at some point in connecting to LaunchDarkly and
%% has received feature flag data. It could still have encountered a connection problem after that point, so
%% this does not guarantee that the flags are up to date. Alternatively, it could also mean that the client
%% is in offline mode.
%%
%% If this value is false, it means the client has not yet connected to LaunchDarkly, or has permanently
%% failed. In this state, feature flag evaluations will always return default values.
%% @end
-spec initialized(Tag :: atom()) -> boolean().
initialized(Tag) ->
IsOffline = is_offline(Tag),
UpdateProcessorInitialized = ldclient_instance:update_processor_initialized(Tag),
IsOffline or UpdateProcessorInitialized.
%% @doc Evaluate given flag key for given context
%%
%% Evaluates the flag and returns the resulting variation value. The default
%% value will be returned in case of any errors.
%% @end
-spec variation(FlagKey :: binary(), Context :: ldclient_user:user() | ldclient_context:context(), DefaultValue :: ldclient_eval:result_value()) ->
ldclient_eval:result_value().
variation(FlagKey, Context, DefaultValue) when is_binary(FlagKey), is_map(Context) ->
variation(FlagKey, ensure_context(Context), DefaultValue, ?DEFAULT_INSTANCE_NAME).
%% @doc Evaluate given flag key for given context and given client instance
%%
%% Evaluates the flag and returns the resulting variation value. The default
%% value will be returned in case of any errors.
%% @end
-spec variation(FlagKey :: binary(), Context :: ldclient_user:user() | ldclient_context:context(), DefaultValue :: ldclient_eval:result_value(), Tag :: atom()) ->
ldclient_eval:result_value().
variation(FlagKey, Context, DefaultValue, Tag) when is_binary(FlagKey), is_map(Context) ->
% Get evaluation result detail
{{_Index, Value, _Reason}, Events} = ldclient_eval:flag_key_for_context(Tag, FlagKey, ensure_context(Context), DefaultValue),
% Send events
SendEventsFun = fun(Event) -> ldclient_event_server:add_event(Tag, Event, #{}) end,
lists:foreach(SendEventsFun, Events),
% Return evaluation result
Value.
%% @doc Evaluate given flag key for given context
%%
%% Evaluates the flag and returns the result detail containing the variation
%% index, value, and reason why the specific result was chosen. The default
%% value will be returned in case of any errors.
%% @end
-spec variation_detail(FlagKey :: binary(), Context :: ldclient_user:user() | ldclient_context:context(), DefaultValue :: ldclient_eval:result_value()) ->
ldclient_eval:detail().
variation_detail(FlagKey, Context, DefaultValue) when is_binary(FlagKey), is_map(Context) ->
variation_detail(FlagKey, ensure_context(Context), DefaultValue, ?DEFAULT_INSTANCE_NAME).
%% @doc Evaluate given flag key for given context and given client instance
%%
%% Evaluates the flag and returns the result detail containing the variation
%% index, value, and reason why the specific result was chosen. The default
%% value will be returned in case of any errors.
%% @end
-spec variation_detail(FlagKey :: binary(), Context :: ldclient_user:user() | ldclient_context:context(), DefaultValue :: ldclient_eval:result_value(), Tag :: atom()) ->
ldclient_eval:detail().
variation_detail(FlagKey, Context, DefaultValue, Tag) when is_binary(FlagKey), is_map(Context) ->
% Get evaluation result detail
{Detail, Events} = ldclient_eval:flag_key_for_context(Tag, FlagKey, ensure_context(Context), DefaultValue),
% Send events
SendEventsFun = fun(Event) -> ldclient_event_server:add_event(Tag, Event, #{include_reasons => true}) end,
lists:foreach(SendEventsFun, Events),
% Return evaluation detail
Detail.
%% @doc Evaluate all flags for a given context and return their values
%%
%% Evaluates all existing flags, but does not create any events as a side
%% effect of the evaluation. It returns a map of flag keys to evaluated values.
%% @end
-spec all_flags_state(Context :: ldclient_user:user() | ldclient_context:context()) -> ldclient_eval:feature_flags_state().
all_flags_state(Context) ->
all_flags_state(Context, ?DEFAULT_INSTANCE_NAME).
%% @doc Evaluate all flags for a given context and given client instance
%%
%% Evaluates all existing flags, but does not create any events as a side
%% effect of the evaluation. It returns a map of flag keys to evaluated values.
%% @end
-spec all_flags_state(Context :: ldclient_user:user() | ldclient_context:context(), Tag :: atom()) -> ldclient_eval:feature_flags_state().
all_flags_state(Context, Tag) ->
ldclient_eval:all_flags_eval(ensure_context(Context), Tag).
%% @doc Returns an object that encapsulates the state of all feature flags for a given context.
%%
%% This includes the flag values, and also metadata that can be used on the front end.
%% The most common use case for this method is to bootstrap a set of client-side feature flags from a
%% back-end service.
%%
%% If you are not using this to boostrap a client, then you likely want all_flags_state/1 or all_flags_state/2.
%% @end
-spec all_flags_state(Context :: ldclient_user:user() | ldclient_context:context(),
Options :: ldclient_eval:all_flags_state_options(), Tag :: atom()) -> ldclient_eval:feature_flags_state().
all_flags_state(Context, Options, Tag) ->
ldclient_eval:all_flags_state(ensure_context(Context), Options, Tag).
%% @doc Identify reports details about a context
%%
%% This function uses the default client instance.
%% @end
-spec identify(Context :: ldclient_user:user() | ldclient_context:context()) -> ok.
identify(Context) ->
identify(ensure_context(Context), ?DEFAULT_INSTANCE_NAME).
%% @doc Identify reports details about a context
%%
%% This is useful to report context to a specific client instance.
%% @end
-spec identify(Context :: ldclient_user:user() | ldclient_context:context(), Tag :: atom()) -> ok.
identify(Context, Tag) when is_atom(Tag) ->
ConvertedContext = ensure_context(Context),
when_is_valid_context(ConvertedContext, false, fun() ->
Event = ldclient_event:new_identify(ConvertedContext),
ldclient_event_server:add_event(Tag, Event, #{})
end).
%% @doc Track reports that a context has performed an event
%%
%% Custom data can be attached to the event.
%% @end
-spec track(Key :: binary(), Context :: ldclient_user:user() | ldclient_context:context(), Data :: ldclient_event:event_data()) -> ok.
track(Key, Context, Data) when is_binary(Key), is_map(Data) ->
track(Key, ensure_context(Context), Data, ?DEFAULT_INSTANCE_NAME).
%% @doc Track reports that a context has performed an event
%%
%% This is useful for specifying a specific client instance.
%% @end
-spec track(Key :: binary(), Context :: ldclient_user:user() | ldclient_context:context(), Data :: ldclient_event:event_data(), Tag :: atom()) -> ok.
track(Key, Context, Data, Tag) when is_atom(Tag), is_binary(Key) ->
ConvertedContext = ensure_context(Context),
when_is_valid_context(ConvertedContext, false, fun() ->
Event = ldclient_event:new_custom(Key, ensure_context(Context), Data),
ldclient_event_server:add_event(Tag, Event, #{})
end).
%% @doc Reports that a context has performed an event, and associates it with a numeric value.
%%
%% This value is used by the LaunchDarkly experimentation feature in numeric custom metrics, and will also
%% be returned as part of the custom event for Data Export.
%%
%% Custom data can also be attached to the event.
%% @end
-spec track_metric(Key :: binary(), Context :: ldclient_user:user() | ldclient_context:context(), ldclient_event:event_data(), Metric :: number()) -> ok.
track_metric(Key, Context, Data, Metric) ->
track_metric(Key, ensure_context(Context), Data, Metric, ?DEFAULT_INSTANCE_NAME).
%% @doc Reports that a context has performed an event, and associates it with a numeric value.
%%
%% This value is used by the LaunchDarkly experimentation feature in numeric custom metrics, and will also
%% be returned as part of the custom event for Data Export.
%%
%% Custom data can also be attached to the event.
%% @end
-spec track_metric(Key :: binary(), Context :: ldclient_user:user() | ldclient_context:context(), ldclient_event:event_data(), Metric :: number(), Tag :: atom()) -> ok.
track_metric(Key, Context, Data, Metric, Tag) ->
ConvertedContext = ensure_context(Context),
when_is_valid_context(ConvertedContext, false, fun() ->
Event = ldclient_event:new_custom(Key, ensure_context(Context), Data, Metric),
ldclient_event_server:add_event(Tag, Event, #{})
end).
%% @doc Take a context or a context and return a context.
%%
%% If the context cannot be converted, then
%% @end
-spec ensure_context(UserOrContext :: ldclient_user:user() | ldclient_context:context()) -> ldclient_context:context().
ensure_context(#{kind := _Kind} = ContextOrContext) -> ContextOrContext;
ensure_context(UserOrContext) ->
case maps:is_key(key, UserOrContext) of
%% We allow legacy users to have an empty key, but we log it.
false -> error_logger:warning_msg("Context key is blank. Flag evaluation will proceed, but the context will not be stored in Launchdarkly");
true -> ok
end,
ldclient_context:new_from_user(UserOrContext).
%% @doc High order function that calls a function when a context is valid, otherwise returns true.
%%
%% @end
-spec when_is_valid_context(Context :: ldclient_context:context(), AllowEmptyKey :: boolean(), Fun :: function()) -> ok.
when_is_valid_context(Context, AllowEmptyKey, Fun) ->
case ldclient_context:is_valid(Context, AllowEmptyKey) of
true -> Fun();
false -> ok
end.