lib/waffle/storage/google/util.ex

defmodule Waffle.Storage.Google.Util do
  @moduledoc """
  A collection of utility functions.
  """

  alias GoogleApi.Gax.{Request, Response}
  alias GoogleApi.Storage.V1.Connection
  alias GoogleApi.Storage.V1.Model.Object

  @library_version Mix.Project.config() |> Keyword.get(:version, "")

  @doc """
  Accepts four forms of variables:

  1. The tuple `{:system, key}`, which uses `System.get_env/1`.
  2. The tuple `{:app, key}`, which uses `Application.get_env/2`.
  3. The tuple `{:app, app, key}`, which usees `Application.get_env/2`.
  4. Anything else which is returned as-is.

  In the second form (`{:app, key}`), the application is assumed to be `:waffle`
  although this can be overriden using the third form (`{:app, app, key}`).
  """
  @spec var(any) :: any
  def var({:system, key}), do: System.get_env(key)
  def var({:app, app, key}), do: Application.get_env(app, key)
  def var({:app, key}), do: var({:app, :waffle, key})
  def var(value), do: value

  @doc """
  Attempts to return a value from a given options list, using the application
  configs as a back-up. The application (used in `Application.get_env/3`) is
  assumed to be `:waffle`.

  This assumes that the application environment is set using `waffle` as the
  application name.

  If the value is not found in any of those location, an optional default value
  can be returned.
  """
  @spec option(Keyword.t, any, any) :: any
  def option(opts, key, default \\ nil) do
    case Keyword.get(opts, key) do
      nil -> Application.get_env(:waffle, key, default)
      val -> val
    end
  end

  @doc """
  If the given string does not already start with a forward slash, this function
  will prepend one and return the result.

  ## Examples

      > prepend_slash("some/path")
      "/some/path"

      > prepend_slash("/im/good")
      "/im/good"
  """
  @spec prepend_slash(String.t) :: String.t
  def prepend_slash("/" <> _rest = path), do: path
  def prepend_slash(path), do: "/#{path}"

  @doc """
  The function `Objects.storage_objects_insert/4` has the wrong URL and will
  always fail to perform an upload. Because these clients are automatically
  generated, there needs to be an investigation as to why this URL is being
  incorrectly set before a solution can be applied. This version of the function
  is an exact copy/paste of the official function except that it uses the
  correct upload URL.
  """
  @spec storage_objects_insert(
    Tesla.Env.client,
    String.t,
    Keyword.t,
    Keyword.t
  ) :: Waffle.Storage.Google.CloudStorage.object_or_error
  def storage_objects_insert(connection, bucket, optional_params \\ [], opts \\ []) do
    optional_params_config = %{
      :alt => :query,
      :fields => :query,
      :key => :query,
      :oauth_token => :query,
      :prettyPrint => :query,
      :quotaUser => :query,
      :userIp => :query,
      :contentEncoding => :query,
      :ifGenerationMatch => :query,
      :ifGenerationNotMatch => :query,
      :ifMetagenerationMatch => :query,
      :ifMetagenerationNotMatch => :query,
      :kmsKeyName => :query,
      :name => :query,
      :predefinedAcl => :query,
      :projection => :query,
      :provisionalUserProject => :query,
      :userProject => :query,
      :body => :body
    }

    request =
      Request.new()
      |> Request.method(:post)
      |> Request.url("/upload/storage/v1/b/{bucket}/o", %{
        "bucket" => URI.encode(bucket, &URI.char_unreserved?/1)
      })
      |> Request.add_optional_params(optional_params_config, optional_params)
      |> Request.library_version(@library_version)

    connection
    |> Connection.execute(request)
    |> Response.decode(opts ++ [struct: %Object{}])
  end
end