Skip to content

Commit

Permalink
Feature. Add Request/Response to API endpoint for flexibility (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
evgenyvalavin authored Jun 1, 2023
1 parent 91408ed commit 641fa89
Show file tree
Hide file tree
Showing 15 changed files with 115 additions and 160 deletions.
2 changes: 1 addition & 1 deletion samples/Arbus.Network.Demo/OrdersResponseDto.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
namespace Arbus.Network.Demo;

public record OrdersResponseDto(List<Guid> Orders);
public record OrdersResponseDto(IReadOnlyList<Guid> Orders);
10 changes: 5 additions & 5 deletions src/Arbus.Network.UnitTests/Arbus.Network.UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageReference Include="Moq" Version="4.18.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.5.0">
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
16 changes: 0 additions & 16 deletions src/Arbus.Network.UnitTests/TestFixture.cs

This file was deleted.

33 changes: 0 additions & 33 deletions src/Arbus.Network.UnitTests/Tests/HttpClientContextTests.cs

This file was deleted.

25 changes: 13 additions & 12 deletions src/Arbus.Network.UnitTests/Tests/NativeHttpClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
using Arbus.Network.Abstractions;
using Arbus.Network.Exceptions;
using Arbus.Network.Extensions;
using Arbus.Network.Implementations;

namespace Arbus.Network.UnitTests.Tests;

public class NativeHttpClientTests : TestFixture
public class NativeHttpClientTests
{
[Test]
public void EnsureNoTimeout_CancellationRequested_ThrowsHttpTimeoutException()
{
var canceallationTokenSource = new CancellationTokenSource();
canceallationTokenSource.Cancel();
var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();

Assert.Throws<HttpTimeoutException>(() => NativeHttpClient.EnsureNoTimeout(canceallationTokenSource));
Assert.Throws<HttpTimeoutException>(() => NativeHttpClient.EnsureNoTimeout(cancellationTokenSource));
}

[Test]
public void EnsureNoTimeout_CancellationNotRequested_NoException()
{
var canceallationTokenSource = new CancellationTokenSource();
var cancellationTokenSource = new CancellationTokenSource();

Assert.DoesNotThrow(() => NativeHttpClient.EnsureNoTimeout(canceallationTokenSource));
Assert.DoesNotThrow(() => NativeHttpClient.EnsureNoTimeout(cancellationTokenSource));
}

[Test]
public void EnsureNetworkAvailable_NetworkNotAvailable_ThrowsNoNetworkConnectionAvailableException()
{
var mockNetworkManager = CreateMock<INetworkManager>();
var mockNetworkManager = new Mock<INetworkManager>();
mockNetworkManager.SetupGet(x => x.IsNetworkAvailable).Returns(false);

NativeHttpClient nativeHttpClient = new(mockNetworkManager.Object);
Expand All @@ -37,7 +38,7 @@ public void EnsureNetworkAvailable_NetworkNotAvailable_ThrowsNoNetworkConnection
[Test]
public void EnsureNetworkAvailable_NetworkAvailable_NoException()
{
var mockNetworkManager = CreateMock<INetworkManager>();
var mockNetworkManager = new Mock<INetworkManager>();
mockNetworkManager.SetupGet(x => x.IsNetworkAvailable).Returns(true);

NativeHttpClient nativeHttpClient = new(mockNetworkManager.Object);
Expand All @@ -53,7 +54,7 @@ public void GetTimeoutCts_InfiniteTimeSpan_AssertNullCts()

var cts = NativeHttpClient.GetTimeoutCts(timeout, default);

Assert.IsNull(cts);
Assert.That(cts, Is.Null);
}

[Test]
Expand All @@ -64,11 +65,11 @@ public void GetTimeoutCts_NotInfiniteTimeSpan_AssertNotNUllCts()

using var cts = NativeHttpClient.GetTimeoutCts(timeout, default);

Assert.NotNull(cts);
Assert.That(cts, Is.Not.Null);
}

[Test]
public void GetTimeoutCts_CancellFirstToken_AssertSecondIsCancelled()
public void GetTimeoutCts_CancelFirstToken_AssertSecondIsCanceled()
{
using HttpRequestMessage timeout = new();
timeout.SetTimeout(TimeSpan.FromSeconds(1));
Expand All @@ -77,6 +78,6 @@ public void GetTimeoutCts_CancellFirstToken_AssertSecondIsCancelled()

using var cts2 = NativeHttpClient.GetTimeoutCts(timeout, cts1.Token);

Assert.IsTrue(cts2?.Token.IsCancellationRequested);
Assert.That(cts2?.Token.IsCancellationRequested, Is.True);
}
}
59 changes: 53 additions & 6 deletions src/Arbus.Network/Abstractions/ApiEndpoint.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Arbus.Network.ContentSerializers;
using Arbus.Network.Extensions;
using System.Net.Http.Headers;
using System.Text;

namespace Arbus.Network.Abstractions;

Expand All @@ -12,14 +14,59 @@ public abstract class ApiEndpoint

public virtual TimeSpan Timeout => _defaultTimeout;

public virtual Dictionary<string, string>? AdditionalHeaders { get; }

public CancellationToken? CancellationToken { get; set; }

public virtual HttpContent? CreateContent() => default;
protected internal virtual HttpRequestMessage CreateRequest(Uri? baseUrl)
{
var requestUri = CreateRequestUri(baseUrl);

var request = new HttpRequestMessage(Method, requestUri)
{
Content = CreateContent()
};
request.SetTimeout(Timeout);

return request;
}

private Uri CreateRequestUri(Uri? baseUrl)
{
Uri uri;
if (baseUrl is null)
uri = new(Path);
else
uri = new(baseUrl, Path);
return uri;
}

protected internal virtual HttpContent? CreateContent() => default;

protected virtual void AddRequestHeaders(HttpRequestHeaders headers)
{
}

protected virtual StringContent ToJson(object value)
{
return new(
JsonSerializer.Serialize(
value, GlobalJsonSerializerOptions.Options), Encoding.UTF8, HttpContentType.Application.Json);
}
}

public abstract class ApiEndpoint<TResponseContent> : ApiEndpoint
public abstract class ApiEndpoint<TResponse> : ApiEndpoint
{
public virtual Task<TResponseContent> GetResponse(HttpContent httpContent) => JsonContentSerializer.Deserialize<TResponseContent>(httpContent);
public virtual ValueTask<TResponse> GetResponse(HttpResponseMessage responseMessage)
=> FromJson<TResponse>(responseMessage.Content);

public static async ValueTask<T> FromJson<T>(HttpContent content, CancellationToken cancellationToken = default)
{
using var responseStream = await content
.ReadAsStreamAsync()
.ConfigureAwait(false);

var deserializedObject = await JsonSerializer.DeserializeAsync<T>(
responseStream, GlobalJsonSerializerOptions.Options, cancellationToken).ConfigureAwait(false);

return deserializedObject ?? throw new Exception("Unable to deserialize stream.");
}
}
2 changes: 1 addition & 1 deletion src/Arbus.Network/Abstractions/IHttpContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
public interface IHttpClientContext
{
Task RunEndpoint(ApiEndpoint endpoint);
Task<T> RunEndpoint<T>(ApiEndpoint<T> endpoint);
Task<TResponse> RunEndpoint<TResponse>(ApiEndpoint<TResponse> endpoint);
Task<TStream> RunStreamEndpoint<TStream>(ApiEndpoint<TStream> endpoint) where TStream : Stream;
Task<THttpContent> RunHttpContentEndpoint<THttpContent>(ApiEndpoint<THttpContent> endpoint) where THttpContent : HttpContent;
}
13 changes: 11 additions & 2 deletions src/Arbus.Network/Arbus.Network.csproj
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<LangVersion>Latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<PropertyGroup>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Authors>ArbusBiz</Authors>
<Description>All the best practices of using the HttpClient in just one library.</Description>
<RepositoryUrl>https://github.com/ArbusBiz/Arbus.Network</RepositoryUrl>
<PackageProjectUrl>https://github.com/ArbusBiz/Arbus.Network</PackageProjectUrl>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Text.Json" Version="7.0.0" />
<PackageReference Include="System.Text.Json" Version="7.0.2" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
<Compile Remove="IsExternalInit.cs"/>
<Compile Remove="IsExternalInit.cs" />
</ItemGroup>

<ItemGroup>
<Using Include="System.Text.Json" />
</ItemGroup>

</Project>
37 changes: 0 additions & 37 deletions src/Arbus.Network/ContentSerializers/DefaultJsonSerializer.cs

This file was deleted.

18 changes: 0 additions & 18 deletions src/Arbus.Network/ContentSerializers/JsonContentSerializer.cs

This file was deleted.

4 changes: 2 additions & 2 deletions src/Arbus.Network/Extensions/HttpClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ namespace Arbus.Network.Extensions;

public static class HttpClientExtensions
{
public static void SetUserAgentHeader(this HttpClient request, ProductInfoHeaderValue productInfoHeader)
=> request.DefaultRequestHeaders.UserAgent.Add(productInfoHeader);
public static void SetUserAgentHeader(this HttpClient httpClient, ProductInfoHeaderValue productInfoHeader)
=> httpClient.DefaultRequestHeaders.UserAgent.Add(productInfoHeader);
}
8 changes: 0 additions & 8 deletions src/Arbus.Network/Extensions/HttpResponseExtensions.cs

This file was deleted.

17 changes: 17 additions & 0 deletions src/Arbus.Network/GlobalJsonSerializerOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Text.Encodings.Web;
using System.Text.Json.Serialization;

namespace Arbus.Network;

public static class GlobalJsonSerializerOptions
{
public static JsonSerializerOptions Options { get; set; } = GetDefaultSerializerOptions();

public static JsonSerializerOptions GetDefaultSerializerOptions() => new()
{
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
}
Loading

0 comments on commit 641fa89

Please sign in to comment.