Skip to content

Commit 7e0887f

Browse files
chore: introduced base URI class and validated that the request goes to a TL address or to the configured ones or to localhost
1 parent 31561cb commit 7e0887f

13 files changed

Lines changed: 246 additions & 59 deletions

File tree

src/TrueLayer/ApiClient.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
using System.Net.Mime;
1010
using TrueLayer.Serialization;
1111
using System.Text.Json;
12+
using Microsoft.Extensions.Options;
13+
using TrueLayer.Common;
1214
using TrueLayer.Signing;
1315
#if NET6_0 || NET6_0_OR_GREATER
1416
using System.Net.Http.Json;
@@ -25,20 +27,23 @@ private static readonly String TlAgentHeader
2527
= $"truelayer-dotnet/{ReflectionUtils.GetAssemblyVersion<ITrueLayerClient>()}";
2628

2729
private readonly HttpClient _httpClient;
30+
private readonly TrueLayerOptions _options;
2831

2932
/// <summary>
3033
/// Creates a new <see cref="ApiClient"/> instance with the provided configuration, HTTP client factory and serializer.
3134
/// </summary>
3235
/// <param name="httpClient">The client used to make HTTP requests.</param>
33-
public ApiClient(HttpClient httpClient)
36+
/// <param name="options"></param>
37+
public ApiClient(HttpClient httpClient, IOptions<TrueLayerOptions> options)
3438
{
3539
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
40+
_options = options.Value ?? throw new ArgumentNullException(nameof(options));
3641
}
3742

3843
/// <inheritdoc />
3944
public async Task<ApiResponse<TData>> GetAsync<TData>(Uri uri, string? accessToken = null, CancellationToken cancellationToken = default)
4045
{
41-
if (uri is null) throw new ArgumentNullException(nameof(uri));
46+
uri.HasValidBaseUri(nameof(uri), _options);
4247

4348
using var httpResponse = await SendRequestAsync(
4449
httpMethod: HttpMethod.Get,
@@ -56,7 +61,7 @@ public async Task<ApiResponse<TData>> GetAsync<TData>(Uri uri, string? accessTok
5661
/// <inheritdoc />
5762
public async Task<ApiResponse<TData>> PostAsync<TData>(Uri uri, HttpContent? httpContent = null, string? accessToken = null, CancellationToken cancellationToken = default)
5863
{
59-
if (uri is null) throw new ArgumentNullException(nameof(uri));
64+
uri.HasValidBaseUri(nameof(uri), _options);
6065

6166
using var httpResponse = await SendRequestAsync(
6267
httpMethod: HttpMethod.Post,
@@ -74,7 +79,7 @@ public async Task<ApiResponse<TData>> PostAsync<TData>(Uri uri, HttpContent? htt
7479
/// <inheritdoc />
7580
public async Task<ApiResponse<TData>> PostAsync<TData>(Uri uri, object? request = null, string? idempotencyKey = null, string? accessToken = null, SigningKey? signingKey = null, CancellationToken cancellationToken = default)
7681
{
77-
if (uri is null) throw new ArgumentNullException(nameof(uri));
82+
uri.HasValidBaseUri(nameof(uri), _options);
7883

7984
using var httpResponse = await SendJsonRequestAsync(
8085
httpMethod: HttpMethod.Post,
@@ -91,7 +96,7 @@ public async Task<ApiResponse<TData>> PostAsync<TData>(Uri uri, object? request
9196

9297
public async Task<ApiResponse> PostAsync(Uri uri, HttpContent? httpContent = null, string? accessToken = null, CancellationToken cancellationToken = default)
9398
{
94-
if (uri is null) throw new ArgumentNullException(nameof(uri));
99+
uri.HasValidBaseUri(nameof(uri), _options);
95100

96101
using var httpResponse = await SendRequestAsync(
97102
httpMethod: HttpMethod.Post,
@@ -108,7 +113,7 @@ public async Task<ApiResponse> PostAsync(Uri uri, HttpContent? httpContent = nul
108113

109114
public async Task<ApiResponse> PostAsync(Uri uri, object? request = null, string? idempotencyKey = null, string? accessToken = null, SigningKey? signingKey = null, CancellationToken cancellationToken = default)
110115
{
111-
if (uri is null) throw new ArgumentNullException(nameof(uri));
116+
uri.HasValidBaseUri(nameof(uri), _options);
112117

113118
using var httpResponse = await SendJsonRequestAsync(
114119
httpMethod: HttpMethod.Post,

src/TrueLayer/Auth/AuthApi.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@
33
using System.Net.Http;
44
using System.Threading;
55
using System.Threading.Tasks;
6+
using TrueLayer.Common;
67
using TrueLayer.Extensions;
78

89
namespace TrueLayer.Auth
910
{
1011
internal class AuthApi : IAuthApi
1112
{
12-
internal const string ProdUrl = "https://auth.truelayer.com/";
13-
internal const string SandboxUrl = "https://auth.truelayer-sandbox.com/";
14-
1513
private readonly IApiClient _apiClient;
1614
private readonly TrueLayerOptions _options;
1715
private readonly Uri _baseUri;
@@ -21,8 +19,11 @@ public AuthApi(IApiClient apiClient, TrueLayerOptions options)
2119
_apiClient = apiClient.NotNull(nameof(apiClient));
2220
_options = options.NotNull(nameof(options));
2321

24-
_baseUri = options.Auth?.Uri ??
25-
new Uri((options.UseSandbox ?? true) ? SandboxUrl : ProdUrl);
22+
var baseUri = (options.UseSandbox ?? true)
23+
? TrueLayerBaseUris.SandboxAuthBaseUri
24+
: TrueLayerBaseUris.ProdAuthBaseUri;
25+
26+
_baseUri = options.Auth?.Uri ?? baseUri;
2627
}
2728

2829
/// <inheritdoc />
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
3+
namespace TrueLayer.Common;
4+
5+
internal static class TrueLayerBaseUris
6+
{
7+
internal static readonly Uri ProdApiBaseUri = new("https://api.truelayer.com/");
8+
internal static readonly Uri SandboxApiBaseUri = new("https://api.truelayer-sandbox.com/");
9+
internal static readonly Uri ProdAuthBaseUri = new("https://auth.truelayer.com/");
10+
internal static readonly Uri SandboxAuthBaseUri = new("https://auth.truelayer-sandbox.com/");
11+
internal static readonly Uri ProdHppBaseUri = new("https://payment.truelayer.com/");
12+
internal static readonly Uri SandboxHppBaseUri = new("https://payment.truelayer-sandbox.com/");
13+
}

src/TrueLayer/Guard.cs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
using System;
22
using System.Diagnostics;
33
using System.Diagnostics.CodeAnalysis;
4-
using System.Linq;
5-
using System.Text.RegularExpressions;
4+
using TrueLayer.Common;
65

76
namespace TrueLayer
87
{
@@ -112,5 +111,69 @@ public static T GreaterThan<T>([NotNull] this T value, T greaterThan, string par
112111
|| value.Contains('.'))
113112
? throw new ArgumentException("Value is malformed", name)
114113
: value;
114+
115+
/// <summary>
116+
/// Validate that the provided URI one of the configured (from the options) URIs as base address, or one of the TrueLayer ones based on the environment used.
117+
/// </summary>
118+
/// <param name="value">The value to validate</param>
119+
/// <param name="name">The name of the argument</param>
120+
/// <param name="options">The <see cref="TrueLayerOptions"/> that contain the custom configured URIs</param>
121+
/// <returns>The value of <paramref name="value"/> if it is valid</returns>
122+
/// <exception cref="ArgumentException">Thrown when the value is not valid</exception>
123+
/// <example>
124+
/// <code>
125+
/// _uri = uri.HasValidBaseUri(nameof(_uri), options);
126+
/// </code>
127+
/// </example>
128+
internal static Uri? HasValidBaseUri(this Uri? value, string name, TrueLayerOptions options)
129+
{
130+
value.NotNull(name);
131+
const string errorMsg = "The URI must be a valid TrueLayer API URI one of those configured in the settings.";
132+
bool result = value.IsLoopback // is localhost?
133+
|| ((options.Payments?.Uri is not null) && options.Payments!.Uri.IsBaseOf(value))
134+
|| ((options.Auth?.Uri is not null) && options.Auth!.Uri.IsBaseOf(value))
135+
|| ((options.Payments?.HppUri is not null) && options.Payments!.HppUri.IsBaseOf(value));
136+
137+
if (options.UseSandbox == true)
138+
{
139+
result = result
140+
|| TrueLayerBaseUris.SandboxAuthBaseUri.IsBaseOf(value)
141+
|| TrueLayerBaseUris.SandboxApiBaseUri.IsBaseOf(value)
142+
|| TrueLayerBaseUris.SandboxHppBaseUri.IsBaseOf(value);
143+
}
144+
else
145+
{
146+
result = result
147+
|| TrueLayerBaseUris.ProdAuthBaseUri.IsBaseOf(value)
148+
|| TrueLayerBaseUris.ProdApiBaseUri.IsBaseOf(value)
149+
|| TrueLayerBaseUris.ProdHppBaseUri.IsBaseOf(value);
150+
}
151+
152+
result.ThrowIfFalse(name, errorMsg);
153+
return value;
154+
}
155+
156+
/// <summary>
157+
/// Validate that the provided value is not false
158+
/// </summary>
159+
/// <param name="value">The value to validate</param>
160+
/// <param name="name">The name of the argument</param>
161+
/// <param name="message">The message that needs to be assigned to the exception</param>
162+
/// <returns>The value of <paramref name="value"/> if not false</returns>
163+
/// <exception cref="ArgumentException">Thrown when the value is false</exception>
164+
/// <example>
165+
/// <code>
166+
/// _value = value.ThrowIfFalse(nameof(_value), "The value cannot be false");
167+
/// </code>
168+
/// </example>
169+
private static bool ThrowIfFalse(this bool value, string name, string message)
170+
{
171+
if (!value)
172+
{
173+
throw new ArgumentException(message, name);
174+
}
175+
176+
return value;
177+
}
115178
}
116179
}

src/TrueLayer/Mandates/MandatesApi.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,25 @@
33
using System.Threading.Tasks;
44
using TrueLayer.Auth;
55
using OneOf;
6+
using TrueLayer.Common;
67
using TrueLayer.Extensions;
8+
using TrueLayer.Mandates.Model;
9+
using TrueLayer.Models;
710

811
namespace TrueLayer.Mandates
912
{
10-
using TrueLayer.Mandates.Model;
11-
using TrueLayer.Models;
1213
using AuthorizationResponseUnion = OneOf<
13-
Models.AuthorisationFlowResponse.AuthorizationFlowAuthorizing,
14-
Models.AuthorisationFlowResponse.AuthorizationFlowAuthorizationFailed>;
14+
AuthorisationFlowResponse.AuthorizationFlowAuthorizing,
15+
AuthorisationFlowResponse.AuthorizationFlowAuthorizationFailed>;
1516
using MandateDetailUnion = OneOf<
16-
Model.MandateDetail.AuthorizationRequiredMandateDetail,
17-
Model.MandateDetail.AuthorizingMandateDetail,
18-
Model.MandateDetail.AuthorizedMandateDetail,
19-
Model.MandateDetail.FailedMandateDetail,
20-
Model.MandateDetail.RevokedMandateDetail>;
17+
MandateDetail.AuthorizationRequiredMandateDetail,
18+
MandateDetail.AuthorizingMandateDetail,
19+
MandateDetail.AuthorizedMandateDetail,
20+
MandateDetail.FailedMandateDetail,
21+
MandateDetail.RevokedMandateDetail>;
2122

2223
internal class MandatesApi : IMandatesApi
2324
{
24-
private const string ProdUrl = "https://api.truelayer.com/v3/mandates/";
25-
private const string SandboxUrl = "https://api.truelayer-sandbox.com/v3/mandates/";
26-
2725
private readonly IApiClient _apiClient;
2826
private readonly TrueLayerOptions _options;
2927
private readonly Uri _baseUri;
@@ -37,10 +35,12 @@ public MandatesApi(IApiClient apiClient, IAuthApi auth, TrueLayerOptions options
3735

3836
options.Payments.NotNull(nameof(options.Payments))!.Validate();
3937

40-
var baseUri = (options.UseSandbox ?? true) ? SandboxUrl : ProdUrl;
41-
_baseUri = options.Payments.Uri is not null
42-
? new Uri(options.Payments.Uri, "/v3/mandates/")
43-
: new Uri(baseUri);
38+
var baseUri = (options.UseSandbox ?? true)
39+
? TrueLayerBaseUris.SandboxApiBaseUri
40+
: TrueLayerBaseUris.ProdApiBaseUri;
41+
42+
_baseUri = (options.Payments.Uri ?? baseUri)
43+
.Append("/v3/mandates/");
4444
}
4545

4646
/// <inheritdoc />

src/TrueLayer/MerchantAccounts/MerchantAccountsApi.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22
using System.Threading;
33
using System.Threading.Tasks;
44
using TrueLayer.Auth;
5+
using TrueLayer.Common;
56
using TrueLayer.Extensions;
67
using TrueLayer.MerchantAccounts.Model;
78

89
namespace TrueLayer.MerchantAccounts
910
{
1011
internal class MerchantAccountsApi : IMerchantAccountsApi
1112
{
12-
private const string ProdUrl = "https://api.truelayer.com/v3/merchant-accounts";
13-
private const string SandboxUrl = "https://api.truelayer-sandbox.com/v3/merchant-accounts";
1413
private readonly IApiClient _apiClient;
1514
private readonly Uri _baseUri;
1615
private readonly IAuthApi _auth;
@@ -22,9 +21,12 @@ public MerchantAccountsApi(IApiClient apiClient, IAuthApi auth, TrueLayerOptions
2221

2322
options.Payments.NotNull(nameof(options.Payments))!.Validate();
2423

25-
_baseUri = options.Payments.Uri is not null
26-
? new Uri(options.Payments.Uri, "/v3/merchant-accounts")
27-
: new Uri(options.UseSandbox ?? true ? SandboxUrl : ProdUrl);
24+
var baseUri = (options.UseSandbox ?? true)
25+
? TrueLayerBaseUris.SandboxApiBaseUri
26+
: TrueLayerBaseUris.ProdApiBaseUri;
27+
28+
_baseUri = (options.Payments.Uri ?? baseUri)
29+
.Append("/v3/merchant-accounts");
2830
}
2931

3032
/// <inheritdoc />

src/TrueLayer/Payments/PaymentsApi.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Threading.Tasks;
44
using OneOf;
55
using TrueLayer.Auth;
6+
using TrueLayer.Common;
67
using TrueLayer.Extensions;
78
using TrueLayer.Payments.Model;
89

@@ -25,9 +26,6 @@ namespace TrueLayer.Payments
2526

2627
internal class PaymentsApi : IPaymentsApi
2728
{
28-
private const string ProdUrl = "https://api.truelayer.com/v3/payments/";
29-
private const string SandboxUrl = "https://api.truelayer-sandbox.com/v3/payments/";
30-
3129
private readonly IApiClient _apiClient;
3230
private readonly TrueLayerOptions _options;
3331
private readonly Uri _baseUri;
@@ -43,10 +41,12 @@ public PaymentsApi(IApiClient apiClient, IAuthApi auth, TrueLayerOptions options
4341

4442
options.Payments.NotNull(nameof(options.Payments))!.Validate();
4543

46-
var baseUri = (options.UseSandbox ?? true) ? SandboxUrl : ProdUrl;
47-
_baseUri = options.Payments.Uri is not null
48-
? new Uri(options.Payments.Uri, "/v3/payments/")
49-
: new Uri(baseUri);
44+
var baseUri = (options.UseSandbox ?? true)
45+
? TrueLayerBaseUris.SandboxApiBaseUri
46+
: TrueLayerBaseUris.ProdApiBaseUri;
47+
48+
_baseUri = (options.Payments.Uri ?? baseUri)
49+
.Append("/v3/payments/");
5050
}
5151

5252
/// <inheritdoc />

src/TrueLayer/PaymentsProviders/PaymentsApi.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
using System;
22
using System.Threading.Tasks;
3+
using TrueLayer.Common;
34
using TrueLayer.Extensions;
45
using TrueLayer.PaymentsProviders.Model;
56

67
namespace TrueLayer.PaymentsProviders
78
{
89
internal class PaymentsProvidersApi : IPaymentsProvidersApi
910
{
10-
private const string ProdUrl = "https://api.truelayer.com/v3/payments-providers/";
11-
private const string SandboxUrl = "https://api.truelayer-sandbox.com/v3/payments-providers/";
12-
1311
private readonly IApiClient _apiClient;
1412
private readonly TrueLayerOptions _options;
1513
private readonly Uri _baseUri;
@@ -21,9 +19,12 @@ public PaymentsProvidersApi(IApiClient apiClient, TrueLayerOptions options)
2119

2220
options.Payments.NotNull(nameof(options.Payments))!.Validate();
2321

24-
_baseUri = options.Payments.Uri is not null
25-
? new Uri(options.Payments.Uri, "/v3/payments-providers/")
26-
: new Uri((options.UseSandbox ?? true) ? SandboxUrl : ProdUrl);
22+
var baseUri = (options.UseSandbox ?? true)
23+
? TrueLayerBaseUris.SandboxApiBaseUri
24+
: TrueLayerBaseUris.ProdApiBaseUri;
25+
26+
_baseUri = (options.Payments.Uri ?? baseUri)
27+
.Append("/v3/payments-providers/");
2728
}
2829

2930
public async Task<ApiResponse<PaymentsProvider>> GetPaymentsProvider(string id)

src/TrueLayer/Payouts/PayoutsApi.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Threading.Tasks;
44
using OneOf;
55
using TrueLayer.Auth;
6+
using TrueLayer.Common;
67
using TrueLayer.Extensions;
78
using TrueLayer.Payouts.Model;
89
using static TrueLayer.Payouts.Model.GetPayoutsResponse;
@@ -18,9 +19,6 @@ namespace TrueLayer.Payouts
1819

1920
internal class PayoutsApi : IPayoutsApi
2021
{
21-
private const string ProdUrl = "https://api.truelayer.com/v3/payouts";
22-
private const string SandboxUrl = "https://api.truelayer-sandbox.com/v3/payouts";
23-
2422
private readonly IApiClient _apiClient;
2523
private readonly TrueLayerOptions _options;
2624
private readonly Uri _baseUri;
@@ -34,10 +32,12 @@ public PayoutsApi(IApiClient apiClient, IAuthApi auth, TrueLayerOptions options)
3432

3533
options.Payments.NotNull(nameof(options.Payments))!.Validate();
3634

37-
string payoutsApiUrl = (options.UseSandbox ?? true) ? SandboxUrl : ProdUrl;
38-
_baseUri = options.Payments.Uri is not null
39-
? new Uri(options.Payments.Uri, "/v3/payouts")
40-
: new Uri(payoutsApiUrl);
35+
var baseUri = (options.UseSandbox ?? true)
36+
? TrueLayerBaseUris.SandboxApiBaseUri
37+
: TrueLayerBaseUris.ProdApiBaseUri;
38+
39+
_baseUri = (options.Payments.Uri ?? baseUri)
40+
.Append("/v3/payouts/");
4141
}
4242

4343
/// <inheritdoc />

src/TrueLayer/TrueLayerServiceCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static IServiceCollection AddTrueLayer(
3939
configureBuilder?.Invoke(httpClientBuilder);
4040

4141
services.AddTransient<ITrueLayerClient, TrueLayerClient>();
42-
42+
4343
return services;
4444
}
4545
}

0 commit comments

Comments
 (0)