-module(erlcloud_util). -export([backoff/1, sha_mac/2, sha256_mac/2, md5/1, sha256/1, is_dns_compliant_name/1, query_all/4, query_all/5, query_all_token/4, make_response/2, get_items/2, to_string/1, encode_list/2, next_token/2, filter_undef/1]). -define(MAX_ITEMS, 1000). %% @doc Sleep after an attempt -spec backoff(pos_integer()) -> ok. backoff(1) -> ok; backoff(Attempt) -> timer:sleep(rand:uniform((1 bsl (Attempt - 1)) * 100)). sha_mac(K, S) -> crypto:mac(hmac, sha, K, S). sha256_mac(K, S) -> crypto:mac(hmac, sha256, K, S). sha256(V) -> crypto:hash(sha256, V). md5(V) -> crypto:hash(md5, V). -spec is_dns_compliant_name(string()) -> boolean(). is_dns_compliant_name(Name) -> RegExp = "^(([a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])\\.)*([a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])$", case re:run(Name, RegExp) of nomatch -> false; _ -> true end. query_all_token(QueryFun, Config, Action, Params) -> query_all_token(QueryFun, Config, Action, Params, undefined, []). query_all_token(QueryFun, Config, Action, Params, Token, Acc) -> NewParams = case Token of undefined -> Params; _ -> [{"NextToken", Token} | Params] end, case QueryFun(Config, Action, NewParams) of {ok, Doc} -> NewToken = next_token("/*/*/NextToken", Doc), Queried = [Doc | Acc], case NewToken of ok -> {ok, lists:reverse(Queried)}; {paged, NewTokenValue} -> query_all_token(QueryFun, Config, Action, Params, NewTokenValue, Queried) end; Error -> Error end. query_all(QueryFun, Config, Action, Params) -> query_all(QueryFun, Config, Action, Params, ?MAX_ITEMS, undefined, []). query_all(QueryFun, Config, Action, Params, MaxItems) -> query_all(QueryFun, Config, Action, Params, MaxItems, undefined, []). query_all(QueryFun, Config, Action, Params, MaxItems, Marker, Acc) -> MarkerParams = case Marker of undefined -> Params; _ -> [{"Marker", Marker} | Params] end, NewParams = [{"MaxItems", MaxItems} | MarkerParams], case QueryFun(Config, Action, NewParams) of {ok, Doc} -> IsTruncated = erlcloud_xml:get_bool("/*/*/IsTruncated", Doc), NewMarker = erlcloud_xml:get_text("/*/*/Marker", Doc), Queried = [Doc | Acc], case IsTruncated of true -> query_all(QueryFun, Config, Action, Params, MaxItems, NewMarker, Queried); false -> {ok, lists:reverse(Queried)} end; Error -> Error end. encode_list(ElementName, Elements) -> Numbered = lists:zip(lists:seq(1, length(Elements)), Elements), [{ElementName ++ ".member." ++ integer_to_list(N), Element} || {N, Element} <- Numbered]. make_response(Xml, Result) -> IsTruncated = erlcloud_xml:get_bool("/*/*/IsTruncated", Xml), Marker = erlcloud_xml:get_text("/*/*/Marker", Xml), case IsTruncated of false -> {ok, Result}; true -> {ok, Result, Marker} end. get_items(ItemPath, Xmls) when is_list(Xmls) -> lists:append([get_items(ItemPath, Xml) || Xml <- Xmls]); get_items(ItemPath, Xml) -> xmerl_xpath:string(ItemPath, Xml). -spec to_string(string() | integer()) -> string(). to_string(X) when is_list(X) -> X; to_string(X) when is_integer(X) -> integer_to_list(X). -spec next_token(string(), term()) -> ok | {paged, string()}. next_token(Path, XML) -> case xmerl_xpath:string(Path, XML) of [Next] -> {paged, erlcloud_xml:get_text(Next)}; _ -> ok end. -spec filter_undef(proplists:proplist()) -> proplists:proplist(). filter_undef(List) -> lists:filter(fun({_Name, Value}) -> Value =/= undefined end, List).