Skip to content

Commit

Permalink
Merge pull request #73 from gocardless/template-changes
Browse files Browse the repository at this point in the history
Changes from gocardless-dotnet-template
  • Loading branch information
jjholmes927 committed Aug 18, 2021
2 parents c8a596f + 2f95d88 commit 34d49e6
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 19 deletions.
68 changes: 64 additions & 4 deletions GoCardless.Tests/ErrorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,13 @@ public async Task InsufficentPermissions()
{
await MakeSomeRequest();
}
catch (InvalidApiUsageException ex)
catch (InsufficientPermissionsException ex)
{
TestHelpers.AssertResponseCanSerializeBackToFixture(ex.ApiErrorResponse, responseFixture);
Assert.AreEqual(ApiErrorType.INVALID_API_USAGE, ex.Type);
Assert.AreEqual(ApiErrorType.INSUFFICIENT_PERMISSIONS, ex.Type);
Assert.AreEqual("Insufficient permissions", ex.Message);
Assert.AreEqual("Insufficient permissions", ex.Errors.Single().Message);
Assert.AreEqual("insufficient_permissions", ex.Errors.Single().Reason);
Assert.AreEqual("insufficient_permissions", ex.Errors.Single().Reason);
Assert.AreEqual("https://developer.gocardless.com/api-reference#insufficient_permissions",
ex.DocumentationUrl);
Assert.AreEqual("b0e48853-abcd-41fa-9554-5f71820e915d", ex.RequestId);
Expand Down Expand Up @@ -133,5 +132,66 @@ public async Task IdempotencyConflictsAreHandledAutomatically()
Assert.AreEqual("BA000123", mandate.Mandate.Links.CustomerBankAccount);
}

[Test]
public async Task WhenAuthenticationError_ShouldShowSpecificError()
{
//Authentication Errors should throw a specific more contextual type rather than generic invalid_api_usage

//Arrange
var responseFixture = "fixtures/authentication_failure.json";
mockHttp.EnqueueResponse(401, responseFixture, resp => resp.Headers.Location = new Uri("/mandates/MD000126", UriKind.Relative));

//Act

try
{
await MakeSomeRequest();
}
catch (AuthenticationFailedException ex)
{
Assert.AreEqual(401, ex.Code);
Assert.AreEqual(ApiErrorType.AUTHENTICATION_FAILED, ex.Type);
Assert.AreEqual("Authentication Failed", ex.Message);
Assert.AreEqual("Authentication Failed", ex.Errors.Single().Message);
Assert.AreEqual("authentication_failed", ex.Errors.Single().Reason);
TestHelpers.AssertResponseCanSerializeBackToFixture(ex.ApiErrorResponse, responseFixture);
Assert.AreEqual("https://developer.gocardless.com/api-reference/#api-usage-errors",
ex.DocumentationUrl);
return;
}

//Assert
Assert.Fail("Exception was not thrown");
}

[Test]
public async Task WhenRateLimitReachedException_ShouldShowSpecificError()
{
//Rate Limit being reached errors should throw a specific more contextual type rather than generic
//Arrange
var responseFixture = "fixtures/rate_limit_reached.json";
mockHttp.EnqueueResponse(429, responseFixture, resp => resp.Headers.Location = new Uri("/mandates/MD000126", UriKind.Relative));

//Act
try
{
await MakeSomeRequest();
}
catch (RateLimitReachedException ex)
{
Assert.AreEqual(429, ex.Code);
Assert.AreEqual(ApiErrorType.RATE_LIMIT_REACHED, ex.Type);
Assert.AreEqual("Rate Limit Reached you have made too many requests", ex.Message);
Assert.AreEqual("Rate Limit Reached you have made too many requests", ex.Errors.Single().Message);
Assert.AreEqual("rate_limit_reached", ex.Errors.Single().Reason);
TestHelpers.AssertResponseCanSerializeBackToFixture(ex.ApiErrorResponse, responseFixture);
Assert.AreEqual("https://developer.gocardless.com/api-reference/#making-requests-rate-limiting",
ex.DocumentationUrl);
return;
}

//Assert
Assert.Fail("Exception was not thrown");
}
}
}
}
6 changes: 6 additions & 0 deletions GoCardless.Tests/GoCardless.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@
<None Update="fixtures\mock_response.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="fixtures\authentication_failure.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="fixtures\rate_limit_reached.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
15 changes: 15 additions & 0 deletions GoCardless.Tests/fixtures/authentication_failure.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"error": {
"message": "Authentication Failed",
"errors": [
{
"reason": "authentication_failed",
"message": "Authentication Failed"
}
],
"documentation_url": "https://developer.gocardless.com/api-reference/#api-usage-errors",
"type": "authentication_failed",
"request_id": "b0e48853-abcd-41fa-9554-5f71820e915d",
"code": 401
}
}
2 changes: 1 addition & 1 deletion GoCardless.Tests/fixtures/insufficient_permissions.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
}
],
"documentation_url": "https://developer.gocardless.com/api-reference#insufficient_permissions",
"type": "invalid_api_usage",
"type": "insufficient_permissions",
"request_id": "b0e48853-abcd-41fa-9554-5f71820e915d",
"code": 403
}
Expand Down
15 changes: 15 additions & 0 deletions GoCardless.Tests/fixtures/rate_limit_reached.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"error": {
"message": "Rate Limit Reached you have made too many requests",
"errors": [
{
"reason": "rate_limit_reached",
"message": "Rate Limit Reached you have made too many requests"
}
],
"documentation_url": "https://developer.gocardless.com/api-reference/#making-requests-rate-limiting",
"type": "rate_limit_reached",
"request_id": "b0e48853-abcd-41fa-9554-5f71820e915d",
"code": 429
}
}
30 changes: 23 additions & 7 deletions GoCardless/Errors/ApiErrorType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ namespace GoCardless.Errors
/// Types of error that can be returned by the API.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum ApiErrorType {
public enum ApiErrorType
{
/// <summary>
/// An error related to an inability to authenticate successfully
/// </summary>
[EnumMember(Value = "authentication_failed")]
AUTHENTICATION_FAILED,
/// <summary>
/// An internal error occurred while processing your request. This should be
/// reported to our support team with the id, so we can resolve the issue.
Expand All @@ -17,10 +23,9 @@ public enum ApiErrorType {
GOCARDLESS,
/// <summary>
/// This is an error with the request you made. It could be an invalid URL, the
/// authentication header could be missing, invalid, or grant insufficient
/// permissions, you may have reached your rate limit, or the syntax of your
/// request could be incorrect. The errors will give more detail of the specific
/// issue.
/// authentication header could be missing, invalid, you may have reached your rate limit,
/// or the syntax of your request could be incorrect.
/// The errors will give more detail of the specific issue.
/// </summary>
[EnumMember(Value = "invalid_api_usage")]
INVALID_API_USAGE,
Expand All @@ -36,6 +41,17 @@ public enum ApiErrorType {
/// fields were invalid and why are included in the response.
/// </summary>
[EnumMember(Value = "validation_failed")]
VALIDATION_FAILED
VALIDATION_FAILED,
/// <summary>
/// This is an error with the request you made. The permissions you have are insufficient
/// for this type of request.
/// </summary>
[EnumMember(Value = "insufficient_permissions")]
INSUFFICIENT_PERMISSIONS,
/// <summary>
/// This error indicates that your rate limit has been reached.
/// </summary>
[EnumMember(Value = "rate_limit_reached")]
RATE_LIMIT_REACHED,
}
}
}
18 changes: 18 additions & 0 deletions GoCardless/Exceptions/AuthenticationFailedException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GoCardless.Errors;

namespace GoCardless.Exceptions
{
public class AuthenticationFailedException : InvalidApiUsageException
{
/// <summary>
///Exception thrown when you have failed the authentication check
/// </summary>
internal AuthenticationFailedException(ApiErrorResponse apiErrorResponse) :base(apiErrorResponse)
{
}
public new IReadOnlyList<Error.IError> Errors => base.Errors.Cast<Error.IError>().ToList().AsReadOnly();
}
}
8 changes: 7 additions & 1 deletion GoCardless/Exceptions/ErrorMapperExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public static ApiException ToException(this ApiErrorResponse apiErrorResponse)
{
switch (apiErrorResponse.Error.Type)
{
case ApiErrorType.AUTHENTICATION_FAILED:
return new AuthenticationFailedException(apiErrorResponse);
case ApiErrorType.GOCARDLESS:
return new InternalException(apiErrorResponse);
case ApiErrorType.INVALID_API_USAGE:
Expand All @@ -25,10 +27,14 @@ public static ApiException ToException(this ApiErrorResponse apiErrorResponse)
return new InvalidStateException(apiErrorResponse);
case ApiErrorType.VALIDATION_FAILED:
return new ValidationFailedException(apiErrorResponse);
case ApiErrorType.INSUFFICIENT_PERMISSIONS:
return new InsufficientPermissionsException(apiErrorResponse);
case ApiErrorType.RATE_LIMIT_REACHED:
return new RateLimitReachedException(apiErrorResponse);
default:
throw new InvalidOperationException($"Unknown ApiErrorType {apiErrorResponse}");

}
}
}
}
}
19 changes: 19 additions & 0 deletions GoCardless/Exceptions/InsufficientPermissionsException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Linq;
using GoCardless.Errors;

namespace GoCardless.Exceptions
{
public class InsufficientPermissionsException : InvalidApiUsageException
{
/// <summary>
///Specific Exception thrown when the requestor has a lack of permission for the desired action
/// </summary>
internal InsufficientPermissionsException(ApiErrorResponse apiErrorResponse) : base(apiErrorResponse)
{
}

public new IReadOnlyList<Error.IError> Errors => base.Errors.Cast<Error.IError>().ToList().AsReadOnly();
}
}

19 changes: 19 additions & 0 deletions GoCardless/Exceptions/RateLimitReachedException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GoCardless.Errors;

namespace GoCardless.Exceptions
{
public class RateLimitReachedException : InvalidApiUsageException
{
/// <summary>
///Exception thrown when you have reached the rate limit for the number of requests you can make
///Currently the default rate limit is sent to 1000 requests per minute per integrator
/// </summary>
internal RateLimitReachedException(ApiErrorResponse apiErrorResponse) :base(apiErrorResponse)
{
}
public new IReadOnlyList<Error.IError> Errors => base.Errors.Cast<Error.IError>().ToList().AsReadOnly();
}
}
15 changes: 13 additions & 2 deletions GoCardless/GoCardless.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<PackageId>GoCardless</PackageId>
<PackageVersion>4.10.0</PackageVersion>
<PackageVersion>5.0.0</PackageVersion>
<Authors>GoCardless Ltd</Authors>
<Description>Client for the GoCardless API - a powerful, simple solution for the collection of recurring bank-to-bank payments</Description>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
Expand All @@ -11,13 +11,24 @@
<Copyright>GoCardless Ltd</Copyright>
<PackageTags>gocardless payments rest api direct debit</PackageTags>
<PackageLicenseUrl>https://github.com/gocardless/gocardless-dotnet/blob/master/LICENSE.txt</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/gocardless/gocardless-dotnet/releases/tag/v4.10.0</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/gocardless/gocardless-dotnet/releases/tag/v5.0.0</PackageReleaseNotes>
<TargetFrameworks>netstandard1.6;netstandard2.0;net46</TargetFrameworks>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\GoCardless.xml</DocumentationFile>
</PropertyGroup>


<!--Constants-->
<PropertyGroup Condition=" $(TargetFramework.StartsWith('netstandard')) ">
<DefineConstants>NETSTANDARD</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition=" $(TargetFramework.StartsWith('net46')) ">
<DefineConstants>NET46</DefineConstants>
</PropertyGroup>


<Target Name="PrepublishScript" BeforeTargets="PrepareForPublish">
<ItemGroup>
<DocFile Include="bin\$(Configuration)\$(TargetFramework)\*.xml" />
Expand Down
39 changes: 37 additions & 2 deletions GoCardless/GoCardlessClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using GoCardless.Errors;
Expand Down Expand Up @@ -212,6 +213,18 @@ private static string GetBaseUrl(Environment env)
var result = JsonConvert.DeserializeObject<ApiErrorResponse>(json, new JsonSerializerSettings());
result.ResponseMessage = responseMessage;

switch (result.Error.Code)
{
case 401:
result.Error.Type = ApiErrorType.AUTHENTICATION_FAILED;
break;
case 403:
result.Error.Type = ApiErrorType.INSUFFICIENT_PERMISSIONS;
break;
case 429:
result.Error.Type = ApiErrorType.RATE_LIMIT_REACHED;
break;
}
throw result.ToException();
}
}
Expand Down Expand Up @@ -252,9 +265,23 @@ private static string GetBaseUrl(Environment env)
var httpMethod = new HttpMethod(method);

var requestMessage = new HttpRequestMessage(httpMethod, new Uri(_baseUrl, path));
requestMessage.Headers.Add("User-Agent", "gocardless-dotnet/4.10.0");
var OSRunningOn = "";
var runtimeFrameworkInformation = "";

#if NETSTANDARD
OSRunningOn = System.Runtime.InteropServices.RuntimeInformation.OSDescription;
runtimeFrameworkInformation = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription;
#endif
#if NET46
OSRunningOn = System.Environment.OSVersion.VersionString;
runtimeFrameworkInformation = System.Runtime.InteropServices.RuntimeEnvironment.GetSystemVersion();
#endif

var userAgentInformation = $" gocardless-dotnet/5.0.0 {runtimeFrameworkInformation} {Helpers.CleanupOSDescriptionString(OSRunningOn)}";

requestMessage.Headers.Add("User-Agent", userAgentInformation);
requestMessage.Headers.Add("GoCardless-Version", "2015-07-06");
requestMessage.Headers.Add("GoCardless-Client-Version", "4.10.0");
requestMessage.Headers.Add("GoCardless-Client-Version", "5.0.0");
requestMessage.Headers.Add("GoCardless-Client-Library", "gocardless-dotnet");
requestMessage.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _accessToken);
Expand Down Expand Up @@ -370,6 +397,14 @@ internal static string QueryStringArgument(KeyValuePair<string, object> argument
var urlEncodedKey = WebUtility.UrlEncode(argument.Key);
return $"{urlEncodedKey}={urlEncodedValue}";
}

// Necessary due to a bug in User Agent header parser when multiple params passed
// fixed in later versions of .NET but required until a later date when we upgrade
internal static string CleanupOSDescriptionString(string input)
{
Regex pattern = new Regex("[;:#()~]");
return pattern.Replace(input, "-");
}
}
}
}
2 changes: 1 addition & 1 deletion GoCardless/Services/BillingRequestTemplateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public Task<BillingRequestTemplateResponse> UpdateAsync(string identity, Billing
new KeyValuePair<string, object>("identity", identity),
};

return _goCardlessClient.ExecuteAsync<BillingRequestTemplateResponse>("PUT", "/billing_requests/:identity", urlParams, request, null, "billing_request_templates", customiseRequestMessage);
return _goCardlessClient.ExecuteAsync<BillingRequestTemplateResponse>("PUT", "/billing_request_templates/:identity", urlParams, request, null, "billing_request_templates", customiseRequestMessage);
}
}

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ For full details of the GoCardless API, see the [API docs](https://developer.goc

To install `GoCardless`, run the following command in the [Package Manager Console](https://docs.microsoft.com/en-us/nuget/tools/package-manager-console)

`Install-Package GoCardless -Version 4.10.0`
`Install-Package GoCardless -Version 5.0.0`


## Usage
Expand Down

0 comments on commit 34d49e6

Please sign in to comment.