defmodule NLdoc.S3 do @moduledoc """ Helper functions for S3. """ @type response() :: {:ok, term()} | {:error, term()} @doc """ Upload a specific file as a stream. """ @spec stream_upload( path :: String.t(), bucket :: String.t(), key :: String.t(), opts :: ExAws.S3.upload_opts() ) :: {:ok, term()} | {:error, term()} def stream_upload(path, bucket, key, opts) do with stream = %File.Stream{} <- ExAws.S3.Upload.stream_file(path), operation = %ExAws.S3.Upload{} <- ExAws.S3.upload(stream, bucket, key, opts) do ExAws.request(operation) end end @doc """ Download a specific file as a stream """ @spec stream_download!(bucket :: String.t(), key :: String.t()) :: Enumerable.t() def stream_download!(bucket, key) do with download = %ExAws.S3.Download{} <- ExAws.S3.download_file(bucket, key, :memory) do ExAws.stream!(download) end end @doc """ Head an object in a bucket. """ @spec head_object(bucket :: String.t(), key :: String.t()) :: response() def head_object(bucket, key) do with operation = %ExAws.Operation.S3{} <- ExAws.S3.head_object(bucket, key) do ExAws.request(operation) end end @doc """ Check if bucket exists. Create it if it does not. """ @spec upsert_bucket(bucket :: String.t(), region :: String.t()) :: response() def upsert_bucket(bucket, region) do case bucket |> ExAws.S3.head_bucket() |> ExAws.request() do {:ok, _} = ok -> ok {:error, {:http_error, 404, _}} -> bucket |> ExAws.S3.put_bucket(region) |> ExAws.request() |> handle_put_bucket_conflicts() {:error, other} -> {:error, other} end end defp handle_put_bucket_conflicts(ok = {:ok, _}), do: ok # If the name is taken globally but owned by us, treat as success. defp handle_put_bucket_conflicts({:error, {:http_error, 409, _}}), do: {:ok, :exists} defp handle_put_bucket_conflicts(other), do: other end