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_attribute_reference.erl
%%-------------------------------------------------------------------
%% @doc Attribute Reference
%% @private
%% @end
%%-------------------------------------------------------------------
-module(ldclient_attribute_reference).
%% API
-export([
new/1,
new_from_legacy/1,
escape_legacy/1
]).
%% Opaque type, consumers are expected to interact with the type using the methods in this module.
-type attribute_reference() :: #{
valid := boolean(),
components => [binary()],
%% Binary representation suitable for redaction list.
binary => binary()
}.
-export_type([attribute_reference/0]).
%%===================================================================
%% Internal functions
%%===================================================================
-type unescape_state() :: #{
valid := boolean(),
acc := binary(),
in_escape := boolean()
}.
%% Expose non-exported methods for tests.
-ifdef(TEST).
-compile(export_all).
-endif.
%% @doc Create a new attribute reference from an attribute reference string.
%% If you want to create a reference from a legacy attribute name, then use new_from_legacy.
%% @end
-spec new(BinaryReference :: binary()) -> attribute_reference().
new(<<"">> = _BinaryReference) ->
#{valid => false};
new(<<"/">> = _BinaryReference) ->
#{valid => false};
new(<<"/", _T/binary>> = BinaryReference) ->
Components = get_components(BinaryReference),
#{valid => valid_components(Components), binary => BinaryReference, components => Components};
%% Binary did not start with "/", so it is a literal.
new(BinaryReference) when is_binary(BinaryReference) ->
#{valid => true, components => [BinaryReference], binary => BinaryReference};
%% Was not a binary value, so it is invalid.
new(_) ->
#{valid => false}.
%% @doc Create a new attribute reference from a legacy attribute name.
%%
%% @end
-spec new_from_legacy(LegacyLiteral :: binary()) -> attribute_reference().
%% An empty string is an invalid literal.
new_from_legacy(<<"">> = _LegacyLiteral) -> #{valid => false};
%% If a legacy literal starts with /, then it will need escaped for the binary reference.
new_from_legacy(<<"/", _T/binary>> = LegacyLiteral) -> new(escape_legacy(LegacyLiteral));
new_from_legacy(LegacyLiteral) -> new(LegacyLiteral).
%% @doc Escape an attribute name to an attribute reference.
%%
%% @end
-spec escape_legacy(Component :: binary()) -> EscapedComponent :: binary().
escape_legacy(Component) -> escape_legacy(Component, <<"/">>).
%%===================================================================
%% Internal functions
%%===================================================================
-spec unescape(Component :: binary()) -> UnescapedComponent :: binary() | error.
unescape(Component) ->
unescape(Component, #{acc => <<"">>, valid => true, in_escape => false}).
-spec unescape(Component :: binary(), State :: unescape_state()) -> UnescapedComponent :: binary() | error.
unescape(<<"~">>, _State) ->
error;
unescape(<<"~", T/binary>>, State) ->
unescape(T, State#{ in_escape => true });
unescape(<<"0", T/binary>>, #{in_escape := true, acc := Acc} = State) ->
unescape(T, State#{ acc => <<Acc/binary, "~">>, in_escape => false});
unescape(<<"1", T/binary>>, #{in_escape := true, acc := Acc} = State) ->
unescape(T, State#{ acc => <<Acc/binary, "/">>, in_escape => false});
unescape(<<_H, _T/binary>>, #{in_escape := true} = _State) ->
%% Was in an escape sequence and the next character was not 0 or 1.
error;
unescape(<<H, T/binary>>, #{acc := Acc} = State) ->
unescape(T, State#{acc => <<Acc/binary, H>>});
unescape(<<>>, #{acc := Acc } = _State) -> Acc.
-spec get_components(BinaryReference :: binary()) -> Components :: [binary() | error].
get_components(BinaryReference) ->
%% Skip the first item in the split because of the leading /.
lists:map(fun unescape/1, tl(binary:split(BinaryReference, <<"/">>, [global]))).
-spec valid_components([error | binary()]) -> boolean().
valid_components([error|_T]) -> false;
%% Empty components indicate multiple or trailing /.
%% /potato/ or /potato//potato
valid_components([<<>>|_T]) -> false;
valid_components([_H|T]) -> valid_components(T);
valid_components([]) -> true.
-spec escape_legacy(Component :: binary(), Acc :: binary()) -> Escaped :: binary().
escape_legacy(<<"~", T/binary>> = _Component, Acc) -> escape_legacy(T, <<Acc/binary, "~0">>);
escape_legacy(<<"/", T/binary>> = _Component, Acc) -> escape_legacy(T, <<Acc/binary, "~1">>);
escape_legacy(<<H, T/binary>> = _Component, Acc) -> escape_legacy(T, <<Acc/binary, H>>);
escape_legacy(<<>> = _Component, Acc) -> Acc.