using System;
using System.Collections;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace ReC.Client
{
///
/// Provides shared helpers for composing requests.
///
internal static class ReCClientHelpers
{
#if NETFRAMEWORK
///
/// Builds a query string from the provided key/value pairs, skipping null values.
///
/// The key/value pairs to include in the query string.
/// A query string beginning with '?', or an empty string if no values are provided.
public static string BuildQuery(params (string Key, object Value)[] parameters)
#else
///
/// Builds a query string from the provided key/value pairs, skipping null values.
///
/// The key/value pairs to include in the query string.
/// A query string beginning with '?', or an empty string if no values are provided.
public static string BuildQuery(params (string Key, object? Value)[] parameters)
#endif
{
var parts = parameters
.Where(p => p.Value != null)
.Select(p => $"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(Convert.ToString(p.Value, CultureInfo.InvariantCulture) ?? string.Empty)}");
var query = string.Join("&", parts);
return string.IsNullOrWhiteSpace(query) ? string.Empty : $"?{query}";
}
///
/// Creates a JSON content payload from the provided object.
///
/// The type of the payload.
/// The payload to serialize.
/// A instance ready for HTTP requests.
public static JsonContent ToJsonContent(T payload) => JsonContent.Create(payload);
///
/// Builds a query string from the public readable properties of ,
/// skipping properties whose values are .
///
/// The payload type.
/// The payload to serialize into a query string.
/// A query string beginning with '?', or an empty string if no values are provided.
public static string BuildQueryFromObject(T payload)
{
if (payload == null)
return string.Empty;
var props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead && p.GetIndexParameters().Length == 0);
var parts = props
.Select(p => new { p.Name, Value = p.GetValue(payload) })
.Where(p => p.Value != null)
.Select(p => $"{Uri.EscapeDataString(p.Name)}={Uri.EscapeDataString(Convert.ToString(p.Value, CultureInfo.InvariantCulture) ?? string.Empty)}");
var query = string.Join("&", parts);
return string.IsNullOrWhiteSpace(query) ? string.Empty : $"?{query}";
}
///
/// Logs the outcome of an HTTP response. Throws a when the
/// response indicates a non-success status code; otherwise (optionally) writes an informational
/// log entry containing the request and response details.
///
/// The HTTP response to inspect.
/// An optional logger used to record the outcome. May be .
/// When , successful responses are not logged.
/// A token to cancel the operation.
#if NETFRAMEWORK
public static async Task HandleResponseAsync(HttpResponseMessage response, ILogger logger = null, bool logSuccess = true, CancellationToken cancel = default)
#else
public static async Task HandleResponseAsync(HttpResponseMessage response, ILogger? logger = null, bool logSuccess = true, CancellationToken cancel = default)
#endif
{
var request = response.RequestMessage;
var method = request?.Method?.Method;
var uri = request?.RequestUri;
var statusCode = (int)response.StatusCode;
if (response.IsSuccessStatusCode)
{
if (logSuccess)
{
logger?.LogInformation(
"ReC API request succeeded. {Method} {Uri} -> {StatusCode} ({ReasonPhrase})",
method,
uri,
statusCode,
response.ReasonPhrase);
}
return;
}
#if NETFRAMEWORK
string body = null;
#else
string? body = null;
#endif
if (response.Content != null)
{
try
{
#if NETFRAMEWORK
body = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
#else
body = await response.Content.ReadAsStringAsync(cancel).ConfigureAwait(false);
#endif
}
catch
{
// Swallow body read failures; status info is still propagated.
}
}
var message = $"ReC API request failed with status {statusCode} ({response.ReasonPhrase}). "
+ $"{method} {uri}"
+ (string.IsNullOrWhiteSpace(body) ? string.Empty : $": {body}");
throw new ReCApiException(message, response.StatusCode, response.ReasonPhrase, body, method, uri);
}
}
}