defmodule RRPproxy.Client do @moduledoc """ This client takes care of communication with the RRPproxy.net API. """ use HTTPoison.Base require Logger defstruct username: Application.get_env(:rrpproxy, :username), password: Application.get_env(:rrpproxy, :password), ote: Application.get_env(:rrpproxy, :ote, true) defp make_url(ote) do if ote do "https://api-ote.rrpproxy.net/api/call" else "https://api.rrpproxy.net/api/call" end end defp fix_field_and_value(field, value) do f = String.replace(field, " ", "_") |> String.downcase() v = try do String.to_integer(value) rescue ArgumentError -> try do String.to_float(value) rescue ArgumentError -> value end end v = if v == 1 do true else if v == 0 do false else v end end {String.to_atom(f), v} end defp work_data_fields({data, info, extra}, field, index, value, is_multi_line, is_single_result) do {f, v} = fix_field_and_value(field, value) multi_line_fields = [ " procedure", " policy", "allowed characters notes", "restrictions", "tag" ] if is_multi_line and Enum.any?(multi_line_fields, fn p -> String.contains?(field, p) end) do new_value = Map.get(extra, f, "") <> "#{v}" {data, info, Map.put(extra, f, new_value)} else IO.puts("field #{f} with value \"#{v}\" is single result #{is_single_result}") if is_single_result do index_data = Map.get(data, "0", %{}) index_data = if Map.has_key?(index_data, f) do new_value = case Map.get(index_data, f) do arr when is_list(arr) -> arr ++ [v] old_value -> [old_value, v] end Map.put(index_data, f, new_value) else Map.put(index_data, f, v) end {Map.put(data, "0", index_data), info, extra} else index_data = Map.get(data, "#{index}", %{}) index_data = if Map.has_key?(index_data, f) do new_value = Map.get(index_data, f) <> "#{v}" Map.put(index_data, f, new_value) else Map.put(index_data, f, v) end {Map.put(data, "#{index}", index_data), info, extra} end end end defp case_parts({data, info, extra} = main, parts, value, is_multi_line, is_single_result) do case parts do ["column", "0"] -> {data, Map.put(info, :column, value), extra} ["first", "0"] -> {data, Map.put(info, :offset, String.to_integer(value)), extra} ["last", "0"] -> {data, Map.put(info, :last, String.to_integer(value)), extra} ["limit", "0"] -> {data, Map.put(info, :limit, String.to_integer(value)), extra} ["total", "0"] -> {data, Map.put(info, :total, String.to_integer(value)), extra} ["count", "0"] -> {data, Map.put(info, :count, String.to_integer(value)), extra} [field, index] -> work_data_fields(main, field, index, value, is_multi_line, is_single_result) _ -> main end end defp response_to_map( {:error, %HTTPoison.Error{id: _, reason: reason}}, _is_multi_line_response, _is_single_result ) do {:error, reason} end defp response_to_map( {:ok, %{status_code: 500}} = {_, %HTTPoison.Response{}}, _is_multi_line_response, _is_single_result ) do {:error, %{code: 500}} end defp response_to_map( {code, %{body: body}} = {_, %HTTPoison.Response{}}, is_multi_line_response, is_single_result ) do parts = String.split(body, "\n") |> Enum.filter(&String.contains?(&1, "=")) |> Enum.map(&Regex.split(~r/\s*=\s*/, &1, parts: 2)) {[_, rcode], parts} = List.pop_at(parts, 0) {[_, rdesc], parts} = List.pop_at(parts, 0) {data, info, extra} = Enum.reduce(parts, {%{}, %{}, %{}}, fn [k, v], red -> parts = Regex.split(~r/(\]\[|\]|\[)/, k) |> Enum.filter(fn x -> x != "" end) |> List.delete_at(0) case_parts(red, parts, v, is_multi_line_response, is_single_result) end) data = Enum.map(data, fn {_, v} -> v end) data = if Enum.count(data) > 1 do data else with_extra = case Enum.at(data, 0) do nil -> [] only_data -> [Map.merge(only_data, extra)] end with_extra end rcode = String.to_integer(rcode) code = if code == :ok and rcode >= 300 do :error else :ok end {code, %{code: rcode, description: rdesc, data: data, info: info}} end def query( command, custom_params \\ [], creds = %__MODULE__{} \\ %__MODULE__{}, is_multi_line_response \\ false, is_single_result \\ false ) do query_tries(creds, command, custom_params, is_multi_line_response, is_single_result) end defp query_tries( creds, command, custom_params, is_multi_line_response, is_single_result, tries \\ 1 ) do url = make_url(creds.ote) request_params = [s_login: creds.username, s_pw: creds.password, command: command] ++ custom_params retry_func = fn error -> if tries > 3 do # return the error as is error else Logger.debug( "[RRPproxy] retrying #{command} #{Kernel.inspect(custom_params)}, try: #{tries}" ) :timer.sleep(:timer.seconds(tries * 5)) query_tries( creds, command, custom_params, is_multi_line_response, is_single_result, tries + 1 ) end end case response_to_map( get(url, [], params: request_params), is_multi_line_response, is_single_result ) do {:error, :timeout} = error -> retry_func.(error) {:error, %{code: 421}} = error -> retry_func.(error) {:error, %{code: 423}} = error -> retry_func.(error) {:error, %{code: 500}} = error -> retry_func.(error) other -> other end end end