lib/key/hd/derivation_path/parser/account.ex

defmodule BitcoinLib.Key.HD.DerivationPath.Parser.Account do
  @moduledoc """
  Splits the key space into independent user identities.

  This is a hardened value.

  See: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#account
  """

  @doc """
  Converts a list of path levels managed up until the account value, extracts
  the account and returns the remaining levels

  ## Examples
      iex> ["0'", "1", "2"]
      ...> |> BitcoinLib.Key.HD.DerivationPath.Parser.Account.extract()
      {:ok, 0, ["1", "2"]}

      iex> ["0", "1", "2"]
      ...> |> BitcoinLib.Key.HD.DerivationPath.Parser.Account.extract()
      {:error, "account number must be a hardened value"}
  """
  @spec extract(list()) :: {:ok, nil | integer(), list()} | {:error, binary()}
  def extract([]), do: {:ok, nil, []}

  def extract([account | rest]) do
    with account <- String.trim(account),
         {:ok, account} <- extract_hardened_value(account) do
      account
      |> Integer.parse()
      |> convert(rest)
    else
      nil -> {:ok, nil}
      {:error, message} -> {:error, message}
    end
  end

  defp extract_hardened_value(""), do: {:ok, nil}

  defp extract_hardened_value(hardened_value) do
    {account, hardened} = String.split_at(hardened_value, -1)

    case hardened do
      "'" -> {:ok, account}
      _ -> {:error, "account number must be a hardened value"}
    end
  end

  defp convert({account, ""}, rest), do: {:ok, account, rest}
  defp convert({_account, _remainder}, _rest), do: {:error, "account should be an integer"}
  defp convert(:error, _rest), do: {:error, "account should be a valid integer"}
end