diff --git a/MailgunAddressValidator/AsyncHelper.cs b/MailgunAddressValidator/AsyncHelper.cs new file mode 100644 index 0000000..edcb06c --- /dev/null +++ b/MailgunAddressValidator/AsyncHelper.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) Balázs Keresztury. All rights reserved. +// +// Source: https://cpratt.co/async-tips-tricks/ + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MailgunAddressValidator +{ + /// + /// Helper class to wrap an async delegate into a syncronous method. + /// + public static class AsyncHelper + { + private static readonly TaskFactory _taskFactory = new + TaskFactory( + CancellationToken.None, + TaskCreationOptions.None, + TaskContinuationOptions.None, + TaskScheduler.Default); + + /// + /// Wraps a generic async delegate into a syncronous method. + /// + /// The type of the result. + /// The asyncronous delegate. + /// The result. + public static TResult RunSync(Func> func) + => _taskFactory + .StartNew(func) + .Unwrap() + .GetAwaiter() + .GetResult(); + + /// + /// Wraps an async delegate into a syncronous method. + /// + /// The asyncronous delegate. + public static void RunSync(Func func) + => _taskFactory + .StartNew(func) + .Unwrap() + .GetAwaiter() + .GetResult(); + } +} \ No newline at end of file diff --git a/MailgunAddressValidator/MailgunAddressValidator.csproj b/MailgunAddressValidator/MailgunAddressValidator.csproj index 80d3626..88c3412 100644 --- a/MailgunAddressValidator/MailgunAddressValidator.csproj +++ b/MailgunAddressValidator/MailgunAddressValidator.csproj @@ -1,7 +1,7 @@  netstandard1.1;netstandard2.0 - 1.1.1 + 1.1.2 Balázs Keresztury This asynchronous library checks email addresses against Mailgun's e-mail validation service @@ -14,8 +14,11 @@ true - - 1701;1702;SA0001 + MailgunAddressValidator.xml + 1701;1702 + + + MailgunAddressValidator.xml @@ -24,6 +27,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all diff --git a/MailgunAddressValidator/MailgunAddressValidator.xml b/MailgunAddressValidator/MailgunAddressValidator.xml new file mode 100644 index 0000000..b3b71c7 --- /dev/null +++ b/MailgunAddressValidator/MailgunAddressValidator.xml @@ -0,0 +1,105 @@ + + + + MailgunAddressValidator + + + + + Helper class to wrap an async delegate into a syncronous method. + + + + + Wraps a generic async delegate into a syncronous method. + + The type of the result. + The asyncronous delegate. + The result. + + + + Wraps an async delegate into a syncronous method. + + The asyncronous delegate. + + + + It is thrown when the client is unable to authorize with the Mailgun API. + + + + + Initializes a new instance of the class. + + + + + Contains the result of a validation. + + + + + Runs the email segments across a valid known provider rule list. If a violation occurs this value is false. + + + + + Email address being validated. + + + + + Parsed segments of the provided email address. + + + + + Null if nothing, however if a potential typo is made, the closest suggestion is provided. + + + + + Contains parsed segments of the provided email address. + + + + + Display name for the address. + + + + + The local part of the e-mail address. + + + + + The domain part of the e-mail address. + + + + + Contains static functions to validate an e-mail address using Mailgun's e-mail validation service. + + + + + Validates an e-mail address. + + E-mail address to validate. + Mailgun e-mail validation API key. + Max. time to wait for the call to complete (in ms). + A ValidationResult object. + + + + Validates an e-mail address. + + E-mail address to validate. + Mailgun e-mail validation API key. + Max. time to wait for the call to complete (in ms). + A Task containg a ValidationResult object. + + + diff --git a/MailgunAddressValidator/Validator.cs b/MailgunAddressValidator/Validator.cs index 715d669..b7fc930 100644 --- a/MailgunAddressValidator/Validator.cs +++ b/MailgunAddressValidator/Validator.cs @@ -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() @@ -28,7 +28,7 @@ public static class Validator }; /// - /// Validates an e-mail address in a syncronous fashion. + /// Validates an e-mail address. /// /// E-mail address to validate. /// Mailgun e-mail validation API key. @@ -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(response.Content.ReadAsStringAsync().Result, _jsonSerializerSettings); + string responseBody = AsyncHelper.RunSync(() => response.Content.ReadAsStringAsync()); + return JsonConvert.DeserializeObject(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."); } } /// - /// Validates an e-mail address in an asyncronous fashion. + /// Validates an e-mail address. /// /// E-mail address to validate. /// Mailgun e-mail validation API key. @@ -66,20 +60,14 @@ public static async Task 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(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings); + string responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return JsonConvert.DeserializeObject(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."); } } @@ -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() { @@ -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; diff --git a/MailgunAddressValidatorTests/MailgunAddressValidatorTests.cs b/MailgunAddressValidatorTests/MailgunAddressValidatorTests.cs index 8f648a1..27a3d25 100644 --- a/MailgunAddressValidatorTests/MailgunAddressValidatorTests.cs +++ b/MailgunAddressValidatorTests/MailgunAddressValidatorTests.cs @@ -20,6 +20,18 @@ public class MailgunAddressValidatorTests private readonly string _validAddress = "sales@mailgun.com"; private readonly string _invalidAddress = "test@nonexistentdomain.com"; + /// + /// Tests if a valid email address returns valid response. + /// + /// A representing the asynchronous unit test. + [Parallelizable] + [Test] + public async Task ValidateValidAddressAsync() + { + ValidationResult result = await Validator.ValidateAsync(_validAddress, _apikey).ConfigureAwait(false); + Assert.That(result.IsValid, Is.True); + } + /// /// Tests if a valid email address returns valid response. /// @@ -31,6 +43,18 @@ public void ValidateValidAddress() Assert.That(result.IsValid, Is.True); } + /// + /// Tests if an invalid e-mail address returns invalid response. + /// + /// A representing the asynchronous unit test. + [Parallelizable] + [Test] + public async Task ValidateInvalidAddressAsync() + { + ValidationResult result = await Validator.ValidateAsync(_invalidAddress, _apikey).ConfigureAwait(false); + Assert.That(result.IsValid, Is.False); + } + /// /// Tests if an invalid e-mail address returns invalid response. /// @@ -42,6 +66,19 @@ public void ValidateInvalidAddress() Assert.That(result.IsValid, Is.False); } + /// + /// Tests if an e-mail containing a typo gets fixed. + /// + /// A representing the asynchronous unit test. + [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")); + } + /// /// Tests if an e-mail containing a typo gets fixed. /// @@ -54,6 +91,16 @@ public void ValidateAddressWithTypo() Assert.That(result.DidYouMean, Is.EqualTo("test@gmail.com")); } + /// + /// Tests if a request with very short timeout actually fails. + /// + [Parallelizable] + [Test] + public void ThrowTimeoutExceptionAsync() + { + Assert.That(async () => await Validator.ValidateAsync(_validAddress, _apikey, 1).ConfigureAwait(false), Throws.TypeOf()); + } + /// /// Tests if a request with very short timeout actually fails. /// @@ -69,21 +116,19 @@ public void ThrowTimeoutException() /// [Parallelizable] [Test] - public void ThrowUnauthorizedExceptionOnBadApiKey() + public void ThrowUnauthorizedExceptionOnBadApiKeyAsync() { - Assert.That(() => Validator.Validate(_validAddress, "blabla"), Throws.TypeOf()); + Assert.That(async () => await Validator.ValidateAsync(_validAddress, "blabla").ConfigureAwait(false), Throws.TypeOf()); } /// - /// Tests asyncronous validation. + /// Tests if an exception is thrown when the API key is invalid. /// - /// A Task. [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()); } } } diff --git a/MailgunAddressValidatorTests/MailgunAddressValidatorTests.csproj b/MailgunAddressValidatorTests/MailgunAddressValidatorTests.csproj index cb699e9..fcfbf04 100644 --- a/MailgunAddressValidatorTests/MailgunAddressValidatorTests.csproj +++ b/MailgunAddressValidatorTests/MailgunAddressValidatorTests.csproj @@ -2,7 +2,16 @@ netcoreapp3.1 - MailgunAddressValidator + MailgunAddressValidatorTests + 1.1.2 + + + + MailgunAddressValidatorTests.xml + + + + MailgunAddressValidatorTests.xml @@ -14,6 +23,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/MailgunAddressValidatorTests/MailgunAddressValidatorTests.xml b/MailgunAddressValidatorTests/MailgunAddressValidatorTests.xml new file mode 100644 index 0000000..7b9150e --- /dev/null +++ b/MailgunAddressValidatorTests/MailgunAddressValidatorTests.xml @@ -0,0 +1,66 @@ + + + + MailgunAddressValidatorTests + + + + + Contains tests for the MailgunAddressValidator project. + + + + + Tests if a valid email address returns valid response. + + A representing the asynchronous unit test. + + + + Tests if a valid email address returns valid response. + + + + + Tests if an invalid e-mail address returns invalid response. + + A representing the asynchronous unit test. + + + + Tests if an invalid e-mail address returns invalid response. + + + + + Tests if an e-mail containing a typo gets fixed. + + A representing the asynchronous unit test. + + + + Tests if an e-mail containing a typo gets fixed. + + + + + Tests if a request with very short timeout actually fails. + + + + + Tests if a request with very short timeout actually fails. + + + + + Tests if an exception is thrown when the API key is invalid. + + + + + Tests if an exception is thrown when the API key is invalid. + + + +