defmodule Observer.Web.Helpers do @moduledoc false alias Phoenix.VerifiedRoutes # Routing Helpers @doc """ Prepare parsed params for URI encoding. ## Examples iex> alias Observer.Web.Helpers ...> assert [nodes: "web-1,web-2"] = Helpers.encode_params(nodes: ~w(web-1 web-2)) ...> assert [args: "a++x"] = Helpers.encode_params(args: [~w(a), "x"]) ...> assert [args: "a,b++x"] = Helpers.encode_params(args: [~w(a b), "x"]) ...> assert [args: "a,b,c++x"] = Helpers.encode_params(args: [~w(a b c), "x"]) ...> assert [args: "a"] = Helpers.encode_params(args: [~w(a)]) ...> assert [args: :hi] = Helpers.encode_params([args: :hi]) """ def encode_params(params) do for {key, val} <- params, val != nil, val != "" do case val do [path, frag] when is_list(path) -> {key, Enum.join(path, ",") <> "++" <> frag} [_ | _] -> {key, Enum.join(val, ",")} _ -> {key, val} end end end @doc """ This function exchange "@", ".", "/" to "-" ## Examples iex> alias Observer.Web.Helpers ...> assert Helpers.normalize_id("my_app-1@host") == "my_app-1-host" ...> assert Helpers.normalize_id("my_app-2@host") == "my_app-2-host" """ def normalize_id(text) do String.replace(text, ["@", ".", "/"], "-") end @doc """ Construct a path to a dashboard page with optional params. Routing is based on a socket and prefix tuple stored in the process dictionary. Proper routing can be disabled for testing by setting the value to `:nowhere`. ## Examples iex> alias Observer.Web.Helpers ...> assert "/" = Helpers.observer_path(:root) ...> assert "/" = Helpers.observer_path(:root, :any) ...> assert_raise RuntimeError, ~r/nothing stored in the :routing key/, fn -> Helpers.observer_path(nil, ["path", "to", "resource"]) end ...> Process.put(:routing, :nowhere) ...> assert "/" = Helpers.observer_path(:tracing, ["path", "to", "resource"]) ...> assert "/" = Helpers.observer_path(["/", "tracing"], ["path", "to", "resource"]) """ def observer_path(route, params \\ %{}) def observer_path(:root, _params), do: "/" def observer_path(route, params) when is_list(route) do route |> Enum.map_join("/", &encode_segment/1) |> observer_path(params) end # NOTE: Filter page to avoid conflicts with the route path def observer_path(route, %{"page" => _name} = params) do observer_path(route, Map.delete(params, "page")) end def observer_path(route, params) do params = params |> Enum.sort() |> encode_params() case Process.get(:routing) do {socket, prefix} -> VerifiedRoutes.unverified_path(socket, socket.router, "#{prefix}/#{route}", params) :nowhere -> "/" nil -> raise RuntimeError, "nothing stored in the :routing key" end end defp encode_segment(segment) do segment |> to_string() |> URI.encode(&URI.char_unreserved?/1) end @doc """ This function converts String PID/PORT to its respective type ## Examples iex> alias Observer.Web.Helpers ...> assert {:pid, _any} = Helpers.parse_identifier("#PID<0.308.0>") ...> assert {:port, _any} = Helpers.parse_identifier("#Port<0.1>") ...> assert {:none, _any} = Helpers.parse_identifier("#Ref<0.0.0.0>") """ def parse_identifier(id) do cond do String.contains?(id, "#PID<") -> {:pid, string_to_pid(id)} String.contains?(id, "#Port<") -> {:port, string_to_port(id)} true -> {:none, id} end end @doc """ This function converts String PID to safe ID to be used in HTML components ## Examples iex> alias Observer.Web.Helpers ...> assert Helpers.identifier_to_safe_id("#PID<0.308.0>") == "pid-0-308-0" ...> assert Helpers.identifier_to_safe_id("#PID<0.308.1>") == "pid-0-308-1" ...> assert Helpers.identifier_to_safe_id("#Port<0.1>") == "port-0-1" ...> assert_raise RuntimeError, fn -> Helpers.identifier_to_safe_id("<0.1>") end """ def identifier_to_safe_id(identifier) when is_binary(identifier) do cond do String.contains?(identifier, "#PID<") -> identifier |> String.replace(["#PID<"], "pid-") |> String.replace(["."], "-") |> String.replace([">"], "") String.contains?(identifier, "#Port<") -> identifier |> String.replace(["#Port<"], "port-") |> String.replace(["."], "-") |> String.replace([">"], "") true -> raise "Invalid identifier" end end @doc """ This function converts String PID to PID type ## Examples iex> alias Observer.Web.Helpers ...> assert "#PID<0.308.0>" |> Helpers.string_to_pid() |> is_pid() ...> assert "#PID<0.308.1>" |> Helpers.string_to_pid() |> is_pid() """ def string_to_pid(string) do string |> String.trim_leading("#PID") |> String.to_charlist() |> :erlang.list_to_pid() end @doc """ This function converts String PORT to Port type ## Examples iex> alias Observer.Web.Helpers ...> assert "#Port<0.1>" |> Helpers.string_to_port() |> is_port() ...> assert "#Port<0.2>" |> Helpers.string_to_port() |> is_port() """ def string_to_port(string) do string |> String.to_charlist() |> :erlang.list_to_port() end end