Friday, 4 April 2025

Elegant HTTP Response Parsing in .NET

/// <summary>
/// Extension methods for parsing API responses into OperationalResult<T>.
/// </summary>
public static class ApiResponseParser
{
    /// <summary>
    /// Parses the HTTP response and returns a standardized OperationalResult<T>.
    /// Handles success, primitive types, and structured error responses.
    /// </summary>
    /// <typeparam name="T">The expected type of the result.</typeparam>
    /// <param name="apiResponse">The HTTP response message.</param>
    /// <returns>An OperationalResult containing the parsed result or an error.
    /// </returns>
    public static async Task<OperationalResult<T>> GetParsedApiResponse<T>
                        (this HttpResponseMessage apiResponse)
    {
        var responseBody = await apiResponse.Content.ReadAsStringAsync();

        if (ValidHttpStatusCodes.Contains(apiResponse.StatusCode)
                && apiResponse.StatusCode != HttpStatusCode.BadRequest)
        {
            var type = typeof(T);

            if (string.IsNullOrEmpty(responseBody))
            {
                return OperationalResult<T>.Successed();
            }

            if (type.IsPrimitive)
            {
                var primitiveValue = (T)Convert.ChangeType(apiResponse.Content,
                                     typeof(T));
                return OperationalResult<T>.Successed(primitiveValue);
            }

            var data = responseBody.JsonDeserializeObject<T>();
            return OperationalResult<T>.Successed(data);
        }
        else
        {
            var apiError = responseBody.JsonDeserializeObjectTryParse<ApiError>();

            if (apiError != null)
            {
                return OperationalResult<T>.Failed(
                    (ResponseStatusType)apiError.Code, apiError.Message);
            }

            return OperationalResult<T>.Failed(
                (ResponseStatusType)apiResponse.StatusCode,
                new ErrorDto($"Error: {apiResponse.StatusCode}"
                , apiResponse.ReasonPhrase)
            );
        }
    }

    /// <summary>
    /// A list of valid HTTP status codes considered as successful responses.
    /// </summary>
    public static readonly IEnumerable<HttpStatusCode> ValidHttpStatusCodes =
                            new List<HttpStatusCode>
                            {
                                HttpStatusCode.OK,
                                HttpStatusCode.Created,
                                HttpStatusCode.NoContent
                            };
}