Skip to content

Commit

Permalink
Merge branch 'release/1.1.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
belidzs committed Feb 15, 2020
2 parents 6748357 + f988c47 commit c82cec1
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 38 deletions.
48 changes: 48 additions & 0 deletions MailgunAddressValidator/AsyncHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// <copyright file="AsyncHelper.cs" company="Balázs Keresztury">
// Copyright (c) Balázs Keresztury. All rights reserved.
// </copyright>
// Source: https://cpratt.co/async-tips-tricks/

using System;
using System.Threading;
using System.Threading.Tasks;

namespace MailgunAddressValidator
{
/// <summary>
/// Helper class to wrap an async delegate into a syncronous method.
/// </summary>
public static class AsyncHelper
{
private static readonly TaskFactory _taskFactory = new
TaskFactory(
CancellationToken.None,
TaskCreationOptions.None,
TaskContinuationOptions.None,
TaskScheduler.Default);

/// <summary>
/// Wraps a generic async delegate into a syncronous method.
/// </summary>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="func">The asyncronous delegate.</param>
/// <returns>The result.</returns>
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
=> _taskFactory
.StartNew(func)
.Unwrap()
.GetAwaiter()
.GetResult();

/// <summary>
/// Wraps an async delegate into a syncronous method.
/// </summary>
/// <param name="func">The asyncronous delegate.</param>
public static void RunSync(Func<Task> func)
=> _taskFactory
.StartNew(func)
.Unwrap()
.GetAwaiter()
.GetResult();
}
}
13 changes: 10 additions & 3 deletions MailgunAddressValidator/MailgunAddressValidator.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.1;netstandard2.0</TargetFrameworks>
<Version>1.1.1</Version>
<Version>1.1.2</Version>
<Authors>Balázs Keresztury</Authors>
<Company></Company>
<Description>This asynchronous library checks email addresses against Mailgun's e-mail validation service</Description>
Expand All @@ -14,8 +14,11 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DocumentationFile></DocumentationFile>
<NoWarn>1701;1702;SA0001</NoWarn>
<DocumentationFile>MailgunAddressValidator.xml</DocumentationFile>
<NoWarn>1701;1702</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>MailgunAddressValidator.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<None Remove="stylecop.json" />
Expand All @@ -24,6 +27,10 @@
<AdditionalFiles Include="stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="4.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
Expand Down
105 changes: 105 additions & 0 deletions MailgunAddressValidator/MailgunAddressValidator.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 15 additions & 27 deletions MailgunAddressValidator/Validator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace MailgunAddressValidator
public static class Validator
{
private const string MailgunBaseUrl = "https://api.mailgun.net";
private const string ValidatorResource = "/v3/address/validate";
private const string MailgunValidatorResource = "/v3/address/validate";
private static readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings()
{
ContractResolver = new DefaultContractResolver()
Expand All @@ -28,7 +28,7 @@ public static class Validator
};

/// <summary>
/// Validates an e-mail address in a syncronous fashion.
/// Validates an e-mail address.
/// </summary>
/// <param name="email">E-mail address to validate.</param>
/// <param name="apikey">Mailgun e-mail validation API key.</param>
Expand All @@ -38,25 +38,19 @@ public static ValidationResult Validate(string email, string apikey, int timeout
{
try
{
var response = GetClient(apikey, timeout).GetAsync(GetUri(email)).Result;
var response = AsyncHelper.RunSync(() => GetConfiguredClient(apikey, timeout).GetAsync(GetUri(email)));
EvaluateResponse(response);
return JsonConvert.DeserializeObject<ValidationResult>(response.Content.ReadAsStringAsync().Result, _jsonSerializerSettings);
string responseBody = AsyncHelper.RunSync(() => response.Content.ReadAsStringAsync());
return JsonConvert.DeserializeObject<ValidationResult>(responseBody, _jsonSerializerSettings);
}
catch (AggregateException ex)
catch (TaskCanceledException)
{
if (ex.InnerException is TaskCanceledException)
{
throw new TimeoutException("API call took longer than expected.");
}
else
{
throw;
}
throw new TimeoutException("API call took longer than expected.");
}
}

/// <summary>
/// Validates an e-mail address in an asyncronous fashion.
/// Validates an e-mail address.
/// </summary>
/// <param name="email">E-mail address to validate.</param>
/// <param name="apikey">Mailgun e-mail validation API key.</param>
Expand All @@ -66,20 +60,14 @@ public static async Task<ValidationResult> ValidateAsync(string email, string ap
{
try
{
var response = await GetClient(apikey, timeout).GetAsync(GetUri(email));
var response = await GetConfiguredClient(apikey, timeout).GetAsync(GetUri(email)).ConfigureAwait(false);
EvaluateResponse(response);
return JsonConvert.DeserializeObject<ValidationResult>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings);
string responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return JsonConvert.DeserializeObject<ValidationResult>(responseBody, _jsonSerializerSettings);
}
catch (AggregateException ex)
catch (TaskCanceledException)
{
if (ex.InnerException is TaskCanceledException)
{
throw new TimeoutException("API call took longer than expected.");
}
else
{
throw;
}
throw new TimeoutException("API call took longer than expected.");
}
}

Expand All @@ -95,7 +83,7 @@ private static void EvaluateResponse(HttpResponseMessage response)
}
}

private static HttpClient GetClient(string apikey, int timeout)
private static HttpClient GetConfiguredClient(string apikey, int timeout)
{
var auth = new HttpClientHandler()
{
Expand All @@ -114,7 +102,7 @@ private static Uri GetUri(string email)
{
UriBuilder ub = new UriBuilder(MailgunBaseUrl)
{
Path = ValidatorResource,
Path = MailgunValidatorResource,
Query = $"address={email}",
};
return ub.Uri;
Expand Down
59 changes: 52 additions & 7 deletions MailgunAddressValidatorTests/MailgunAddressValidatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ public class MailgunAddressValidatorTests
private readonly string _validAddress = "sales@mailgun.com";
private readonly string _invalidAddress = "test@nonexistentdomain.com";

/// <summary>
/// Tests if a valid email address returns valid response.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Parallelizable]
[Test]
public async Task ValidateValidAddressAsync()
{
ValidationResult result = await Validator.ValidateAsync(_validAddress, _apikey).ConfigureAwait(false);
Assert.That(result.IsValid, Is.True);
}

/// <summary>
/// Tests if a valid email address returns valid response.
/// </summary>
Expand All @@ -31,6 +43,18 @@ public void ValidateValidAddress()
Assert.That(result.IsValid, Is.True);
}

/// <summary>
/// Tests if an invalid e-mail address returns invalid response.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Parallelizable]
[Test]
public async Task ValidateInvalidAddressAsync()
{
ValidationResult result = await Validator.ValidateAsync(_invalidAddress, _apikey).ConfigureAwait(false);
Assert.That(result.IsValid, Is.False);
}

/// <summary>
/// Tests if an invalid e-mail address returns invalid response.
/// </summary>
Expand All @@ -42,6 +66,19 @@ public void ValidateInvalidAddress()
Assert.That(result.IsValid, Is.False);
}

/// <summary>
/// Tests if an e-mail containing a typo gets fixed.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Parallelizable]
[Test]
public async Task ValidateAddressWithTypoAsync()
{
ValidationResult result = await Validator.ValidateAsync("test@gmaill.com", _apikey).ConfigureAwait(false);
Assert.That(result.IsValid, Is.True);
Assert.That(result.DidYouMean, Is.EqualTo("test@gmail.com"));
}

/// <summary>
/// Tests if an e-mail containing a typo gets fixed.
/// </summary>
Expand All @@ -54,6 +91,16 @@ public void ValidateAddressWithTypo()
Assert.That(result.DidYouMean, Is.EqualTo("test@gmail.com"));
}

/// <summary>
/// Tests if a request with very short timeout actually fails.
/// </summary>
[Parallelizable]
[Test]
public void ThrowTimeoutExceptionAsync()
{
Assert.That(async () => await Validator.ValidateAsync(_validAddress, _apikey, 1).ConfigureAwait(false), Throws.TypeOf<TimeoutException>());
}

/// <summary>
/// Tests if a request with very short timeout actually fails.
/// </summary>
Expand All @@ -69,21 +116,19 @@ public void ThrowTimeoutException()
/// </summary>
[Parallelizable]
[Test]
public void ThrowUnauthorizedExceptionOnBadApiKey()
public void ThrowUnauthorizedExceptionOnBadApiKeyAsync()
{
Assert.That(() => Validator.Validate(_validAddress, "blabla"), Throws.TypeOf<UnauthorizedException>());
Assert.That(async () => await Validator.ValidateAsync(_validAddress, "blabla").ConfigureAwait(false), Throws.TypeOf<UnauthorizedException>());
}

/// <summary>
/// Tests asyncronous validation.
/// Tests if an exception is thrown when the API key is invalid.
/// </summary>
/// <returns>A Task.</returns>
[Parallelizable]
[Test]
public async Task ValidateValidAddressAsync()
public void ThrowUnauthorizedExceptionOnBadApiKey()
{
ValidationResult result = await Validator.ValidateAsync(_validAddress, _apikey);
Assert.That(result.IsValid, Is.True);
Assert.That(() => Validator.Validate(_validAddress, "blabla"), Throws.TypeOf<UnauthorizedException>());
}
}
}
Loading

0 comments on commit c82cec1

Please sign in to comment.