From dfb17e2022329c2116ed44d4276ca275e01e9726 Mon Sep 17 00:00:00 2001 From: Ales Pour Date: Mon, 16 Feb 2026 14:04:17 +0100 Subject: [PATCH 01/15] fix: handle structured error response --- .../v3/client/internal/RestClient.java | 101 ++++++++++++++---- .../v3/client/internal/RestClientTest.java | 89 ++++++++++++++- 2 files changed, 170 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/influxdb/v3/client/internal/RestClient.java b/src/main/java/com/influxdb/v3/client/internal/RestClient.java index 0453e662..3627523d 100644 --- a/src/main/java/com/influxdb/v3/client/internal/RestClient.java +++ b/src/main/java/com/influxdb/v3/client/internal/RestClient.java @@ -219,34 +219,20 @@ HttpResponse request(@Nonnull final String path, if (statusCode < 200 || statusCode >= 300) { String reason = ""; String body = response.body(); - if (!body.isEmpty()) { - try { - final JsonNode root = objectMapper.readTree(body); - final List possibilities = List.of("message", "error_message", "error"); - for (final String field : possibilities) { - final JsonNode node = root.findValue(field); - if (node != null) { - reason = node.asText(); - break; - } - } - } catch (JsonProcessingException e) { - LOG.debug("Can't parse msg from response {}", response); - } - } + reason = formatErrorMessage(body, response.headers().firstValue("Content-Type").orElse(null)); - if (reason.isEmpty()) { + if (reason == null || reason.isEmpty()) { reason = Stream.of("X-Platform-Error-Code", "X-Influx-Error", "X-InfluxDb-Error") .map(name -> response.headers().firstValue(name).orElse(null)) .filter(message -> message != null && !message.isEmpty()).findFirst() .orElse(""); } - if (reason.isEmpty()) { + if (reason == null || reason.isEmpty()) { reason = body; } - if (reason.isEmpty()) { + if (reason == null || reason.isEmpty()) { reason = HttpResponseStatus.valueOf(statusCode).reasonPhrase(); } @@ -257,6 +243,85 @@ HttpResponse request(@Nonnull final String path, return response; } + @Nullable + private String formatErrorMessage(@Nonnull final String body, @Nullable final String contentType) { + if (body.isEmpty()) { + return null; + } + + if (contentType != null + && !contentType.isEmpty() + && !contentType.regionMatches(true, 0, "application/json", 0, "application/json".length())) { + return null; + } + + try { + final JsonNode root = objectMapper.readTree(body); + if (!root.isObject()) { + return null; + } + + final JsonNode messageNode = root.get("message"); + if (messageNode != null && !messageNode.isNull() && !messageNode.asText().isEmpty()) { + return messageNode.asText(); + } + + final JsonNode errorNode = root.get("error"); + final String error = (errorNode != null && !errorNode.isNull() && !errorNode.asText().isEmpty()) + ? errorNode.asText() : null; + final JsonNode dataNode = root.get("data"); + + // InfluxDB 3 Core/Enterprise write error format: + // {"error":"...","data":[{"error_message":"...","line_number":2,"original_line":"..."}]} + if (error != null && dataNode != null && dataNode.isArray()) { + final StringBuilder message = new StringBuilder(error); + boolean hasDetails = false; + for (JsonNode item : dataNode) { + if (item == null || !item.isObject()) { + continue; + } + final JsonNode errorMessageNode = item.get("error_message"); + if (errorMessageNode == null || errorMessageNode.isNull() || errorMessageNode.asText().isEmpty()) { + continue; + } + if (!hasDetails) { + message.append(':'); + hasDetails = true; + } + if (item.hasNonNull("line_number") + && item.hasNonNull("original_line") + && !item.get("original_line").asText().isEmpty()) { + final String lineNumber = item.get("line_number").asText(); + message.append("\n\tline ") + .append(lineNumber) + .append(": ") + .append(errorMessageNode.asText()) + .append(" (") + .append(item.get("original_line").asText()) + .append(')'); + } else { + message.append("\n\t").append(errorMessageNode.asText()); + } + } + return message.toString(); + } + + // Core/Enterprise object format: + // {"error":"...","data":{"error_message":"..."}} + if (dataNode != null && dataNode.isObject() && dataNode.hasNonNull("error_message")) { + final String errorMessage = dataNode.get("error_message").asText(); + if (!errorMessage.isEmpty()) { + return errorMessage; + } + } + + return error; + } catch (JsonProcessingException e) { + LOG.debug("Can't parse msg from response body {}", body, e); + return null; + } + } + private X509TrustManager getX509TrustManagerFromFile(@Nonnull final String filePath) { try { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); diff --git a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java index 10cbd110..dc2ab4b6 100644 --- a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java +++ b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java @@ -402,7 +402,24 @@ public void errorFromBody() { } @Test - public void errorFromBodyEdgeWithoutMessage() { // OSS/Edge error message + public void errorFromBodyIgnoredForNonJsonContentType() { + mockServer.enqueue(createResponse(400, + Map.of("content-type", "text/plain"), + "{\"message\":\"token does not have sufficient permissions\"}")); + + restClient = new RestClient(new ClientConfig.Builder() + .host(baseURL) + .build()); + + Assertions.assertThatThrownBy( + () -> restClient.request("ping", HttpMethod.GET, null, null, null) + ) + .isInstanceOf(InfluxDBApiException.class) + .hasMessage("HTTP status code: 400; Message: {\"message\":\"token does not have sufficient permissions\"}"); + } + + @Test + public void errorFromBodyV3WithoutMessage() { // Core/Enterprise error message mockServer.enqueue(createResponse(400, null, @@ -420,7 +437,7 @@ public void errorFromBodyEdgeWithoutMessage() { // OSS/Edge error message } @Test - public void errorFromBodyEdgeWithMessage() { // OSS/Edge specific error message + public void errorFromBodyV3WithDataObject() { // Core/Enterprise object format mockServer.enqueue(createResponse(400, null, @@ -437,6 +454,74 @@ public void errorFromBodyEdgeWithMessage() { // OSS/Edge specific error message .hasMessage("HTTP status code: 400; Message: invalid field value"); } + @Test + public void errorFromBodyV3WithDataArray() { + mockServer.enqueue(createResponse(400, + Map.of("content-type", "application/json"), + "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":\"invalid column type for column 'v', expected iox::column_type::field::integer, got iox::column_type::field::float\",\"line_number\":2,\"original_line\":\"testa6a3ad v=1 17702\"}]}")); + + restClient = new RestClient(new ClientConfig.Builder() + .host(baseURL) + .build()); + + Assertions.assertThatThrownBy( + () -> restClient.request("ping", HttpMethod.GET, null, null, null) + ) + .isInstanceOf(InfluxDBApiException.class) + .hasMessage("HTTP status code: 400; Message: partial write of line protocol occurred:\n\tline 2: invalid column type for column 'v', expected iox::column_type::field::integer, got iox::column_type::field::float (testa6a3ad v=1 17702)"); + } + + @Test + public void errorFromBodyV3WithDataArrayMessageOnly() { + mockServer.enqueue(createResponse(400, + Map.of("content-type", "application/json"), + "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":\"only error message\"}]}")); + + restClient = new RestClient(new ClientConfig.Builder() + .host(baseURL) + .build()); + + Assertions.assertThatThrownBy( + () -> restClient.request("ping", HttpMethod.GET, null, null, null) + ) + .isInstanceOf(InfluxDBApiException.class) + .hasMessage("HTTP status code: 400; Message: partial write of line protocol occurred:\n\tonly error message"); + } + + @Test + public void errorFromBodyV3WithDataArrayNonObjectSkipped() { + mockServer.enqueue(createResponse(400, + Map.of("content-type", "application/json"), + "{\"error\":\"partial write of line protocol occurred\",\"data\":[null,{\"error_message\":\"bad line\",\"line_number\":2,\"original_line\":\"bad lp\"}]}")); + + restClient = new RestClient(new ClientConfig.Builder() + .host(baseURL) + .build()); + + Assertions.assertThatThrownBy( + () -> restClient.request("ping", HttpMethod.GET, null, null, null) + ) + .isInstanceOf(InfluxDBApiException.class) + .hasMessage("HTTP status code: 400; Message: partial write of line protocol occurred:\n\tline 2: bad line (bad lp)"); + } + + @Test + public void errorFromBodyV3WithDataArrayNoDetailFields() { + mockServer.enqueue(createResponse(400, + Map.of("content-type", "application/json"), + "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"line_number\":2}]}")); + + restClient = new RestClient(new ClientConfig.Builder() + .host(baseURL) + .build()); + + Assertions.assertThatThrownBy( + () -> restClient.request("ping", HttpMethod.GET, null, null, null) + ) + .isInstanceOf(InfluxDBApiException.class) + .hasMessage("HTTP status code: 400; Message: partial write of line protocol occurred"); + } + @Test public void errorFromBodyText() { From be5f40fd5806a1362a5eb01a6761eda83390d4f1 Mon Sep 17 00:00:00 2001 From: Ales Pour Date: Mon, 16 Feb 2026 14:34:59 +0100 Subject: [PATCH 02/15] test: fix long lineds --- .../v3/client/internal/RestClientTest.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java index dc2ab4b6..92f9ff86 100644 --- a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java +++ b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java @@ -458,7 +458,10 @@ public void errorFromBodyV3WithDataObject() { // Core/Enterprise object format public void errorFromBodyV3WithDataArray() { mockServer.enqueue(createResponse(400, Map.of("content-type", "application/json"), - "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":\"invalid column type for column 'v', expected iox::column_type::field::integer, got iox::column_type::field::float\",\"line_number\":2,\"original_line\":\"testa6a3ad v=1 17702\"}]}")); + "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":" + + "\"invalid column type for column 'v', expected iox::column_type::field::integer," + + " got iox::column_type::field::float\",\"line_number\":2," + + "\"original_line\":\"testa6a3ad v=1 17702\"}]}")); restClient = new RestClient(new ClientConfig.Builder() .host(baseURL) @@ -468,14 +471,17 @@ public void errorFromBodyV3WithDataArray() { () -> restClient.request("ping", HttpMethod.GET, null, null, null) ) .isInstanceOf(InfluxDBApiException.class) - .hasMessage("HTTP status code: 400; Message: partial write of line protocol occurred:\n\tline 2: invalid column type for column 'v', expected iox::column_type::field::integer, got iox::column_type::field::float (testa6a3ad v=1 17702)"); + .hasMessage("HTTP status code: 400; Message: partial write of line protocol occurred:\n" + + "\tline 2: invalid column type for column 'v', expected iox::column_type::field::integer," + + " got iox::column_type::field::float (testa6a3ad v=1 17702)"); } @Test public void errorFromBodyV3WithDataArrayMessageOnly() { mockServer.enqueue(createResponse(400, Map.of("content-type", "application/json"), - "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":\"only error message\"}]}")); + "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":" + + "\"only error message\"}]}")); restClient = new RestClient(new ClientConfig.Builder() .host(baseURL) @@ -492,7 +498,8 @@ public void errorFromBodyV3WithDataArrayMessageOnly() { public void errorFromBodyV3WithDataArrayNonObjectSkipped() { mockServer.enqueue(createResponse(400, Map.of("content-type", "application/json"), - "{\"error\":\"partial write of line protocol occurred\",\"data\":[null,{\"error_message\":\"bad line\",\"line_number\":2,\"original_line\":\"bad lp\"}]}")); + "{\"error\":\"partial write of line protocol occurred\",\"data\":[null,{\"error_message\":" + + "\"bad line\",\"line_number\":2,\"original_line\":\"bad lp\"}]}")); restClient = new RestClient(new ClientConfig.Builder() .host(baseURL) @@ -502,7 +509,8 @@ public void errorFromBodyV3WithDataArrayNonObjectSkipped() { () -> restClient.request("ping", HttpMethod.GET, null, null, null) ) .isInstanceOf(InfluxDBApiException.class) - .hasMessage("HTTP status code: 400; Message: partial write of line protocol occurred:\n\tline 2: bad line (bad lp)"); + .hasMessage("HTTP status code: 400; Message: partial write of line protocol occurred:\n" + + "\tline 2: bad line (bad lp)"); } @Test From 96616f8045984afc51872224b37b6d86c345e4af Mon Sep 17 00:00:00 2001 From: Ales Pour Date: Mon, 16 Feb 2026 15:00:38 +0100 Subject: [PATCH 03/15] test: fix content types --- .../v3/client/AbstractMockServerTest.java | 22 ++++++++++++++----- .../v3/client/internal/RestClientTest.java | 6 +++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/influxdb/v3/client/AbstractMockServerTest.java b/src/test/java/com/influxdb/v3/client/AbstractMockServerTest.java index 44b05560..75478e07 100644 --- a/src/test/java/com/influxdb/v3/client/AbstractMockServerTest.java +++ b/src/test/java/com/influxdb/v3/client/AbstractMockServerTest.java @@ -65,10 +65,23 @@ protected MockResponse createResponse(final int responseCode, @Nullable final Map headers, @Nullable final String body) { + return createResponse(responseCode, "text/csv; charset=utf-8", headers, body); + } + + @Nonnull + protected MockResponse createResponse(final int responseCode, + @Nullable final String contentType, + @Nullable final Map headers, + @Nullable final String body) { + MockResponse.Builder mrb = new MockResponse.Builder(); mrb.code(responseCode); - Map effectiveHeaders = new HashMap<>(Map.of("Content-Type", "text/csv; charset=utf-8", - "Date", "Tue, 26 Jun 2018 13:15:01 GMT")); + Map effectiveHeaders = new HashMap<>(Map.of( + "Date", "Tue, 26 Jun 2018 13:15:01 GMT" + )); + if (contentType != null) { + effectiveHeaders.put("Content-Type", contentType); + } if (headers != null) { effectiveHeaders.putAll(headers); } @@ -85,9 +98,6 @@ protected MockResponse createResponse(final int responseCode, @Nonnull protected MockResponse createResponse(final int responseCode) { - return createResponse(responseCode, Map.of( - "Content-Type", "text/csv; charset=utf-8", - "Date", "Tue, 26 Jun 2018 13:15:01 GMT" - ), null); + return createResponse(responseCode, "text/csv; charset=utf-8", null, null); } } diff --git a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java index 92f9ff86..e47fd31a 100644 --- a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java +++ b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java @@ -387,6 +387,7 @@ public void errorFromHeader() { public void errorFromBody() { mockServer.enqueue(createResponse(401, + "application/json", Map.of("X-Influx-Errpr", "not used"), "{\"message\":\"token does not have sufficient permissions\"}")); @@ -419,9 +420,10 @@ public void errorFromBodyIgnoredForNonJsonContentType() { } @Test - public void errorFromBodyV3WithoutMessage() { // Core/Enterprise error message + public void errorFromBodyV3WithoutMessageAndWithoutContentType() { mockServer.enqueue(createResponse(400, + null, null, "{\"error\":\"parsing failed\"}")); @@ -440,7 +442,7 @@ public void errorFromBodyV3WithoutMessage() { // Core/Enterprise error message public void errorFromBodyV3WithDataObject() { // Core/Enterprise object format mockServer.enqueue(createResponse(400, - null, + Map.of("content-type", "application/json"), "{\"error\":\"parsing failed\",\"data\":{\"error_message\":\"invalid field value\"}}")); restClient = new RestClient(new ClientConfig.Builder() From 0ad848f22377424d827f0ef0dee0b65ad514de4d Mon Sep 17 00:00:00 2001 From: Ales Pour Date: Mon, 16 Feb 2026 15:06:34 +0100 Subject: [PATCH 04/15] test: add invalid JSON error body test --- .../v3/client/internal/RestClientTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java index e47fd31a..5189cecc 100644 --- a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java +++ b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java @@ -419,6 +419,23 @@ public void errorFromBodyIgnoredForNonJsonContentType() { .hasMessage("HTTP status code: 400; Message: {\"message\":\"token does not have sufficient permissions\"}"); } + @Test + public void errorFromBodyInvalidJsonFallsBackToBody() { + mockServer.enqueue(createResponse(400, + Map.of("content-type", "application/json"), + "{\"message\":\"token does not have sufficient permissions\"")); + + restClient = new RestClient(new ClientConfig.Builder() + .host(baseURL) + .build()); + + Assertions.assertThatThrownBy( + () -> restClient.request("ping", HttpMethod.GET, null, null, null) + ) + .isInstanceOf(InfluxDBApiException.class) + .hasMessage("HTTP status code: 400; Message: {\"message\":\"token does not have sufficient permissions\""); + } + @Test public void errorFromBodyV3WithoutMessageAndWithoutContentType() { From 5db49a1608a9864a1b7fd5320bcb19c2fefb309b Mon Sep 17 00:00:00 2001 From: Ales Pour Date: Mon, 16 Feb 2026 15:12:03 +0100 Subject: [PATCH 05/15] test: add more error body tests --- .../v3/client/internal/RestClientTest.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java index 5189cecc..cab599c7 100644 --- a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java +++ b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java @@ -436,6 +436,23 @@ public void errorFromBodyInvalidJsonFallsBackToBody() { .hasMessage("HTTP status code: 400; Message: {\"message\":\"token does not have sufficient permissions\""); } + @Test + public void errorFromBodyJsonArrayFallsBackToBody() { + mockServer.enqueue(createResponse(400, + Map.of("content-type", "application/json"), + "[]")); + + restClient = new RestClient(new ClientConfig.Builder() + .host(baseURL) + .build()); + + Assertions.assertThatThrownBy( + () -> restClient.request("ping", HttpMethod.GET, null, null, null) + ) + .isInstanceOf(InfluxDBApiException.class) + .hasMessage("HTTP status code: 400; Message: []"); + } + @Test public void errorFromBodyV3WithoutMessageAndWithoutContentType() { @@ -549,6 +566,25 @@ public void errorFromBodyV3WithDataArrayNoDetailFields() { .hasMessage("HTTP status code: 400; Message: partial write of line protocol occurred"); } + @Test + public void errorFromBodyV3WithDataArrayEmptyErrorMessageSkipped() { + mockServer.enqueue(createResponse(400, + Map.of("content-type", "application/json"), + "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":\"\"}," + + "{\"error_message\":\"bad line\",\"line_number\":2,\"original_line\":\"bad lp\"}]}")); + + restClient = new RestClient(new ClientConfig.Builder() + .host(baseURL) + .build()); + + Assertions.assertThatThrownBy( + () -> restClient.request("ping", HttpMethod.GET, null, null, null) + ) + .isInstanceOf(InfluxDBApiException.class) + .hasMessage("HTTP status code: 400; Message: partial write of line protocol occurred:\n" + + "\tline 2: bad line (bad lp)"); + } + @Test public void errorFromBodyText() { From 90c260c0648ec02f7bfa6473c9b7cd10e47b24bf Mon Sep 17 00:00:00 2001 From: Ales Pour Date: Mon, 16 Feb 2026 15:20:00 +0100 Subject: [PATCH 06/15] refactor: paramterized tests --- pom.xml | 7 ++ .../v3/client/internal/RestClientTest.java | 98 ++++++++----------- 2 files changed, 47 insertions(+), 58 deletions(-) diff --git a/pom.xml b/pom.xml index a67ad2cf..e9b2daad 100644 --- a/pom.xml +++ b/pom.xml @@ -317,6 +317,13 @@ test + + org.junit.jupiter + junit-jupiter-params + ${junit-jupiter.version} + test + + org.assertj assertj-core diff --git a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java index cab599c7..472f7cad 100644 --- a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java +++ b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java @@ -34,6 +34,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Stream; import io.netty.handler.codec.http.HttpMethod; import mockwebserver3.MockResponse; @@ -42,6 +43,9 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import com.influxdb.v3.client.AbstractMockServerTest; import com.influxdb.v3.client.InfluxDBApiException; @@ -512,48 +516,15 @@ public void errorFromBodyV3WithDataArray() { + " got iox::column_type::field::float (testa6a3ad v=1 17702)"); } - @Test - public void errorFromBodyV3WithDataArrayMessageOnly() { - mockServer.enqueue(createResponse(400, - Map.of("content-type", "application/json"), - "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":" - + "\"only error message\"}]}")); - - restClient = new RestClient(new ClientConfig.Builder() - .host(baseURL) - .build()); - - Assertions.assertThatThrownBy( - () -> restClient.request("ping", HttpMethod.GET, null, null, null) - ) - .isInstanceOf(InfluxDBApiException.class) - .hasMessage("HTTP status code: 400; Message: partial write of line protocol occurred:\n\tonly error message"); - } - - @Test - public void errorFromBodyV3WithDataArrayNonObjectSkipped() { - mockServer.enqueue(createResponse(400, - Map.of("content-type", "application/json"), - "{\"error\":\"partial write of line protocol occurred\",\"data\":[null,{\"error_message\":" - + "\"bad line\",\"line_number\":2,\"original_line\":\"bad lp\"}]}")); - - restClient = new RestClient(new ClientConfig.Builder() - .host(baseURL) - .build()); + @ParameterizedTest(name = "{0}") + @MethodSource("errorFromBodyV3WithDataArrayCases") + public void errorFromBodyV3WithDataArrayCase(final String testName, + final String body, + final String expectedMessage) { - Assertions.assertThatThrownBy( - () -> restClient.request("ping", HttpMethod.GET, null, null, null) - ) - .isInstanceOf(InfluxDBApiException.class) - .hasMessage("HTTP status code: 400; Message: partial write of line protocol occurred:\n" - + "\tline 2: bad line (bad lp)"); - } - - @Test - public void errorFromBodyV3WithDataArrayNoDetailFields() { mockServer.enqueue(createResponse(400, Map.of("content-type", "application/json"), - "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"line_number\":2}]}")); + body)); restClient = new RestClient(new ClientConfig.Builder() .host(baseURL) @@ -563,26 +534,37 @@ public void errorFromBodyV3WithDataArrayNoDetailFields() { () -> restClient.request("ping", HttpMethod.GET, null, null, null) ) .isInstanceOf(InfluxDBApiException.class) - .hasMessage("HTTP status code: 400; Message: partial write of line protocol occurred"); - } - - @Test - public void errorFromBodyV3WithDataArrayEmptyErrorMessageSkipped() { - mockServer.enqueue(createResponse(400, - Map.of("content-type", "application/json"), - "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":\"\"}," - + "{\"error_message\":\"bad line\",\"line_number\":2,\"original_line\":\"bad lp\"}]}")); - - restClient = new RestClient(new ClientConfig.Builder() - .host(baseURL) - .build()); - - Assertions.assertThatThrownBy( - () -> restClient.request("ping", HttpMethod.GET, null, null, null) + .hasMessage(expectedMessage); + } + + private static Stream errorFromBodyV3WithDataArrayCases() { + return Stream.of( + Arguments.of( + "message-only detail", + "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":" + + "\"only error message\"}]}", + "HTTP status code: 400; Message: partial write of line protocol occurred:\n\tonly error message" + ), + Arguments.of( + "non-object item skipped", + "{\"error\":\"partial write of line protocol occurred\",\"data\":[null,{\"error_message\":" + + "\"bad line\",\"line_number\":2,\"original_line\":\"bad lp\"}]}", + "HTTP status code: 400; Message: partial write of line protocol occurred:\n" + + "\tline 2: bad line (bad lp)" + ), + Arguments.of( + "no detail fields", + "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"line_number\":2}]}", + "HTTP status code: 400; Message: partial write of line protocol occurred" + ), + Arguments.of( + "empty error_message skipped", + "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":\"\"}," + + "{\"error_message\":\"bad line\",\"line_number\":2,\"original_line\":\"bad lp\"}]}", + "HTTP status code: 400; Message: partial write of line protocol occurred:\n" + + "\tline 2: bad line (bad lp)" ) - .isInstanceOf(InfluxDBApiException.class) - .hasMessage("HTTP status code: 400; Message: partial write of line protocol occurred:\n" - + "\tline 2: bad line (bad lp)"); + ); } @Test From ecb77d253453f3df4a6fded4c8faed413c061728 Mon Sep 17 00:00:00 2001 From: Ales Pour Date: Mon, 16 Feb 2026 15:32:57 +0100 Subject: [PATCH 07/15] fix: simplify error checks --- .../com/influxdb/v3/client/internal/RestClient.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/influxdb/v3/client/internal/RestClient.java b/src/main/java/com/influxdb/v3/client/internal/RestClient.java index 3627523d..402ca513 100644 --- a/src/main/java/com/influxdb/v3/client/internal/RestClient.java +++ b/src/main/java/com/influxdb/v3/client/internal/RestClient.java @@ -217,22 +217,26 @@ HttpResponse request(@Nonnull final String path, int statusCode = response.statusCode(); if (statusCode < 200 || statusCode >= 300) { - String reason = ""; + String reason; String body = response.body(); reason = formatErrorMessage(body, response.headers().firstValue("Content-Type").orElse(null)); - if (reason == null || reason.isEmpty()) { + if (reason == null) { + reason = ""; + } + + if (reason.isEmpty()) { reason = Stream.of("X-Platform-Error-Code", "X-Influx-Error", "X-InfluxDb-Error") .map(name -> response.headers().firstValue(name).orElse(null)) .filter(message -> message != null && !message.isEmpty()).findFirst() .orElse(""); } - if (reason == null || reason.isEmpty()) { + if (reason.isEmpty()) { reason = body; } - if (reason == null || reason.isEmpty()) { + if (reason.isEmpty()) { reason = HttpResponseStatus.valueOf(statusCode).reasonPhrase(); } From 223abe6e1bfa9e1f6427e9078a08e90a95d2a6f3 Mon Sep 17 00:00:00 2001 From: Ales Pour Date: Mon, 16 Feb 2026 15:33:14 +0100 Subject: [PATCH 08/15] docs: update CHANGELOG --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b0ba711..c68898b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ ### Features -1. [#352](https://github.com/InfluxCommunity/influxdb3-java/pull/352): Update Apache Flight client dependencies. +1. [#352](https://github.com/InfluxCommunity/influxdb3-java/pull/352): Upgrade Arrow Flight client dependencies. + +### Bug Fixes + +1. [#351](https://github.com/InfluxCommunity/influxdb3-java/pull/351): Enterprise/Core structured errors handling. ### CI From 43ba0b5d91f3f0e1b25f990ba1264d15fc387ad9 Mon Sep 17 00:00:00 2001 From: Ales Pour Date: Mon, 16 Feb 2026 15:47:08 +0100 Subject: [PATCH 09/15] test: fix content-type overrides in tests --- .../v3/client/InfluxDBClientWriteTest.java | 3 ++- .../v3/client/internal/RestClientTest.java | 21 ++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/influxdb/v3/client/InfluxDBClientWriteTest.java b/src/test/java/com/influxdb/v3/client/InfluxDBClientWriteTest.java index 4d3a2cba..cd4242d7 100644 --- a/src/test/java/com/influxdb/v3/client/InfluxDBClientWriteTest.java +++ b/src/test/java/com/influxdb/v3/client/InfluxDBClientWriteTest.java @@ -517,7 +517,8 @@ void defaultTags() throws InterruptedException { @Test public void retryHandled429Test() { mockServer.enqueue(createResponse(429, - Map.of("retry-after", "42", "content-type", "application/json"), + "application/json", + Map.of("retry-after", "42"), "{ \"message\" : \"Too Many Requests\" }")); Point point = Point.measurement("mem") diff --git a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java index 472f7cad..b1c8c8d8 100644 --- a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java +++ b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java @@ -409,7 +409,8 @@ public void errorFromBody() { @Test public void errorFromBodyIgnoredForNonJsonContentType() { mockServer.enqueue(createResponse(400, - Map.of("content-type", "text/plain"), + "text/plain", + null, "{\"message\":\"token does not have sufficient permissions\"}")); restClient = new RestClient(new ClientConfig.Builder() @@ -426,7 +427,8 @@ public void errorFromBodyIgnoredForNonJsonContentType() { @Test public void errorFromBodyInvalidJsonFallsBackToBody() { mockServer.enqueue(createResponse(400, - Map.of("content-type", "application/json"), + "application/json", + null, "{\"message\":\"token does not have sufficient permissions\"")); restClient = new RestClient(new ClientConfig.Builder() @@ -443,7 +445,8 @@ public void errorFromBodyInvalidJsonFallsBackToBody() { @Test public void errorFromBodyJsonArrayFallsBackToBody() { mockServer.enqueue(createResponse(400, - Map.of("content-type", "application/json"), + "application/json", + null, "[]")); restClient = new RestClient(new ClientConfig.Builder() @@ -480,7 +483,8 @@ public void errorFromBodyV3WithoutMessageAndWithoutContentType() { public void errorFromBodyV3WithDataObject() { // Core/Enterprise object format mockServer.enqueue(createResponse(400, - Map.of("content-type", "application/json"), + "application/json", + null, "{\"error\":\"parsing failed\",\"data\":{\"error_message\":\"invalid field value\"}}")); restClient = new RestClient(new ClientConfig.Builder() @@ -497,7 +501,8 @@ public void errorFromBodyV3WithDataObject() { // Core/Enterprise object format @Test public void errorFromBodyV3WithDataArray() { mockServer.enqueue(createResponse(400, - Map.of("content-type", "application/json"), + "application/json", + null, "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":" + "\"invalid column type for column 'v', expected iox::column_type::field::integer," + " got iox::column_type::field::float\",\"line_number\":2," @@ -523,7 +528,8 @@ public void errorFromBodyV3WithDataArrayCase(final String testName, final String expectedMessage) { mockServer.enqueue(createResponse(400, - Map.of("content-type", "application/json"), + "application/json", + null, body)); restClient = new RestClient(new ClientConfig.Builder() @@ -604,7 +610,8 @@ public void errorHttpExceptionThrown() { String retryDate = Instant.now().plus(300, ChronoUnit.SECONDS).toString(); mockServer.enqueue(createResponse(503, - Map.of("retry-after", retryDate, "content-type", "application/json"), + "application/json", + Map.of("retry-after", retryDate), "{\"message\":\"temporarily offline\"}")); restClient = new RestClient(new ClientConfig.Builder() From af35d90e1bde39d5c22515ba3f8633586847ff5d Mon Sep 17 00:00:00 2001 From: Ales Pour Date: Mon, 16 Feb 2026 15:56:29 +0100 Subject: [PATCH 10/15] test: more coverage --- .../v3/client/internal/RestClientTest.java | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java index b1c8c8d8..2f7fcbea 100644 --- a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java +++ b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java @@ -442,6 +442,42 @@ public void errorFromBodyInvalidJsonFallsBackToBody() { .hasMessage("HTTP status code: 400; Message: {\"message\":\"token does not have sufficient permissions\""); } + @Test + public void errorFromBodyNullMessageFallsBackToError() { + mockServer.enqueue(createResponse(400, + "application/json", + null, + "{\"message\":null,\"error\":\"parsing failed\"}")); + + restClient = new RestClient(new ClientConfig.Builder() + .host(baseURL) + .build()); + + Assertions.assertThatThrownBy( + () -> restClient.request("ping", HttpMethod.GET, null, null, null) + ) + .isInstanceOf(InfluxDBApiException.class) + .hasMessage("HTTP status code: 400; Message: parsing failed"); + } + + @Test + public void errorFromBodyEmptyMessageFallsBackToError() { + mockServer.enqueue(createResponse(400, + "application/json", + null, + "{\"message\":\"\",\"error\":\"parsing failed\"}")); + + restClient = new RestClient(new ClientConfig.Builder() + .host(baseURL) + .build()); + + Assertions.assertThatThrownBy( + () -> restClient.request("ping", HttpMethod.GET, null, null, null) + ) + .isInstanceOf(InfluxDBApiException.class) + .hasMessage("HTTP status code: 400; Message: parsing failed"); + } + @Test public void errorFromBodyJsonArrayFallsBackToBody() { mockServer.enqueue(createResponse(400, @@ -460,6 +496,25 @@ public void errorFromBodyJsonArrayFallsBackToBody() { .hasMessage("HTTP status code: 400; Message: []"); } + @Test + public void errorFromBodyV3WithoutMessageAndEmptyContentType() { + + mockServer.enqueue(createResponse(400, + "", + null, + "{\"error\":\"parsing failed\"}")); + + restClient = new RestClient(new ClientConfig.Builder() + .host(baseURL) + .build()); + + Assertions.assertThatThrownBy( + () -> restClient.request("ping", HttpMethod.GET, null, null, null) + ) + .isInstanceOf(InfluxDBApiException.class) + .hasMessage("HTTP status code: 400; Message: parsing failed"); + } + @Test public void errorFromBodyV3WithoutMessageAndWithoutContentType() { @@ -569,6 +624,77 @@ private static Stream errorFromBodyV3WithDataArrayCases() { + "{\"error_message\":\"bad line\",\"line_number\":2,\"original_line\":\"bad lp\"}]}", "HTTP status code: 400; Message: partial write of line protocol occurred:\n" + "\tline 2: bad line (bad lp)" + ), + Arguments.of( + "non-object primitive item skipped", + "{\"error\":\"partial write of line protocol occurred\",\"data\":[1,{\"error_message\":" + + "\"bad line\",\"line_number\":2,\"original_line\":\"bad lp\"}]}", + "HTTP status code: 400; Message: partial write of line protocol occurred:\n" + + "\tline 2: bad line (bad lp)" + ), + Arguments.of( + "null error_message skipped", + "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":null}," + + "{\"error_message\":\"bad line\",\"line_number\":2,\"original_line\":\"bad lp\"}]}", + "HTTP status code: 400; Message: partial write of line protocol occurred:\n" + + "\tline 2: bad line (bad lp)" + ), + Arguments.of( + "empty original_line uses message-only detail", + "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":" + + "\"only error message\",\"line_number\":2,\"original_line\":\"\"}]}", + "HTTP status code: 400; Message: partial write of line protocol occurred:\n\tonly error message" + ) + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("errorFromBodyV3FallbackCases") + public void errorFromBodyV3FallbackCase(final String testName, + final String body, + final String expectedMessage) { + + mockServer.enqueue(createResponse(400, + "application/json", + null, + body)); + + restClient = new RestClient(new ClientConfig.Builder() + .host(baseURL) + .build()); + + Assertions.assertThatThrownBy( + () -> restClient.request("ping", HttpMethod.GET, null, null, null) + ) + .isInstanceOf(InfluxDBApiException.class) + .hasMessage(expectedMessage); + } + + private static Stream errorFromBodyV3FallbackCases() { + return Stream.of( + Arguments.of( + "missing error with data array falls back to body", + "{\"data\":[{\"error_message\":\"bad line\",\"line_number\":2,\"original_line\":\"bad lp\"}]}", + "HTTP status code: 400; Message: " + + "{\"data\":[{\"error_message\":\"bad line\",\"line_number\":2,\"original_line\":\"bad lp\"}]}" + ), + Arguments.of( + "empty error with data array falls back to body", + "{\"error\":\"\",\"data\":[{\"error_message\":\"bad line\",\"line_number\":2,\"original_line\":" + + "\"bad lp\"}]}", + "HTTP status code: 400; Message: " + + "{\"error\":\"\",\"data\":[{\"error_message\":\"bad line\",\"line_number\":2,\"original_line\":" + + "\"bad lp\"}]}" + ), + Arguments.of( + "data object without error_message falls back to error", + "{\"error\":\"parsing failed\",\"data\":{}}", + "HTTP status code: 400; Message: parsing failed" + ), + Arguments.of( + "data object with empty error_message falls back to error", + "{\"error\":\"parsing failed\",\"data\":{\"error_message\":\"\"}}", + "HTTP status code: 400; Message: parsing failed" ) ); } From 5bdddade7a87dc7ce91cb82a7d6a50f3895dabfd Mon Sep 17 00:00:00 2001 From: Ales Pour Date: Wed, 18 Feb 2026 08:47:04 +0100 Subject: [PATCH 11/15] fix: var shadowing --- .../v3/client/internal/RestClient.java | 76 ++++++++++++------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/influxdb/v3/client/internal/RestClient.java b/src/main/java/com/influxdb/v3/client/internal/RestClient.java index 402ca513..c0deb0ae 100644 --- a/src/main/java/com/influxdb/v3/client/internal/RestClient.java +++ b/src/main/java/com/influxdb/v3/client/internal/RestClient.java @@ -265,14 +265,12 @@ private String formatErrorMessage(@Nonnull final String body, @Nullable final St return null; } - final JsonNode messageNode = root.get("message"); - if (messageNode != null && !messageNode.isNull() && !messageNode.asText().isEmpty()) { - return messageNode.asText(); + final String rootMessage = errNonEmptyField(root, "message"); + if (rootMessage != null) { + return rootMessage; } - final JsonNode errorNode = root.get("error"); - final String error = (errorNode != null && !errorNode.isNull() && !errorNode.asText().isEmpty()) - ? errorNode.asText() : null; + final String error = errNonEmptyField(root, "error"); final JsonNode dataNode = root.get("data"); // InfluxDB 3 Core/Enterprise write error format: @@ -281,40 +279,24 @@ private String formatErrorMessage(@Nonnull final String body, @Nullable final St final StringBuilder message = new StringBuilder(error); boolean hasDetails = false; for (JsonNode item : dataNode) { - if (item == null || !item.isObject()) { - continue; - } - final JsonNode errorMessageNode = item.get("error_message"); - if (errorMessageNode == null || errorMessageNode.isNull() || errorMessageNode.asText().isEmpty()) { + final String detail = errFormatDataArrayDetail(item); + if (detail == null) { continue; } if (!hasDetails) { message.append(':'); hasDetails = true; } - if (item.hasNonNull("line_number") - && item.hasNonNull("original_line") - && !item.get("original_line").asText().isEmpty()) { - final String lineNumber = item.get("line_number").asText(); - message.append("\n\tline ") - .append(lineNumber) - .append(": ") - .append(errorMessageNode.asText()) - .append(" (") - .append(item.get("original_line").asText()) - .append(')'); - } else { - message.append("\n\t").append(errorMessageNode.asText()); - } + message.append("\n\t").append(detail); } return message.toString(); } // Core/Enterprise object format: // {"error":"...","data":{"error_message":"..."}} - if (dataNode != null && dataNode.isObject() && dataNode.hasNonNull("error_message")) { - final String errorMessage = dataNode.get("error_message").asText(); - if (!errorMessage.isEmpty()) { + if (dataNode != null && dataNode.isObject()) { + final String errorMessage = errNonEmptyField(dataNode, "error_message"); + if (errorMessage != null) { return errorMessage; } } @@ -326,6 +308,44 @@ private String formatErrorMessage(@Nonnull final String body, @Nullable final St } } + @Nullable + private String errNonEmptyText(@Nullable final JsonNode node) { + if (node == null || node.isNull()) { + return null; + } + final String value = node.asText(); + return value.isEmpty() ? null : value; + } + + @Nullable + private String errNonEmptyField(@Nullable final JsonNode object, @Nonnull final String fieldName) { + if (object == null || !object.isObject()) { + return null; + } + return errNonEmptyText(object.get(fieldName)); + } + + @Nullable + private String errFormatDataArrayDetail(@Nullable final JsonNode item) { + if (item == null || !item.isObject()) { + return null; + } + + final String errorMessage = errNonEmptyField(item, "error_message"); + if (errorMessage == null) { + return null; + } + + if (item.hasNonNull("line_number")) { + final String originalLine = errNonEmptyField(item, "original_line"); + if (originalLine != null) { + final String lineNumber = item.get("line_number").asText(); + return "line " + lineNumber + ": " + errorMessage + " (" + originalLine + ")"; + } + } + return errorMessage; + } + private X509TrustManager getX509TrustManagerFromFile(@Nonnull final String filePath) { try { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); From 6a70421e90ad930a643e7fd8e96f0a430571f0d1 Mon Sep 17 00:00:00 2001 From: Ales Pour Date: Wed, 18 Feb 2026 09:08:44 +0100 Subject: [PATCH 12/15] fix: remove redundant guard --- .../java/com/influxdb/v3/client/internal/RestClient.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/influxdb/v3/client/internal/RestClient.java b/src/main/java/com/influxdb/v3/client/internal/RestClient.java index c0deb0ae..d8738fe8 100644 --- a/src/main/java/com/influxdb/v3/client/internal/RestClient.java +++ b/src/main/java/com/influxdb/v3/client/internal/RestClient.java @@ -294,11 +294,9 @@ private String formatErrorMessage(@Nonnull final String body, @Nullable final St // Core/Enterprise object format: // {"error":"...","data":{"error_message":"..."}} - if (dataNode != null && dataNode.isObject()) { - final String errorMessage = errNonEmptyField(dataNode, "error_message"); - if (errorMessage != null) { - return errorMessage; - } + final String errorMessage = errNonEmptyField(dataNode, "error_message"); + if (errorMessage != null) { + return errorMessage; } return error; From 54fd87eb23157899b2f739cf589fbddc9310ae1d Mon Sep 17 00:00:00 2001 From: Ales Pour Date: Wed, 18 Feb 2026 09:09:18 +0100 Subject: [PATCH 13/15] test: coverage --- .../influxdb/v3/client/internal/RestClientTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java index 2f7fcbea..26d093e3 100644 --- a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java +++ b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java @@ -695,6 +695,16 @@ private static Stream errorFromBodyV3FallbackCases() { "data object with empty error_message falls back to error", "{\"error\":\"parsing failed\",\"data\":{\"error_message\":\"\"}}", "HTTP status code: 400; Message: parsing failed" + ), + Arguments.of( + "data string falls back to error", + "{\"error\":\"parsing failed\",\"data\":\"not-an-object\"}", + "HTTP status code: 400; Message: parsing failed" + ), + Arguments.of( + "data number falls back to error", + "{\"error\":\"parsing failed\",\"data\":123}", + "HTTP status code: 400; Message: parsing failed" ) ); } From 08c7ed7ab45429b3c8b8cc3184ee13c57fce1d37 Mon Sep 17 00:00:00 2001 From: Ales Pour Date: Wed, 18 Feb 2026 09:17:45 +0100 Subject: [PATCH 14/15] fix: remove never-happen guard --- src/main/java/com/influxdb/v3/client/internal/RestClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/influxdb/v3/client/internal/RestClient.java b/src/main/java/com/influxdb/v3/client/internal/RestClient.java index d8738fe8..6fb0e05b 100644 --- a/src/main/java/com/influxdb/v3/client/internal/RestClient.java +++ b/src/main/java/com/influxdb/v3/client/internal/RestClient.java @@ -325,7 +325,7 @@ private String errNonEmptyField(@Nullable final JsonNode object, @Nonnull final @Nullable private String errFormatDataArrayDetail(@Nullable final JsonNode item) { - if (item == null || !item.isObject()) { + if (!item.isObject()) { return null; } From 93c3bef686bc70785b15d4622b67e989bce1d922 Mon Sep 17 00:00:00 2001 From: Ales Pour Date: Wed, 18 Feb 2026 09:23:59 +0100 Subject: [PATCH 15/15] test: coverage --- .../com/influxdb/v3/client/internal/RestClientTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java index 26d093e3..b6d4a26b 100644 --- a/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java +++ b/src/test/java/com/influxdb/v3/client/internal/RestClientTest.java @@ -644,6 +644,14 @@ private static Stream errorFromBodyV3WithDataArrayCases() { "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":" + "\"only error message\",\"line_number\":2,\"original_line\":\"\"}]}", "HTTP status code: 400; Message: partial write of line protocol occurred:\n\tonly error message" + ), + Arguments.of( + "multiple valid details append without extra colon", + "{\"error\":\"partial write of line protocol occurred\",\"data\":[{\"error_message\":" + + "\"bad line\",\"line_number\":2,\"original_line\":\"bad lp\"},{\"error_message\":\"second issue\"}]}", + "HTTP status code: 400; Message: partial write of line protocol occurred:\n" + + "\tline 2: bad line (bad lp)\n" + + "\tsecond issue" ) ); }