Quiver uses Splode for structured error classification. Every error carries a :class that tells you how to react.

Error Classes

ClassMeaningAction
:transientTemporary failure, the same request may succeed on retryRetry with backoff
:invalidCaller-side mistake, the request itself is wrongFix the input
:unrecoverableInfrastructure broken, won't resolve on its ownEscalate / alert

Pattern Matching on Errors

case Quiver.new(:get, url) |> Quiver.request(:http_client) do
  {:ok, response} ->
    process(response)

  {:error, %Quiver.Error{class: :transient} = error} ->
    Logger.warning("Transient error: #{Exception.message(error)}")
    retry(url)

  {:error, %Quiver.Error{class: :invalid} = error} ->
    Logger.error("Invalid request: #{Exception.message(error)}")
    {:error, :bad_request}

  {:error, %Quiver.Error{class: :unrecoverable} = error} ->
    Logger.error("Unrecoverable: #{Exception.message(error)}")
    {:error, :service_unavailable}
end

You can also match on specific error types:

case result do
  {:error, %Quiver.Error.Timeout{}} ->
    # Handle timeout specifically

  {:error, %Quiver.Error.TLSVerificationFailed{host: host}} ->
    Logger.error("Certificate verification failed for #{host}")

  {:error, _} ->
    # Generic fallback
end

Retry Strategies by Class

Transient errors -- retry with backoff

Transient errors are safe to retry. Use exponential backoff:

defp request_with_retry(request, name, retries \\ 3, delay \\ 100)
defp request_with_retry(_request, _name, 0, _delay), do: {:error, :max_retries}

defp request_with_retry(request, name, retries, delay) do
  case Quiver.request(request, name) do
    {:ok, response} ->
      {:ok, response}

    {:error, %Quiver.Error{class: :transient}} ->
      Process.sleep(delay)
      request_with_retry(request, name, retries - 1, delay * 2)

    {:error, _} = error ->
      error
  end
end

Invalid errors -- fix and retry

Invalid errors indicate a problem with the request. Don't retry without changing the input.

Unrecoverable errors -- escalate

Unrecoverable errors signal infrastructure problems (TLS misconfiguration, protocol violations). Log them, alert your monitoring system, and investigate.

Error Reference

Transient Errors

ErrorDescription
Quiver.Error.TimeoutConnect or receive timeout
Quiver.Error.ConnectionClosedRemote peer closed the connection
Quiver.Error.ConnectionRefusedConnection refused by remote host
Quiver.Error.ConnectionFailedGeneric connection failure
Quiver.Error.DNSResolutionFailedDNS lookup failed for the host
Quiver.Error.CheckoutTimeoutPool had no available connection within the timeout
Quiver.Error.PoolStartFailedDynamic pool creation failed
Quiver.Error.StreamClosedOperation on a closed HTTP/2 stream
Quiver.Error.MaxConcurrentStreamsReachedServer's max concurrent streams limit hit
Quiver.Error.StreamErrorError while consuming a streaming response body
Quiver.Error.GoAwayUnprocessedStream was never processed before GOAWAY; safe to retry on a new connection

Invalid Errors

ErrorDescription
Quiver.Error.InvalidSchemeUnsupported URI scheme
Quiver.Error.MalformedHeadersUnparseable HTTP header line
Quiver.Error.InvalidContentLengthNon-numeric or conflicting content-length
Quiver.Error.InvalidPoolOptsPool options failed validation
Quiver.Error.InvalidPoolRulePool config key not a valid origin pattern

Unrecoverable Errors

ErrorDescription
Quiver.Error.TLSVerificationFailedTLS certificate verification failed
Quiver.Error.TLSHandshakeFailedTLS handshake failed (cipher mismatch, protocol error)
Quiver.Error.ProtocolViolationMalformed status line, invalid version, garbage bytes
Quiver.Error.GoAwayConnection-level GOAWAY signal; the connection is shutting down
Quiver.Error.StreamResetRemote peer reset a specific HTTP/2 stream
Quiver.Error.FrameSizeErrorHTTP/2 frame exceeds maximum size
Quiver.Error.CompressionErrorHPACK header decompression failed