lib/redis_graph/util.ex

defmodule RedisGraph.Util do
  @moduledoc "Provides utility functions for RedisGraph modules."

  @doc """
  Generate a random string of characters `a-z` of length `n`.

  This is used to create a random alias for a node or relationship if one is not already set.
  """
  @spec random_string(pos_integer()) :: String.t()
  def random_string(n \\ 10) do
    1..n
    |> Enum.reduce("", fn _, acc -> acc <> to_string(Enum.take_random(?a..?z, 1)) end)
  end

  @doc """
  Converts properties map into string representation. Otherwise returns empty string.

  This is used to serialize edisGraph.Node or RedisGraph.Relationship properties
  into strings when preparing redisgraph queries.

  ## Example
  ```
  alias RedisGraph.{Node, Util}

  bob = Node.new(%{
    alias: :n,
    labels: ["person"],
    properties: %{
      name: "Bob Thomsen",
      age: 22
    }
  })

  bob_properties = Util.properties_to_string(bob.properties)

  ```
  """
  @spec properties_to_string(map() | any()) :: String.t()
  def properties_to_string(properties) do
    props =
      if is_map(properties) and map_size(properties) > 0 do
        Stream.map(properties, fn {k, x} -> "#{k}: #{value_to_string(x)}" end)
        |> Enum.join(", ")
      else
        ""
      end

    case String.length(props) do
      0 -> ""
      _ -> " {#{props}}"
    end
  end

  @doc """
  Converts labels list into string representation. Otherwise returns empty string.

  This is used to serialize RedisGraph.Node labels into strings when preparing redisgraph queries.

  ## Example
  ```
  alias RedisGraph.{Node, Util}

  bob = Node.new(%{
    alias: :n,
    labels: ["person", "student"],
    properties: %{
      name: "Bob Thomsen",
      age: 22
    }
  })

  bob_labels = Util.labels_to_string(bob.labels)

  ```
  """
  @spec labels_to_string(list(String.t()) | any()) :: String.t()
  def labels_to_string(labels) do
    if is_list(labels) and length(labels) > 0 do
      ":" <> Enum.join(labels, ":")
    else
      ""
    end
  end

  @doc """
  Converts labels list into string representation. Otherwise returns empty string.

  This is used to serialize RedisGraph.Node labels into strings when preparing redisgraph queries.

  ## Example
  ```
  alias RedisGraph.{Relationship, Util}

  relationship = Relationship.new(%{
    src_node: 1,
    dest_node: 2,
    type: "friend",
    properties: %{
      best_friend: true
    }
  })
  relationship_type = Util.type_to_string(relationship.type)

  ```
  """
  @spec type_to_string(list(String.t()) | any()) :: String.t()
  def type_to_string(type) do
    if is_binary(type) and String.length(type) > 0 do
      ":" <> type
    else
      ""
    end
  end

  @doc """
  Converts received value into string representation.

  This is used to serialize value into strings when preparing redisgraph queries.

  ## Example
  ```
  alias RedisGraph.{Node, Util}

  list_to_string = Util.value_to_string(["test", 11, 12.12, false, nil, ["hi", "bye"], %{me: "you"}])
  ```
  """
  @spec value_to_string(any()) :: String.t()
  def value_to_string(val) do
    cond do
      # check if string contains function
      is_binary(val) and String.contains?(val, "(") and String.contains?(val, ")") ->
        val

      is_binary(val) and String.length(val) > 0 ->
        "'" <> val <> "'"

      is_number(val) ->
        "#{val}"

      is_boolean(val) ->
        "#{val}"

      is_nil(val) ->
        "null"

      is_atom(val) ->
        Atom.to_string(val)

      is_list(val) ->
        "[#{Stream.map(val, fn individual_value -> value_to_string(individual_value) end) |> Enum.join(", ")}]"

      is_map(val) ->
        "{#{Stream.map(val, fn {k, x} -> "#{k}: #{value_to_string(x)}" end) |> Enum.join(", ")}}"

      true ->
        "#{val}"
    end
  end
end