Skip to content

Commit

Permalink
Simplify throws, use configure await, and cancellation tokens and flo…
Browse files Browse the repository at this point in the history
…w them through.
  • Loading branch information
IEvangelist committed Feb 11, 2024
1 parent 2dc6f5c commit 9f755a6
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 113 deletions.
24 changes: 19 additions & 5 deletions src/HaveIBeenPwned.Client.Abstractions/IPwnedBreachesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ public interface IPwnedBreachesClient
/// See <a href="https://haveibeenpwned.com/API/v3#BreachesForAccount"></a>
/// </summary>
/// <param name="account">The account to search for breaches.</param>
/// <param name="cancellationToken">Used to signal cancellation.</param>
/// <returns>An array of breach headers if found, or an empty array if not found.</returns>
/// <remarks>
/// The <c>truncateResponse</c> is set to <c>true</c>, causing only the breach headers to be returned.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// When the <paramref name="account"/> is either <see langword="null" />, empty or whitespace.
/// </exception>
Task<BreachHeader[]> GetBreachHeadersForAccountAsync(string account);
Task<BreachHeader[]> GetBreachHeadersForAccountAsync(
string account,
CancellationToken cancellationToken = default);

/// <summary>
/// Retrieves an asynchronous stream of <see cref="BreachHeader"/> objects representing all known data breaches that include the specified account.
Expand Down Expand Up @@ -50,14 +53,17 @@ public interface IPwnedBreachesClient
/// See <a href="https://haveibeenpwned.com/API/v3#BreachesForAccount"></a>
/// </summary>
/// <param name="account">The account to search for breaches.</param>
/// <param name="cancellationToken">Used to signal cancellation.</param>
/// <returns>An array of breach details if found, or an empty array if not found.</returns>
/// <remarks>
/// The <c>truncateResponse</c> is set to <c>false</c>, allows breach details to be returned.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// When the <paramref name="account"/> is <see langword="null" />, empty or whitespace.
/// </exception>
Task<BreachDetails[]> GetBreachesForAccountAsync(string account);
Task<BreachDetails[]> GetBreachesForAccountAsync(
string account,
CancellationToken cancellationToken = default);

/// <summary>
/// Retrieves a collection of all breaches that a particular account has been involved in, as an asynchronous enumerable.
Expand All @@ -75,17 +81,21 @@ public interface IPwnedBreachesClient
/// When the <paramref name="account"/> is <see langword="null" />, empty or whitespace.
/// </exception>
IAsyncEnumerable<BreachDetails?> GetBreachesForAccountAsAsyncEnumerable(
string account, CancellationToken cancellationToken = default);
string account,
CancellationToken cancellationToken = default);

/// <summary>
/// Gets an array of <see cref="BreachHeader"/>, optionally filtering on <paramref name="domain"/>.
/// </summary>
/// <param name="domain">An optional domain to filter the returned breaches to.</param>
/// <param name="cancellationToken">Used to signal cancellation.</param>
/// <returns>An array of breach headers, or an empty array.</returns>
/// <remarks>
/// Example JSON payload: <a href="https://haveibeenpwned.com/api/v3/breaches"></a>
/// </remarks>
Task<BreachHeader[]> GetBreachesAsync(string? domain = default);
Task<BreachHeader[]> GetBreachesAsync(
string? domain = default,
CancellationToken cancellationToken = default);

/// <summary>
/// Retrieves a collection of breach headers as an asynchronous enumerable, optionally filtering on <paramref name="domain"/>.
Expand All @@ -104,14 +114,17 @@ public interface IPwnedBreachesClient
/// Gets the <see cref="BreachDetails"/> object for the given <paramref name="breachName"/>.
/// </summary>
/// <param name="breachName">The name of the breach to get.</param>
/// <param name="cancellationToken">Used to signal cancellation.</param>
/// <returns>The breach details for the given <paramref name="breachName"/>, if unable to find one <see langword="null" /> is returned.</returns>
/// <remarks>
/// Example JSON payload: <a href="https://haveibeenpwned.com/api/v3/breach/Adobe"></a>
/// </remarks>
/// <exception cref="ArgumentNullException">
/// When the <paramref name="breachName"/> is <see langword="null" />, empty or whitespace.
/// </exception>
Task<BreachDetails?> GetBreachAsync(string breachName);
Task<BreachDetails?> GetBreachAsync(
string breachName,
CancellationToken cancellationToken = default);

/// <summary>
/// Gets all of the data classes possible for any given <see cref="BreachDetails.DataClasses"/>.
Expand All @@ -120,6 +133,7 @@ public interface IPwnedBreachesClient
/// <remarks>
/// Example JSON payload: <a href="https://haveibeenpwned.com/api/v3/dataclasses"></a>
/// </remarks>
/// <param name="cancellationToken">Used to signal cancellation.</param>
Task<string[]> GetDataClassesAsync(CancellationToken cancellationToken = default);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ public interface IPwnedPasswordsClient
/// all hashed passwords in the HIBP range data.
/// </summary>
/// <param name="plainTextPassword">The plain text password to evaluate.</param>
/// <param name="cancellationToken">Used to signal cancellation.</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException">
/// When the <paramref name="plainTextPassword"/> is <see langword="null" />, empty or whitespace.
/// </exception>
Task<PwnedPassword> GetPwnedPasswordAsync(string plainTextPassword);
Task<PwnedPassword> GetPwnedPasswordAsync(
string plainTextPassword,
CancellationToken cancellationToken = default);
}
5 changes: 4 additions & 1 deletion src/HaveIBeenPwned.Client.Abstractions/IPwnedPastesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ public interface IPwnedPastesClient
/// See <a href="https://haveibeenpwned.com/API/v3#PastesForAccount"></a>
/// </summary>
/// <param name="account">The email address to get pastes for.</param>
/// <param name="cancellationToken">Used to signal cancellation.</param>
/// <returns>An array of <see cref="Pastes"/> if found, otherwise an empty array.</returns>
/// <exception cref="ArgumentNullException">
/// When the <paramref name="account"/> is <see langword="null" />, empty or whitespace.
/// </exception>
Task<Pastes[]> GetPastesAsync(string account);
Task<Pastes[]> GetPastesAsync(
string account,
CancellationToken cancellationToken = default);

/// <summary>
/// The API takes a single parameter which is the email address to be searched for.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static class PwnedClientServiceCollectionExtensions
/// </summary>
/// <param name="services">The service collection to add services to.</param>
/// <param name="namedConfigurationSection">The name configuration section to bind options from.</param>
/// <param name="configureResilienceOptions">The rety policy configuration function, when provided adds transient HTTP error policy.</param>
/// <param name="configureResilienceOptions">The retry policy configuration function, when provided adds transient HTTP error policy.</param>
/// <returns>The same <paramref name="services"/> instance with other services added.</returns>
/// <exception cref="ArgumentNullException">
/// If either the <paramref name="services"/> or <paramref name="namedConfigurationSection"/> are <see langword="null" />.
Expand Down
114 changes: 60 additions & 54 deletions src/HaveIBeenPwned.Client/DefaultPwnedClient.Breaches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,24 @@ namespace HaveIBeenPwned.Client;

internal sealed partial class DefaultPwnedClient
{
/// <inheritdoc cref="IPwnedBreachesClient.GetBreachAsync(string)" />
async Task<BreachDetails?> IPwnedBreachesClient.GetBreachAsync(string breachName)
/// <inheritdoc cref="IPwnedBreachesClient.GetBreachAsync(string, CancellationToken)" />
async Task<BreachDetails?> IPwnedBreachesClient.GetBreachAsync(
string breachName,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(breachName))
{
throw new ArgumentException(
"The breachName cannot be either null, empty or whitespace.", nameof(breachName));
}
ArgumentException.ThrowIfNullOrWhiteSpace(breachName);

try
{
var client = httpClientFactory.CreateClient(HibpClient);

var breachDetails =
await client.GetFromJsonAsync<BreachDetails>(
$"breach/{breachName}",
SourceGeneratorContext.Default.BreachDetails);
$"breach/{breachName}",
SourceGeneratorContext.Default.BreachDetails,
cancellationToken
)
.ConfigureAwait(false);

return breachDetails;
}
Expand All @@ -33,8 +34,10 @@ await client.GetFromJsonAsync<BreachDetails>(
}
}

/// <inheritdoc cref="IPwnedBreachesClient.GetBreachesAsync(string?)" />
async Task<BreachHeader[]> IPwnedBreachesClient.GetBreachesAsync(string? domain)
/// <inheritdoc cref="IPwnedBreachesClient.GetBreachesAsync(string?, CancellationToken)" />
async Task<BreachHeader[]> IPwnedBreachesClient.GetBreachesAsync(
string? domain,
CancellationToken cancellationToken)
{
try
{
Expand All @@ -46,8 +49,11 @@ async Task<BreachHeader[]> IPwnedBreachesClient.GetBreachesAsync(string? domain)

var breachHeaders =
await client.GetFromJsonAsync<BreachHeader[]>(
$"breaches{queryString}",
SourceGeneratorContext.Default.BreachHeaderArray);
$"breaches{queryString}",
SourceGeneratorContext.Default.BreachHeaderArray,
cancellationToken
)
.ConfigureAwait(false);

return breachHeaders ?? [];
}
Expand All @@ -61,7 +67,8 @@ await client.GetFromJsonAsync<BreachHeader[]>(

/// <inheritdoc path="IPwnedBreachesClient.GetBreachesAsAsyncEnumerable(string?, CancellationToken)" />
IAsyncEnumerable<BreachHeader?> IPwnedBreachesClient.GetBreachesAsAsyncEnumerable(
string? domain, CancellationToken cancellationToken)
string? domain,
CancellationToken cancellationToken)
{
try
{
Expand All @@ -72,9 +79,9 @@ await client.GetFromJsonAsync<BreachHeader[]>(
: $"?domain={domain}";

return client.GetFromJsonAsAsyncEnumerable<BreachHeader>(
$"breaches{queryString}",
SourceGeneratorContext.Default.BreachHeader,
cancellationToken: cancellationToken);
$"breaches{queryString}",
SourceGeneratorContext.Default.BreachHeader,
cancellationToken: cancellationToken);
}
catch (Exception ex)
{
Expand All @@ -84,23 +91,24 @@ await client.GetFromJsonAsync<BreachHeader[]>(
}
}

/// <inheritdoc cref="IPwnedBreachesClient.GetBreachesForAccountAsync(string)" />
async Task<BreachDetails[]> IPwnedBreachesClient.GetBreachesForAccountAsync(string account)
/// <inheritdoc cref="IPwnedBreachesClient.GetBreachesForAccountAsync(string, CancellationToken)" />
async Task<BreachDetails[]> IPwnedBreachesClient.GetBreachesForAccountAsync(
string account,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(account))
{
throw new ArgumentException(
"The account cannot be either null, empty or whitespace.", nameof(account));
}
ArgumentException.ThrowIfNullOrWhiteSpace(account);

try
{
var client = httpClientFactory.CreateClient(HibpClient);

var breachDetails =
await client.GetFromJsonAsync<BreachDetails[]>(
$"breachedaccount/{HttpUtility.UrlEncode(account)}?truncateResponse=false",
SourceGeneratorContext.Default.BreachDetailsArray);
$"breachedaccount/{HttpUtility.UrlEncode(account)}?truncateResponse=false",
SourceGeneratorContext.Default.BreachDetailsArray,
cancellationToken
)
.ConfigureAwait(false);

return breachDetails ?? [];
}
Expand All @@ -114,16 +122,17 @@ await client.GetFromJsonAsync<BreachDetails[]>(

/// <inheritdoc path="IPwnedBreachesClient.GetBreachesForAccountAsAsyncEnumerable(string, CancellationToken)" />"
IAsyncEnumerable<BreachDetails?> IPwnedBreachesClient.GetBreachesForAccountAsAsyncEnumerable(
string account, CancellationToken cancellationToken)
string account,
CancellationToken cancellationToken)
{
try
{
var client = httpClientFactory.CreateClient(HibpClient);

return client.GetFromJsonAsAsyncEnumerable<BreachDetails>(
$"breachedaccount/{HttpUtility.UrlEncode(account)}?truncateResponse=false",
SourceGeneratorContext.Default.BreachDetails,
cancellationToken: cancellationToken);
$"breachedaccount/{HttpUtility.UrlEncode(account)}?truncateResponse=false",
SourceGeneratorContext.Default.BreachDetails,
cancellationToken: cancellationToken);
}
catch (Exception ex)
{
Expand All @@ -133,23 +142,21 @@ await client.GetFromJsonAsync<BreachDetails[]>(
}
}

/// <inheritdoc cref="IPwnedBreachesClient.GetBreachHeadersForAccountAsync(string)" />
async Task<BreachHeader[]> IPwnedBreachesClient.GetBreachHeadersForAccountAsync(string account)
/// <inheritdoc cref="IPwnedBreachesClient.GetBreachHeadersForAccountAsync(string, CancellationToken)" />
async Task<BreachHeader[]> IPwnedBreachesClient.GetBreachHeadersForAccountAsync(
string account,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(account))
{
throw new ArgumentException(
"The account cannot be either null, empty or whitespace.", nameof(account));
}
ArgumentException.ThrowIfNullOrWhiteSpace(account);

try
{
var client = httpClientFactory.CreateClient(HibpClient);

var breachHeaders =
await client.GetFromJsonAsync<BreachHeader[]>(
$"breachedaccount/{HttpUtility.UrlEncode(account)}?truncateResponse=true",
SourceGeneratorContext.Default.BreachHeaderArray);
var breachHeaders = await client.GetFromJsonAsync<BreachHeader[]>(
$"breachedaccount/{HttpUtility.UrlEncode(account)}?truncateResponse=true",
SourceGeneratorContext.Default.BreachHeaderArray,
cancellationToken);

return breachHeaders ?? [];
}
Expand All @@ -163,13 +170,10 @@ await client.GetFromJsonAsync<BreachHeader[]>(

/// <inheritdoc path="IPwnedBreachesClient.GetBreachHeadersForAccountAsAsyncEnumerable(string, CancellationToken)" />
IAsyncEnumerable<BreachHeader?> IPwnedBreachesClient.GetBreachHeadersForAccountAsAsyncEnumerable(
string account, CancellationToken cancellationToken)
string account,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(account))
{
throw new ArgumentException(
"The account cannot be either null, empty or whitespace.", nameof(account));
}
ArgumentException.ThrowIfNullOrWhiteSpace(account);

try
{
Expand Down Expand Up @@ -197,9 +201,11 @@ async Task<string[]> IPwnedBreachesClient.GetDataClassesAsync(CancellationToken

var dataClasses =
await client.GetFromJsonAsync<string[]>(
"dataclasses",
SourceGeneratorContext.Default.StringArray,
cancellationToken: cancellationToken);
"dataclasses",
SourceGeneratorContext.Default.StringArray,
cancellationToken: cancellationToken
)
.ConfigureAwait(false);

return dataClasses ?? [];
}
Expand All @@ -211,17 +217,17 @@ await client.GetFromJsonAsync<string[]>(
}
}

/// <inheritdoc path="IPwnedBreachesClient.GetDataClassesAsAsyncEnumerable" />
/// <inheritdoc path="IPwnedBreachesClient.GetDataClassesAsAsyncEnumerable(CancellationToken)" />
IAsyncEnumerable<string?> IPwnedBreachesClient.GetDataClassesAsAsyncEnumerable(CancellationToken cancellationToken)
{
try
{
var client = httpClientFactory.CreateClient(HibpClient);

return client.GetFromJsonAsAsyncEnumerable<string>(
"dataclasses",
SourceGeneratorContext.Default.String,
cancellationToken: cancellationToken);
"dataclasses",
SourceGeneratorContext.Default.String,
cancellationToken: cancellationToken);
}
catch (Exception ex)
{
Expand Down
Loading

0 comments on commit 9f755a6

Please sign in to comment.