lib/ch/error.ex

defmodule Ch.Error do
  @moduledoc "Error struct wrapping ClickHouse error responses."
  defexception [:code, :message]
  @type t :: %__MODULE__{code: pos_integer | nil, message: String.t()}

  def exception(code, message) do
    message = IO.iodata_to_binary(ensure_printable(message))
    exception(code: code, message: message)
  end

  @dialyzer :no_improper_lists

  @doc false
  def ensure_printable(message) do
    printable(message, 0, 0, message, [])
  end

  defguardp is_printable(char) when char in 32..127 or char == ?\n

  defp printable(<<char, rest::bytes>>, from, len, original, acc) when is_printable(char) do
    printable(rest, from, len + 1, original, acc)
  end

  defp printable(<<_char, rest::bytes>>, from, len, original, acc) do
    acc = [acc | binary_part(original, from, len)]
    unprintable(rest, from + len, 1, original, acc)
  end

  defp printable(<<>>, from, len, original, acc) do
    [acc | binary_part(original, from, len)]
  end

  defp unprintable(<<char, rest::bytes>>, from, len, original, acc) when is_printable(char) do
    acc = [acc | inspect(binary_part(original, from, len))]
    printable(rest, from + len, 1, original, acc)
  end

  defp unprintable(<<_char, rest::bytes>>, from, len, original, acc) do
    unprintable(rest, from, len + 1, original, acc)
  end

  defp unprintable(<<>>, from, len, original, acc) do
    [acc | inspect(binary_part(original, from, len))]
  end
end