errors.go

 1package fantasy
 2
 3import (
 4	"fmt"
 5	"net/http"
 6	"strings"
 7
 8	"github.com/charmbracelet/x/exp/slice"
 9)
10
11// Error is a custom error type for the fantasy package.
12type Error struct {
13	Message string
14	Title   string
15	Cause   error
16}
17
18func (err *Error) Error() string {
19	if err.Title == "" {
20		return err.Message
21	}
22	return fmt.Sprintf("%s: %s", err.Title, err.Message)
23}
24
25func (err Error) Unwrap() error {
26	return err.Cause
27}
28
29// ProviderError represents an error returned by an external provider.
30type ProviderError struct {
31	Message string
32	Title   string
33	Cause   error
34
35	URL             string
36	StatusCode      int
37	RequestBody     []byte
38	ResponseHeaders map[string]string
39	ResponseBody    []byte
40}
41
42func (m *ProviderError) Error() string {
43	if m.Title == "" {
44		return m.Message
45	}
46	return fmt.Sprintf("%s: %s", m.Title, m.Message)
47}
48
49// IsRetryable checks if the error is retryable based on the status code.
50func (m *ProviderError) IsRetryable() bool {
51	return m.StatusCode == http.StatusRequestTimeout || m.StatusCode == http.StatusConflict || m.StatusCode == http.StatusTooManyRequests
52}
53
54// RetryError represents an error that occurred during retry operations.
55type RetryError struct {
56	Errors []error
57}
58
59func (e *RetryError) Error() string {
60	if err, ok := slice.Last(e.Errors); ok {
61		return fmt.Sprintf("retry error: %v", err)
62	}
63	return "retry error: no underlying errors"
64}
65
66func (e RetryError) Unwrap() error {
67	if err, ok := slice.Last(e.Errors); ok {
68		return err
69	}
70	return nil
71}
72
73// ErrorTitleForStatusCode returns a human-readable title for a given HTTP status code.
74func ErrorTitleForStatusCode(statusCode int) string {
75	return strings.ToLower(http.StatusText(statusCode))
76}