Skip to content

Commit

Permalink
fix: add ConfigureAwait on async code to avoid potential deadlocks
Browse files Browse the repository at this point in the history
  • Loading branch information
Tr00d committed May 6, 2024
1 parent a404398 commit a97f908
Show file tree
Hide file tree
Showing 19 changed files with 331 additions and 324 deletions.
24 changes: 12 additions & 12 deletions Vonage/Accounts/AccountClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,22 @@ public class AccountClient : IAccountClient
{
private readonly Configuration configuration;
private readonly ITimeProvider timeProvider = new TimeProvider();

public AccountClient(Credentials creds = null)
{
this.Credentials = creds;
this.configuration = Configuration.Instance;
}

internal AccountClient(Credentials creds, Configuration configuration, ITimeProvider timeProvider)
{
this.Credentials = creds;
this.configuration = configuration;
this.timeProvider = timeProvider;
}

public Credentials Credentials { get; set; }

/// <inheritdoc />
public Task<AccountSettingsResult> ChangeAccountSettingsAsync(AccountSettingsRequest request,
Credentials creds = null) =>
Expand All @@ -34,7 +34,7 @@ public Task<AccountSettingsResult> ChangeAccountSettingsAsync(AccountSettingsReq
ApiRequest.GetBaseUriFor(this.configuration, "/account/settings"),
request
);

/// <inheritdoc />
public Task<Secret> CreateApiSecretAsync(CreateSecretRequest request, string apiKey = null,
Credentials creds = null) =>
Expand All @@ -45,14 +45,14 @@ public Task<Secret> CreateApiSecretAsync(CreateSecretRequest request, string api
request,
AuthType.Basic
);

/// <inheritdoc />
public Task<Balance> GetAccountBalanceAsync(Credentials creds = null) =>
ApiRequest.Build(this.GetCredentials(creds), this.configuration, this.timeProvider)
.DoGetRequestWithQueryParametersAsync<Balance>(
ApiRequest.GetBaseUriFor(this.configuration, "/account/get-balance"),
AuthType.Query);

/// <inheritdoc />
public Task<Secret> RetrieveApiSecretAsync(string secretId, string apiKey = null, Credentials creds = null) =>
ApiRequest.Build(this.GetCredentials(creds), this.configuration, this.timeProvider)
Expand All @@ -61,15 +61,15 @@ public Task<Secret> RetrieveApiSecretAsync(string secretId, string apiKey = null
$"/accounts/{apiKey}/secrets/{secretId}"),
AuthType.Basic
);

/// <inheritdoc />
public Task<SecretsRequestResult> RetrieveApiSecretsAsync(string apiKey = null, Credentials creds = null) =>
ApiRequest.Build(this.GetCredentials(creds), this.configuration, this.timeProvider)
.DoGetRequestWithQueryParametersAsync<SecretsRequestResult>(
ApiRequest.GetBaseUri(ApiRequest.UriType.Api, this.configuration, $"/accounts/{apiKey}/secrets"),
AuthType.Basic
);

/// <inheritdoc />
public async Task<bool> RevokeApiSecretAsync(string secretId, string apiKey = null, Credentials creds = null)
{
Expand All @@ -79,10 +79,10 @@ await ApiRequest.Build(this.GetCredentials(creds), this.configuration, this.time
$"/accounts/{apiKey}/secrets/{secretId}"),
null,
AuthType.Basic
);
).ConfigureAwait(false);
return true;
}

/// <inheritdoc />
public Task<TopUpResult> TopUpAccountBalanceAsync(TopUpRequest request, Credentials creds = null) =>
ApiRequest.Build(this.GetCredentials(creds), this.configuration, this.timeProvider)
Expand All @@ -91,6 +91,6 @@ public Task<TopUpResult> TopUpAccountBalanceAsync(TopUpRequest request, Credenti
AuthType.Query,
request
);

private Credentials GetCredentials(Credentials overridenCredentials) => overridenCredentials ?? this.Credentials;
}
20 changes: 10 additions & 10 deletions Vonage/Applications/ApplicationClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,22 @@ public class ApplicationClient : IApplicationClient
{
private readonly Configuration configuration;
private readonly ITimeProvider timeProvider = new TimeProvider();

public ApplicationClient(Credentials creds = null)
{
this.Credentials = creds;
this.configuration = Configuration.Instance;
}

internal ApplicationClient(Credentials credentials, Configuration configuration, ITimeProvider timeProvider)
{
this.Credentials = credentials;
this.configuration = configuration;
this.timeProvider = timeProvider;
}

public Credentials Credentials { get; set; }

/// <inheritdoc />
public Task<Application> CreateApplicationAsync(CreateApplicationRequest request, Credentials creds = null) =>
ApiRequest.Build(this.GetCredentials(creds), this.configuration, this.timeProvider)
Expand All @@ -34,7 +34,7 @@ public Task<Application> CreateApplicationAsync(CreateApplicationRequest request
request,
AuthType.Basic
);

/// <inheritdoc/>
public async Task<bool> DeleteApplicationAsync(string id, Credentials creds = null)
{
Expand All @@ -43,18 +43,18 @@ await ApiRequest.Build(this.GetCredentials(creds), this.configuration, this.time
ApiRequest.GetBaseUri(ApiRequest.UriType.Api, this.configuration, $"/v2/applications/{id}"),
null,
AuthType.Basic
);
).ConfigureAwait(false);
return true;
}

/// <inheritdoc/>
public Task<Application> GetApplicationAsync(string id, Credentials creds = null) =>
ApiRequest.Build(this.GetCredentials(creds), this.configuration, this.timeProvider)
.DoGetRequestWithQueryParametersAsync<Application>(
ApiRequest.GetBaseUri(ApiRequest.UriType.Api, this.configuration, $"/v2/applications/{id}"),
AuthType.Basic
);

/// <inheritdoc/>
public Task<ApplicationPage> ListApplicationsAsync(ListApplicationsRequest request, Credentials creds = null) =>
ApiRequest.Build(this.GetCredentials(creds), this.configuration, this.timeProvider)
Expand All @@ -63,7 +63,7 @@ public Task<ApplicationPage> ListApplicationsAsync(ListApplicationsRequest reque
AuthType.Basic,
request
);

/// <inheritdoc/>
public Task<Application> UpdateApplicationAsync(string id, CreateApplicationRequest request,
Credentials creds = null) =>
Expand All @@ -74,6 +74,6 @@ public Task<Application> UpdateApplicationAsync(string id, CreateApplicationRequ
request,
AuthType.Basic
);

private Credentials GetCredentials(Credentials overridenCredentials) => overridenCredentials ?? this.Credentials;
}
45 changes: 24 additions & 21 deletions Vonage/Common/Client/VonageHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class VonageHttpClient
private readonly HttpClient client;
private readonly IJsonSerializer jsonSerializer;
private readonly Result<HttpClientOptions> requestOptions;

/// <summary>
/// Creates a custom Http Client for Vonage purposes.
/// </summary>
Expand All @@ -30,24 +30,25 @@ public VonageHttpClient(VonageHttpClientConfiguration configuration, IJsonSerial
.Map(header =>
new HttpClientOptions(header, UserAgentProvider.GetFormattedUserAgent(configuration.UserAgent)));
}

/// <summary>
/// Sends a HttpRequest.
/// </summary>
/// <param name="request">The request to send.</param>
/// <returns>Success if the operation succeeds, Failure it if fails.</returns>
public async Task<Result<Unit>> SendAsync<T>(Result<T> request) where T : IVonageRequest =>
await this.SendRequest(request, this.BuildHttpRequestMessage, this.ParseFailure<Unit>, CreateSuccessResult);

await this.SendRequest(request, this.BuildHttpRequestMessage, this.ParseFailure<Unit>, CreateSuccessResult)
.ConfigureAwait(false);

/// <summary>
/// Sends a HttpRequest without Authorization and UserAgent headers.
/// </summary>
/// <param name="request">The request to send.</param>
/// <returns>Success if the operation succeeds, Failure it if fails.</returns>
public async Task<Result<Unit>> SendWithoutHeadersAsync<T>(Result<T> request) where T : IVonageRequest =>
await this.SendRequest(request, value => value.BuildRequestMessage(), this.ParseFailure<Unit>,
CreateSuccessResult);

CreateSuccessResult).ConfigureAwait(false);
/// <summary>
/// Sends a HttpRequest and returns the raw content.
/// </summary>
Expand All @@ -57,8 +58,8 @@ await this.SendRequest(request, value => value.BuildRequestMessage(), this.Parse
public async Task<Result<string>> SendWithRawResponseAsync<TRequest>(Result<TRequest> request)
where TRequest : IVonageRequest =>
await this.SendRequest(request, this.BuildHttpRequestMessage, this.ParseFailure<string>,
responseData => responseData.Content);

responseData => responseData.Content).ConfigureAwait(false);
/// <summary>
/// Sends a HttpRequest and parses the response.
/// </summary>
Expand All @@ -67,38 +68,39 @@ await this.SendRequest(request, this.BuildHttpRequestMessage, this.ParseFailure<
public async Task<Result<TResponse>> SendWithResponseAsync<TRequest, TResponse>(Result<TRequest> request)
where TRequest : IVonageRequest =>
await this.SendRequest(request, this.BuildHttpRequestMessage, this.ParseFailure<TResponse>,
this.ParseSuccess<TResponse>);

this.ParseSuccess<TResponse>).ConfigureAwait(false);
private Result<HttpRequestMessage> BuildHttpRequestMessage<T>(T value) where T : IVonageRequest =>
this.requestOptions
.Map(options => value
.BuildRequestMessage()
.WithAuthenticationHeader(options.AuthenticationHeader)
.WithUserAgent(options.UserAgent));

private HttpFailure CreateFailureResult(HttpStatusCode code, string responseContent) =>
this.jsonSerializer
.DeserializeObject<ErrorResponse>(responseContent)
.Match(success => HttpFailure.From(code, success.Message, responseContent),
failure => HttpFailure.From(code, failure.GetFailureMessage(), responseContent));

private static HttpFailure CreateFailureResult(HttpStatusCode code) => HttpFailure.From(code);

private static Result<Unit> CreateSuccessResult(ResponseData response) => Result<Unit>.FromSuccess(Unit.Default);

private static async Task<ResponseData> ExtractResponseData(HttpResponseMessage response) =>
new(response.StatusCode, response.IsSuccessStatusCode, await response.Content.ReadAsStringAsync());

new ResponseData(response.StatusCode, response.IsSuccessStatusCode,
await response.Content.ReadAsStringAsync().ConfigureAwait(false));

private Result<T> ParseFailure<T>(ResponseData response) =>
MaybeExtensions.From(response.Content)
.Match(value => this.CreateFailureResult(response.Code, value), () => CreateFailureResult(response.Code))
.ToResult<T>();

private Result<T> ParseSuccess<T>(ResponseData response) =>
this.jsonSerializer
.DeserializeObject<T>(response.Content)
.Match(Result<T>.FromSuccess, Result<T>.FromFailure);

private async Task<Result<TResponse>> SendRequest<TRequest, TResponse>(
Result<TRequest> request,
Func<TRequest, Result<HttpRequestMessage>> httpRequestConversion,
Expand All @@ -108,9 +110,10 @@ await request
.Bind(httpRequestConversion)
.MapAsync(value => this.client.SendAsync(value))
.MapAsync(ExtractResponseData)
.Bind(response => !response.IsSuccessStatusCode ? failure(response) : success(response));

.Bind(response => !response.IsSuccessStatusCode ? failure(response) : success(response))
.ConfigureAwait(false);

private sealed record ResponseData(HttpStatusCode Code, bool IsSuccessStatusCode, string Content);

private sealed record HttpClientOptions(AuthenticationHeaderValue AuthenticationHeader, string UserAgent);
}
Loading

0 comments on commit a97f908

Please sign in to comment.