Skip to content

Commit

Permalink
fix(request-content-type): use provided content type in case of null …
Browse files Browse the repository at this point in the history
…body (#75)
  • Loading branch information
hamzamahmood authored Aug 28, 2024
1 parent f8c2005 commit 518dc0b
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 33 deletions.
28 changes: 27 additions & 1 deletion APIMatic.Core.Test/Api/HttpPost/ApiCallPostTest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
Expand Down Expand Up @@ -494,5 +494,31 @@ public void ApiCall_PostNullHeaderValue_OKResponse()
Assert.NotNull(actual.Data);
Assert.AreEqual(actual.Data.Message, expected.Message);
}

[Test]
public void ApiCall_PostNullBodyValue_OKResponse()
{
//Arrange
const string url = "/apicall/null-body-post/200";

handlerMock.When(GetCompleteUrl(url))
.With(req =>
{
Assert.AreEqual("application/json", req.Content?.Headers.ContentType?.MediaType);
return true;
})
.Respond(HttpStatusCode.OK);

var apiCall = CreateApiCall<ServerResponse>()
.RequestBuilder(requestBuilderAction => requestBuilderAction
.Setup(HttpMethod.Post, url)
.Parameters(p => p
.Body(b => b.Setup(null))
.Header(h => h.Setup("content-type", "application/json"))))
.ExecuteAsync();

// Act
CoreHelper.RunTask(apiCall);
}
}
}
72 changes: 41 additions & 31 deletions APIMatic.Core/Http/HttpClientWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using System.Threading.Tasks;
using APIMatic.Core.Http.Configuration;
using APIMatic.Core.Types.Sdk;
using APIMatic.Core.Utilities;
using Polly;
using Polly.Retry;
using Polly.Timeout;
Expand Down Expand Up @@ -104,38 +105,19 @@ private HttpRequestMessage CreateHttpRequestMessageFromRequest(CoreRequest reque
HttpRequestMessage requestMessage = new HttpRequestMessage
{
RequestUri = new Uri(request.QueryUrl),
Method = request.HttpMethod,
Method = request.HttpMethod
};

if (request.Headers != null)
{
foreach (var headers in request.Headers)
{
requestMessage.Headers.TryAddWithoutValidation(headers.Key, headers.Value);
}
}
AddHeadersToRequestMessage(requestMessage, request);
if (IsHeaderOnlyHttpMethod(request.HttpMethod)) return requestMessage;

if (IsHeaderOnlyHttpMethod(request.HttpMethod))
if (request.HasFormParameters)
{
requestMessage.Content = GetFormContent(request);
return requestMessage;
}

if (request.Body == null)
{
if (CheckFormParametersForMultiPart(request.FormParameters))
{
requestMessage.Content = GetMultipartFormDataContentFromRequest(request);
return requestMessage;
}

requestMessage.Content = new FormUrlEncodedContent(request.FormParameters.Select(param => new KeyValuePair<string, string>(param.Key, param.Value.ToString())).ToList());
return requestMessage;
}

string contentType = request.Headers?.Where(p => p.Key.Equals("content-type", StringComparison.InvariantCultureIgnoreCase))
.Select(x => x.Value)
.FirstOrDefault();

var contentType = request.GetContentType();
if (request.Body is CoreFileStreamInfo file)
{
file.FileStream.Position = 0;
Expand All @@ -146,21 +128,50 @@ private HttpRequestMessage CreateHttpRequestMessageFromRequest(CoreRequest reque

if (string.IsNullOrEmpty(contentType))
{
requestMessage.Content = new StringContent(request.Body.ToString(), Encoding.UTF8, "text/plain");
requestMessage.Content =
new StringContent(request.GetBodyAsString(), Encoding.UTF8, "text/plain");
return requestMessage;
}

if (contentType.Equals("application/json; charset=utf-8", StringComparison.OrdinalIgnoreCase))
if (IsContentTypeJsonUtf8(contentType))
{
requestMessage.Content = new StringContent(request.Body.ToString(), Encoding.UTF8, "application/json");
requestMessage.Content =
new StringContent(request.GetBodyAsString(), Encoding.UTF8, "application/json");
return requestMessage;
}

if (request.Body == null) return requestMessage;

requestMessage.Content = GetByteArrayContentFromRequestBody(request.Body);
GetByteArrayContentType(requestMessage.Content.Headers, contentType);
return requestMessage;
}

private static void AddHeadersToRequestMessage(HttpRequestMessage requestMessage, CoreRequest request)
{
foreach (var headers in request.Headers ?? Enumerable.Empty<KeyValuePair<string, string>>())
{
requestMessage.Headers.TryAddWithoutValidation(headers.Key, headers.Value);
}
}

private static HttpContent GetFormContent(CoreRequest request)
{
if (CheckFormParametersForMultiPart(request.FormParameters))
{
return GetMultipartFormDataContentFromRequest(request);
}

return new FormUrlEncodedContent(request.FormParameters
.Select(param => new KeyValuePair<string, string>(param.Key, param.Value.ToString())).ToList());
}

private static bool IsContentTypeJsonUtf8(string contentType)
{
return contentType.EqualsIgnoreCase("application/json") ||
contentType.EqualsIgnoreCase("application/json; charset=utf-8");
}

private static void GetByteArrayContentType(HttpContentHeaders contentHeader, string contentType)
{
try
Expand Down Expand Up @@ -239,9 +250,8 @@ private static bool IsHeaderOnlyHttpMethod(HttpMethod method)

private static bool CheckFormParametersForMultiPart(IReadOnlyCollection<KeyValuePair<string, object>> formParameters)
{
return formParameters != null &&
(formParameters.Any(f => f.Value is MultipartContent) ||
formParameters.Any(f => f.Value is CoreFileStreamInfo));
return formParameters.Any(f => f.Value is MultipartContent) ||
formParameters.Any(f => f.Value is CoreFileStreamInfo);
}

private bool ShouldRetry(HttpResponseMessage response, RetryOption retryOption)
Expand Down
2 changes: 1 addition & 1 deletion APIMatic.Core/Request/RequestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ private bool ContentHeaderKeyRequired(string key)
{
return false;
}
if (headers.Any(p => p.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)))
if (headers.Any(p => p.Key.EqualsIgnoreCase(key)))
{
return false;
}
Expand Down
26 changes: 26 additions & 0 deletions APIMatic.Core/Types/Sdk/CoreRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ public CoreRequest(HttpMethod method, string queryUrl, Dictionary<string, string
/// </summary>
internal bool HasBinaryResponse { get; set; }

/// <summary>
/// Gets a value indicating whether the request is likely to have form content.
/// This is determined by checking if the <see cref="Body"/> is null
/// and if the <see cref="FormParameters"/> collection is not null and contains any items.
/// </summary>
internal bool HasFormParameters => Body == null && FormParameters != null && FormParameters.Any();

/// <summary>
/// Concatenate values from a Dictionary to this object.
/// </summary>
Expand All @@ -103,6 +110,25 @@ public void AddQueryParameters(Dictionary<string, object> queryParamaters)
?? new Dictionary<string, object>(queryParamaters);
}

/// <summary>
/// Retrieves the value of the "Content-Type" header from the request headers.
/// </summary>
/// <returns>
/// The value of the "Content-Type" header if present; otherwise, <c>null</c>.
/// </returns>
internal string GetContentType() => Headers?.Where(p => p.Key.EqualsIgnoreCase("content-type"))
.Select(x => x.Value)
.FirstOrDefault();

/// <summary>
/// Converts the body of the request to a string representation.
/// </summary>
/// <returns>
/// The string representation of the <see cref="Body"/> if it is not <c>null</c>;
/// otherwise, returns an empty string.
/// </returns>
internal string GetBodyAsString() => Body == null ? string.Empty : Body.ToString();

/// <inheritdoc/>
public override string ToString()
{
Expand Down
15 changes: 15 additions & 0 deletions APIMatic.Core/Utilities/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// <copyright file="StringExtensions.cs" company="APIMatic">
// Copyright (c) APIMatic. All rights reserved.
// </copyright>
using System;

namespace APIMatic.Core.Utilities
{
internal static class StringExtensions
{
public static bool EqualsIgnoreCase(this string source, string target)
{
return source.Equals(target, StringComparison.InvariantCultureIgnoreCase);
}
}
}

0 comments on commit 518dc0b

Please sign in to comment.