Feature 08 task 2: OpenAI-compatible adapter #35
+26
-18
@@ -32,22 +32,30 @@ type RateLimitedError struct {
|
|||||||
// RetryAfter is how long the vendor asked callers to wait; 0 means it
|
// RetryAfter is how long the vendor asked callers to wait; 0 means it
|
||||||
// did not say.
|
// did not say.
|
||||||
RetryAfter time.Duration
|
RetryAfter time.Duration
|
||||||
secret string
|
// text is the redacted Error text set by NewRateLimitedError; "" means
|
||||||
|
// the error was built without a credential.
|
||||||
|
text string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRateLimitedError returns the error for a throttled request. secret is
|
// NewRateLimitedError returns the error for a throttled request. secret is
|
||||||
// the credential sent with the request, or "" when the request sent none;
|
// the credential sent with the request, or "" when the request sent none.
|
||||||
// Error removes every occurrence of a non-empty secret, even where the
|
// Every occurrence of a non-empty secret is removed from the error text
|
||||||
// fixed text happens to spell it.
|
// when it is built, even where the fixed text happens to spell it, and the
|
||||||
|
// secret itself is not kept.
|
||||||
func NewRateLimitedError(retryAfter time.Duration, secret string) *RateLimitedError {
|
func NewRateLimitedError(retryAfter time.Duration, secret string) *RateLimitedError {
|
||||||
return &RateLimitedError{RetryAfter: retryAfter, secret: secret}
|
e := &RateLimitedError{RetryAfter: retryAfter}
|
||||||
|
e.text = Redact(e.Error(), secret)
|
||||||
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RateLimitedError) Error() string {
|
func (e *RateLimitedError) Error() string {
|
||||||
if e.RetryAfter == 0 {
|
if e.text != "" {
|
||||||
return Redact(ErrRateLimited.Error(), e.secret)
|
return e.text
|
||||||
}
|
}
|
||||||
return Redact(fmt.Sprintf("%s: retry after %v", ErrRateLimited, e.RetryAfter), e.secret)
|
if e.RetryAfter == 0 {
|
||||||
|
return ErrRateLimited.Error()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s: retry after %v", ErrRateLimited, e.RetryAfter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is reports whether target is ErrRateLimited.
|
// Is reports whether target is ErrRateLimited.
|
||||||
@@ -84,7 +92,7 @@ func Redact(s, secret string) string {
|
|||||||
type ProviderError struct {
|
type ProviderError struct {
|
||||||
status int
|
status int
|
||||||
body string
|
body string
|
||||||
secret string
|
text string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProviderError returns the error for an unexpected HTTP status. It
|
// NewProviderError returns the error for an unexpected HTTP status. It
|
||||||
@@ -92,8 +100,9 @@ type ProviderError struct {
|
|||||||
// credential sent with the request, or "" when the request sent none; every
|
// credential sent with the request, or "" when the request sent none; every
|
||||||
// occurrence of a non-empty secret in body is removed by Redact before the
|
// occurrence of a non-empty secret in body is removed by Redact before the
|
||||||
// body is cut, so no part of it survives, and again after the cut, so the
|
// body is cut, so no part of it survives, and again after the cut, so the
|
||||||
// truncation mark cannot complete it. Error removes it from the complete
|
// truncation mark cannot complete it. It is removed from the complete error
|
||||||
// text too, even where the fixed text happens to spell it.
|
// text too, even where the fixed text happens to spell it, and the secret
|
||||||
|
// itself is not kept.
|
||||||
func NewProviderError(status int, body []byte, secret string) *ProviderError {
|
func NewProviderError(status int, body []byte, secret string) *ProviderError {
|
||||||
s := strings.TrimSpace(Redact(string(body), secret))
|
s := strings.TrimSpace(Redact(string(body), secret))
|
||||||
if len(s) > MaxBodyExcerpt {
|
if len(s) > MaxBodyExcerpt {
|
||||||
@@ -103,7 +112,11 @@ func NewProviderError(status int, body []byte, secret string) *ProviderError {
|
|||||||
}
|
}
|
||||||
s = Redact(s[:cut]+"…", secret)
|
s = Redact(s[:cut]+"…", secret)
|
||||||
}
|
}
|
||||||
return &ProviderError{status: status, body: s, secret: secret}
|
text := fmt.Sprintf("%s: HTTP %d", ErrProvider, status)
|
||||||
|
if s != "" {
|
||||||
|
text += ": " + s
|
||||||
|
}
|
||||||
|
return &ProviderError{status: status, body: s, text: Redact(text, secret)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status returns the HTTP status code.
|
// Status returns the HTTP status code.
|
||||||
@@ -113,12 +126,7 @@ func (e *ProviderError) Status() int { return e.status }
|
|||||||
// when the vendor sent no body.
|
// when the vendor sent no body.
|
||||||
func (e *ProviderError) Body() string { return e.body }
|
func (e *ProviderError) Body() string { return e.body }
|
||||||
|
|
||||||
func (e *ProviderError) Error() string {
|
func (e *ProviderError) Error() string { return e.text }
|
||||||
if e.body == "" {
|
|
||||||
return Redact(fmt.Sprintf("%s: HTTP %d", ErrProvider, e.status), e.secret)
|
|
||||||
}
|
|
||||||
return Redact(fmt.Sprintf("%s: HTTP %d: %s", ErrProvider, e.status, e.body), e.secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is reports whether target is ErrProvider.
|
// Is reports whether target is ErrProvider.
|
||||||
func (e *ProviderError) Is(target error) bool { return target == ErrProvider }
|
func (e *ProviderError) Is(target error) bool { return target == ErrProvider }
|
||||||
|
|||||||
@@ -184,3 +184,15 @@ func TestRateLimitedErrorWithFixedWordSecretNeverLeaksSecret(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTypedErrorsDoNotKeepSecret(t *testing.T) {
|
||||||
|
const secret = "sk-live-0123456789abcdef"
|
||||||
|
for _, err := range []error{
|
||||||
|
NewProviderError(500, []byte("down "+secret), secret),
|
||||||
|
NewRateLimitedError(30*time.Second, secret),
|
||||||
|
} {
|
||||||
|
if got := fmt.Sprintf("%#v %+v", err, err); strings.Contains(got, secret) {
|
||||||
|
t.Fatalf("formatting %T leaks the secret: %q", err, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user