lib/consul/api/v1/kv.ex

defmodule Consul.Api.V1.Kv do
  @moduledoc """
  Higher level abstraction on top of Consul's Key Value API.
  """

  alias Consul.{Connection, JsonCodec, Request, Response, YamlCodec}

  @doc """
  Get the value and metadata for a given key from the KV Store.
  """
  def get(connection, key, optional_params \\ [], opts \\ []) do
    optional_params_config = %{
      :dc => :query,
      :recurse => :query,
      :raw => :query,
      :keys => :query,
      :separator => :query,
      :ns => :query,
      :index => :query,
      :wait => :query
    }

    request =
      Request.new()
      |> Request.method(:get)
      |> Request.url("/v1/kv/{key}", %{
        "key" => key
      })
      |> Request.add_optional_params(optional_params_config, optional_params)

    connection
    |> Connection.execute(request)
    |> Response.decode(opts ++ [as: %{}])
    |> case do
      {:ok, %{body: results} = env} when is_list(results) ->
        result =
          results
          |> Enum.map(fn
            %{"Value" => value} = map when value != nil ->
              value = Base.decode64!(value)

              value =
                if Keyword.get(opts, :decode, true),
                  do: decode_value(value),
                  else: value

              Map.put(map, "Value", value)

            other ->
              other
          end)

        {:ok, %{env | body: result}}

      other ->
        other
    end
  end

  defp decode_value(value) do
    case JsonCodec.decode(value) do
      {:ok, map} ->
        map

      ## poison new releases return the position
      {:error, _, _pos} ->
        check_yaml(value)

      {:error, _} ->
        check_yaml(value)
    end
  end

  def check_yaml(value) do
    case YamlCodec.read_from_string(value) do
      {:ok, value} -> value
      {:error, _} -> raise RuntimeError, "expected JSON or YAML value"
    end
  end
end