Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add response headers to HttpException #658

Merged
merged 12 commits into from
Sep 2, 2024
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## 4.18.0 [unreleased]

### Features:
1. [#658](https://github.com/influxdata/influxdb-client-csharp/pull/658): Add HttpHeaders as `IEnumerable<RestSharp.HttpParameter>` to `HttpException` and facilitate access in `WriteErrorEvent`. Includes new example `HttpErrorHandling`.

### Dependencies
Update dependencies:

Expand Down
9 changes: 9 additions & 0 deletions Client.Core/Exceptions/InfluxException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using InfluxDB.Client.Core.Internal;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
Expand Down Expand Up @@ -56,6 +57,13 @@ public HttpException(string message, int status, Exception exception = null) : b
/// </summary>
public int? RetryAfter { get; set; }

#nullable enable
/// <summary>
/// The response headers
/// </summary>
public IEnumerable<HeaderParameter>? Headers { get; private set; }
#nullable disable

public static HttpException Create(RestResponse requestResult, object body)
{
Arguments.CheckNotNull(requestResult, nameof(requestResult));
Expand Down Expand Up @@ -162,6 +170,7 @@ public static HttpException Create(object content, IEnumerable<HeaderParameter>

err.ErrorBody = errorBody;
err.RetryAfter = retryAfter;
err.Headers = headers;

return err;
}
Expand Down
46 changes: 46 additions & 0 deletions Client.Test/InfluxExceptionTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using InfluxDB.Client.Core.Exceptions;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using RestSharp;

namespace InfluxDB.Client.Test
{
[TestFixture]
public class InfluxExceptionTest
{
[Test]
public void ExceptionHeadersTest()
{
try
{
throw HttpException.Create(
JObject.Parse("{\"callId\": \"123456789\", \"message\": \"error in content object\"}"),
new List<HeaderParameter>
{
new HeaderParameter("Trace-Id", "123456789ABCDEF0"),
new HeaderParameter("X-Influx-Version", "1.0.0"),
new HeaderParameter("X-Platform-Error-Code", "unavailable"),
new HeaderParameter("Retry-After", "60000")
},
null,
HttpStatusCode.ServiceUnavailable);
}
catch (HttpException he)
{
Assert.AreEqual("error in content object", he?.Message);

Assert.AreEqual(4, he?.Headers.Count());
var headers = new Dictionary<string, string>();
foreach (var header in he?.Headers) headers.Add(header.Name, header.Value);
Assert.AreEqual("123456789ABCDEF0", headers["Trace-Id"]);
Assert.AreEqual("1.0.0", headers["X-Influx-Version"]);
Assert.AreEqual("unavailable", headers["X-Platform-Error-Code"]);
Assert.AreEqual("60000", headers["Retry-After"]);
}
}
}
}
103 changes: 103 additions & 0 deletions Client.Test/ItErrorEventsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System;
using NUnit.Framework;
using System.Collections.Generic;
using System.Threading.Tasks;
using InfluxDB.Client.Api.Domain;
using InfluxDB.Client.Writes;

namespace InfluxDB.Client.Test
{
[TestFixture]
public class ItErrorEventsTest : AbstractItClientTest
{
private Organization _org;
private Bucket _bucket;
private string _token;
private InfluxDBClientOptions _options;

[SetUp]
public new async Task SetUp()
{
_org = await FindMyOrg();
_bucket = await Client.GetBucketsApi()
.CreateBucketAsync(GenerateName("fliers"), null, _org);

//
// Add Permissions to read and write to the Bucket
//
var resource = new PermissionResource(PermissionResource.TypeBuckets, _bucket.Id, null,
_org.Id);

var readBucket = new Permission(Permission.ActionEnum.Read, resource);
var writeBucket = new Permission(Permission.ActionEnum.Write, resource);

var loggedUser = await Client.GetUsersApi().MeAsync();
Assert.IsNotNull(loggedUser);

var authorization = await Client.GetAuthorizationsApi()
.CreateAuthorizationAsync(_org,
new List<Permission> { readBucket, writeBucket });

_token = authorization.Token;

Client.Dispose();

_options = new InfluxDBClientOptions(InfluxDbUrl)
{
Token = _token,
Org = _org.Id,
Bucket = _bucket.Id
};

Client = new InfluxDBClient(_options);
}


[Test]
public void HandleEvents()
{
using (var writeApi = Client.GetWriteApi())
{
writeApi.EventHandler += (sender, eventArgs) =>
{
switch (eventArgs)
{
case WriteSuccessEvent successEvent:
Assert.Fail("Call should not succeed");
break;
case WriteErrorEvent errorEvent:
Assert.AreEqual("unable to parse 'velocity,unit=C3PO mps=': missing field value",
errorEvent.Exception.Message);
var eventHeaders = errorEvent.GetHeaders();
if (eventHeaders == null)
{
Assert.Fail("WriteErrorEvent must return headers.");
}

var headers = new Dictionary<string, string> { };
foreach (var hp in eventHeaders)
{
Console.WriteLine("DEBUG {0}: {1}", hp.Name, hp.Value);
headers.Add(hp.Name, hp.Value);
}

Assert.AreEqual(4, headers.Count);
Assert.AreEqual("OSS", headers["X-Influxdb-Build"]);
Assert.True(headers["X-Influxdb-Version"].StartsWith('v'));
Assert.AreEqual("invalid", headers["X-Platform-Error-Code"]);
Assert.AreNotEqual("missing", headers.GetValueOrDefault("Date", "missing"));
break;
case WriteRetriableErrorEvent retriableErrorEvent:
Assert.Fail("Call should not be retriable.");
break;
case WriteRuntimeExceptionEvent runtimeExceptionEvent:
Assert.Fail("Call should not result in runtime exception. {0}", runtimeExceptionEvent);
break;
}
};

writeApi.WriteRecord("velocity,unit=C3PO mps=", WritePrecision.S, _bucket.Name, _org.Name);
}
}
}
}
25 changes: 25 additions & 0 deletions Client.Test/ItWriteApiAsyncTest.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using InfluxDB.Client.Api.Domain;
using InfluxDB.Client.Core;
using InfluxDB.Client.Core.Exceptions;
using InfluxDB.Client.Core.Flux.Domain;
using InfluxDB.Client.Core.Test;
using InfluxDB.Client.Writes;
Expand Down Expand Up @@ -185,5 +187,28 @@ public async Task WriteULongValues()

Assert.AreEqual(ulong.MaxValue, query[0].Records[0].GetValue());
}

[Test]
public async Task WriteWithError()
{
try
{
await _writeApi.WriteRecordAsync("h2o,location=fox_hollow water_level=");
Assert.Fail("Call should fail");
}
catch (HttpException exception)
{
Assert.AreEqual("unable to parse 'h2o,location=fox_hollow water_level=': missing field value",
exception.Message);
Assert.AreEqual(400, exception.Status);
Assert.GreaterOrEqual(4, exception.Headers.Count());
var headers = new Dictionary<string, string>();
foreach (var header in exception?.Headers) headers.Add(header.Name, header.Value);
Assert.AreEqual("OSS", headers["X-Influxdb-Build"]);
Assert.AreEqual("invalid", headers["X-Platform-Error-Code"]);
Assert.IsTrue(headers["X-Influxdb-Version"].StartsWith('v'));
Assert.NotNull(headers["Date"]);
}
}
}
}
57 changes: 57 additions & 0 deletions Client.Test/WriteApiTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.IO;
using System.Linq;
using System.Threading;
using Castle.Core.Smtp;
using InfluxDB.Client.Api.Domain;
using InfluxDB.Client.Core;
using InfluxDB.Client.Core.Exceptions;
Expand Down Expand Up @@ -506,6 +507,62 @@ public void WriteRuntimeException()
Assert.AreEqual(0, MockServer.LogEntries.Count());
}

[Test]
public void WriteExceptionWithHeaders()
{
var localWriteApi = _client.GetWriteApi(new WriteOptions { RetryInterval = 1_000 });

var traceId = Guid.NewGuid().ToString();
const string buildName = "TestBuild";
const string version = "v99.9.9";

localWriteApi.EventHandler += (sender, eventArgs) =>
{
switch (eventArgs)
{
case WriteErrorEvent errorEvent:
Assert.AreEqual("just a test", errorEvent.Exception.Message);
var errHeaders = errorEvent.GetHeaders();
var headers = new Dictionary<string, string>();
foreach (var h in errHeaders)
headers.Add(h.Name, h.Value);
Assert.AreEqual(6, headers.Count);
Assert.AreEqual(traceId, headers["Trace-Id"]);
Assert.AreEqual(buildName, headers["X-Influxdb-Build"]);
Assert.AreEqual(version, headers["X-Influxdb-Version"]);
break;
default:
Assert.Fail("Expect only WriteErrorEvents but got {0}", eventArgs.GetType());
break;
}
};
MockServer
.Given(Request.Create().WithPath("/api/v2/write").UsingPost())
.RespondWith(
CreateResponse("{ \"message\": \"just a test\", \"status-code\": \"Bad Request\"}")
.WithStatusCode(400)
.WithHeaders(new Dictionary<string, string>()
{
{ "Content-Type", "application/json" },
{ "Trace-Id", traceId },
{ "X-Influxdb-Build", buildName },
{ "X-Influxdb-Version", version }
})
);


var measurement = new SimpleModel
{
Time = new DateTime(2024, 09, 01, 6, 15, 00),
Device = "R2D2",
Value = 1976
};

localWriteApi.WriteMeasurement(measurement, WritePrecision.S, "b1", "org1");

localWriteApi.Dispose();
}

[Test]
public void RequiredOrgBucketWriteApi()
{
Expand Down
11 changes: 11 additions & 0 deletions Client/Writes/Events.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using InfluxDB.Client.Api.Domain;
using InfluxDB.Client.Core;
using InfluxDB.Client.Core.Exceptions;
using RestSharp;

namespace InfluxDB.Client.Writes
{
Expand Down Expand Up @@ -42,6 +45,14 @@ internal override void LogEvent()
{
Trace.TraceError($"The error occurred during writing of data: {Exception.Message}");
}

/// <summary>
/// Get headers from the nested exception.
/// </summary>
public IEnumerable<HeaderParameter> GetHeaders()
{
return ((HttpException)Exception)?.Headers;
}
}

/// <summary>
Expand Down
Loading