diff --git a/.circleci/config.yml b/.circleci/config.yml index 26eeb4a28..a361b04be 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -61,7 +61,7 @@ jobs: parameters: dotnet-image: type: string - default: &default-dotnet-image "mcr.microsoft.com/dotnet/sdk:7.0" + default: &default-dotnet-image "mcr.microsoft.com/dotnet/sdk:8.0" dotnet-target-version: type: string default: "netstandard2.1" @@ -132,11 +132,11 @@ jobs: command: /c/influxdata/influxdb-1.8.0-1/influxd.exe -config "Scripts/influxdb.conf" background: true - run: | - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Core.Test/Client.Core.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Test/Client.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Legacy.Test/Client.Legacy.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Linq.Test/Client.Linq.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Examples/Examples.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Core.Test/Client.Core.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Test/Client.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Legacy.Test/Client.Legacy.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Client.Linq.Test/Client.Linq.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net5.0'<\/TargetFramework>' Examples/Examples.csproj - run: dotnet sln remove Examples/ExampleBlazor/ExampleBlazor.csproj - run: dotnet nuget locals --clear all - run: dotnet restore --no-cache --force -s https://api.nuget.org/v3/index.json @@ -149,16 +149,16 @@ jobs: steps: - checkout - run: | - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net7.0'<\/TargetFramework>' Client.Core.Test/Client.Core.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net7.0'<\/TargetFramework>' Client.Test/Client.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net7.0'<\/TargetFramework>' Client.Legacy.Test/Client.Legacy.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net7.0'<\/TargetFramework>' Client.Linq.Test/Client.Linq.Test.csproj - sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'net7.0'<\/TargetFramework>' Examples/Examples.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net8.0'<\/TargetFramework>' Client.Core.Test/Client.Core.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net8.0'<\/TargetFramework>' Client.Test/Client.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net8.0'<\/TargetFramework>' Client.Legacy.Test/Client.Legacy.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net8.0'<\/TargetFramework>' Client.Linq.Test/Client.Linq.Test.csproj + sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'net8.0'<\/TargetFramework>' Examples/Examples.csproj - run: name: Check compilation warnings command: | dotnet clean --configuration Release - dotnet build --configuration Release -warnAsError -warnAsMessage:CS0618,NETSDK1138 + dotnet build --configuration Release -warnAsError -warnAsMessage:CS0419,CS0618,CS1591,CS1573,CS1574,NETSDK1138 check-code-formatting: docker: @@ -225,6 +225,9 @@ workflows: dotnet-image: "mcr.microsoft.com/dotnet/sdk:6.0" - tests-dotnet: name: dotnet-7.0 + dotnet-image: "mcr.microsoft.com/dotnet/sdk:7.0" + - tests-dotnet: + name: dotnet-8.0 - tests-windows: name: dotnet-windows - deploy-preview: @@ -236,6 +239,7 @@ workflows: - dotnet-5.0 - dotnet-6.0 - dotnet-7.0 + - dotnet-8.0 - dotnet-windows filters: branches: diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e398c97b..5683da693 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,72 @@ +## 4.19.0 [unreleased] + +### Dependencies + +⚠️ Important Notice: Starting from this release, we won’t be listing every dependency change in our changelog. This helps us maintain the project faster and focus on important features for our InfluxDB client. + +### CI +1. [#681](https://github.com/influxdata/influxdb-client-csharp/pull/681): Add build for `dotnet8` + +## 4.18.0 [2024-09-13] + +### Features: +1. [#658](https://github.com/influxdata/influxdb-client-csharp/pull/658): Add HttpHeaders as `IEnumerable` to `HttpException` and facilitate access in `WriteErrorEvent`. Includes new example `HttpErrorHandling`. + +### Dependencies +Update dependencies: + +#### Build: + - [#659](https://github.com/influxdata/influxdb-client-csharp/pull/659): `RestSharp` to `112.0.0` + +#### Test: + - [#663](https://github.com/influxdata/influxdb-client-csharp/pull/663): `WireMock.Net` to `1.6.1` + +## 4.17.0 [2024-08-12] + +### Breaking Changes + +#### API + +- `ApiResponse` headers has been changed to `IEnumerable<(string Name, string Value)>` + +### Bug Fixes +1. [#649](https://github.com/influxdata/influxdb-client-csharp/pull/649): Use HttpCompletionOption.ResponseHeadersRead for asynchronous QueryApi + +### Dependencies +Update dependencies: + +#### Build: + - [#650](https://github.com/influxdata/influxdb-client-csharp/pull/650): `RestSharp` to `111.4.0` + - [#662](https://github.com/influxdata/influxdb-client-csharp/pull/662): `Microsoft.Extensions.ObjectPool` to `8.0.8` + +#### Test: + - [#652](https://github.com/influxdata/influxdb-client-csharp/pull/652): `NUnit3TestAdapter` to `4.6.0` + - [#654](https://github.com/influxdata/influxdb-client-csharp/pull/654): `WireMock.Net` to `1.5.62` + - [#661](https://github.com/influxdata/influxdb-client-csharp/pull/661): `Microsoft.NET.Test.Sdk` to `17.11.0` + +#### Examples: + - [#664](https://github.com/influxdata/influxdb-client-csharp/pull/664): `Radzen.Blazor` to `5.1.6` + +## 4.16.0 [2024-06-24] + +### Features: + - [#633](https://github.com/influxdata/influxdb-client-csharp/pull/633): Add package XML documentation. + +### Dependencies +Update dependencies: + +#### Build: + - [#634](https://github.com/influxdata/influxdb-client-csharp/pull/634): `CsvHelper` to `32.0.3` + - [#635](https://github.com/influxdata/influxdb-client-csharp/pull/635): `Microsoft.Extensions.ObjectPool` to `8.0.6` + - [#638](https://github.com/influxdata/influxdb-client-csharp/pull/638): `System.Reactive` to `6.0.1` + +#### Test: + - [#639](https://github.com/influxdata/influxdb-client-csharp/pull/639): `Microsoft.NET.Test.Sdk` to `17.10.0` + - [#641](https://github.com/influxdata/influxdb-client-csharp/pull/641): `WireMock.Net` to `1.5.56` + +#### Examples: + - [#636](https://github.com/influxdata/influxdb-client-csharp/pull/636): `Radzen.Blazor` to `4.32.3` + ## 4.15.0 [2024-05-17] ### Bug Fixes diff --git a/Client.Core.Test/Client.Core.Test.csproj b/Client.Core.Test/Client.Core.Test.csproj index a10bddabf..d504ffbf8 100644 --- a/Client.Core.Test/Client.Core.Test.csproj +++ b/Client.Core.Test/Client.Core.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1;net5.0;net6.0;net7.0 + netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 8 false @@ -13,10 +13,10 @@ - + - - + + @@ -24,11 +24,11 @@ - |net5.0|net6.0|net7.0| + |net5.0|net6.0|net7.0|net8.0| - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Client.Core/Client.Core.csproj b/Client.Core/Client.Core.csproj index 9f89a5fbe..69b59f496 100644 --- a/Client.Core/Client.Core.csproj +++ b/Client.Core/Client.Core.csproj @@ -7,8 +7,8 @@ InfluxDB Client Core - exceptions, validations, REST client. influxdb-client-csharp Contributors InfluxDB.Client.Core - 4.15.0 - + 4.19.0 + dev InfluxDB.Client.Core influxdata;timeseries;flux;influxdb @@ -27,16 +27,20 @@ true + + true + + - + - + - + diff --git a/Client.Core/Exceptions/InfluxException.cs b/Client.Core/Exceptions/InfluxException.cs index d413f25c5..ef679c58b 100644 --- a/Client.Core/Exceptions/InfluxException.cs +++ b/Client.Core/Exceptions/InfluxException.cs @@ -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; @@ -56,6 +57,13 @@ public HttpException(string message, int status, Exception exception = null) : b /// public int? RetryAfter { get; set; } +#nullable enable + /// + /// The response headers + /// + public IEnumerable? Headers { get; private set; } +#nullable disable + public static HttpException Create(RestResponse requestResult, object body) { Arguments.CheckNotNull(requestResult, nameof(requestResult)); @@ -162,6 +170,7 @@ public static HttpException Create(object content, IEnumerable err.ErrorBody = errorBody; err.RetryAfter = retryAfter; + err.Headers = headers; return err; } diff --git a/Client.Core/Internal/AbstractQueryClient.cs b/Client.Core/Internal/AbstractQueryClient.cs index 519586783..b184be46b 100644 --- a/Client.Core/Internal/AbstractQueryClient.cs +++ b/Client.Core/Internal/AbstractQueryClient.cs @@ -13,6 +13,7 @@ using InfluxDB.Client.Core.Flux.Internal; using Newtonsoft.Json.Linq; using RestSharp; +using RestSharp.Interceptors; namespace InfluxDB.Client.Core.Internal { @@ -39,7 +40,7 @@ protected AbstractQueryClient(IFluxResultMapper mapper, FluxCsvParser csvParser) _csvParser = csvParser; } - protected Task Query(Func, RestRequest> queryFn, + protected Task Query(RestRequest query, FluxCsvParser.IFluxResponseConsumer responseConsumer, Action onError, Action onComplete, CancellationToken cancellationToken) @@ -56,10 +57,10 @@ void Consumer(Stream bufferedStream) } } - return Query(queryFn, Consumer, onError, onComplete, cancellationToken); + return Query(query, Consumer, onError, onComplete, cancellationToken); } - protected Task QueryRaw(Func, RestRequest> queryFn, + protected Task QueryRaw(RestRequest query, Action onResponse, Action onError, Action onComplete, CancellationToken cancellationToken) @@ -76,10 +77,10 @@ void Consumer(Stream bufferedStream) } } - return Query(queryFn, Consumer, onError, onComplete, cancellationToken); + return Query(query, Consumer, onError, onComplete, cancellationToken); } - protected void QuerySync(Func, RestRequest> queryFn, + protected void QuerySync(RestRequest query, FluxCsvParser.IFluxResponseConsumer responseConsumer, Action onError, Action onComplete, @@ -97,21 +98,21 @@ void Consumer(CancellationToken cancellable, Stream bufferedStream) } } - QuerySync(queryFn, Consumer, onError, onComplete, cancellationToken); + QuerySync(query, Consumer, onError, onComplete, cancellationToken); } - private async Task Query(Func, RestRequest> queryFn, + private async Task Query(RestRequest query, Action consumer, Action onError, Action onComplete, CancellationToken cancellationToken) { - Arguments.CheckNotNull(queryFn, "queryFn"); + Arguments.CheckNotNull(query, "query"); Arguments.CheckNotNull(consumer, "consumer"); Arguments.CheckNotNull(onError, "onError"); Arguments.CheckNotNull(onComplete, "onComplete"); try { - var query = queryFn.Invoke((response, request) => + query.AdvancedResponseWriter = (response, request) => { var result = GetStreamFromResponse(response, cancellationToken); result = AfterIntercept((int)response.StatusCode, @@ -122,7 +123,7 @@ private async Task Query(Func, RestRequest> queryFn, + private void QuerySync(RestRequest query, Action consumer, Action onError, Action onComplete, CancellationToken cancellationToken) { - Arguments.CheckNotNull(queryFn, "queryFn"); + Arguments.CheckNotNull(query, "query"); Arguments.CheckNotNull(consumer, "consumer"); Arguments.CheckNotNull(onError, "onError"); Arguments.CheckNotNull(onComplete, "onComplete"); try { - var query = queryFn.Invoke((response, request) => + query.AdvancedResponseWriter = (response, request) => { var result = GetStreamFromResponse(response, cancellationToken); result = AfterIntercept((int)response.StatusCode, @@ -166,7 +167,7 @@ private void QuerySync(Func consumer(cancellationToken, result); return FromHttpResponseMessage(response, request); - }); + }; BeforeIntercept(query); @@ -188,32 +189,21 @@ private void QuerySync(Func } protected async IAsyncEnumerable QueryEnumerable( - Func, RestRequest> queryFn, + RestRequest query, Func convert, [EnumeratorCancellation] CancellationToken cancellationToken) { - Arguments.CheckNotNull(queryFn, nameof(queryFn)); + Arguments.CheckNotNull(query, nameof(query)); - Stream stream = null; - var query = queryFn.Invoke((response, request) => + query.Interceptors = new List { - stream = GetStreamFromResponse(response, cancellationToken); - stream = AfterIntercept((int)response.StatusCode, - () => response.Headers.ToHeaderParameters(response.Content.Headers), stream); - - RaiseForInfluxError(response, stream); - - return FromHttpResponseMessage(response, request); - }); - - BeforeIntercept(query); - - var restResponse = await RestClient.ExecuteAsync(query, cancellationToken).ConfigureAwait(false); - if (restResponse.ErrorException != null) - { - throw restResponse.ErrorException; - } + new RequestBeforeAfterInterceptor( + BeforeIntercept, + (statusCode, headers, body) => AfterIntercept(statusCode, headers, body) + ) + }; + var stream = await RestClient.DownloadStreamAsync(query, cancellationToken).ConfigureAwait(false); await foreach (var (_, record) in _csvParser .ParseFluxResponseAsync(new StreamReader(stream), cancellationToken) .ConfigureAwait(false)) @@ -409,4 +399,42 @@ private Stream GetStreamFromResponse(HttpResponseMessage response, CancellationT return streamFromResponse; } } + + /// + /// The interceptor that is called before and after the request. + /// + internal class RequestBeforeAfterInterceptor : Interceptor + { + private readonly Action _beforeRequest; + private readonly Action>, T> _afterRequest; + + /// + /// Construct the interceptor. + /// + /// Intercept request before HTTP call + /// Intercept response before parsing resutlts + internal RequestBeforeAfterInterceptor( + Action beforeRequest = null, + Action>, T> afterRequest = null) + { + _beforeRequest = beforeRequest; + _afterRequest = afterRequest; + } + + public override ValueTask BeforeRequest(RestRequest request, CancellationToken cancellationToken) + { + _beforeRequest?.Invoke(request); + return base.BeforeRequest(request, cancellationToken); + } + + public override ValueTask AfterHttpRequest(HttpResponseMessage responseMessage, + CancellationToken cancellationToken) + { + _afterRequest?.Invoke( + (int)responseMessage.StatusCode, + () => responseMessage.Headers.ToHeaderParameters(responseMessage.Content.Headers), + default); + return base.AfterHttpRequest(responseMessage, cancellationToken); + } + } } \ No newline at end of file diff --git a/Client.Core/Internal/RestSharpExtensions.cs b/Client.Core/Internal/RestSharpExtensions.cs index fcb01b114..72e1c5541 100644 --- a/Client.Core/Internal/RestSharpExtensions.cs +++ b/Client.Core/Internal/RestSharpExtensions.cs @@ -1,11 +1,7 @@ -using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; using System.Net.Http.Headers; -using System.Reflection; using System.Threading; -using System.Threading.Tasks; using RestSharp; namespace InfluxDB.Client.Core.Internal @@ -27,20 +23,10 @@ internal static IEnumerable ToHeaderParameters(this HttpHeaders .Select(x => new HeaderParameter(x.Key, x.y)); } - internal static RestRequest AddAdvancedResponseHandler(this RestRequest restRequest, - Func advancedResponseWriter) - { - var field = restRequest.GetType() - .GetField("_advancedResponseHandler", BindingFlags.Instance | BindingFlags.NonPublic); - field!.SetValue(restRequest, advancedResponseWriter); - - return restRequest; - } - internal static RestResponse ExecuteSync(this RestClient client, RestRequest request, CancellationToken cancellationToken = default) { - return client.Execute(request, cancellationToken); + return client.Execute(request); } } } \ No newline at end of file diff --git a/Client.Legacy.Test/Client.Legacy.Test.csproj b/Client.Legacy.Test/Client.Legacy.Test.csproj index 300635e88..6f7dc4d54 100644 --- a/Client.Legacy.Test/Client.Legacy.Test.csproj +++ b/Client.Legacy.Test/Client.Legacy.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1;net5.0;net6.0;net7.0 + netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 8 false @@ -11,8 +11,8 @@ - - + + @@ -21,11 +21,11 @@ - |net5.0|net6.0|net7.0| + |net5.0|net6.0|net7.0|net8.0| - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Client.Legacy/Client.Legacy.csproj b/Client.Legacy/Client.Legacy.csproj index 87c98ffaa..b10393a7f 100644 --- a/Client.Legacy/Client.Legacy.csproj +++ b/Client.Legacy/Client.Legacy.csproj @@ -6,8 +6,8 @@ The client that allow perform Flux Query against the InfluxDB 1.7+. influxdb-client-csharp Contributors InfluxDB.Client.Flux - 4.15.0 - + 4.19.0 + dev InfluxDB.Client.Flux influxdata;timeseries;flux;influxdb @@ -27,6 +27,10 @@ true + + true + + diff --git a/Client.Legacy/FluxClient.cs b/Client.Legacy/FluxClient.cs index 1c6e935dd..e5bfe6f0e 100644 --- a/Client.Legacy/FluxClient.cs +++ b/Client.Legacy/FluxClient.cs @@ -423,10 +423,9 @@ private RestRequest PingRequest() return new RestRequest("ping"); } - private Func, RestRequest> QueryRequest(string query) + private RestRequest QueryRequest(string query) { - return advancedResponseWriter => new RestRequest("api/v2/query", Method.Post) - .AddAdvancedResponseHandler(advancedResponseWriter) + return new RestRequest("api/v2/query", Method.Post) .AddParameter(new BodyParameter("application/json", query, "application/json")); } } diff --git a/Client.Linq.Test/Client.Linq.Test.csproj b/Client.Linq.Test/Client.Linq.Test.csproj index 2353a9e07..bdb960d6d 100644 --- a/Client.Linq.Test/Client.Linq.Test.csproj +++ b/Client.Linq.Test/Client.Linq.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1;net5.0;net6.0;net7.0 + netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 8 false @@ -11,9 +11,9 @@ - + - + @@ -23,11 +23,11 @@ - |net5.0|net6.0|net7.0| + |net5.0|net6.0|net7.0|net8.0| - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Client.Linq/Client.Linq.csproj b/Client.Linq/Client.Linq.csproj index fac1c82e2..fcadbde5f 100644 --- a/Client.Linq/Client.Linq.csproj +++ b/Client.Linq/Client.Linq.csproj @@ -7,8 +7,8 @@ The library supports querying InfluxDB 2.x by LINQ expressions. influxdb-client-csharp Contributors InfluxDB.Client.Linq - 4.15.0 - + 4.19.0 + dev InfluxDB.Client.Linq influxdata;timeseries;flux;influxdb;linq @@ -28,6 +28,10 @@ true + + true + + diff --git a/Client.Test/Client.Test.csproj b/Client.Test/Client.Test.csproj index 540e9b6d0..1a4ca903c 100644 --- a/Client.Test/Client.Test.csproj +++ b/Client.Test/Client.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1;net5.0;net6.0;net7.0 + netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 8 false @@ -12,10 +12,10 @@ - + - - + + @@ -24,11 +24,11 @@ - |net5.0|net6.0|net7.0| + |net5.0|net6.0|net7.0|net8.0| - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Client.Test/InfluxExceptionTest.cs b/Client.Test/InfluxExceptionTest.cs new file mode 100644 index 000000000..2c7e6a73c --- /dev/null +++ b/Client.Test/InfluxExceptionTest.cs @@ -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 + { + 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(); + 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"]); + } + } + } +} \ No newline at end of file diff --git a/Client.Test/ItErrorEventsTest.cs b/Client.Test/ItErrorEventsTest.cs new file mode 100644 index 000000000..e604200a5 --- /dev/null +++ b/Client.Test/ItErrorEventsTest.cs @@ -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 { 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 { }; + 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); + } + } + } +} \ No newline at end of file diff --git a/Client.Test/ItWriteApiAsyncTest.cs b/Client.Test/ItWriteApiAsyncTest.cs index 6cfb6323e..a9aba7798 100644 --- a/Client.Test/ItWriteApiAsyncTest.cs +++ b/Client.Test/ItWriteApiAsyncTest.cs @@ -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; @@ -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(); + 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"]); + } + } } } \ No newline at end of file diff --git a/Client.Test/ItWriteQueryApiTest.cs b/Client.Test/ItWriteQueryApiTest.cs index 56ec9e237..4f8d3bf67 100644 --- a/Client.Test/ItWriteQueryApiTest.cs +++ b/Client.Test/ItWriteQueryApiTest.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; +using System.Threading.Tasks; using InfluxDB.Client.Api.Domain; using InfluxDB.Client.Core; using InfluxDB.Client.Writes; @@ -944,5 +945,37 @@ public async Task GzipWithLargeAmountOfData() Assert.AreEqual(1000, tables.Count); Assert.AreEqual(1, tables[0].Records.Count); } + + [Test] + public async Task QueryAsyncEnumerableGzip() + { + Client.EnableGzip(); + Client.SetLogLevel(LogLevel.Body); + + await Client.GetWriteApiAsync().WriteMeasurementsAsync(new[] + { + new H20Measurement + { + Location = "angel_bay", Level = 2.927, Time = DateTime.UtcNow.Add(-TimeSpan.FromSeconds(10)) + }, + new H20Measurement + { + Location = "angel_bay", Level = 1.927, Time = DateTime.UtcNow.Add(-TimeSpan.FromSeconds(20)) + } + }); + + var query = $@"from(bucket: ""{_bucket.Name}"") + |> range(start: 0) + |> filter(fn: (r) => r[""location""] == ""angel_bay"") + |> pivot(rowKey:[""_time""], columnKey: [""_field""], valueColumn: ""_value"")"; + + var list = new List(); + await foreach (var item in _queryApi.QueryAsyncEnumerable(query).ConfigureAwait(false)) + list.Add(item); + + Assert.AreEqual(2, list.Count); + Assert.AreEqual(1.927, list[0].Level); + Assert.AreEqual(2.927, list[1].Level); + } } } \ No newline at end of file diff --git a/Client.Test/WriteApiTest.cs b/Client.Test/WriteApiTest.cs index ec2b3fc3a..83fe59a14 100644 --- a/Client.Test/WriteApiTest.cs +++ b/Client.Test/WriteApiTest.cs @@ -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; @@ -380,6 +381,27 @@ public void RetryContainsMessage() StringAssert.Contains(message, writer.ToString()); } + [Test] + public void Created() + { + var listener = new EventListener(_writeApi); + + MockServer.Reset(); + MockServer + .Given(Request.Create().UsingPost()) + .RespondWith(CreateResponse( + "OK", + 201)); + + _writeApi.WriteRecord("h2o_feet,location=coyote_creek level\\ description=\"feet 1\",water_level=1.0 1", + WritePrecision.Ns, "b1", "org1"); + _writeApi.Flush(); + + var writeSuccessEvent = listener.Get(); + Assert.AreEqual("h2o_feet,location=coyote_creek level\\ description=\"feet 1\",water_level=1.0 1", + writeSuccessEvent.LineProtocol); + } + [Test] public void TwiceDispose() { @@ -506,6 +528,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(); + 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() + { + { "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() { diff --git a/Client/Client.csproj b/Client/Client.csproj index ab8a7031a..bda849b60 100644 --- a/Client/Client.csproj +++ b/Client/Client.csproj @@ -7,8 +7,8 @@ The reference client that allows query, write and management (bucket, organization, users) for the InfluxDB 2.x. influxdb-client-csharp Contributors InfluxDB.Client - 4.15.0 - + 4.19.0 + dev InfluxDB.Client influxdata;timeseries;flux;influxdb @@ -28,6 +28,10 @@ true + + true + + @@ -36,11 +40,11 @@ - - - - - + + + + + diff --git a/Client/InfluxDB.Client.Api/Client/ApiResponse.cs b/Client/InfluxDB.Client.Api/Client/ApiResponse.cs index 0849b4b39..d2942306b 100644 --- a/Client/InfluxDB.Client.Api/Client/ApiResponse.cs +++ b/Client/InfluxDB.Client.Api/Client/ApiResponse.cs @@ -43,7 +43,7 @@ public class ApiResponse /// HTTP status code. /// HTTP headers. /// Data (parsed HTTP body) - public ApiResponse(int statusCode, IEnumerable<(string Name, object Value)> headers, T data) + public ApiResponse(int statusCode, IEnumerable<(string Name, string Value)> headers, T data) { StatusCode = statusCode; Headers = headers diff --git a/Client/InvokableScriptsApi.cs b/Client/InvokableScriptsApi.cs index c26d1d373..9e603d29d 100644 --- a/Client/InvokableScriptsApi.cs +++ b/Client/InvokableScriptsApi.cs @@ -300,14 +300,13 @@ private Task InvokeScript(string scriptId, FluxCsvParser.IFluxResponseConsumer c cancellationToken); } - private Func, RestRequest> CreateRequest(string scriptId, + private RestRequest CreateRequest(string scriptId, Dictionary bindParams = default) { Arguments.CheckNonEmptyString(scriptId, nameof(scriptId)); - return advancedResponseWriter => _service - .PostScriptsIDInvokeWithRestRequest(scriptId, new ScriptInvocationParams(bindParams)) - .AddAdvancedResponseHandler(advancedResponseWriter); + return _service + .PostScriptsIDInvokeWithRestRequest(scriptId, new ScriptInvocationParams(bindParams)); } } } \ No newline at end of file diff --git a/Client/QueryApi.cs b/Client/QueryApi.cs index 361d10457..2946ff26b 100644 --- a/Client/QueryApi.cs +++ b/Client/QueryApi.cs @@ -783,17 +783,18 @@ private Task QueryAsync(Query query, FluxCsvParser.IFluxResponseConsumer consume cancellationToken); } - private Func, RestRequest> CreateRequest(Query query, - string org = null) + private RestRequest CreateRequest(Query query, string org = null) { Arguments.CheckNotNull(query, nameof(query)); var optionsOrg = org ?? _options.Org; Arguments.CheckNonEmptyString(optionsOrg, OrgArgumentValidation); - return advancedResponseWriter => _service - .PostQueryWithRestRequest(null, "application/json", null, optionsOrg, null, query) - .AddAdvancedResponseHandler(advancedResponseWriter); + var postQueryWithRestRequest = _service + .PostQueryWithRestRequest(null, "application/json", null, optionsOrg, null, query, + HttpCompletionOption.ResponseHeadersRead); + + return postQueryWithRestRequest; } internal static Query CreateQuery(string query, Dialect dialect = null) diff --git a/Client/QueryApiSync.cs b/Client/QueryApiSync.cs index dc0c30622..ce0baa26e 100644 --- a/Client/QueryApiSync.cs +++ b/Client/QueryApiSync.cs @@ -140,15 +140,11 @@ public List QuerySync(Query query, string org = null, CancellationToken ca var consumer = new FluxResponseConsumerPoco(poco => { measurements.Add(poco); }, Mapper); - RestRequest QueryFn(Func advancedResponseWriter) - { - return _service - .PostQueryWithRestRequest(null, "application/json", null, optionsOrg, null, query, - HttpCompletionOption.ResponseHeadersRead) - .AddAdvancedResponseHandler(advancedResponseWriter); - } + var restRequest = _service + .PostQueryWithRestRequest(null, "application/json", null, optionsOrg, null, query, + HttpCompletionOption.ResponseHeadersRead); - QuerySync(QueryFn, consumer, ErrorConsumer, EmptyAction, cancellationToken); + QuerySync(restRequest, consumer, ErrorConsumer, EmptyAction, cancellationToken); return measurements; } @@ -193,15 +189,11 @@ public List QuerySync(Query query, string org = null, CancellationTok var optionsOrg = org ?? _options.Org; Arguments.CheckNonEmptyString(optionsOrg, OrgArgumentValidation); - RestRequest QueryFn(Func advancedResponseWriter) - { - return _service - .PostQueryWithRestRequest(null, "application/json", null, optionsOrg, null, query, - HttpCompletionOption.ResponseHeadersRead) - .AddAdvancedResponseHandler(advancedResponseWriter); - } + var restRequest = _service + .PostQueryWithRestRequest(null, "application/json", null, optionsOrg, null, query, + HttpCompletionOption.ResponseHeadersRead); - QuerySync(QueryFn, consumer, ErrorConsumer, EmptyAction, cancellationToken); + QuerySync(restRequest, consumer, ErrorConsumer, EmptyAction, cancellationToken); return consumer.Tables; } diff --git a/Client/Writes/Events.cs b/Client/Writes/Events.cs index 14ae98db8..dd8b32227 100644 --- a/Client/Writes/Events.cs +++ b/Client/Writes/Events.cs @@ -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 { @@ -42,6 +45,14 @@ internal override void LogEvent() { Trace.TraceError($"The error occurred during writing of data: {Exception.Message}"); } + + /// + /// Get headers from the nested exception. + /// + public IEnumerable GetHeaders() + { + return ((HttpException)Exception)?.Headers; + } } /// diff --git a/Examples/ExampleBlazor/ExampleBlazor.csproj b/Examples/ExampleBlazor/ExampleBlazor.csproj index 91f1c1de1..53dbaa35f 100644 --- a/Examples/ExampleBlazor/ExampleBlazor.csproj +++ b/Examples/ExampleBlazor/ExampleBlazor.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 enable enable @@ -9,7 +9,7 @@ - + diff --git a/Examples/Examples.csproj b/Examples/Examples.csproj index 75f8ec077..32dfbbd68 100644 --- a/Examples/Examples.csproj +++ b/Examples/Examples.csproj @@ -2,10 +2,10 @@ Exe - netcoreapp3.1;net5.0;net6.0;net7.0 + netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 8 - 4.15.0 - + 4.19.0 + dev false ../Keys/Key.snk diff --git a/Examples/HttpErrorHandling.cs b/Examples/HttpErrorHandling.cs new file mode 100644 index 000000000..3c1f3fc80 --- /dev/null +++ b/Examples/HttpErrorHandling.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using InfluxDB.Client; +using InfluxDB.Client.Api.Domain; +using InfluxDB.Client.Core.Exceptions; +using InfluxDB.Client.Writes; +using RestSharp; + +namespace Examples +{ + public class HttpErrorHandling + { + private static InfluxDBClient _client; + private static List _lpRecords; + + private static void Setup() + { + _client = new InfluxDBClient("http://localhost:9999", + "my-user", "my-password"); + var nowMillis = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + + _lpRecords = new List() + { + $"temperature,location=north value=42 {nowMillis}", + $"temperature,location=north value=17 {nowMillis - 10000}", + $"temperature,location=north value= {nowMillis - 20000}", // one flaky record + $"temperature,location=north value=5 {nowMillis - 30000}" + }; + } + + private static void TearDown() + { + _client.Dispose(); + } + + private static Dictionary Headers2Dictionary(IEnumerable headers) + { + var result = new Dictionary(); + foreach (var hp in headers) + result.Add(hp.Name, hp.Value); + return result; + } + + private static async Task WriteRecordsAsync() + { + Console.WriteLine("Write records async with one invalid record."); + + // + // Write Data + // + var writeApiAsync = _client.GetWriteApiAsync(); + + try + { + await writeApiAsync.WriteRecordsAsync(_lpRecords, WritePrecision.Ms, + "my-bucket", "my-org"); + } + catch (HttpException he) + { + Console.WriteLine(" WARNING write failed"); + var headersDix = Headers2Dictionary(he.Headers); + Console.WriteLine(" Caught Exception({0}) \"{1}\"", + he.GetType(), + he.Message); + Console.WriteLine(" Response Status: {0}", he.Status); + Console.WriteLine(" Headers:"); + foreach (var key in headersDix.Keys) + Console.WriteLine($" {key}: {headersDix[key]}"); + } + finally + { + Console.WriteLine(" ===================="); + } + } + + private static void WriteRecordsWithErrorEvent() + { + Console.WriteLine("Write records with error event."); + + var caughtError = false; + using (var writeApi = _client.GetWriteApi()) + { + writeApi.EventHandler += (sender, eventArgs) => + { + switch (eventArgs) + { + case WriteErrorEvent wee: + Console.WriteLine(" WARNING write failed"); + Console.WriteLine(" Received event WriteErrorEvent with:"); + Console.WriteLine(" Status: {0}", ((HttpException)wee.Exception).Status); + Console.WriteLine(" Exception: {0}", wee.Exception.GetType()); + Console.WriteLine(" Message: {0}", wee.Exception.Message); + Console.WriteLine(" Headers:"); + var headersDix = Headers2Dictionary(wee.GetHeaders()); + foreach (var key in headersDix.Keys) + Console.WriteLine($" {key}: {headersDix[key]}"); + caughtError = true; + break; + default: + throw new Exception("Should only receive WriteErrorEvent"); + } + }; + Console.WriteLine("Trying the records list"); + writeApi.WriteRecords(_lpRecords, WritePrecision.Ms, "my-bucket", "my-org"); + var slept = 0; + while (!caughtError && slept < 3001) + { + Thread.Sleep(1000); + slept += 1000; + } + + if (!caughtError) + { + Console.WriteLine("WARN, did not encounter expected error"); + } + + + // manually retry the bad record + Console.WriteLine("Manually retrying the bad record."); + writeApi.WriteRecord(_lpRecords[2], WritePrecision.Ms, "my-bucket", "my-org"); + caughtError = false; + slept = 0; + while (!caughtError && slept < 3001) + { + Thread.Sleep(1000); + slept += 1000; + } + + if (!caughtError) + { + Console.WriteLine("WARN, did not encounter expected error"); + } + } + } + + public static async Task Main() + { + Console.WriteLine("Main()"); + Setup(); + await WriteRecordsAsync(); + WriteRecordsWithErrorEvent(); + TearDown(); + } + } +} \ No newline at end of file diff --git a/Examples/README.md b/Examples/README.md index 6eca76b7a..e8681b3f4 100644 --- a/Examples/README.md +++ b/Examples/README.md @@ -2,6 +2,7 @@ ## Writes - [WriteEventHandlerExample.cs](WriteEventHandlerExample.cs) - How to use WriteAPI with batch options and how to handle events +- [HttpErrorHandling.cs](HttpErrorHandling.cs) - How to handle errors on write and retrieve response headers. ## Others - [InvokableScripts.cs](InvokableScripts.cs) - How to use Invokable scripts Cloud API to create custom endpoints that query data diff --git a/Examples/RunExamples.cs b/Examples/RunExamples.cs index 91d5e59f8..a40688819 100644 --- a/Examples/RunExamples.cs +++ b/Examples/RunExamples.cs @@ -72,6 +72,9 @@ public static async Task Main(string[] args) case "RecordRowExample": await RecordRowExample.Main(); break; + case "HttpErrorHandling": + await HttpErrorHandling.Main(); + break; } } else diff --git a/Scripts/ci-test.sh b/Scripts/ci-test.sh index ffc662d02..b6d258413 100755 --- a/Scripts/ci-test.sh +++ b/Scripts/ci-test.sh @@ -20,11 +20,11 @@ echo "$NET_TEST_VERSION" DEFAULT_NET_TARGET_VERSION="netstandard2.1" NET_TARGET_VERSION="${NET_TARGET_VERSION:-$DEFAULT_NET_TARGET_VERSION}" -sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Core.Test/Client.Core.Test.csproj -sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Test/Client.Test.csproj -sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Legacy.Test/Client.Legacy.Test.csproj -sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Linq.Test/Client.Linq.Test.csproj -sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Examples/Examples.csproj +sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Core.Test/Client.Core.Test.csproj +sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Test/Client.Test.csproj +sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Legacy.Test/Client.Legacy.Test.csproj +sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Client.Linq.Test/Client.Linq.Test.csproj +sed -i '/netcoreapp3.1;net5.0;net6.0;net7.0;net8.0<\/TargetFrameworks>/c\'"${NET_TEST_VERSION}"'<\/TargetFramework>' Examples/Examples.csproj sed -i '/netstandard2.0;netstandard2.1<\/TargetFrameworks>/c\'"${NET_TARGET_VERSION}"'<\/TargetFramework>' Client.Core/Client.Core.csproj sed -i '/netstandard2.0;netstandard2.1<\/TargetFrameworks>/c\'"${NET_TARGET_VERSION}"'<\/TargetFramework>' Client/Client.csproj @@ -42,10 +42,13 @@ else TRX2JUNIT_VERSION="1.3.2" fi -if [[ "$NET_TEST_VERSION" = "netcoreapp6.0" || "$NET_TEST_VERSION" = "netcoreapp7.0" ]] +if [[ "$NET_TEST_VERSION" = "netcoreapp6.0" || "$NET_TEST_VERSION" = "netcoreapp7.0" || "$NET_TEST_VERSION" = "netcoreapp8.0" ]] +then + TRX2JUNIT_VERSION="2.1.0" +fi + +if [[ "$NET_TEST_VERSION" != "netcoreapp8.0" ]] then - TRX2JUNIT_VERSION="2.0.4" -else dotnet sln remove Examples/ExampleBlazor/ExampleBlazor.csproj fi pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy