-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.go
More file actions
173 lines (154 loc) · 5.84 KB
/
Copy patherrors.go
File metadata and controls
173 lines (154 loc) · 5.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package visionapi
import (
"fmt"
"net/http"
"strconv"
"time"
)
// Error codes. Every failure the API returns has the same envelope —
// {"error": {code, message, details?}} — and code is contract while message is prose that
// changes. Compare against these constants, never against the message text.
const (
CodeInvalidRequest = "invalid_request"
CodeInvalidAPIKey = "invalid_api_key"
CodeUnauthorized = "unauthorized"
CodeForbidden = "forbidden"
CodeEmailNotVerified = "email_not_verified"
CodeInsufficientCredits = "insufficient_credits"
CodeTaskNotFound = "task_not_found"
CodeSchemaNotFound = "schema_not_found"
CodeNotFound = "not_found"
CodeConflict = "conflict"
CodeResultExpired = "result_expired"
CodeFileTooLarge = "file_too_large"
CodePageLimitExceeded = "page_limit_exceeded"
CodeUnsupportedType = "unsupported_type"
CodePDFEncrypted = "pdf_encrypted"
CodeInvalidPageSelection = "invalid_page_selection"
CodeInvalidSchema = "invalid_schema"
CodeSchemaFieldConflict = "schema_field_conflict"
CodeTooManyQuestions = "too_many_questions"
CodeRateLimited = "rate_limited"
CodeTooManyTasks = "too_many_tasks"
CodeInternalError = "internal_error"
CodeProviderError = "provider_error"
CodeSyncTimeout = "sync_timeout"
)
// APIError is a structured failure from the API.
//
// Match it with errors.As, then switch on Code:
//
// var apiErr *visionapi.APIError
// if errors.As(err, &apiErr) {
// switch apiErr.Code {
// case visionapi.CodeInsufficientCredits:
// // apiErr.Required() and apiErr.Available() say by how much. Never retryable.
// case visionapi.CodeSyncTimeout:
// // Re-submit through AnalyzeAsync.
// }
// }
type APIError struct {
// StatusCode is the HTTP status.
StatusCode int
// Code is the stable machine-readable value from the envelope.
Code string
// Message is human-readable prose. Do not branch on it.
Message string
// Details carries whatever the endpoint attached: required/available on 402,
// request_id on 500, max_pages on 413.
Details map[string]any
// RequestID is the correlation id, when one was returned. Quote it in support requests.
RequestID string
// Header is the response header set, for Retry-After and anything else you need raw.
Header http.Header
}
func (e *APIError) Error() string {
return fmt.Sprintf("visionapi: %s (%d): %s", e.Code, e.StatusCode, e.Message)
}
// Retryable reports whether the identical call could plausibly succeed on a second
// attempt. False for every input error, and for insufficient_credits — no amount of
// retrying changes a balance.
// CodeTooManyTasks is 429 but absent on purpose: unlike a rate limit it does not clear on
// a timer, it clears when one of the caller's own in-flight tasks finishes. Sleeping would
// hold the very slot being waited for, so the caller must drain their own queue instead.
func (e *APIError) Retryable() bool {
switch e.Code {
case CodeRateLimited, CodeInternalError, CodeProviderError:
return true
}
return false
}
// RetryAfter is the server's own number on a 429. Honor it rather than guessing at a
// backoff. Returns 0 when the response carried none.
func (e *APIError) RetryAfter() time.Duration {
if raw := e.Header.Get("Retry-After"); raw != "" {
if secs, err := strconv.ParseFloat(raw, 64); err == nil && secs > 0 {
return time.Duration(secs * float64(time.Second))
}
}
if v, ok := e.Details["retry_after"].(float64); ok && v > 0 {
return time.Duration(v * float64(time.Second))
}
return 0
}
// Required is the credit cost of the request that was refused, on a 402.
func (e *APIError) Required() int {
v, _ := e.Details["required"].(float64)
return int(v)
}
// Available is the balance at the time of the refusal, on a 402.
func (e *APIError) Available() int {
v, _ := e.Details["available"].(float64)
return int(v)
}
// IsCode reports whether err is an *APIError with the given code. The convenience form of
// the errors.As dance, for the common single-code check.
//
// if visionapi.IsCode(err, visionapi.CodeInsufficientCredits) { … }
func IsCode(err error, code string) bool {
var apiErr *APIError
if !asAPIError(err, &apiErr) {
return false
}
return apiErr.Code == code
}
// UsageError is a mistake in how the client was called, caught before any HTTP request —
// and therefore before any credit could move.
type UsageError struct{ Message string }
func (e *UsageError) Error() string { return "visionapi: " + e.Message }
// ConnectionError means the request never produced a response: DNS, TLS, a reset
// connection, or a deadline that elapsed locally.
type ConnectionError struct {
Op string
Err error
}
func (e *ConnectionError) Error() string {
return fmt.Sprintf("visionapi: %s: %v", e.Op, e.Err)
}
func (e *ConnectionError) Unwrap() error { return e.Err }
// TaskFailedError means an async task came back failed. A failed task costs 0 credits —
// the reservation is released in full.
type TaskFailedError struct {
TaskID string
Code string
Message string
Details map[string]any
}
func (e *TaskFailedError) Error() string {
return fmt.Sprintf("visionapi: task %s failed — %s: %s", e.TaskID, e.Code, e.Message)
}
// TaskTimeoutError means WaitForTask gave up while the task was still running. The task
// itself keeps going, so the id is still worth polling later.
type TaskTimeoutError struct {
TaskID string
Waited time.Duration
}
func (e *TaskTimeoutError) Error() string {
return fmt.Sprintf("visionapi: task %s did not finish within %s", e.TaskID, e.Waited)
}
// WebhookSignatureError means a delivery could not be trusted. Reject the request with a
// 400 and do not parse the body.
type WebhookSignatureError struct{ Reason string }
func (e *WebhookSignatureError) Error() string {
return "visionapi: webhook signature rejected: " + e.Reason
}