From 59a5e7eb022ee52aa236729e02a7953fb95d2ce2 Mon Sep 17 00:00:00 2001 From: karel rehor Date: Wed, 4 Sep 2024 13:30:11 +0200 Subject: [PATCH 1/6] feat: (WIP) adds response headers to ServerError --- influxdb3/client.go | 2 ++ influxdb3/client_e2e_test.go | 26 ++++++++++++++++++++++++++ influxdb3/error.go | 3 +++ influxdb3/write_test.go | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/influxdb3/client.go b/influxdb3/client.go index 1ca5bf1..bf0e95a 100644 --- a/influxdb3/client.go +++ b/influxdb3/client.go @@ -243,6 +243,8 @@ func (c *Client) resolveHTTPError(r *http.Response) error { } } + httpError.Headers = r.Header + return &httpError.ServerError } diff --git a/influxdb3/client_e2e_test.go b/influxdb3/client_e2e_test.go index 9317c19..af793da 100644 --- a/influxdb3/client_e2e_test.go +++ b/influxdb3/client_e2e_test.go @@ -29,6 +29,7 @@ import ( "context" "fmt" "os" + "strconv" "testing" "time" @@ -282,3 +283,28 @@ func TestQuerySchemaInfluxQL(t *testing.T) { require.NoError(t, err) assert.NotNil(t, iterator.Raw()) } + +func TestWriteError(t *testing.T) { + url := os.Getenv("TESTING_INFLUXDB_URL") + token := os.Getenv("TESTING_INFLUXDB_TOKEN") + database := os.Getenv("TESTING_INFLUXDB_DATABASE") + + client, err := influxdb3.New(influxdb3.ClientConfig{ + Host: url, + Token: token, + Database: database, + }) + require.NoError(t, err) + + err = client.Write(context.Background(), []byte("test,type=negative val=")) + require.Error(t, err) + assert.NotPanics(t, func() { _ = err.(*influxdb3.ServerError) }) + assert.Regexp(t, "[0-9a-f]{16}", err.(*influxdb3.ServerError).Headers["Trace-Id"][0]) + b, perr := strconv.ParseBool(err.(*influxdb3.ServerError).Headers["Trace-Sampled"][0]) + require.NoError(t, perr) + assert.False(t, b) + assert.NotNil(t, err.(*influxdb3.ServerError).Headers["Strict-Transport-Security"]) + assert.Regexp(t, "[0-9a-f]{32}", err.(*influxdb3.ServerError).Headers["X-Influxdb-Request-Id"][0]) + assert.NotNil(t, err.(*influxdb3.ServerError).Headers["X-Influxdb-Build"][0]) + +} diff --git a/influxdb3/error.go b/influxdb3/error.go index 827eed7..1fcfa13 100644 --- a/influxdb3/error.go +++ b/influxdb3/error.go @@ -24,6 +24,7 @@ package influxdb3 import ( "fmt" + "net/http" ) // ServerError represents an error returned from an InfluxDB API server. @@ -36,6 +37,8 @@ type ServerError struct { StatusCode int `json:"-"` // RetryAfter holds the value of Retry-After header if sent by server, otherwise zero RetryAfter int `json:"-"` + // Headers hold the response headers + Headers http.Header `json:"headers"` } // NewServerError returns new with just a message diff --git a/influxdb3/write_test.go b/influxdb3/write_test.go index 232c7c8..e5ce5ce 100644 --- a/influxdb3/write_test.go +++ b/influxdb3/write_test.go @@ -733,6 +733,39 @@ func TestHttpError(t *testing.T) { assert.ErrorContains(t, err, "error calling") } +func TestHttpErrorWithHeaders(t *testing.T) { + traceID := "123456789ABCDEF0" + tsVersion := "v0.0.1" + build := "TestServer" + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Trace-ID", traceID) + w.Header().Set("X-Influxdb-Build", build) + w.Header().Set("X-Influxdb-Version", tsVersion) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + _, err := w.Write([]byte("{ \"message\": \"Test Response\" }")) + if err != nil { + assert.FailNow(t, err.Error()) + } + })) + defer ts.Close() + tc, err := New(ClientConfig{ + Host: ts.URL, + Token: "my-token", + Database: "my-database", + }) + require.NoError(t, err) + err = tc.WriteData(context.Background(), []any{}) + require.Error(t, err) + assert.NotPanics(t, func() { _ = err.(*ServerError) }) + assert.Equal(t, 400, err.(*ServerError).StatusCode) + assert.Equal(t, "Test Response", err.(*ServerError).Message) + assert.Equal(t, 6, len(err.(*ServerError).Headers)) + assert.Equal(t, traceID, err.(*ServerError).Headers["Trace-Id"][0]) + assert.Equal(t, build, err.(*ServerError).Headers["X-Influxdb-Build"][0]) + assert.Equal(t, tsVersion, err.(*ServerError).Headers["X-Influxdb-Version"][0]) +} + func TestWriteDatabaseNotSet(t *testing.T) { p := NewPointWithMeasurement("cpu") p.SetTag("host", "local") From 7720796c61ff5cc5b6bf389a04380aa3f7d4a0c8 Mon Sep 17 00:00:00 2001 From: karel rehor Date: Wed, 4 Sep 2024 13:43:02 +0200 Subject: [PATCH 2/6] test: refactor TestHttpErrorWithHeaders --- influxdb3/write_test.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/influxdb3/write_test.go b/influxdb3/write_test.go index e5ce5ce..67f8fc8 100644 --- a/influxdb3/write_test.go +++ b/influxdb3/write_test.go @@ -25,6 +25,7 @@ package influxdb3 import ( "compress/gzip" "context" + "errors" "fmt" "io" "math/rand" @@ -738,7 +739,7 @@ func TestHttpErrorWithHeaders(t *testing.T) { tsVersion := "v0.0.1" build := "TestServer" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Trace-ID", traceID) + w.Header().Set("Trace-Id", traceID) w.Header().Set("X-Influxdb-Build", build) w.Header().Set("X-Influxdb-Version", tsVersion) w.Header().Set("Content-Type", "application/json") @@ -757,13 +758,16 @@ func TestHttpErrorWithHeaders(t *testing.T) { require.NoError(t, err) err = tc.WriteData(context.Background(), []any{}) require.Error(t, err) - assert.NotPanics(t, func() { _ = err.(*ServerError) }) - assert.Equal(t, 400, err.(*ServerError).StatusCode) - assert.Equal(t, "Test Response", err.(*ServerError).Message) - assert.Equal(t, 6, len(err.(*ServerError).Headers)) - assert.Equal(t, traceID, err.(*ServerError).Headers["Trace-Id"][0]) - assert.Equal(t, build, err.(*ServerError).Headers["X-Influxdb-Build"][0]) - assert.Equal(t, tsVersion, err.(*ServerError).Headers["X-Influxdb-Version"][0]) + var serr *ServerError + assert.NotPanics(t, func() { + errors.As(err, &serr) + }) + assert.Equal(t, 400, serr.StatusCode) + assert.Equal(t, "Test Response", serr.Message) + assert.Equal(t, 6, len(serr.Headers)) + assert.Equal(t, traceID, serr.Headers["Trace-Id"][0]) + assert.Equal(t, build, serr.Headers["X-Influxdb-Build"][0]) + assert.Equal(t, tsVersion, serr.Headers["X-Influxdb-Version"][0]) } func TestWriteDatabaseNotSet(t *testing.T) { From 8a8c8336eaa7a02ca0d7c6b65cdb86e377d9bdb7 Mon Sep 17 00:00:00 2001 From: karel rehor Date: Wed, 4 Sep 2024 13:58:35 +0200 Subject: [PATCH 3/6] chore: fix assert in new test --- influxdb3/write_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/influxdb3/write_test.go b/influxdb3/write_test.go index 67f8fc8..54f3ac7 100644 --- a/influxdb3/write_test.go +++ b/influxdb3/write_test.go @@ -764,7 +764,7 @@ func TestHttpErrorWithHeaders(t *testing.T) { }) assert.Equal(t, 400, serr.StatusCode) assert.Equal(t, "Test Response", serr.Message) - assert.Equal(t, 6, len(serr.Headers)) + assert.Len(t, serr.Headers, 6) assert.Equal(t, traceID, serr.Headers["Trace-Id"][0]) assert.Equal(t, build, serr.Headers["X-Influxdb-Build"][0]) assert.Equal(t, tsVersion, serr.Headers["X-Influxdb-Version"][0]) From 3fcbe49a20061d123523f83ffa9842b8a4b8ec94 Mon Sep 17 00:00:00 2001 From: karel rehor Date: Wed, 4 Sep 2024 15:02:02 +0200 Subject: [PATCH 4/6] docs: adds examples for working with HTTP response headers in ServerError. --- examples/General/httpErrorHandled.go | 56 ++++++++++++++++++++++++++++ influxdb3/example_write_test.go | 22 +++++++++++ 2 files changed, 78 insertions(+) create mode 100644 examples/General/httpErrorHandled.go diff --git a/examples/General/httpErrorHandled.go b/examples/General/httpErrorHandled.go new file mode 100644 index 0000000..7809c27 --- /dev/null +++ b/examples/General/httpErrorHandled.go @@ -0,0 +1,56 @@ +package main + +import ( + "context" + "errors" + "fmt" + "log" + "os" + + "github.com/InfluxCommunity/influxdb3-go/influxdb3" +) + +// Demonstrates working with HTTP response headers in ServerError +func main() { + // Retrieve credentials from environment variables. + url := os.Getenv("INFLUX_URL") + token := os.Getenv("INFLUX_TOKEN") + database := os.Getenv("INFLUX_DATABASE") + + // Instantiate a client using your credentials. + client, err := influxdb3.New(influxdb3.ClientConfig{ + Host: url, + Token: token, + Database: database, + }) + if err != nil { + panic(err) + } + + // Close the client when finished and raise any errors. + defer func(client *influxdb3.Client) { + err := client.Close() + if err != nil { + panic(err) + } + }(client) + + // Attempt to write line protocol synchronously + // N.B. faulty line protocol used here, because it + // guarantees a server error, but errors can be thrown + // for other reasons, such as 503 temporary unavailable + // or even 429 too many requests. + err = client.Write(context.Background(), + []byte("air,sensor=HRF03,device_ID=42 humidity=67.1,temperature=")) + + if err != nil { + logMessage := "WARNING write error: " + err.Error() + logMessage += "\n ServerError.Headers:\n" + var svErr *influxdb3.ServerError + errors.As(err, &svErr) + for key, value := range svErr.Headers { + logMessage += fmt.Sprintf(" %s: %s\n", key, value) + } + log.Println(logMessage) + } +} diff --git a/influxdb3/example_write_test.go b/influxdb3/example_write_test.go index bcb2812..287e0ac 100644 --- a/influxdb3/example_write_test.go +++ b/influxdb3/example_write_test.go @@ -24,6 +24,7 @@ package influxdb3 import ( "context" + "errors" "log" "strings" "time" @@ -123,3 +124,24 @@ func ExampleClient_WriteData() { log.Fatal() } } + +func ExampleClient_severError() { + client, err := NewFromEnv() + if err != nil { + log.Fatal() + } + defer client.Close() + + err = client.Write(context.Background(), + []byte("air,sensor=HRF03,device_ID=42 humidity=67.1,temperature=")) + + if err != nil { + log.Printf("WARN write failed: %s", err.Error()) + var svErr *ServerError + errors.As(err, &svErr) + log.Printf(" ServerError headers:") + for key, val := range svErr.Headers { + log.Printf(" %s = %s", key, val) + } + } +} From 530e280648c27a15a49f61851fe6a53f867dba39 Mon Sep 17 00:00:00 2001 From: karel rehor Date: Thu, 5 Sep 2024 10:09:27 +0200 Subject: [PATCH 5/6] docs: update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 607db2e..b42d051 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 0.10.0 [unreleased] +### Features + +1. [#100](https://github.com/InfluxCommunity/influxdb3-go/pull/100): Expose HTTP Response headers in `ServerError` + ### Bug Fixes 1. [#94](https://github.com/InfluxCommunity/influxdb3-go/pull/94): Resource leak from unclosed `Response` From b3c49c739d3b5696cfc8c9d8163257ecf8794751 Mon Sep 17 00:00:00 2001 From: karel rehor Date: Thu, 5 Sep 2024 13:55:01 +0200 Subject: [PATCH 6/6] chore: improve assert error type --- influxdb3/write_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/influxdb3/write_test.go b/influxdb3/write_test.go index 54f3ac7..89b8d48 100644 --- a/influxdb3/write_test.go +++ b/influxdb3/write_test.go @@ -25,7 +25,6 @@ package influxdb3 import ( "compress/gzip" "context" - "errors" "fmt" "io" "math/rand" @@ -759,9 +758,7 @@ func TestHttpErrorWithHeaders(t *testing.T) { err = tc.WriteData(context.Background(), []any{}) require.Error(t, err) var serr *ServerError - assert.NotPanics(t, func() { - errors.As(err, &serr) - }) + require.ErrorAs(t, err, &serr) assert.Equal(t, 400, serr.StatusCode) assert.Equal(t, "Test Response", serr.Message) assert.Len(t, serr.Headers, 6)