From 94232057aa700d8ad93e0758d880ef51905036bd Mon Sep 17 00:00:00 2001 From: RobinTTY Date: Sun, 18 Aug 2024 13:36:39 +0200 Subject: [PATCH] Prepare ability to check rate limits (#18) --- docs/release-notes/v10_0_0.md | 4 +- .../Endpoints/AccountsEndpoint.cs | 9 ++- .../Endpoints/InstitutionsEndpoint.cs | 3 +- .../Models/Responses/ApiRateLimits.cs | 60 +++++++++++++++++++ .../Models/Responses/NordigenApiResponse.cs | 34 +++++++++-- .../NordigenClient.cs | 9 +-- .../release-notes.txt | 2 +- 7 files changed, 105 insertions(+), 16 deletions(-) create mode 100644 src/RobinTTY.NordigenApiClient/Models/Responses/ApiRateLimits.cs diff --git a/docs/release-notes/v10_0_0.md b/docs/release-notes/v10_0_0.md index 83a0167..cea4c17 100644 --- a/docs/release-notes/v10_0_0.md +++ b/docs/release-notes/v10_0_0.md @@ -39,8 +39,8 @@ public async Task, BasicResponse>> GetInst The `TokenPairUpdated` event is now raised whenever the `JsonWebTokenPair` property is successfully updated. Not only when it was automatically updated but also when done so by the user. -**Full Changelog**: [v9.0.0...v10.0.0](https://github.com/RobinTTY/NordigenApiClient/compare/v9.0.0...v10.0.0) - ## Other improvements A full documentation of the library is now available at: [https://robintty.github.io/NordigenApiClient/](https://robintty.github.io/NordigenApiClient/) + +**Full Changelog**: [v9.0.0...v10.0.0](https://github.com/RobinTTY/NordigenApiClient/compare/v9.0.0...v10.0.0) diff --git a/src/RobinTTY.NordigenApiClient/Endpoints/AccountsEndpoint.cs b/src/RobinTTY.NordigenApiClient/Endpoints/AccountsEndpoint.cs index 4113822..82e6ec0 100644 --- a/src/RobinTTY.NordigenApiClient/Endpoints/AccountsEndpoint.cs +++ b/src/RobinTTY.NordigenApiClient/Endpoints/AccountsEndpoint.cs @@ -46,8 +46,9 @@ private async Task, AccountsError>> GetBalance { var response = await _nordigenClient.MakeRequest( $"{NordigenEndpointUrls.AccountsEndpoint}{accountId}/balances/", HttpMethod.Get, cancellationToken); + return new NordigenApiResponse, AccountsError>(response.StatusCode, response.IsSuccess, - response.Result?.Balances, response.Error); + response.Result?.Balances, response.Error, response.RateLimits); } /// @@ -65,8 +66,9 @@ private async Task> GetAc { var response = await _nordigenClient.MakeRequest( $"{NordigenEndpointUrls.AccountsEndpoint}{id}/details/", HttpMethod.Get, cancellationToken); + return new NordigenApiResponse(response.StatusCode, response.IsSuccess, - response.Result?.Account, response.Error); + response.Result?.Account, response.Error, response.RateLimits); } /// @@ -111,8 +113,9 @@ private async Task> GetT var response = await _nordigenClient.MakeRequest( $"{NordigenEndpointUrls.AccountsEndpoint}{id}/transactions/", HttpMethod.Get, cancellationToken, query); + return new NordigenApiResponse(response.StatusCode, response.IsSuccess, - response.Result?.Transactions, response.Error); + response.Result?.Transactions, response.Error, response.RateLimits); } #if NET6_0_OR_GREATER diff --git a/src/RobinTTY.NordigenApiClient/Endpoints/InstitutionsEndpoint.cs b/src/RobinTTY.NordigenApiClient/Endpoints/InstitutionsEndpoint.cs index c2aa573..9d624d0 100644 --- a/src/RobinTTY.NordigenApiClient/Endpoints/InstitutionsEndpoint.cs +++ b/src/RobinTTY.NordigenApiClient/Endpoints/InstitutionsEndpoint.cs @@ -60,8 +60,7 @@ public async Task, BasicResponse>> GetInst NordigenEndpointUrls.InstitutionsEndpoint, HttpMethod.Get, cancellationToken, query); return new NordigenApiResponse, BasicResponse>(response.StatusCode, response.IsSuccess, - response.Result, - response.Error); + response.Result, response.Error, response.RateLimits); } /// diff --git a/src/RobinTTY.NordigenApiClient/Models/Responses/ApiRateLimits.cs b/src/RobinTTY.NordigenApiClient/Models/Responses/ApiRateLimits.cs new file mode 100644 index 0000000..9f7f1b8 --- /dev/null +++ b/src/RobinTTY.NordigenApiClient/Models/Responses/ApiRateLimits.cs @@ -0,0 +1,60 @@ +using RobinTTY.NordigenApiClient.Endpoints; + +namespace RobinTTY.NordigenApiClient.Models.Responses; + +/// +/// The rate limits of the GoCardless API. +/// +public class ApiRateLimits +{ + /// + /// Indicates the maximum number of allowed requests within the defined time window. + /// + public int RequestLimit { get; set; } + /// + /// Indicates the number of remaining requests you can make in the current time window. + /// + public int RemainingRequests { get; set; } + /// + /// Indicates the time remaining in the current time window (in seconds). + /// + public int RemainingSecondsInTimeWindow { get; set; } + /// + /// Indicates the maximum number of allowed requests to the + /// within the defined time window. + /// + public int MaxAccountRequests { get; set; } + /// + /// Indicates the number of remaining requests to the + /// you can make in the current time window. + /// + public int RemainingAccountRequests { get; set; } + /// + /// Indicates the time remaining in the current time window (in seconds) for requests + /// to the . + /// + public int RemainingSecondsInAccountTimeWindow { get; set; } + + /// + /// Creates a new instance of . + /// + /// Indicates the maximum number of allowed requests within the defined time window. + /// Indicates the number of remaining requests you can make in the current time window. + /// Indicates the time remaining in the current time window (in seconds). + /// Indicates the maximum number of allowed requests to the + /// within the defined time window. + /// Indicates the number of remaining requests to the + /// you can make in the current time window. + /// Indicates the time remaining in the current time window (in seconds) for requests + /// to the . + public ApiRateLimits(int requestLimit, int remainingRequests, int remainingTimeInTimeWindow, int maxAccountRequests, + int remainingAccountRequests, int remainingTimeInAccountTimeWindow) + { + RequestLimit = requestLimit; + RemainingRequests = remainingRequests; + RemainingSecondsInTimeWindow = remainingTimeInTimeWindow; + MaxAccountRequests = maxAccountRequests; + RemainingAccountRequests = remainingAccountRequests; + RemainingSecondsInAccountTimeWindow = remainingTimeInAccountTimeWindow; + } +} diff --git a/src/RobinTTY.NordigenApiClient/Models/Responses/NordigenApiResponse.cs b/src/RobinTTY.NordigenApiClient/Models/Responses/NordigenApiResponse.cs index 1b23285..abf0ef4 100644 --- a/src/RobinTTY.NordigenApiClient/Models/Responses/NordigenApiResponse.cs +++ b/src/RobinTTY.NordigenApiClient/Models/Responses/NordigenApiResponse.cs @@ -34,6 +34,11 @@ public class NordigenApiResponse where TResult : class where TE /// The error returned by the API. Null if the HTTP response was successful. /// public TError? Error { get; } + + /// + /// The rate limits of the GoCardless API. + /// + public ApiRateLimits RateLimits { get; } /// /// Creates a new instance of . @@ -42,12 +47,15 @@ public class NordigenApiResponse where TResult : class where TE /// Indicates whether the HTTP response was successful. /// The result returned by the API. Null if the the HTTP response was not successful. /// The error returned by the API. Null if the HTTP response was successful. - public NordigenApiResponse(HttpStatusCode statusCode, bool isSuccess, TResult? result, TError? apiError) + /// The rate limits of the GoCardless API. + public NordigenApiResponse(HttpStatusCode statusCode, bool isSuccess, TResult? result, + TError? apiError, ApiRateLimits rateLimits) { StatusCode = statusCode; IsSuccess = isSuccess; Result = result; Error = apiError; + RateLimits = rateLimits; } /// @@ -60,25 +68,25 @@ public NordigenApiResponse(HttpStatusCode statusCode, bool isSuccess, TResult? r internal static async Task> FromHttpResponse(HttpResponseMessage response, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { + var rateLimits = GetRateLimits(response); #if NET6_0_OR_GREATER var responseJson = await response.Content.ReadAsStringAsync(cancellationToken); #else var responseJson = await response.Content.ReadAsStringAsync(); #endif - try { if (response.IsSuccessStatusCode) { var result = await response.Content.ReadFromJsonAsync(options, cancellationToken); return new NordigenApiResponse(response.StatusCode, response.IsSuccessStatusCode, - result, null); + result, null, rateLimits); } else { var result = await response.Content.ReadFromJsonAsync(options, cancellationToken); return new NordigenApiResponse(response.StatusCode, response.IsSuccessStatusCode, - null, result); + null, result, rateLimits); } } catch (JsonException ex) @@ -88,4 +96,22 @@ internal static async Task> FromHttpRespons $"The following JSON content caused the problem: {responseJson}", ex); } } + + private static ApiRateLimits GetRateLimits(HttpResponseMessage response) + { + response.Headers.TryGetValues("HTTP_X_RATELIMIT_LIMIT", out var requestLimitInTimeWindow); + response.Headers.TryGetValues("HTTP_X_RATELIMIT_REMAINING", out var remainingRequestsInTimeWindow); + response.Headers.TryGetValues("HTTP_X_RATELIMIT_RESET", out var remainingTimeInTimeWindow); + response.Headers.TryGetValues("HTTP_X_RATELIMIT_ACCOUNT_SUCCESS_LIMIT", out var maxAccountRequestsInTimeWindow); + response.Headers.TryGetValues("HTTP_X_RATELIMIT_ACCOUNT_SUCCESS_REMAINING", out var remainingAccountRequestsInTimeWindow); + response.Headers.TryGetValues("HTTP_X_RATELIMIT_ACCOUNT_SUCCESS_RESET", out var remainingTimeInAccountTimeWindow); + + return new ApiRateLimits( + requestLimitInTimeWindow != null ? int.Parse(requestLimitInTimeWindow.First()) : 0, + remainingRequestsInTimeWindow != null ? int.Parse(remainingRequestsInTimeWindow.First()) : 0, + remainingTimeInTimeWindow != null ? int.Parse(remainingTimeInTimeWindow.First()) : 0, + maxAccountRequestsInTimeWindow != null ? int.Parse(maxAccountRequestsInTimeWindow.First()) : 0, + remainingAccountRequestsInTimeWindow != null ? int.Parse(remainingAccountRequestsInTimeWindow.First()) : 0, + remainingTimeInAccountTimeWindow != null ? int.Parse(remainingTimeInAccountTimeWindow.First()) : 0); + } } diff --git a/src/RobinTTY.NordigenApiClient/NordigenClient.cs b/src/RobinTTY.NordigenApiClient/NordigenClient.cs index a3ee5a0..305492b 100644 --- a/src/RobinTTY.NordigenApiClient/NordigenClient.cs +++ b/src/RobinTTY.NordigenApiClient/NordigenClient.cs @@ -119,7 +119,8 @@ internal async Task> MakeRequest(tokenResponse.StatusCode, tokenResponse.IsSuccess, - null, new TError {Summary = tokenResponse.Error.Summary, Detail = tokenResponse.Error.Detail}); + null, new TError {Summary = tokenResponse.Error.Summary, Detail = tokenResponse.Error.Detail}, + tokenResponse.RateLimits); } finally { @@ -180,13 +181,13 @@ private async Task> TryGetV ? new JsonWebTokenPair(response.Result.AccessToken, JsonWebTokenPair.RefreshToken, response.Result!.AccessExpires, JsonWebTokenPair.RefreshExpires) : null; - + return new NordigenApiResponse(response.StatusCode, response.IsSuccess, - token, response.Error); + token, response.Error, response.RateLimits); } // Token pair is still valid and can be returned - wrap in NordigenApiResponse return new NordigenApiResponse(HttpStatusCode.OK, true, JsonWebTokenPair, - null); + null, new ApiRateLimits(-1, -1, -1, -1, 0, -1)); } } diff --git a/src/RobinTTY.NordigenApiClient/release-notes.txt b/src/RobinTTY.NordigenApiClient/release-notes.txt index cdc48b7..6711509 100644 --- a/src/RobinTTY.NordigenApiClient/release-notes.txt +++ b/src/RobinTTY.NordigenApiClient/release-notes.txt @@ -1,2 +1,2 @@ This version improves many aspects of the library. Since this version contains breaking changes please check the release notes before updating. -For the full release notes please see: https://github.com/RobinTTY/NordigenApiClient/releases/tag/v9.0.0 \ No newline at end of file +For the full release notes please see: https://github.com/RobinTTY/NordigenApiClient/releases/tag/v10.0.0 \ No newline at end of file