(new): expand runtime APIs
Golang lint / lint (push) Successful in 58s
Golang lint / lint (pull_request) Successful in 13m10s

(fix): harden concurrent lifecycle
(tests): add regression coverage
(doc): update v1.2 guidance
This commit is contained in:
2026-08-20 11:08:45 +03:00
parent 29b208eeec
commit 24040fe164
37 changed files with 1129 additions and 157 deletions
+1 -1
View File
@@ -272,7 +272,7 @@ func (r TelegramRequest[R, P]) doRequest(ctx context.Context, api *API) (R, erro
api.logger.Debugln("REQ", url, redactRequestLog(reqData))
resp, err := api.client.Do(req)
if err != nil {
return zero, fmt.Errorf("HTTP request failed: %w", err)
return zero, fmt.Errorf("HTTP request failed: %w", redactHTTPError(err, api.token))
}
respData, err := readBody(resp.Body)
+29
View File
@@ -2,7 +2,9 @@ package tgapi
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"strings"
)
@@ -55,3 +57,30 @@ func redactLogValue(value any) {
func responseLogSummary(method string, size int) string {
return fmt.Sprintf("method=%s bytes=%d body=omitted", method, size)
}
type redactedError struct {
err error
secret string
}
func (e *redactedError) Error() string {
return strings.ReplaceAll(e.err.Error(), e.secret, redactedLogValue)
}
func (e *redactedError) Unwrap() error { return e.err }
func redactHTTPError(err error, token string) error {
if err == nil || token == "" || !strings.Contains(err.Error(), token) {
return err
}
var urlErr *url.Error
if !errors.As(err, &urlErr) {
return &redactedError{err: err, secret: token}
}
redactedURL := *urlErr
redactedURL.URL = strings.ReplaceAll(redactedURL.URL, token, redactedLogValue)
redactedURL.Err = &redactedError{err: urlErr.Err, secret: token}
return &redactedURL
}
+29
View File
@@ -1,6 +1,9 @@
package tgapi
import (
"errors"
"fmt"
"net/url"
"strings"
"testing"
)
@@ -37,3 +40,29 @@ func TestResponseLogSummaryNeverContainsBody(t *testing.T) {
t.Fatalf("response summary does not explain omission: %s", got)
}
}
func TestRedactHTTPErrorRemovesTokenAndPreservesCause(t *testing.T) {
const token = "123456:secret-token"
cause := errors.New("transport failed")
original := &url.Error{
Op: "Post",
URL: "https://api.telegram.org/bot" + token + "/sendMessage",
Err: fmt.Errorf("request for %s failed: %w", token, cause),
}
got := redactHTTPError(original, token)
if strings.Contains(got.Error(), token) {
t.Fatalf("redacted HTTP error contains bot token: %v", got)
}
if !errors.Is(got, cause) {
t.Fatalf("redacted HTTP error lost its cause: %v", got)
}
var gotURLError *url.Error
if !errors.As(got, &gotURLError) {
t.Fatalf("redacted HTTP error lost url.Error type: %T", got)
}
if strings.Contains(gotURLError.Error(), token) {
t.Fatalf("redacted url.Error contains bot token: %v", gotURLError)
}
}
+1 -1
View File
@@ -336,7 +336,7 @@ func (api *API) openFileByLink(ctx context.Context, link string) (io.ReadCloser,
res, err := api.client.Do(req)
if err != nil {
return nil, err
return nil, redactHTTPError(err, api.token)
}
if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusMultipleChoices {
defer func() {
+1 -1
View File
@@ -156,7 +156,7 @@ func (r UploaderRequest[R, P]) doRequest(ctx context.Context, up *Uploader) (R,
resp, err := up.api.client.Do(req)
_ = requestBody.Close()
if err != nil {
return zero, fmt.Errorf("HTTP upload request failed: %w", err)
return zero, fmt.Errorf("HTTP upload request failed: %w", redactHTTPError(err, up.api.token))
}
body, err := readBody(resp.Body)