Skip to content

Commit

Permalink
Merge pull request #121 from dragonfruitnetwork/validaterequest-async
Browse files Browse the repository at this point in the history
Make `ValidateRequest(ApiClient)` return a `Task`
  • Loading branch information
aspriddell authored Jul 2, 2022
2 parents 16d0c47 + d252bf6 commit dbd0d33
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 23 deletions.
17 changes: 12 additions & 5 deletions src/ApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Xml;
using DragonFruit.Data.Exceptions;
using DragonFruit.Data.Headers;
using DragonFruit.Data.Requests;
using DragonFruit.Data.Serializers;
using DragonFruit.Data.Utils;
using Nito.AsyncEx;
Expand Down Expand Up @@ -122,7 +123,6 @@ protected async Task<T> InternalPerform<T>(HttpRequestMessage request, Func<Http
try
{
// send request
// ReSharper disable once MethodSupportsCancellation (we need to run regardless of cancellation to release lock)
response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false);

// evaluate task status and update monitor
Expand All @@ -137,7 +137,6 @@ protected async Task<T> InternalPerform<T>(HttpRequestMessage request, Func<Http
response?.Dispose();
}

// exit the read lock after fully processing
clientLock.Dispose();
}
}
Expand Down Expand Up @@ -166,9 +165,17 @@ protected virtual async Task<T> ValidateAndProcess<T>(HttpResponseMessage respon
/// <param name="request">The request to validate</param>
/// <exception cref="NullRequestException">The request can't be performed due to a poorly-formed url</exception>
/// <exception cref="ClientValidationException">The client can't be used because there is no auth url.</exception>
protected virtual void ValidateRequest(ApiRequest request)
protected virtual async Task ValidateRequest(ApiRequest request)
{
request.OnRequestExecuting(this);
if (request is IRequestExecutingCallback syncCallback)
{
syncCallback.OnRequestExecuting(this);
}

if (request is IAsyncRequestExecutingCallback asyncCallback)
{
await asyncCallback.OnRequestExecutingAsync(this).ConfigureAwait(false);
}

// note request path is validated on build
if (request.RequireAuth && string.IsNullOrEmpty(Authorization))
Expand Down Expand Up @@ -262,7 +269,7 @@ public Func<HttpMessageHandler> Handler
/// </summary>
/// <remarks>
/// This should be used when a library needs to enforce a <see cref="DelegatingHandler"/> is wrapped over the <see cref="Handler"/>.
/// If overriden, it should be sealed to prevent misuse
/// If overriden, the client should be sealed to prevent unintended changes
/// </remarks>
protected virtual HttpMessageHandler CreateHandler() => Handler?.Invoke();

Expand Down
14 changes: 7 additions & 7 deletions src/ApiClient_Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ public Task<T> PerformAsync<T>(string url, CancellationToken token = default) wh
/// <summary>
/// Perform an <see cref="ApiRequest"/> with a specified return type.
/// </summary>
public Task<T> PerformAsync<T>(ApiRequest requestData, CancellationToken token = default) where T : class
public async Task<T> PerformAsync<T>(ApiRequest requestData, CancellationToken token = default) where T : class
{
ValidateRequest(requestData);
return PerformAsync<T>(requestData.Build(this), token);
await ValidateRequest(requestData).ConfigureAwait(false);
return await PerformAsync<T>(requestData.Build(this), token).ConfigureAwait(false);
}

/// <summary>
Expand All @@ -51,10 +51,10 @@ public Task<HttpResponseMessage> PerformAsync(string url, CancellationToken toke
/// <summary>
/// Perform a <see cref="ApiRequest"/> that returns the response message.
/// </summary>
public Task<HttpResponseMessage> PerformAsync(ApiRequest requestData, CancellationToken token = default)
public async Task<HttpResponseMessage> PerformAsync(ApiRequest requestData, CancellationToken token = default)
{
ValidateRequest(requestData);
return PerformAsync(requestData.Build(this), token);
await ValidateRequest(requestData).ConfigureAwait(false);
return await PerformAsync(requestData.Build(this), token).ConfigureAwait(false);
}

/// <summary>
Expand All @@ -74,7 +74,7 @@ public Task<HttpResponseMessage> PerformAsync(HttpRequestMessage request, Cancel
public async Task PerformAsync(ApiFileRequest request, Action<long, long?> progressUpdated = null, CancellationToken token = default)
{
// check request data is valid
ValidateRequest(request);
await ValidateRequest(request).ConfigureAwait(false);

if (string.IsNullOrWhiteSpace(request.Destination))
{
Expand Down
7 changes: 0 additions & 7 deletions src/ApiRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,6 @@ internal string QueryString
/// </summary>
protected virtual IEnumerable<KeyValuePair<string, string>> AdditionalQueries { get; }

/// <summary>
/// Overridable method for specifying an action to occur before sending the request to the <see cref="HttpClient"/>
/// </summary>
protected internal virtual void OnRequestExecuting(ApiClient client)
{
}

/// <summary>
/// Create a <see cref="HttpResponseMessage"/> for this <see cref="ApiRequest"/>, which can then be modified manually or overriden by <see cref="ApiClient.SetupRequest"/>
/// </summary>
Expand Down
20 changes: 20 additions & 0 deletions src/Requests/IAsyncRequestExecutingCallback.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// DragonFruit.Data Copyright DragonFruit Network
// Licensed under the MIT License. Please refer to the LICENSE file at the root of this project for details

using System.Net.Http;
using System.Threading.Tasks;

namespace DragonFruit.Data.Requests
{
/// <summary>
/// Specifies the <see cref="ApiRequest"/> should have its <see cref="OnRequestExecutingAsync"/> method called after when the request is being executed
/// </summary>
public interface IAsyncRequestExecutingCallback
{
/// <summary>
/// Overridable method for specifying an action to occur before sending the request to the <see cref="HttpClient"/>.
/// Unlike <see cref="IRequestExecutingCallback"/>, this will be run asynchronously and must return a <see cref="ValueTask"/>.
/// </summary>
ValueTask OnRequestExecutingAsync(ApiClient client);
}
}
16 changes: 16 additions & 0 deletions src/Requests/IRequestExecutingCallback.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// DragonFruit.Data Copyright DragonFruit Network
// Licensed under the MIT License. Please refer to the LICENSE file at the root of this project for details

namespace DragonFruit.Data.Requests
{
/// <summary>
/// Specifies the <see cref="ApiRequest"/> should have its <see cref="OnRequestExecuting"/> method called after when the request is being executed
/// </summary>
public interface IRequestExecutingCallback
{
/// <summary>
/// Overridable method for specifying an action to occur before sending the request to the <see cref="HttpClient"/>
/// </summary>
void OnRequestExecuting(ApiClient client);
}
}
9 changes: 5 additions & 4 deletions tests/Requests/RequestFilterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License. Please refer to the LICENSE file at the root of this project for details

using System;
using DragonFruit.Data.Requests;
using NUnit.Framework;

namespace DragonFruit.Data.Tests.Requests
Expand All @@ -12,15 +13,15 @@ public class RequestFilterTests : ApiTest
[Test]
public void TestFilteredRequests()
{
Assert.Catch<ArgumentException>(() => Client.Perform(new FilteredRequest()));
Assert.Catch<ArgumentException>(() => Client.Perform(new InheritedRequest()));
Assert.Catch<AggregateException>(() => Client.Perform(new FilteredRequest()));
Assert.CatchAsync<ArgumentException>(() => Client.PerformAsync(new InheritedRequest()));
}

internal class FilteredRequest : ApiRequest
internal class FilteredRequest : ApiRequest, IRequestExecutingCallback
{
public override string Path { get; }

protected override void OnRequestExecuting(ApiClient client)
void IRequestExecutingCallback.OnRequestExecuting(ApiClient client)
{
throw new ArgumentException();
}
Expand Down

0 comments on commit dbd0d33

Please sign in to comment.