View Source Error Handling

This guide covers error handling strategies for robust file storage operations with Buckets.

Error Types

Common Errors

Buckets operations can return various error types:

# Configuration errors
{:error, [:bucket, :access_key_id]}  # Missing required fields

# Network errors
{:error, %Mint.TransportError{}}     # Connection failed
{:error, {:http_error, 500}}         # Server error

# Permission errors
{:error, :unauthorized}              # Invalid credentials
{:error, :forbidden}                 # Insufficient permissions

# Resource errors
{:error, :not_found}                 # Object doesn't exist
{:error, :already_exists}            # Duplicate object

# Validation errors
{:error, :file_too_large}            # Size limit exceeded
{:error, :invalid_content_type}      # Type not allowed

Basic Error Handling

Pattern Matching

case MyApp.Cloud.insert(object) do
  {:ok, stored} ->
    # Success path
    {:ok, stored}
    
  {:error, :unauthorized} ->
    # Handle auth error
    Logger.error("Invalid cloud storage credentials")
    {:error, "Authentication failed"}
    
  {:error, %{status: 507}} ->
    # Storage full
    Logger.error("Cloud storage quota exceeded")
    {:error, "Storage limit reached"}
    
  {:error, reason} ->
    # Generic error
    Logger.error("Upload failed: #{inspect(reason)}")
    {:error, "Upload failed"}
end

Using Bang Functions

def upload_or_raise(file_path) do
  try do
    object = Buckets.Object.from_file(file_path)
    stored = MyApp.Cloud.insert!(object)
    {:ok, stored}
  rescue
    e in RuntimeError ->
      Logger.error("Upload failed: #{e.message}")
      reraise e, __STACKTRACE__
  end
end

Retry Strategies

Simple Retry

defmodule MyApp.Storage.Retry do
  def with_retry(fun, opts \\ []) do
    max_attempts = Keyword.get(opts, :max_attempts, 3)
    delay = Keyword.get(opts, :delay, 1000)
    
    do_retry(fun, max_attempts, delay, 1)
  end
  
  defp do_retry(fun, max_attempts, delay, attempt) do
    case fun.() do
      {:ok, result} ->
        {:ok, result}
        
      {:error, reason} when attempt < max_attempts ->
        if retryable_error?(reason) do
          Process.sleep(delay * attempt)  # Exponential backoff
          do_retry(fun, max_attempts, delay, attempt + 1)
        else
          {:error, reason}
        end
        
      {:error, reason} ->
        {:error, reason}
    end
  end
  
  defp retryable_error?(:timeout), do: true
  defp retryable_error?({:http_error, status}) when status >= 500, do: true
  defp retryable_error?(%Mint.TransportError{}), do: true
  defp retryable_error?(_), do: false
end

# Usage
MyApp.Storage.Retry.with_retry(fn ->
  MyApp.Cloud.insert(object)
end, max_attempts: 5, delay: 2000)

User-Friendly Errors

Error Translation

defmodule MyApp.Storage.ErrorMessages do
  def translate({:error, :unauthorized}) do
    "Storage authentication failed. Please check your credentials."
  end
  
  def translate({:error, :not_found}) do
    "The requested file could not be found."
  end
  
  def translate({:error, {:http_error, 507}}) do
    "Storage quota exceeded. Please upgrade your plan."
  end
  
  def translate({:error, :timeout}) do
    "The operation timed out. Please try again."
  end
  
  def translate({:error, :file_too_large}) do
    "The file is too large. Maximum size is 10MB."
  end
  
  def translate(_error) do
    "An unexpected error occurred. Please try again later."
  end
end

# In controller
case MyApp.Cloud.insert(object) do
  {:ok, _} ->
    put_flash(conn, :info, "Upload successful")
    
  {:error, _} = error ->
    message = MyApp.Storage.ErrorMessages.translate(error)
    put_flash(conn, :error, message)
end