Skip to content

Commit 562ba62

Browse files
authored
Merge pull request #1 from The-Poolz/init
Init
2 parents 6b768a1 + 9b153eb commit 562ba62

9 files changed

Lines changed: 219 additions & 1 deletion

Poolz.Finance.CSharp.Http.sln

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.13.35828.75
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
7+
EndProject
8+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C43445A2-69BB-48F5-8D8B-45F82D29028D}"
9+
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Poolz.Finance.CSharp.Http", "src\Poolz.Finance.CSharp.Http\Poolz.Finance.CSharp.Http.csproj", "{6EAF7CF1-0D92-218F-D1AC-37BD1E926245}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Poolz.Finance.CSharp.Http.Tests", "tests\Poolz.Finance.CSharp.Http.Tests\Poolz.Finance.CSharp.Http.Tests.csproj", "{7B8DC796-734F-C107-EB88-001D0D01E09F}"
13+
EndProject
14+
Global
15+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
16+
Debug|Any CPU = Debug|Any CPU
17+
Release|Any CPU = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
20+
{6EAF7CF1-0D92-218F-D1AC-37BD1E926245}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{6EAF7CF1-0D92-218F-D1AC-37BD1E926245}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{6EAF7CF1-0D92-218F-D1AC-37BD1E926245}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{6EAF7CF1-0D92-218F-D1AC-37BD1E926245}.Release|Any CPU.Build.0 = Release|Any CPU
24+
{7B8DC796-734F-C107-EB88-001D0D01E09F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25+
{7B8DC796-734F-C107-EB88-001D0D01E09F}.Debug|Any CPU.Build.0 = Debug|Any CPU
26+
{7B8DC796-734F-C107-EB88-001D0D01E09F}.Release|Any CPU.ActiveCfg = Release|Any CPU
27+
{7B8DC796-734F-C107-EB88-001D0D01E09F}.Release|Any CPU.Build.0 = Release|Any CPU
28+
EndGlobalSection
29+
GlobalSection(SolutionProperties) = preSolution
30+
HideSolutionNode = FALSE
31+
EndGlobalSection
32+
GlobalSection(NestedProjects) = preSolution
33+
{6EAF7CF1-0D92-218F-D1AC-37BD1E926245} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
34+
{7B8DC796-734F-C107-EB88-001D0D01E09F} = {C43445A2-69BB-48F5-8D8B-45F82D29028D}
35+
EndGlobalSection
36+
GlobalSection(ExtensibilityGlobals) = postSolution
37+
SolutionGuid = {AF2F8BF3-0CF6-467C-944A-792A7D687E3C}
38+
EndGlobalSection
39+
EndGlobal

README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,25 @@
1-
# Poolz.Finance.CSharp.LibTemplate
1+
# Poolz.Finance.CSharp.Http
2+
3+
Poolz.Finance.CSharp.Http is a lightweight .NET 8 library that centralizes the creation of `HttpClient` instances for Poolz Finance services.
4+
It wraps the default `HttpClientHandler` with a custom `FailureOnlyLoggingHandler` that surfaces detailed information when requests fail while keeping successful responses silent.
5+
6+
## Getting started
7+
8+
```csharp
9+
using Poolz.Finance.CSharp.Http;
10+
11+
var factory = new HttpClientFactory();
12+
var client = factory.Create(
13+
url: "https://api.poolz.finance",
14+
configureHeaders: headers =>
15+
{
16+
headers.Add("Authorization", "Bearer <token>");
17+
headers.Add("User-Agent", "Poolz-SDK-Demo/1.0");
18+
});
19+
20+
var response = await client.GetAsync("/v1/pools");
21+
var content = await response.Content.ReadAsStringAsync();
22+
```
23+
24+
If the request fails, `FailureOnlyLoggingHandler` rethrows an exception that includes the HTTP method and URL so that you immediately know which call needs attention.
25+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace Poolz.Finance.CSharp.Http;
2+
3+
public class FailureOnlyLoggingHandler(HttpMessageHandler innerHandler) : DelegatingHandler(innerHandler)
4+
{
5+
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage req, CancellationToken ct)
6+
{
7+
try
8+
{
9+
var response = await base.SendAsync(req, ct);
10+
response.EnsureSuccessStatusCode();
11+
return response;
12+
}
13+
catch (HttpRequestException exception)
14+
{
15+
throw new HttpRequestException(
16+
$"HTTP request failed. METHOD: {req.Method}. URL: {req.RequestUri}",
17+
exception,
18+
exception.StatusCode
19+
);
20+
}
21+
}
22+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.Net.Http.Headers;
2+
3+
namespace Poolz.Finance.CSharp.Http;
4+
5+
public class HttpClientFactory : IHttpClientFactory
6+
{
7+
public HttpClient Create(string url, Action<HttpRequestHeaders>? configureHeaders = null)
8+
{
9+
var client = new HttpClient(new FailureOnlyLoggingHandler(new HttpClientHandler()))
10+
{
11+
BaseAddress = new Uri(url)
12+
};
13+
14+
configureHeaders?.Invoke(client.DefaultRequestHeaders);
15+
return client;
16+
}
17+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System.Net.Http.Headers;
2+
3+
namespace Poolz.Finance.CSharp.Http;
4+
5+
public interface IHttpClientFactory
6+
{
7+
public HttpClient Create(string url, Action<HttpRequestHeaders>? configure = null);
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
</Project>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using Xunit;
2+
using System.Net;
3+
4+
namespace Poolz.Finance.CSharp.Http.Tests;
5+
6+
public class FailureOnlyLoggingHandlerTests
7+
{
8+
[Fact]
9+
public async Task SendAsync_ReturnsResponse_WhenStatusIsSuccessful()
10+
{
11+
using var handler = new FailureOnlyLoggingHandler(new StubHandler((_, _) =>
12+
Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))));
13+
using var invoker = new HttpMessageInvoker(handler);
14+
using var request = new HttpRequestMessage(HttpMethod.Get, "https://poolz.finance/success");
15+
16+
var response = await invoker.SendAsync(request, CancellationToken.None);
17+
18+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
19+
}
20+
21+
[Fact]
22+
public async Task SendAsync_ThrowsHttpRequestExceptionWithContext_WhenStatusIsFailure()
23+
{
24+
using var handler = new FailureOnlyLoggingHandler(new StubHandler((_, _) =>
25+
Task.FromResult(new HttpResponseMessage(HttpStatusCode.BadRequest))));
26+
using var invoker = new HttpMessageInvoker(handler);
27+
using var request = new HttpRequestMessage(HttpMethod.Post, "https://poolz.finance/failure");
28+
29+
var exception = await Assert.ThrowsAsync<HttpRequestException>(() => invoker.SendAsync(request, CancellationToken.None));
30+
31+
Assert.Equal(HttpStatusCode.BadRequest, exception.StatusCode);
32+
Assert.NotNull(exception.InnerException);
33+
Assert.Contains("METHOD: POST", exception.Message);
34+
Assert.Contains("URL: https://poolz.finance/failure", exception.Message);
35+
}
36+
37+
private sealed class StubHandler(
38+
Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> responseFactory)
39+
: HttpMessageHandler
40+
{
41+
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
42+
{
43+
return responseFactory(request, cancellationToken);
44+
}
45+
}
46+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using Xunit;
2+
3+
namespace Poolz.Finance.CSharp.Http.Tests;
4+
5+
public class HttpClientFactoryTests
6+
{
7+
[Fact]
8+
public void Create_ConfiguresBaseAddressAndHeaders()
9+
{
10+
var factory = new HttpClientFactory();
11+
var expectedBaseAddress = new Uri("https://api.poolz.finance/");
12+
13+
using var client = factory.Create(expectedBaseAddress.ToString(), headers =>
14+
{
15+
headers.Add("X-Test", "42");
16+
});
17+
18+
Assert.Equal(expectedBaseAddress, client.BaseAddress);
19+
Assert.True(client.DefaultRequestHeaders.Contains("X-Test"));
20+
Assert.Equal("42", client.DefaultRequestHeaders.GetValues("X-Test").Single());
21+
}
22+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<RootNamespace>Poolz.Finance.CSharp.Http.Tests</RootNamespace>
8+
<AssemblyName>Poolz.Finance.CSharp.Http.Tests</AssemblyName>
9+
<IsPackable>false</IsPackable>
10+
<IsTestProject>true</IsTestProject>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="FluentAssertions" Version="6.11.0" />
15+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
16+
<PackageReference Include="xunit" Version="2.4.2" />
17+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
18+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
19+
<PrivateAssets>all</PrivateAssets>
20+
</PackageReference>
21+
<PackageReference Include="coverlet.collector" Version="3.2.0">
22+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
23+
<PrivateAssets>all</PrivateAssets>
24+
</PackageReference>
25+
</ItemGroup>
26+
27+
<ItemGroup>
28+
<ProjectReference Include="..\..\src\Poolz.Finance.CSharp.Http\Poolz.Finance.CSharp.Http.csproj" />
29+
</ItemGroup>
30+
31+
</Project>

0 commit comments

Comments
 (0)