< Summary

Information
Class: NanoRoute.AwsLambda.DtoMappingExtensions
Assembly: NanoRoute.AwsLambda.dll
File(s): /home/runner/work/nanoroute/nanoroute/Src/NanoRoute.AwsLambda/Private/DtoMappingExtensions.cs
Line coverage
100%
Covered lines: 45
Uncovered lines: 0
Coverable lines: 45
Total lines: 158
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBlocks covered Blocks not covered
DtoMappingExtensions()20
CreateUri(...)360
CreateRequestMessage(...)480

File(s)

/home/runner/work/nanoroute/nanoroute/Src/NanoRoute.AwsLambda/Private/DtoMappingExtensions.cs

#LineLine coverage
 1/********************************************************************************
 2* DtoMappingExtensions.cs                                                       *
 3*                                                                               *
 4* Author: Denes Solti                                                           *
 5********************************************************************************/
 6using System;
 7using System.Collections.Generic;
 8using System.IO;
 9using System.Net.Http;
 10using System.Net.Http.Headers;
 11using System.Text.RegularExpressions;
 12using System.Threading.Tasks;
 13
 14using Amazon.Lambda.APIGatewayEvents;
 15
 16namespace NanoRoute.AwsLambda
 17{
 18    using Properties;
 19
 20    /// <summary>
 21    /// https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
 22    /// </summary>
 23    internal static class DtoMappingExtensions
 24    {
 125        private static readonly Regex s_protoMatcher = new(@"(?:^|;\s*)proto=(?:""(?<proto>[^""]+)""|(?<proto>[^;]+))");
 26
 27        public static Uri CreateUri(this APIGatewayHttpApiV2ProxyRequest request)
 128        {
 129            if
 130            (
 131                HostAndPort(request.Headers) is not { Length: > 0 } hostAndPort ||
 132                Scheme(request.Headers) is not { Length: > 0 } scheme ||
 133                // Parse the base URI as a URI so host:port and IPv6 literals are handled correctly
 134                !Uri.TryCreate($"{scheme}://{hostAndPort}", UriKind.Absolute, out Uri baseUri)
 135            )
 136                throw new InvalidOperationException(Resources.ERR_UNKNOWN_URI);
 37
 138            UriBuilder builder = new(baseUri)
 139            {
 140                Path = request.RawPath is { Length: > 0 } path ? path : "/",
 141                Query = request.RawQueryString is { Length: > 0 } query ? query : null
 142            };
 43
 144            return builder.Uri;
 45
 46            static string? HostAndPort(IDictionary<string, string> headers)
 47            {
 48                if (headers.TryGetValue("host" /*AWS lowercases the header names*/, out string hostAndPort))
 49                    return hostAndPort;
 50
 51                return null;
 52            }
 53
 54            static string? Scheme(IDictionary<string, string> headers)
 55            {
 56                if (headers.TryGetValue("forwarded", out string forwarded) && s_protoMatcher.Match(forwarded) is { Succe
 57                    return match.Groups["proto"].Value;
 58
 59                if (headers.TryGetValue("x-forwarded-proto", out string proto))
 60                    return proto;
 61
 62                return null;
 63            }
 164        }
 65
 66        public static HttpRequestMessage CreateRequestMessage(this APIGatewayHttpApiV2ProxyRequest request)
 167        {
 168            HttpRequestMessage requestMessage = new(new HttpMethod(request.RequestContext.Http.Method), request.CreateUr
 169            {
 170                Content = request switch
 171                {
 172                    { IsBase64Encoded: false } and { Body.Length: > 0 } => new StringContent(request.Body),
 173                    { IsBase64Encoded: true }  and { Body.Length: > 0 } => new StreamContent
 174                    (
 175                        new MemoryStream
 176                        (
 177                            Convert.FromBase64String(request.Body)
 178                        )
 179                    ),
 180                    _ => null
 181                }
 182            };
 83
 184            foreach (KeyValuePair<string, string> header in request.Headers)
 185            {
 186                HttpHeaders headers = requestMessage.Content is not null && HttpRequestMessage.ContentHeaders.Contains(h
 187                    ? requestMessage.Content.Headers
 188                    : requestMessage.Headers;
 89
 90                // Some header (like Content-Type) has its default value. Without this line we'd just append the value l
 191                headers.Remove(header.Key);
 192                headers.TryAddWithoutValidation(header.Key, header.Value);
 193            }
 94
 195            requestMessage.Properties[Router.OriginalRequestName] = request;
 196            requestMessage.Properties[Router.TraceIdName] = request.RequestContext.RequestId;
 97
 198            return requestMessage;
 199        }
 100
 101        public static async Task<APIGatewayHttpApiV2ProxyResponse> CreateResponse(this HttpResponseMessage responseMessa
 102        {
 103            Dictionary<string, string> headers = new(StringComparer.OrdinalIgnoreCase);
 104            List<string> cookies = new();
 105
 106            CopyHeaders(responseMessage.Headers, headers, cookies);
 107
 108            if (responseMessage.Content is not null)
 109                CopyHeaders(responseMessage.Content.Headers, headers, cookies);
 110
 111            APIGatewayHttpApiV2ProxyResponse response = new()
 112            {
 113                StatusCode = (int) responseMessage.StatusCode,
 114                Headers = headers,
 115                Cookies = cookies.ToArray()
 116            };
 117
 118            switch (responseMessage.Content)
 119            {
 120                case StringContent stringContent:
 121                {
 122                    if (await stringContent.ReadAsStringAsync() is { Length: > 0 } body)
 123                        response.Body = body;
 124                    break;
 125                }
 126                case { } byteContent:
 127                {
 128                    if (await byteContent.ReadAsByteArrayAsync() is { Length: > 0 } body)
 129                    {
 130                        response.Body = Convert.ToBase64String(body);
 131                        response.IsBase64Encoded = true;
 132                    }
 133                    break;
 134                }
 135            }
 136
 137            return response;
 138
 139            static void CopyHeaders(IEnumerable<KeyValuePair<string, IEnumerable<string>>> source, Dictionary<string, st
 140            {
 141                foreach (KeyValuePair<string, IEnumerable<string>> header in source)
 142                {
 143                    if (string.Equals(header.Key, "Set-Cookie", StringComparison.OrdinalIgnoreCase))
 144                    {
 145                        cookies.AddRange(header.Value);
 146                        continue;
 147                    }
 148
 149                    string value = string.Join(",", header.Value);
 150
 151                    headers[header.Key] = headers.TryGetValue(header.Key, out string? existing)
 152                        ? $"{existing},{value}"
 153                        : value;
 154                }
 155            }
 156        }
 157    }
 158}