Skip to content

Commit af74d7a

Browse files
committed
feat: create write/Error wrapper to better handle http/Error with Headers.
1 parent 4e3ff5f commit af74d7a

File tree

6 files changed

+145
-22
lines changed

6 files changed

+145
-22
lines changed

api/http/error_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2020-2021 InfluxData, Inc. All rights reserved.
1+
// Copyright 2020-2024 InfluxData, Inc. All rights reserved.
22
// Use of this source code is governed by MIT
33
// license that can be found in the LICENSE file.
44

api/write/error.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2020-2024 InfluxData, Inc. All rights reserved.
2+
// Use of this source code is governed by MIT
3+
// license that can be found in the LICENSE file.
4+
5+
package write
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
"github.com/influxdata/influxdb-client-go/v2/api/http"
11+
iHttp "net/http"
12+
"net/textproto"
13+
)
14+
15+
// Error wraps an error that may have occurred during a write call. Most often this will be an http.Error.
16+
type Error struct {
17+
origin error
18+
message string
19+
}
20+
21+
// NewError returns a new created Error instance wrapping the original error.
22+
func NewError(origin error, message string) *Error {
23+
return &Error{
24+
origin: origin,
25+
message: message,
26+
}
27+
}
28+
29+
// Error fulfills the error interface
30+
func (e *Error) Error() string {
31+
return fmt.Sprintf("%s:\n %s", e.message, e.origin)
32+
}
33+
34+
func (e *Error) Unwrap() error {
35+
return e.origin
36+
}
37+
38+
// HTTPHeader returns the Header of a wrapped http.Error. If the original error is not http.Error returns standard error.
39+
func (e *Error) HTTPHeader() (iHttp.Header, error) {
40+
var err *http.Error
41+
ok := errors.As(e.origin, &err)
42+
if ok {
43+
return err.Header, nil
44+
}
45+
return nil, fmt.Errorf(fmt.Sprintf("Origin error: (%s) is not of type *http.Error.\n", e.origin.Error()))
46+
}
47+
48+
// GetHeaderValues returns the values from a Header key. If original error is not http.Error return standard error.
49+
func (e *Error) GetHeaderValues(key string) ([]string, error) {
50+
var err *http.Error
51+
if errors.As(e.origin, &err) {
52+
return err.Header.Values(textproto.CanonicalMIMEHeaderKey(key)), nil
53+
}
54+
return nil, fmt.Errorf(fmt.Sprintf("Origin error: (%s) is not of type http.Header.\n", e.origin.Error()))
55+
}
56+
57+
// GetHeader returns the first value from a header key. If origin is not http.Error or if no match is found returns "".
58+
func (e *Error) GetHeader(key string) string {
59+
var err *http.Error
60+
if errors.As(e.origin, &err) {
61+
return err.Header.Get(textproto.CanonicalMIMEHeaderKey(key))
62+
}
63+
return ""
64+
}

api/write/error_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2020-2024 InfluxData, Inc. All rights reserved.
2+
// Use of this source code is governed by MIT
3+
// license that can be found in the LICENSE file.
4+
5+
package write
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
"github.com/influxdata/influxdb-client-go/v2/api/http"
11+
"github.com/stretchr/testify/assert"
12+
ihttp "net/http"
13+
"testing"
14+
)
15+
16+
func TestNewErrorNotHttpError(t *testing.T) {
17+
err := NewError(fmt.Errorf("origin error"), "error message")
18+
var errTest *http.Error
19+
assert.False(t, errors.As(err, &errTest))
20+
header, okh := err.HTTPHeader()
21+
assert.Nil(t, header)
22+
assert.Error(t, okh)
23+
values, okv := err.GetHeaderValues("Date")
24+
assert.Nil(t, values)
25+
assert.Error(t, okv)
26+
assert.Equal(t, "", err.GetHeader("Date"))
27+
assert.Equal(t, "error message:\n origin error", err.Error())
28+
}
29+
30+
func TestNewErrorHttpError(t *testing.T) {
31+
header := ihttp.Header{
32+
"Date": []string{"2024-08-07T12:00:00.009"},
33+
"Content-Length": []string{"12"},
34+
"Content-Type": []string{"application/json", "encoding UTF-8"},
35+
"X-Test-Value1": []string{"SaturnV"},
36+
"X-Test-Value2": []string{"Apollo11"},
37+
"Retry-After": []string{"2044"},
38+
"Trace-Id": []string{"123456789ABCDEF0"},
39+
}
40+
41+
err := NewError(&http.Error{
42+
StatusCode: ihttp.StatusBadRequest,
43+
Code: "bad request",
44+
Message: "this is just a test",
45+
Err: nil,
46+
RetryAfter: 2044,
47+
Header: header,
48+
}, "should be httpError")
49+
50+
var errTest *http.Error
51+
assert.True(t, errors.As(err.Unwrap(), &errTest))
52+
header, okh := err.HTTPHeader()
53+
assert.NotNil(t, header)
54+
assert.Nil(t, okh)
55+
date, okd := err.GetHeaderValues("Date")
56+
assert.Equal(t, []string{"2024-08-07T12:00:00.009"}, date)
57+
assert.Nil(t, okd)
58+
cType, okc := err.GetHeaderValues("Content-Type")
59+
assert.Equal(t, []string{"application/json", "encoding UTF-8"}, cType)
60+
assert.Nil(t, okc)
61+
assert.Equal(t, "2024-08-07T12:00:00.009", err.GetHeader("Date"))
62+
assert.Equal(t, "SaturnV", err.GetHeader("X-Test-Value1"))
63+
assert.Equal(t, "Apollo11", err.GetHeader("X-Test-Value2"))
64+
assert.Equal(t, "123456789ABCDEF0", err.GetHeader("Trace-Id"))
65+
assert.Equal(t, "should be httpError:\n bad request: this is just a test", err.Error())
66+
}

api/write_test.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -294,13 +294,15 @@ func TestWriteApiErrorHeaders(t *testing.T) {
294294
for i := 0; i < 3; i++ {
295295
recErr = <-errCh
296296
assert.NotNil(t, recErr, "errCh should not run out of values")
297-
assert.Len(t, recErr.(*http.Error).Header, 6)
298-
assert.NotEqual(t, "", recErr.(*http.Error).Header.Get("Date"))
299-
assert.NotEqual(t, "", recErr.(*http.Error).Header.Get("Content-Length"))
300-
assert.NotEqual(t, "", recErr.(*http.Error).Header.Get("Content-Type"))
301-
assert.Equal(t, strconv.Itoa(i+1), recErr.(*http.Error).Header.Get("X-Call-Count"))
302-
assert.Equal(t, "Not All Correct", recErr.(*http.Error).Header.Get("X-Test-Val1"))
303-
assert.Equal(t, "Atlas LV-3B", recErr.(*http.Error).Header.Get("X-Test-Val2"))
297+
header, okh := recErr.(*write.Error).HTTPHeader()
298+
assert.Nil(t, okh)
299+
assert.Len(t, header, 6)
300+
assert.NotEqual(t, "", recErr.(*write.Error).GetHeader("Date"))
301+
assert.NotEqual(t, "", recErr.(*write.Error).GetHeader("Content-Length"))
302+
assert.NotEqual(t, "", recErr.(*write.Error).GetHeader("Content-Type"))
303+
assert.Equal(t, strconv.Itoa(i+1), recErr.(*write.Error).GetHeader("X-Call-Count"))
304+
assert.Equal(t, "Not All Correct", recErr.(*write.Error).GetHeader("X-Test-Val1"))
305+
assert.Equal(t, "Atlas LV-3B", recErr.(*write.Error).GetHeader("X-Test-Val2"))
304306
}
305307
wg.Done()
306308
}()

internal/write/service.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -210,16 +210,7 @@ func (w *Service) HandleWrite(ctx context.Context, batch *Batch) error {
210210
}
211211
log.Error(logMessage)
212212
}
213-
return &http2.Error{
214-
StatusCode: int(perror.StatusCode),
215-
Code: perror.Code,
216-
Message: fmt.Errorf(
217-
"write failed (attempts %d): %w", batchToWrite.RetryAttempts, perror,
218-
).Error(),
219-
Err: perror.Err,
220-
RetryAfter: perror.RetryAfter,
221-
Header: perror.Header,
222-
}
213+
return write.NewError(perror, fmt.Sprintf("write failed (retry attempts %d)", batchToWrite.RetryAttempts))
223214
}
224215
}
225216

internal/write/service_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ func TestMaxRetryTime(t *testing.T) {
339339
err = srv.HandleWrite(ctx, b)
340340
require.NotNil(t, err)
341341
// 1st Batch expires and writing 2nd trows error
342-
assert.Equal(t, "write failed (attempts 1): Unexpected status code 429", err.Error())
342+
assert.Equal(t, "write failed (retry attempts 1):\n Unexpected status code 429", err.Error())
343343
assert.Equal(t, 1, srv.retryQueue.list.Len())
344344

345345
//wait until remaining accumulated retryDelay has passed, because there hasn't been a successful write yet
@@ -715,7 +715,7 @@ func TestHttpErrorHeaders(t *testing.T) {
715715
write.DefaultOptions())
716716
err := svc.HandleWrite(context.Background(), NewBatch("1", 20))
717717
assert.Error(t, err)
718-
assert.Equal(t, "400 Bad Request: write failed (attempts 0): 400 Bad Request: { \"code\": \"bad request\", \"message\": \"test header\" }", err.Error())
719-
assert.Equal(t, "Not All Correct", err.(*http.Error).Header.Get("X-Test-Val1"))
720-
assert.Equal(t, "Atlas LV-3B", err.(*http.Error).Header.Get("X-Test-Val2"))
718+
assert.Equal(t, "write failed (retry attempts 0):\n 400 Bad Request: { \"code\": \"bad request\", \"message\": \"test header\" }", err.Error())
719+
assert.Equal(t, "Not All Correct", err.(*write.Error).GetHeader("X-Test-Val1"))
720+
assert.Equal(t, "Atlas LV-3B", err.(*write.Error).GetHeader("X-Test-Val2"))
721721
}

0 commit comments

Comments
 (0)