Current section

Files

Jump to
sumo_db src utils sumo_utils.erl
Raw

src/utils/sumo_utils.erl

%%% @doc Set of useful functions for the app
-module(sumo_utils).
%%% API
-export([
doc_filter_fields_by/2,
doc_transform/2,
transform_conditions/4,
is_datetime/1,
keyfind/2,
keyfind/3,
is_key/2,
pipe/2,
text/2,
to_bin/1,
to_atom/1,
to_list/1,
to_int/1,
to_float/1,
to_boolean/1,
to_datetime/1
]).
-type keyword() :: [{atom(), term()}].
-type datetime() :: calendar:date() | calendar:datetime().
%%%=============================================================================
%%% API
%%%=============================================================================
-spec doc_transform(Fun, Doc1) -> Doc2 when
Fun :: fun((FieldType, FieldName, Value1, FieldAttrs) -> Value2),
FieldType :: sumo:field_type(),
FieldName :: sumo:field_name(),
Value1 :: sumo:field_value(),
FieldAttrs :: sumo:field_attrs(),
Value2 :: sumo:field_value(),
Doc1 :: sumo_internal:doc(),
Doc2 :: sumo_internal:doc().
doc_transform(Fun, Doc) ->
DocName = sumo_internal:doc_name(Doc),
Schema = sumo_internal:get_schema(DocName),
SchemaFields = sumo_internal:schema_fields(Schema),
lists:foldl(fun(Field, Acc) ->
FieldType = sumo_internal:field_type(Field),
FieldName = sumo_internal:field_name(Field),
FieldValue = sumo_internal:get_field(FieldName, Doc),
FieldAttrs = sumo_internal:field_attrs(Field),
NewValue = Fun(FieldType, FieldName, FieldValue, FieldAttrs),
sumo_internal:set_field(FieldName, NewValue, Acc)
end, Doc, SchemaFields).
-spec doc_filter_fields_by(DocNameOrDoc, FilteredFieldTypes) -> Response when
DocNameOrDoc :: atom() | sumo_internal:doc(),
FilteredFieldTypes :: [sumo:field_type()],
FieldName :: sumo:field_name(),
FieldType :: sumo:field_type(),
FieldValue :: sumo:field_value(),
ResTuple :: {FieldName, FieldType, FieldValue},
Response :: [ResTuple].
doc_filter_fields_by(DocNameOrDoc, FilteredFieldTypes) when is_atom(DocNameOrDoc) ->
filter_fields(DocNameOrDoc, undefined, FilteredFieldTypes);
doc_filter_fields_by(DocNameOrDoc, FilteredFieldTypes) ->
DocName = sumo_internal:doc_name(DocNameOrDoc),
filter_fields(DocName, DocNameOrDoc, FilteredFieldTypes).
%% @private
filter_fields(DocName, Doc, FilteredFieldTypes) ->
Schema = sumo_internal:get_schema(DocName),
SchemaFields = sumo_internal:schema_fields(Schema),
lists:foldl(fun(Field, Acc) ->
FieldType = sumo_internal:field_type(Field),
case lists:member(FieldType, FilteredFieldTypes) of
true -> [set_filter_response(Field, Doc) | Acc];
_ -> Acc
end
end, [], SchemaFields).
%% @private
set_filter_response(Field, Doc) ->
FieldType = sumo_internal:field_type(Field),
FieldName = sumo_internal:field_name(Field),
FieldValue = case Doc /= undefined of
true -> sumo_internal:get_field(FieldName, Doc);
_ -> undefined
end,
{FieldName, FieldType, FieldValue}.
-spec transform_conditions(Fun, DocName, Conditions, FieldTypes) -> Res when
Fun :: fun((DocSum) -> sumo:field_value()),
DocSum :: {sumo:field_type(), sumo:field_name(), sumo:field_value()},
DocName :: atom(),
Conditions :: sumo:conditions(),
FieldTypes :: [sumo:field_type()],
Res :: sumo:conditions().
transform_conditions(Fun, DocName, Conditions, FieldTypes) when is_list(Conditions) ->
DTFields = doc_filter_fields_by(DocName, FieldTypes),
lists:foldl(fun
({K, V}, Acc) when K == 'and'; K == 'or' ->
[{K, transform_conditions(Fun, DocName, V, FieldTypes)} | Acc];
({'not', V}, Acc) ->
[NotCond] = transform_conditions(Fun, DocName, [V], FieldTypes),
[{'not', NotCond} | Acc];
({K, V} = KV, Acc) ->
case lists:keyfind(K, 1, DTFields) of
{K, FieldType, _} ->
[{K, Fun({FieldType, K, V})} | Acc];
false ->
[KV | Acc]
end;
({K, Op, V} = KV, Acc) ->
case lists:keyfind(K, 1, DTFields) of
{K, FieldType, _} ->
[{K, Op, Fun({FieldType, K, V})} | Acc];
false ->
[KV | Acc]
end;
(Cond, Acc) ->
[Cond | Acc]
end, [], Conditions);
transform_conditions(Fun, DocName, Conditions, FieldTypes) ->
transform_conditions(Fun, DocName, [Conditions], FieldTypes).
-spec is_datetime(calendar:date() | calendar:datetime()) -> boolean().
is_datetime({_, _, _} = Date) ->
calendar:valid_date(Date);
is_datetime({{_, _, _} = Date, {H, M, S}}) ->
calendar:valid_date(Date) and
(H >= 0 andalso H =< 23) and
(M >= 0 andalso M =< 59) and
(S >= 0 andalso S =< 59);
is_datetime(_) ->
false.
-spec keyfind(Key, KVList) -> Val | undefined when
Key :: term(),
KVList :: [{Key, Val}],
Val :: term().
keyfind(Key, KVList) ->
keyfind(Key, KVList, undefined).
-spec keyfind(Key, KVList, Default) -> Val | Default when
Key :: term(),
KVList :: [{Key, Val}],
Val :: term(),
Default :: term().
keyfind(Key, KVList, Default) ->
case lists:keyfind(Key, 1, KVList) of
{Key, Value} -> Value;
_ -> Default
end.
-spec is_key(atom(), keyword()) -> boolean().
is_key(Key, Map) when is_atom(Key), is_map(Map) ->
maps:is_key(Key, Map);
is_key(Key, Keyword) when is_atom(Key), is_list(Keyword) ->
lists:keymember(Key, 1, Keyword).
-spec pipe(Initial, Pipeline) -> Return when
Initial :: term(),
Module :: module(),
Fun :: atom(),
Args :: [term()],
FunSpec :: {fun(), Args} | {Module, Fun, Args},
Pipeline :: [FunSpec],
Return :: term().
pipe(Initial, Pipeline) ->
lists:foldl(fun
({Module, Fun, Args}, Acc) ->
apply(Module, Fun, [Acc | Args]);
({Fun, Args}, Acc) ->
apply(Fun, [Acc | Args])
end, Initial, Pipeline).
-spec text(string(), [term()]) -> binary().
text(Msg, Args) ->
iolist_to_binary(io_lib:format(Msg, Args)).
-spec to_bin(Data :: term()) -> binary().
to_bin(Data) when is_integer(Data) ->
integer_to_binary(Data);
to_bin(Data) when is_float(Data) ->
float_to_binary(Data);
to_bin(Data) when is_atom(Data) ->
atom_to_binary(Data, utf8);
to_bin(Data) when is_list(Data) ->
iolist_to_binary(Data);
to_bin(Data) when is_pid(Data); is_reference(Data); is_tuple(Data) ->
integer_to_binary(erlang:phash2(Data));
to_bin(Data) ->
Data.
-spec to_atom(Data :: term()) -> atom().
to_atom(Data) when is_binary(Data) ->
binary_to_atom(Data, utf8);
to_atom(Data) when is_list(Data) ->
list_to_atom(Data);
to_atom(Data) when is_pid(Data); is_reference(Data); is_tuple(Data) ->
list_to_atom(integer_to_list(erlang:phash2(Data)));
to_atom(Data) ->
Data.
-spec to_list(Data :: term()) -> list().
to_list(Data) when is_binary(Data) ->
binary_to_list(Data);
to_list(Data) when is_integer(Data) ->
integer_to_list(Data);
to_list(Data) when is_float(Data) ->
float_to_list(Data);
to_list(Data) when is_atom(Data) ->
atom_to_list(Data);
to_list(Data) when is_pid(Data); is_reference(Data); is_tuple(Data) ->
integer_to_list(erlang:phash2(Data));
to_list(Data) ->
Data.
-spec to_int(Data :: term()) -> integer().
to_int(Data) when is_binary(Data) ->
binary_to_integer(Data);
to_int(Data) when is_list(Data) ->
list_to_integer(Data);
to_int(Data) when is_float(Data) ->
trunc(Data);
to_int(Data) when is_pid(Data); is_reference(Data); is_tuple(Data) ->
erlang:phash2(Data);
to_int(Data) ->
Data.
-spec to_float(Data :: term()) -> float().
to_float(Data) when is_binary(Data) ->
binary_to_float(Data);
to_float(Data) when is_list(Data) ->
case string:to_float(Data) of
{error, no_float} -> list_to_integer(Data);
{F, _Rest} -> F
end;
to_float(Data) when is_integer(Data) ->
Data / 1;
to_float(Data) when is_pid(Data); is_reference(Data); is_tuple(Data) ->
erlang:phash2(Data) / 1;
to_float(Data) ->
Data.
-spec to_boolean(any()) -> boolean().
to_boolean(Data) ->
case to_atom(Data) of
V when V == true; V == false ->
V;
_ ->
error({badarg, Data})
end.
-spec to_datetime(any()) -> datetime() | no_return().
to_datetime(Data) ->
case is_datetime(Data) of
true -> Data;
_ -> error({badarg, Data})
end.