From a685af86f5cef70bac3ebc5870a89e4ff01ef00a Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 2 Dec 2025 07:35:14 -0500 Subject: [PATCH 01/38] feat(api-gateway): enhance response handling in `Program.cs` - Updated handler to utilize `ApiGatewayResponseEnvelopes` for dual response types. - Added support for bad request responses with `ErrorDetails`. - Introduced utility methods (`Ok`, `BadRequest`) for response creation. - Extended JSON serialization to include `ErrorDetails`. --- .../MinimalLambda.Example.Events/Program.cs | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/examples/MinimalLambda.Example.Events/Program.cs b/examples/MinimalLambda.Example.Events/Program.cs index 752238b1..f7355b47 100644 --- a/examples/MinimalLambda.Example.Events/Program.cs +++ b/examples/MinimalLambda.Example.Events/Program.cs @@ -1,4 +1,6 @@ -using System; +#region + +using System; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; @@ -8,6 +10,8 @@ using MinimalLambda.Builder; using MinimalLambda.Envelopes.ApiGateway; +#endregion + var builder = LambdaApplication.CreateBuilder(); builder.Services.ConfigureLambdaHostOptions(options => @@ -26,16 +30,19 @@ var lambda = builder.Build(); lambda.MapHandler( - ([Event] ApiGatewayRequestEnvelope request, ILogger logger) => + ApiGatewayResponseEnvelopes ( + [Event] ApiGatewayRequestEnvelope request, + ILogger logger + ) => { logger.LogInformation("In Handler. Payload: {Payload}", request.Body); - return new ApiGatewayResponseEnvelope - { - BodyContent = new Response($"Hello {request.BodyContent?.Name}!", DateTime.UtcNow), - StatusCode = 201, - Headers = new Dictionary { ["Content-Type"] = "application/json" }, - }; + if (request.BodyContent == null) + return ApiGatewayResponse.BadRequest(new ErrorDetails("bummer")); + + return ApiGatewayResponse.Ok( + new Response($"Hello {request.BodyContent?.Name}!", DateTime.UtcNow) + ); } ); @@ -43,10 +50,32 @@ internal record Response(string Message, DateTime TimestampUtc); +internal record ErrorDetails(string Message); + internal record Request(string Name); [JsonSerializable(typeof(ApiGatewayRequestEnvelope))] -[JsonSerializable(typeof(ApiGatewayResponseEnvelope))] +[JsonSerializable(typeof(ApiGatewayResponseEnvelopes))] [JsonSerializable(typeof(Request))] +[JsonSerializable(typeof(ErrorDetails))] [JsonSerializable(typeof(Response))] internal partial class SerializerContext : JsonSerializerContext; + +public static class ApiGatewayResponse +{ + public static ApiGatewayResponseEnvelope Ok(T bodyContent) => + new() + { + BodyContent = bodyContent, + StatusCode = 200, + Headers = new Dictionary { ["Content-Type"] = "application/json" }, + }; + + public static ApiGatewayResponseEnvelope BadRequest(T bodyContent) => + new() + { + BodyContent = bodyContent, + StatusCode = 400, + Headers = new Dictionary { ["Content-Type"] = "application/json" }, + }; +} From bd5a6b0d0fff654e19536cf7f89aab5bf0e50653 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 2 Dec 2025 15:20:56 -0500 Subject: [PATCH 02/38] refactor(api-gateway): rename `ApiGatewayResponseEnvelope` to `ApiGatewayResult` - Updated all references from `ApiGatewayResponseEnvelope` to `ApiGatewayResult`. - Renamed test classes and methods to align with the new type name. - Adjusted extension methods in `Program.cs` to work with `ApiGatewayResult`. - Simplified response creation to use new naming conventions. - Enhanced JSON serialization logic to accommodate updated class structure. --- .../MinimalLambda.Example.Events/Program.cs | 62 ++++++++++++------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/examples/MinimalLambda.Example.Events/Program.cs b/examples/MinimalLambda.Example.Events/Program.cs index f7355b47..0c8b079f 100644 --- a/examples/MinimalLambda.Example.Events/Program.cs +++ b/examples/MinimalLambda.Example.Events/Program.cs @@ -30,7 +30,7 @@ var lambda = builder.Build(); lambda.MapHandler( - ApiGatewayResponseEnvelopes ( + ApiGatewayResult ( [Event] ApiGatewayRequestEnvelope request, ILogger logger ) => @@ -38,9 +38,12 @@ ILogger logger logger.LogInformation("In Handler. Payload: {Payload}", request.Body); if (request.BodyContent == null) - return ApiGatewayResponse.BadRequest(new ErrorDetails("bummer")); + return ApiGatewayResult.InternalServerError(new BadErrorDetails("Bad error")); - return ApiGatewayResponse.Ok( + if (request.BodyContent.Name == "error") + return ApiGatewayResult.BadRequest(new ErrorDetails("bummer")); + + return ApiGatewayResult.Ok( new Response($"Hello {request.BodyContent?.Name}!", DateTime.UtcNow) ); } @@ -48,34 +51,47 @@ ILogger logger await lambda.RunAsync(); +public static class ApiGatewayResultExtensions +{ + extension(ApiGatewayResult) + { + public static ApiGatewayResult Ok(T bodyContent) => + new() + { + BodyContent = bodyContent, + StatusCode = 200, + Headers = new Dictionary { ["Content-Type"] = "application/json" }, + }; + + public static ApiGatewayResult BadRequest(T bodyContent) => + new() + { + BodyContent = bodyContent, + StatusCode = 400, + Headers = new Dictionary { ["Content-Type"] = "application/json" }, + }; + + public static ApiGatewayResult InternalServerError(T bodyContent) => + new() + { + BodyContent = bodyContent, + StatusCode = 500, + Headers = new Dictionary { ["Content-Type"] = "application/json" }, + }; + } +} + internal record Response(string Message, DateTime TimestampUtc); internal record ErrorDetails(string Message); +internal record BadErrorDetails(string Message); + internal record Request(string Name); [JsonSerializable(typeof(ApiGatewayRequestEnvelope))] -[JsonSerializable(typeof(ApiGatewayResponseEnvelopes))] +[JsonSerializable(typeof(ApiGatewayResult))] [JsonSerializable(typeof(Request))] [JsonSerializable(typeof(ErrorDetails))] [JsonSerializable(typeof(Response))] internal partial class SerializerContext : JsonSerializerContext; - -public static class ApiGatewayResponse -{ - public static ApiGatewayResponseEnvelope Ok(T bodyContent) => - new() - { - BodyContent = bodyContent, - StatusCode = 200, - Headers = new Dictionary { ["Content-Type"] = "application/json" }, - }; - - public static ApiGatewayResponseEnvelope BadRequest(T bodyContent) => - new() - { - BodyContent = bodyContent, - StatusCode = 400, - Headers = new Dictionary { ["Content-Type"] = "application/json" }, - }; -} From a7db5fc788ff81dcbb7f106a6ed4df6381591139 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 2 Dec 2025 17:07:37 -0500 Subject: [PATCH 03/38] feat(local-dev): enhance AWS Lambda test tool configuration - Updated `LocalDevTasks.yml` to include a `--config-storage-path` option for the lambda test tool. - Added a default API Gateway request example in `lambda_test_tool/SavedRequests`. --- .../API Gateway - Basic.json | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 lambda_test_tool/SavedRequests/__DefaultFunction__/API Gateway - Basic.json diff --git a/lambda_test_tool/SavedRequests/__DefaultFunction__/API Gateway - Basic.json b/lambda_test_tool/SavedRequests/__DefaultFunction__/API Gateway - Basic.json new file mode 100644 index 00000000..8b46b39d --- /dev/null +++ b/lambda_test_tool/SavedRequests/__DefaultFunction__/API Gateway - Basic.json @@ -0,0 +1,123 @@ +{ + "body": "{\"name\":\"jonas\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": true, + "queryStringParameters": { + "foo": "bar" + }, + "multiValueQueryStringParameters": { + "foo": [ + "bar" + ] + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" + ], + "Accept-Encoding": [ + "gzip, deflate, sdch" + ], + "Accept-Language": [ + "en-US,en;q=0.8" + ], + "Cache-Control": [ + "max-age=0" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-Country": [ + "US" + ], + "Host": [ + "0123456789.execute-api.us-east-1.amazonaws.com" + ], + "Upgrade-Insecure-Requests": [ + "1" + ], + "User-Agent": [ + "Custom User Agent String" + ], + "Via": [ + "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==" + ], + "X-Forwarded-For": [ + "127.0.0.1, 127.0.0.2" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} \ No newline at end of file From b3208b2fcb75a6a102d19a3b4eb6d5e6c558b8f6 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 2 Dec 2025 17:07:48 -0500 Subject: [PATCH 04/38] feat(api-gateway): add Microsoft.AspNetCore.Http package reference - Added `Microsoft.AspNetCore.Http` reference to project file for API Gateway use cases. - Updated `Directory.Packages.props` with version `2.3.0` for consistent dependency management. --- .../MinimalLambda.Envelopes.ApiGateway.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/MinimalLambda.Envelopes.ApiGateway.csproj b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/MinimalLambda.Envelopes.ApiGateway.csproj index 98c6f1f4..8cb38cae 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/MinimalLambda.Envelopes.ApiGateway.csproj +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/MinimalLambda.Envelopes.ApiGateway.csproj @@ -16,6 +16,7 @@ + From eb49a411507316c85874136a8e599d0a1e0d6f2f Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 2 Dec 2025 17:08:01 -0500 Subject: [PATCH 05/38] refactor(api-gateway): update class naming from `ApiGatewayResponseEnvelope` to `ApiGatewayResult` - Renamed `ApiGatewayResponseEnvelope` to `ApiGatewayResult` across the project for consistency. - Updated test class and method names to align with the new naming convention. - Adjusted logic in tests and production code to work with the renamed classes. - Introduced a new `ApiGatewayResult` file for improved structure and clarity. - Removed `ApiGatewayResponseEnvelope` related artifacts and references. --- .../ApiGatewayResult.cs | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs new file mode 100644 index 00000000..9dee416e --- /dev/null +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs @@ -0,0 +1,136 @@ +using System.Text.Json.Serialization; +using Amazon.Lambda.APIGatewayEvents; +using AwsLambda.Host.Options; +using Microsoft.AspNetCore.Http; + +namespace AwsLambda.Host.Envelopes.ApiGateway; + +public sealed class ApiGatewayResult : APIGatewayProxyResponse, IResponseEnvelope +{ + [JsonIgnore] + private readonly IResponseEnvelope? _inner; + + private ApiGatewayResult(APIGatewayProxyResponse response) + { + _inner = response as IResponseEnvelope; + StatusCode = response.StatusCode; + Headers = response.Headers; + MultiValueHeaders = response.MultiValueHeaders; + Body = response.Body; + IsBase64Encoded = response.IsBase64Encoded; + } + + /// + public void PackPayload(EnvelopeOptions options) + { + if (_inner is null) + return; + + _inner.PackPayload(options); + Body = ((APIGatewayProxyResponse)_inner).Body; + } + + // ┌──────────────────────────────────────────────────────────┐ + // │ Headers Helpers │ + // └──────────────────────────────────────────────────────────┘ + + public ApiGatewayResult AddHeader(string key, string value) + { + Headers[key] = value; + + return this; + } + + public ApiGatewayResult AddContentType(string contentType) => + AddHeader("Content-Type", contentType); + + // ┌──────────────────────────────────────────────────────────┐ + // │ Basic Fatories │ + // └──────────────────────────────────────────────────────────┘ + + public static ApiGatewayResult Create(APIGatewayProxyResponse response) => new(response); + + public static ApiGatewayResult Create(ApiGatewayResponseEnvelopeBase response) => + new(response); + + public static ApiGatewayResult Create( + int statusCode, + T? bodyContent, + Dictionary headers, + IDictionary> multiValueHeaders + ) => + Create( + new ApiGatewayResponseEnvelope + { + BodyContent = bodyContent, + StatusCode = statusCode, + Headers = headers, + MultiValueHeaders = multiValueHeaders, + } + ); + + public static ApiGatewayResult Create( + int statusCode, + string body, + Dictionary headers, + IDictionary> multiValueHeaders + ) => + Create( + new APIGatewayProxyResponse + { + StatusCode = statusCode, + Headers = headers, + MultiValueHeaders = multiValueHeaders, + Body = body, + } + ); + + public static ApiGatewayResult Json(int statusCode, T bodyContent) => + Create( + new ApiGatewayResponseEnvelope + { + BodyContent = bodyContent, + StatusCode = statusCode, + Headers = new Dictionary + { + ["Content-Type"] = "application/json; charset=utf-8", + }, + } + ); + + public static ApiGatewayResult Text(int statusCode, string body) => + Create( + new APIGatewayProxyResponse + { + StatusCode = statusCode, + Headers = new Dictionary + { + ["Content-Type"] = "text/plain; charset=utf-8", + }, + Body = body, + } + ); + + public static ApiGatewayResult Status(int statusCode) => + Create(new APIGatewayProxyResponse { StatusCode = statusCode }); + + // ┌──────────────────────────────────────────────────────────┐ + // │ Status Code Factories │ + // └──────────────────────────────────────────────────────────┘ + + public static ApiGatewayResult Ok() => Status(StatusCodes.Status200OK); + + public static ApiGatewayResult Ok(T bodyContent) => + Json(StatusCodes.Status200OK, bodyContent); + + public static ApiGatewayResult BadRequest() => Status(StatusCodes.Status400BadRequest); + + public static ApiGatewayResult BadRequest(T bodyContent) => + Json(StatusCodes.Status400BadRequest, bodyContent); + + public static ApiGatewayResult InternalServerError() => + Status(StatusCodes.Status500InternalServerError); + + public static ApiGatewayResult InternalServerError(T bodyContent) => + Json(StatusCodes.Status500InternalServerError, bodyContent); +} From 3c7a91e09d344e8f05a1eead2ec8f896b529758a Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 2 Dec 2025 17:08:12 -0500 Subject: [PATCH 06/38] refactor(api-gateway): simplify handler logic and remove unused extensions - Streamlined handler implementation in `Program.cs` for better clarity. - Removed redundant `#region` and unused import statements. - Eliminated `ApiGatewayResultExtensions` to reduce duplication. - Enhanced response handling with header support in `ApiGatewayResult.Ok`. - Adjusted JSON serialization types to match updated logic. --- .../MinimalLambda.Example.Events/Program.cs | 50 +++---------------- 1 file changed, 6 insertions(+), 44 deletions(-) diff --git a/examples/MinimalLambda.Example.Events/Program.cs b/examples/MinimalLambda.Example.Events/Program.cs index 0c8b079f..36d268d4 100644 --- a/examples/MinimalLambda.Example.Events/Program.cs +++ b/examples/MinimalLambda.Example.Events/Program.cs @@ -1,7 +1,4 @@ -#region - -using System; -using System.Collections.Generic; +using System; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.DependencyInjection; @@ -10,8 +7,6 @@ using MinimalLambda.Builder; using MinimalLambda.Envelopes.ApiGateway; -#endregion - var builder = LambdaApplication.CreateBuilder(); builder.Services.ConfigureLambdaHostOptions(options => @@ -30,10 +25,7 @@ var lambda = builder.Build(); lambda.MapHandler( - ApiGatewayResult ( - [Event] ApiGatewayRequestEnvelope request, - ILogger logger - ) => + ([Event] ApiGatewayRequestEnvelope request, ILogger logger) => { logger.LogInformation("In Handler. Payload: {Payload}", request.Body); @@ -43,44 +35,14 @@ ILogger logger if (request.BodyContent.Name == "error") return ApiGatewayResult.BadRequest(new ErrorDetails("bummer")); - return ApiGatewayResult.Ok( - new Response($"Hello {request.BodyContent?.Name}!", DateTime.UtcNow) - ); + return ApiGatewayResult + .Ok(new Response($"Hello {request.BodyContent?.Name}!", DateTime.UtcNow)) + .AddHeader("X-Custom-Header", "Custom Value"); } ); await lambda.RunAsync(); -public static class ApiGatewayResultExtensions -{ - extension(ApiGatewayResult) - { - public static ApiGatewayResult Ok(T bodyContent) => - new() - { - BodyContent = bodyContent, - StatusCode = 200, - Headers = new Dictionary { ["Content-Type"] = "application/json" }, - }; - - public static ApiGatewayResult BadRequest(T bodyContent) => - new() - { - BodyContent = bodyContent, - StatusCode = 400, - Headers = new Dictionary { ["Content-Type"] = "application/json" }, - }; - - public static ApiGatewayResult InternalServerError(T bodyContent) => - new() - { - BodyContent = bodyContent, - StatusCode = 500, - Headers = new Dictionary { ["Content-Type"] = "application/json" }, - }; - } -} - internal record Response(string Message, DateTime TimestampUtc); internal record ErrorDetails(string Message); @@ -90,7 +52,7 @@ internal record BadErrorDetails(string Message); internal record Request(string Name); [JsonSerializable(typeof(ApiGatewayRequestEnvelope))] -[JsonSerializable(typeof(ApiGatewayResult))] +[JsonSerializable(typeof(ApiGatewayResult))] [JsonSerializable(typeof(Request))] [JsonSerializable(typeof(ErrorDetails))] [JsonSerializable(typeof(Response))] From 574476491d9a7fcfbaa61c7fa916198ee7f2d1f4 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 2 Dec 2025 17:15:21 -0500 Subject: [PATCH 07/38] feat(api-gateway): extend `ApiGatewayResult` with additional status code factories - Added factory methods for various status codes, including `NoContent`, `Unauthorized`, `NotFound`, `Conflict`, and `UnprocessableEntity`. - Updated existing methods to improve consistency by renaming `Status` to `StatusCode`. - Enhanced readability with categorized comments for status code methods. --- .../ApiGatewayResult.cs | 49 ++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs index 9dee416e..6508db33 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs @@ -13,7 +13,7 @@ public sealed class ApiGatewayResult : APIGatewayProxyResponse, IResponseEnvelop private ApiGatewayResult(APIGatewayProxyResponse response) { _inner = response as IResponseEnvelope; - StatusCode = response.StatusCode; + base.StatusCode = response.StatusCode; Headers = response.Headers; MultiValueHeaders = response.MultiValueHeaders; Body = response.Body; @@ -111,25 +111,62 @@ public static ApiGatewayResult Text(int statusCode, string body) => } ); - public static ApiGatewayResult Status(int statusCode) => + public static ApiGatewayResult StatusCode(int statusCode) => Create(new APIGatewayProxyResponse { StatusCode = statusCode }); // ┌──────────────────────────────────────────────────────────┐ - // │ Status Code Factories │ + // │ StatusCode Code Factories │ // └──────────────────────────────────────────────────────────┘ - public static ApiGatewayResult Ok() => Status(StatusCodes.Status200OK); + // ── 200 Ok ─────────────────────────────────────────────────────────────────────── + + public static ApiGatewayResult Ok() => StatusCode(StatusCodes.Status200OK); public static ApiGatewayResult Ok(T bodyContent) => Json(StatusCodes.Status200OK, bodyContent); - public static ApiGatewayResult BadRequest() => Status(StatusCodes.Status400BadRequest); + // ── 201 No Content ─────────────────────────────────────────────────────────────── + + public static ApiGatewayResult NoContent() => StatusCode(StatusCodes.Status204NoContent); + + // ── 401 Unauthorized ───────────────────────────────────────────────────────────── + + public static ApiGatewayResult Unauthorized() => StatusCode(StatusCodes.Status401Unauthorized); + + // ── 404 Not Found ──────────────────────────────────────────────────────────────── + + public static ApiGatewayResult NotFound() => StatusCode(StatusCodes.Status404NotFound); + + public static ApiGatewayResult NotFound(T bodyContent) => + Json(StatusCodes.Status404NotFound, bodyContent); + + // ── 404 Bad Request ────────────────────────────────────────────────────────────── + + public static ApiGatewayResult BadRequest() => StatusCode(StatusCodes.Status400BadRequest); public static ApiGatewayResult BadRequest(T bodyContent) => Json(StatusCodes.Status400BadRequest, bodyContent); + // ── 409 Conflict ───────────────────────────────────────────────────────────────── + + public static ApiGatewayResult Conflict() => + StatusCode(StatusCodes.Status500InternalServerError); + + public static ApiGatewayResult Conflict(T bodyContent) => + Json(StatusCodes.Status409Conflict, bodyContent); + + // ── 422 Unprocessable Entity ───────────────────────────────────────────────────── + + public static ApiGatewayResult UnprocessableEntity() => + StatusCode(StatusCodes.Status422UnprocessableEntity); + + public static ApiGatewayResult UnprocessableEntity(T bodyContent) => + Json(StatusCodes.Status422UnprocessableEntity, bodyContent); + + // ── 500 Internal Server Error ──────────────────────────────────────────────────── + public static ApiGatewayResult InternalServerError() => - Status(StatusCodes.Status500InternalServerError); + StatusCode(StatusCodes.Status500InternalServerError); public static ApiGatewayResult InternalServerError(T bodyContent) => Json(StatusCodes.Status500InternalServerError, bodyContent); From 958bbf96ea3f0757e0b507958e55634b97e70996 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Tue, 2 Dec 2025 17:23:56 -0500 Subject: [PATCH 08/38] feat(api-gateway): add factory methods for `201 Created` status in `ApiGatewayResult` - Introduced `Created()` and `Created(T bodyContent)` factory methods to handle `201 Created`. - Updated comments for better categorization and consistency with status code methods. --- .../ApiGatewayResult.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs index 6508db33..f0c1d3e1 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs @@ -125,7 +125,14 @@ public static ApiGatewayResult StatusCode(int statusCode) => public static ApiGatewayResult Ok(T bodyContent) => Json(StatusCodes.Status200OK, bodyContent); - // ── 201 No Content ─────────────────────────────────────────────────────────────── + // ── 201 Created ────────────────────────────────────────────────────────────────── + + public static ApiGatewayResult Created() => StatusCode(StatusCodes.Status201Created); + + public static ApiGatewayResult Created(T bodyContent) => + Json(StatusCodes.Status201Created, bodyContent); + + // ── 204 No Content ─────────────────────────────────────────────────────────────── public static ApiGatewayResult NoContent() => StatusCode(StatusCodes.Status204NoContent); From b9f80a8c3c17f6b7d8a0f33ce2d355811a0c8191 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 17:43:01 -0500 Subject: [PATCH 09/38] feat(dependencies): add Microsoft.AspNetCore.Http package to Directory.Packages.props - Added `Microsoft.AspNetCore.Http` version `2.3.0` to support enhanced HTTP scenarios. - Ensured consistent dependency tracking in `Directory.Packages.props`. --- Directory.Packages.props | 1 + 1 file changed, 1 insertion(+) diff --git a/Directory.Packages.props b/Directory.Packages.props index 67464db4..70a68015 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,6 +13,7 @@ + From 5fa44c4d1a7260ed5a5de92db40cadb610af631e Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 17:44:11 -0500 Subject: [PATCH 10/38] refactor(api-gateway): update namespace and dependency references in `ApiGatewayResult` - Changed namespace from `AwsLambda.Host.Envelopes.ApiGateway` to `MinimalLambda.Envelopes.ApiGateway`. - Replaced `AwsLambda.Host.Options` with `MinimalLambda.Options` for consistency. --- .../MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs index f0c1d3e1..b55c90a8 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs @@ -1,9 +1,9 @@ using System.Text.Json.Serialization; using Amazon.Lambda.APIGatewayEvents; -using AwsLambda.Host.Options; using Microsoft.AspNetCore.Http; +using MinimalLambda.Options; -namespace AwsLambda.Host.Envelopes.ApiGateway; +namespace MinimalLambda.Envelopes.ApiGateway; public sealed class ApiGatewayResult : APIGatewayProxyResponse, IResponseEnvelope { From 9f7fd714d1945b31bb3ce71ac8565061101516b7 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 19:15:14 -0500 Subject: [PATCH 11/38] feat(api-gateway): refactor and generalize `ApiGatewayResult` response handling - Updated `ApiGatewayResult` to implement `IHttpResult` for generalized usage. - Replaced multiple factory methods with a single generalized static factory method. - Removed redundant status code-specific static methods for streamlined extensibility. - Introduced `IHttpResult` interface to standardize HTTP response contract. - Added support for reusable status code handling with `BaseHttpResultExtensions` and `HttpResultExtensions`. - Simplified implementation and enhanced flexibility for custom HTTP responses in API Gateway. --- .../ApiGatewayResult.cs | 196 +++++++----------- .../IHttpResult.cs | 147 +++++++++++++ 2 files changed, 223 insertions(+), 120 deletions(-) create mode 100644 src/Envelopes/MinimalLambda.Envelopes.ApiGateway/IHttpResult.cs diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs index b55c90a8..5ad63694 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs @@ -1,11 +1,10 @@ using System.Text.Json.Serialization; using Amazon.Lambda.APIGatewayEvents; -using Microsoft.AspNetCore.Http; using MinimalLambda.Options; namespace MinimalLambda.Envelopes.ApiGateway; -public sealed class ApiGatewayResult : APIGatewayProxyResponse, IResponseEnvelope +public sealed class ApiGatewayResult : APIGatewayProxyResponse, IHttpResult { [JsonIgnore] private readonly IResponseEnvelope? _inner; @@ -48,133 +47,90 @@ public ApiGatewayResult AddContentType(string contentType) => // │ Basic Fatories │ // └──────────────────────────────────────────────────────────┘ - public static ApiGatewayResult Create(APIGatewayProxyResponse response) => new(response); - - public static ApiGatewayResult Create(ApiGatewayResponseEnvelopeBase response) => - new(response); - public static ApiGatewayResult Create( int statusCode, T? bodyContent, - Dictionary headers, - IDictionary> multiValueHeaders + string? body, + IDictionary? headers, + bool isBase64Encoded ) => - Create( + new( new ApiGatewayResponseEnvelope - { - BodyContent = bodyContent, - StatusCode = statusCode, - Headers = headers, - MultiValueHeaders = multiValueHeaders, - } - ); - - public static ApiGatewayResult Create( - int statusCode, - string body, - Dictionary headers, - IDictionary> multiValueHeaders - ) => - Create( - new APIGatewayProxyResponse { StatusCode = statusCode, - Headers = headers, - MultiValueHeaders = multiValueHeaders, - Body = body, - } - ); - - public static ApiGatewayResult Json(int statusCode, T bodyContent) => - Create( - new ApiGatewayResponseEnvelope - { BodyContent = bodyContent, - StatusCode = statusCode, - Headers = new Dictionary - { - ["Content-Type"] = "application/json; charset=utf-8", - }, - } - ); - - public static ApiGatewayResult Text(int statusCode, string body) => - Create( - new APIGatewayProxyResponse - { - StatusCode = statusCode, - Headers = new Dictionary - { - ["Content-Type"] = "text/plain; charset=utf-8", - }, - Body = body, + Body = body ?? string.Empty, + Headers = headers, + IsBase64Encoded = isBase64Encoded, } ); - public static ApiGatewayResult StatusCode(int statusCode) => - Create(new APIGatewayProxyResponse { StatusCode = statusCode }); - - // ┌──────────────────────────────────────────────────────────┐ - // │ StatusCode Code Factories │ - // └──────────────────────────────────────────────────────────┘ - - // ── 200 Ok ─────────────────────────────────────────────────────────────────────── - - public static ApiGatewayResult Ok() => StatusCode(StatusCodes.Status200OK); - - public static ApiGatewayResult Ok(T bodyContent) => - Json(StatusCodes.Status200OK, bodyContent); - - // ── 201 Created ────────────────────────────────────────────────────────────────── - - public static ApiGatewayResult Created() => StatusCode(StatusCodes.Status201Created); - - public static ApiGatewayResult Created(T bodyContent) => - Json(StatusCodes.Status201Created, bodyContent); - - // ── 204 No Content ─────────────────────────────────────────────────────────────── - - public static ApiGatewayResult NoContent() => StatusCode(StatusCodes.Status204NoContent); - - // ── 401 Unauthorized ───────────────────────────────────────────────────────────── - - public static ApiGatewayResult Unauthorized() => StatusCode(StatusCodes.Status401Unauthorized); - - // ── 404 Not Found ──────────────────────────────────────────────────────────────── - - public static ApiGatewayResult NotFound() => StatusCode(StatusCodes.Status404NotFound); - - public static ApiGatewayResult NotFound(T bodyContent) => - Json(StatusCodes.Status404NotFound, bodyContent); - - // ── 404 Bad Request ────────────────────────────────────────────────────────────── - - public static ApiGatewayResult BadRequest() => StatusCode(StatusCodes.Status400BadRequest); - - public static ApiGatewayResult BadRequest(T bodyContent) => - Json(StatusCodes.Status400BadRequest, bodyContent); - - // ── 409 Conflict ───────────────────────────────────────────────────────────────── - - public static ApiGatewayResult Conflict() => - StatusCode(StatusCodes.Status500InternalServerError); - - public static ApiGatewayResult Conflict(T bodyContent) => - Json(StatusCodes.Status409Conflict, bodyContent); - - // ── 422 Unprocessable Entity ───────────────────────────────────────────────────── - - public static ApiGatewayResult UnprocessableEntity() => - StatusCode(StatusCodes.Status422UnprocessableEntity); - - public static ApiGatewayResult UnprocessableEntity(T bodyContent) => - Json(StatusCodes.Status422UnprocessableEntity, bodyContent); - - // ── 500 Internal Server Error ──────────────────────────────────────────────────── - - public static ApiGatewayResult InternalServerError() => - StatusCode(StatusCodes.Status500InternalServerError); - - public static ApiGatewayResult InternalServerError(T bodyContent) => - Json(StatusCodes.Status500InternalServerError, bodyContent); + // public static ApiGatewayResult Create(ApiGatewayResponseEnvelopeBase response) => + // new(response); + // + // public static ApiGatewayResult Create( + // int statusCode, + // T? bodyContent, + // Dictionary headers, + // IDictionary> multiValueHeaders + // ) => + // Create( + // new ApiGatewayResponseEnvelope + // { + // BodyContent = bodyContent, + // StatusCode = statusCode, + // Headers = headers, + // MultiValueHeaders = multiValueHeaders, + // } + // ); + // + // public static ApiGatewayResult Create( + // int statusCode, + // string body, + // Dictionary headers, + // IDictionary> multiValueHeaders + // ) => + // Create( + // new APIGatewayProxyResponse + // { + // StatusCode = statusCode, + // Headers = headers, + // MultiValueHeaders = multiValueHeaders, + // Body = body, + // } + // ); + // + // public static ApiGatewayResult Json(int statusCode, T bodyContent) => + // Create( + // new ApiGatewayResponseEnvelope + // { + // BodyContent = bodyContent, + // StatusCode = statusCode, + // Headers = new Dictionary + // { + // ["Content-Type"] = "application/json; charset=utf-8", + // }, + // } + // ); + // + // public static ApiGatewayResult Text(int statusCode, string body) => + // Create( + // new APIGatewayProxyResponse + // { + // StatusCode = statusCode, + // Headers = new Dictionary + // { + // ["Content-Type"] = "text/plain; charset=utf-8", + // }, + // Body = body, + // } + // ); + // + // public static ApiGatewayResult StatusCode(int statusCode) => + // Create(new APIGatewayProxyResponse { StatusCode = statusCode }); + // + // // ┌──────────────────────────────────────────────────────────┐ + // // │ StatusCode Code Factories │ + // // └──────────────────────────────────────────────────────────┘ + // } diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/IHttpResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/IHttpResult.cs new file mode 100644 index 00000000..f6bdc256 --- /dev/null +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/IHttpResult.cs @@ -0,0 +1,147 @@ +using Microsoft.AspNetCore.Http; + +namespace MinimalLambda.Envelopes.ApiGateway; + +public interface IHttpResult : IResponseEnvelope + where TSelf : IHttpResult +{ + static abstract TSelf Create( + int statusCode, + TResponse? bodyContent, + string? body, + IDictionary? headers, + bool isBase64Encoded + ); +} + +public static class BaseHttpResultExtensions +{ + extension(IHttpResult) + where THttpResult : IHttpResult + { + public static THttpResult StatusCode(int statusCode) => + THttpResult.Create( + statusCode, + null, + null, + new Dictionary(), + false + ); + + public static THttpResult Text(int statusCode, string body) => + THttpResult.Create( + statusCode, + null, + body, + new Dictionary { ["Content-Type"] = "text/plain; charset=utf-8" }, + false + ); + + public static THttpResult Json(int statusCode, T bodyContent) => + THttpResult.Create( + statusCode, + bodyContent, + null, + new Dictionary + { + ["Content-Type"] = "application/json; charset=utf-8", + }, + false + ); + } +} + +public static class HttpResultExtensions +{ + extension(IHttpResult) + where THttpResult : IHttpResult + { + // ── 200 Ok ─────────────────────────────────────────────────────────────────────── + + public static THttpResult Ok(T bodyContent) => + BaseHttpResultExtensions.Json(StatusCodes.Status200OK, bodyContent); + + public static THttpResult Ok() => + BaseHttpResultExtensions.StatusCode(StatusCodes.Status200OK); + + // ── 201 Created ────────────────────────────────────────────────────────────────── + + public static THttpResult Created() => + BaseHttpResultExtensions.StatusCode(StatusCodes.Status201Created); + + public static THttpResult Created(T bodyContent) => + BaseHttpResultExtensions.Json( + StatusCodes.Status201Created, + bodyContent + ); + + // ── 204 No Content ─────────────────────────────────────────────────────────────── + + public static THttpResult NoContent() => + BaseHttpResultExtensions.StatusCode(StatusCodes.Status204NoContent); + + // ── 400 Bad Request ────────────────────────────────────────────────────────────── + + public static THttpResult BadRequest() => + BaseHttpResultExtensions.StatusCode(StatusCodes.Status400BadRequest); + + public static THttpResult BadRequest(T bodyContent) => + BaseHttpResultExtensions.Json( + StatusCodes.Status400BadRequest, + bodyContent + ); + + // ── 401 Unauthorized ───────────────────────────────────────────────────────────── + + public static THttpResult Unauthorized() => + BaseHttpResultExtensions.StatusCode(StatusCodes.Status401Unauthorized); + + // ── 404 Not Found ──────────────────────────────────────────────────────────────── + + public static THttpResult NotFound() => + BaseHttpResultExtensions.StatusCode(StatusCodes.Status404NotFound); + + public static THttpResult NotFound(T bodyContent) => + BaseHttpResultExtensions.Json( + StatusCodes.Status404NotFound, + bodyContent + ); + + // ── 409 Conflict ───────────────────────────────────────────────────────────────── + + public static THttpResult Conflict() => + BaseHttpResultExtensions.StatusCode(StatusCodes.Status409Conflict); + + public static THttpResult Conflict(T bodyContent) => + BaseHttpResultExtensions.Json( + StatusCodes.Status409Conflict, + bodyContent + ); + + // ── 422 Unprocessable Entity ───────────────────────────────────────────────────── + + public static THttpResult UnprocessableEntity() => + BaseHttpResultExtensions.StatusCode( + StatusCodes.Status422UnprocessableEntity + ); + + public static THttpResult UnprocessableEntity(T bodyContent) => + BaseHttpResultExtensions.Json( + StatusCodes.Status422UnprocessableEntity, + bodyContent + ); + + // ── 500 Internal Server Error ──────────────────────────────────────────────────── + + public static THttpResult InternalServerError() => + BaseHttpResultExtensions.StatusCode( + StatusCodes.Status500InternalServerError + ); + + public static THttpResult InternalServerError(T bodyContent) => + BaseHttpResultExtensions.Json( + StatusCodes.Status500InternalServerError, + bodyContent + ); + } +} From 5caaac614bb6b5f96fde24fbb1195f575fe6cdfb Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 19:34:48 -0500 Subject: [PATCH 12/38] feat(local-dev): add `--config-storage-path` option to AWS Lambda test tool command - Updated `LocalDevTasks.yml` to include `--config-storage-path` for improved configurability. --- tasks/LocalDevTasks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/LocalDevTasks.yml b/tasks/LocalDevTasks.yml index b6b5d94a..ea6a1a03 100644 --- a/tasks/LocalDevTasks.yml +++ b/tasks/LocalDevTasks.yml @@ -47,7 +47,7 @@ tasks: desc: Start the AWS Lambda test tool silent: true cmds: - - dotnet lambda-test-tool start --lambda-emulator-port 5050 + - dotnet lambda-test-tool start --lambda-emulator-port 5050 --config-storage-path ./lambda_test_tool run-docs: desc: Run MKDocs server locally From 2793bb8fb1a48a19c8c576e75e5a058f78b50f53 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 19:38:29 -0500 Subject: [PATCH 13/38] feat(api-gateway): introduce `ApiGatewayV2Result` and remove redundant methods from `ApiGatewayResult` - Added `ApiGatewayV2Result` to support AWS HTTP API Gateway v2 response handling. - Removed redundant methods and comments from `ApiGatewayResult` for cleaner implementation. - Updated `IHttpResult` interface with extended HTTP response properties. - Introduced extensions for header handling with `UpdateHttpResultExtensions`. - Simplified and streamlined codebase for improved maintainability and flexibility. --- .../ApiGatewayResult.cs | 125 ++++++------------ .../IHttpResult.cs | 24 ++++ 2 files changed, 65 insertions(+), 84 deletions(-) diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs index 5ad63694..d8cb1f98 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs @@ -14,7 +14,6 @@ private ApiGatewayResult(APIGatewayProxyResponse response) _inner = response as IResponseEnvelope; base.StatusCode = response.StatusCode; Headers = response.Headers; - MultiValueHeaders = response.MultiValueHeaders; Body = response.Body; IsBase64Encoded = response.IsBase64Encoded; } @@ -29,25 +28,52 @@ public void PackPayload(EnvelopeOptions options) Body = ((APIGatewayProxyResponse)_inner).Body; } - // ┌──────────────────────────────────────────────────────────┐ - // │ Headers Helpers │ - // └──────────────────────────────────────────────────────────┘ + public static ApiGatewayResult Create( + int statusCode, + T? bodyContent, + string? body, + IDictionary? headers, + bool isBase64Encoded + ) => + new( + new ApiGatewayResponseEnvelope + { + StatusCode = statusCode, + BodyContent = bodyContent, + Body = body ?? string.Empty, + Headers = headers, + IsBase64Encoded = isBase64Encoded, + } + ); +} - public ApiGatewayResult AddHeader(string key, string value) - { - Headers[key] = value; +public sealed class ApiGatewayV2Result + : APIGatewayHttpApiV2ProxyResponse, + IHttpResult +{ + [JsonIgnore] + private readonly IResponseEnvelope? _inner; - return this; + private ApiGatewayV2Result(APIGatewayHttpApiV2ProxyResponse response) + { + _inner = response as IResponseEnvelope; + StatusCode = response.StatusCode; + Headers = response.Headers; + Body = response.Body; + IsBase64Encoded = response.IsBase64Encoded; } - public ApiGatewayResult AddContentType(string contentType) => - AddHeader("Content-Type", contentType); + /// + public void PackPayload(EnvelopeOptions options) + { + if (_inner is null) + return; - // ┌──────────────────────────────────────────────────────────┐ - // │ Basic Fatories │ - // └──────────────────────────────────────────────────────────┘ + _inner.PackPayload(options); + Body = ((APIGatewayHttpApiV2ProxyResponse)_inner).Body; + } - public static ApiGatewayResult Create( + public static ApiGatewayV2Result Create( int statusCode, T? bodyContent, string? body, @@ -55,7 +81,7 @@ public static ApiGatewayResult Create( bool isBase64Encoded ) => new( - new ApiGatewayResponseEnvelope + new ApiGatewayV2ResponseEnvelope { StatusCode = statusCode, BodyContent = bodyContent, @@ -64,73 +90,4 @@ bool isBase64Encoded IsBase64Encoded = isBase64Encoded, } ); - - // public static ApiGatewayResult Create(ApiGatewayResponseEnvelopeBase response) => - // new(response); - // - // public static ApiGatewayResult Create( - // int statusCode, - // T? bodyContent, - // Dictionary headers, - // IDictionary> multiValueHeaders - // ) => - // Create( - // new ApiGatewayResponseEnvelope - // { - // BodyContent = bodyContent, - // StatusCode = statusCode, - // Headers = headers, - // MultiValueHeaders = multiValueHeaders, - // } - // ); - // - // public static ApiGatewayResult Create( - // int statusCode, - // string body, - // Dictionary headers, - // IDictionary> multiValueHeaders - // ) => - // Create( - // new APIGatewayProxyResponse - // { - // StatusCode = statusCode, - // Headers = headers, - // MultiValueHeaders = multiValueHeaders, - // Body = body, - // } - // ); - // - // public static ApiGatewayResult Json(int statusCode, T bodyContent) => - // Create( - // new ApiGatewayResponseEnvelope - // { - // BodyContent = bodyContent, - // StatusCode = statusCode, - // Headers = new Dictionary - // { - // ["Content-Type"] = "application/json; charset=utf-8", - // }, - // } - // ); - // - // public static ApiGatewayResult Text(int statusCode, string body) => - // Create( - // new APIGatewayProxyResponse - // { - // StatusCode = statusCode, - // Headers = new Dictionary - // { - // ["Content-Type"] = "text/plain; charset=utf-8", - // }, - // Body = body, - // } - // ); - // - // public static ApiGatewayResult StatusCode(int statusCode) => - // Create(new APIGatewayProxyResponse { StatusCode = statusCode }); - // - // // ┌──────────────────────────────────────────────────────────┐ - // // │ StatusCode Code Factories │ - // // └──────────────────────────────────────────────────────────┘ - // } diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/IHttpResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/IHttpResult.cs index f6bdc256..8e4c50a9 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/IHttpResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/IHttpResult.cs @@ -5,6 +5,14 @@ namespace MinimalLambda.Envelopes.ApiGateway; public interface IHttpResult : IResponseEnvelope where TSelf : IHttpResult { + public int StatusCode { get; set; } + + public IDictionary Headers { get; set; } + + public string Body { get; set; } + + public bool IsBase64Encoded { get; set; } + static abstract TSelf Create( int statusCode, TResponse? bodyContent, @@ -145,3 +153,19 @@ public static THttpResult InternalServerError(T bodyContent) => ); } } + +public static class UpdateHttpResultExtensions +{ + extension(THttpResult result) + where THttpResult : IHttpResult + { + public THttpResult AddHeader(string key, string value) + { + result.Headers[key] = value; + return result; + } + + public THttpResult AddContentType(string contentType) => + result.AddHeader("Content-Type", contentType); + } +} From 0426f7f9e9d9f2bf3e63c16a28d2abff82b8a61e Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 19:44:42 -0500 Subject: [PATCH 14/38] feat(envelopes): add `MinimalLambda.Envelopes` project to solution - Created `MinimalLambda.Envelopes` project targeting `net8.0`, `net9.0`, and `net10.0`. - Enabled nullable reference types, implicit usings, and preview language features. - Configured project for NuGet package generation with metadata placeholders. - Added `README.md` for documentation and included it in the package. - Integrated the new project into `MinimalLambda.sln`. --- MinimalLambda.sln | 15 +++++++++++++++ .../MinimalLambda.Envelopes.csproj | 18 ++++++++++++++++++ .../MinimalLambda.Envelopes/README.md | 0 3 files changed, 33 insertions(+) create mode 100644 src/Envelopes/MinimalLambda.Envelopes/MinimalLambda.Envelopes.csproj create mode 100644 src/Envelopes/MinimalLambda.Envelopes/README.md diff --git a/MinimalLambda.sln b/MinimalLambda.sln index 8413ae0f..10dd44a9 100644 --- a/MinimalLambda.sln +++ b/MinimalLambda.sln @@ -89,6 +89,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinimalLambda.Testing.UnitT EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lambdas", "Lambdas", "{D9109C8A-AFA8-49C8-A19C-381500902B4D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinimalLambda.Envelopes", "src\Envelopes\MinimalLambda.Envelopes\MinimalLambda.Envelopes.csproj", "{190CC9C7-007F-48C5-867D-03B911D53397}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -435,6 +437,18 @@ Global {381F49C0-297F-4B61-8A82-E9E502E523AD}.Release|x64.Build.0 = Release|Any CPU {381F49C0-297F-4B61-8A82-E9E502E523AD}.Release|x86.ActiveCfg = Release|Any CPU {381F49C0-297F-4B61-8A82-E9E502E523AD}.Release|x86.Build.0 = Release|Any CPU + {190CC9C7-007F-48C5-867D-03B911D53397}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {190CC9C7-007F-48C5-867D-03B911D53397}.Debug|Any CPU.Build.0 = Debug|Any CPU + {190CC9C7-007F-48C5-867D-03B911D53397}.Debug|x64.ActiveCfg = Debug|Any CPU + {190CC9C7-007F-48C5-867D-03B911D53397}.Debug|x64.Build.0 = Debug|Any CPU + {190CC9C7-007F-48C5-867D-03B911D53397}.Debug|x86.ActiveCfg = Debug|Any CPU + {190CC9C7-007F-48C5-867D-03B911D53397}.Debug|x86.Build.0 = Debug|Any CPU + {190CC9C7-007F-48C5-867D-03B911D53397}.Release|Any CPU.ActiveCfg = Release|Any CPU + {190CC9C7-007F-48C5-867D-03B911D53397}.Release|Any CPU.Build.0 = Release|Any CPU + {190CC9C7-007F-48C5-867D-03B911D53397}.Release|x64.ActiveCfg = Release|Any CPU + {190CC9C7-007F-48C5-867D-03B911D53397}.Release|x64.Build.0 = Release|Any CPU + {190CC9C7-007F-48C5-867D-03B911D53397}.Release|x86.ActiveCfg = Release|Any CPU + {190CC9C7-007F-48C5-867D-03B911D53397}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -471,5 +485,6 @@ Global {7350482B-DFDE-4DCD-A0C5-899D5EA00F74} = {D9109C8A-AFA8-49C8-A19C-381500902B4D} {381F49C0-297F-4B61-8A82-E9E502E523AD} = {D9109C8A-AFA8-49C8-A19C-381500902B4D} {7E7B3004-C6C4-4A8C-8610-2A1CB61F834A} = {D9109C8A-AFA8-49C8-A19C-381500902B4D} + {190CC9C7-007F-48C5-867D-03B911D53397} = {1C3C52D9-2936-4A0C-A9C8-F330F22B8359} EndGlobalSection EndGlobal diff --git a/src/Envelopes/MinimalLambda.Envelopes/MinimalLambda.Envelopes.csproj b/src/Envelopes/MinimalLambda.Envelopes/MinimalLambda.Envelopes.csproj new file mode 100644 index 00000000..5157ae4f --- /dev/null +++ b/src/Envelopes/MinimalLambda.Envelopes/MinimalLambda.Envelopes.csproj @@ -0,0 +1,18 @@ + + + net8.0;net9.0;net10.0 + preview + enable + enable + true + true + true + + MinimalLambda.Envelopes + "" + README.md + + + + + diff --git a/src/Envelopes/MinimalLambda.Envelopes/README.md b/src/Envelopes/MinimalLambda.Envelopes/README.md new file mode 100644 index 00000000..e69de29b From 9b73c77e96e02296e4de61ce67852b73f2203c38 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 19:47:36 -0500 Subject: [PATCH 15/38] feat(api-gateway): split extensions and enhance `ApiGatewayV2Result` handling - Moved `BaseHttpResultExtensions`, `HttpResultExtensions`, and `UpdateHttpResultExtensions` into separate files for modularity. - Revamped `ApiGatewayV2Result` to improve response handling with added flexibility. - Updated `MinimalLambda.Envelopes.ApiGateway` and project references to align with changes. - Added `Microsoft.AspNetCore.Http.Abstractions` package for HTTP helper methods and integrations. - Removed duplicate or redundant logic from `IHttpResult`. --- Directory.Packages.props | 1 + .../ApiGatewayResult.cs | 45 ------------ .../ApiGatewayV2Result.cs | 50 +++++++++++++ .../MinimalLambda.Envelopes.ApiGateway.csproj | 2 +- .../BaseHttpResultExtensions.cs | 38 ++++++++++ .../HttpResultExtensions.cs} | 73 ------------------- .../MinimalLambda.Envelopes/IHttpResult.cs | 21 ++++++ .../MinimalLambda.Envelopes.csproj | 6 ++ .../UpdateHttpResultExtensions.cs | 17 +++++ 9 files changed, 134 insertions(+), 119 deletions(-) create mode 100644 src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs create mode 100644 src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs rename src/Envelopes/{MinimalLambda.Envelopes.ApiGateway/IHttpResult.cs => MinimalLambda.Envelopes/HttpResultExtensions.cs} (71%) create mode 100644 src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs create mode 100644 src/Envelopes/MinimalLambda.Envelopes/UpdateHttpResultExtensions.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 70a68015..c73722ce 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -18,6 +18,7 @@ + diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs index d8cb1f98..cb80a6a3 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs @@ -46,48 +46,3 @@ bool isBase64Encoded } ); } - -public sealed class ApiGatewayV2Result - : APIGatewayHttpApiV2ProxyResponse, - IHttpResult -{ - [JsonIgnore] - private readonly IResponseEnvelope? _inner; - - private ApiGatewayV2Result(APIGatewayHttpApiV2ProxyResponse response) - { - _inner = response as IResponseEnvelope; - StatusCode = response.StatusCode; - Headers = response.Headers; - Body = response.Body; - IsBase64Encoded = response.IsBase64Encoded; - } - - /// - public void PackPayload(EnvelopeOptions options) - { - if (_inner is null) - return; - - _inner.PackPayload(options); - Body = ((APIGatewayHttpApiV2ProxyResponse)_inner).Body; - } - - public static ApiGatewayV2Result Create( - int statusCode, - T? bodyContent, - string? body, - IDictionary? headers, - bool isBase64Encoded - ) => - new( - new ApiGatewayV2ResponseEnvelope - { - StatusCode = statusCode, - BodyContent = bodyContent, - Body = body ?? string.Empty, - Headers = headers, - IsBase64Encoded = isBase64Encoded, - } - ); -} diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs new file mode 100644 index 00000000..38d9e248 --- /dev/null +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs @@ -0,0 +1,50 @@ +using System.Text.Json.Serialization; +using Amazon.Lambda.APIGatewayEvents; +using MinimalLambda.Options; + +namespace MinimalLambda.Envelopes.ApiGateway; + +public sealed class ApiGatewayV2Result + : APIGatewayHttpApiV2ProxyResponse, + IHttpResult +{ + [JsonIgnore] + private readonly IResponseEnvelope? _inner; + + private ApiGatewayV2Result(APIGatewayHttpApiV2ProxyResponse response) + { + _inner = response as IResponseEnvelope; + StatusCode = response.StatusCode; + Headers = response.Headers; + Body = response.Body; + IsBase64Encoded = response.IsBase64Encoded; + } + + /// + public void PackPayload(EnvelopeOptions options) + { + if (_inner is null) + return; + + _inner.PackPayload(options); + Body = ((APIGatewayHttpApiV2ProxyResponse)_inner).Body; + } + + public static ApiGatewayV2Result Create( + int statusCode, + T? bodyContent, + string? body, + IDictionary? headers, + bool isBase64Encoded + ) => + new( + new ApiGatewayV2ResponseEnvelope + { + StatusCode = statusCode, + BodyContent = bodyContent, + Body = body ?? string.Empty, + Headers = headers, + IsBase64Encoded = isBase64Encoded, + } + ); +} diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/MinimalLambda.Envelopes.ApiGateway.csproj b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/MinimalLambda.Envelopes.ApiGateway.csproj index 8cb38cae..f80233dc 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/MinimalLambda.Envelopes.ApiGateway.csproj +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/MinimalLambda.Envelopes.ApiGateway.csproj @@ -16,11 +16,11 @@ - + diff --git a/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs b/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs new file mode 100644 index 00000000..6ead6a3f --- /dev/null +++ b/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs @@ -0,0 +1,38 @@ +namespace MinimalLambda.Envelopes.ApiGateway; + +public static class BaseHttpResultExtensions +{ + extension(IHttpResult) + where THttpResult : IHttpResult + { + public static THttpResult StatusCode(int statusCode) => + THttpResult.Create( + statusCode, + null, + null, + new Dictionary(), + false + ); + + public static THttpResult Text(int statusCode, string body) => + THttpResult.Create( + statusCode, + null, + body, + new Dictionary { ["Content-Type"] = "text/plain; charset=utf-8" }, + false + ); + + public static THttpResult Json(int statusCode, T bodyContent) => + THttpResult.Create( + statusCode, + bodyContent, + null, + new Dictionary + { + ["Content-Type"] = "application/json; charset=utf-8", + }, + false + ); + } +} diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/IHttpResult.cs b/src/Envelopes/MinimalLambda.Envelopes/HttpResultExtensions.cs similarity index 71% rename from src/Envelopes/MinimalLambda.Envelopes.ApiGateway/IHttpResult.cs rename to src/Envelopes/MinimalLambda.Envelopes/HttpResultExtensions.cs index 8e4c50a9..44bd580c 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/IHttpResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes/HttpResultExtensions.cs @@ -2,63 +2,6 @@ namespace MinimalLambda.Envelopes.ApiGateway; -public interface IHttpResult : IResponseEnvelope - where TSelf : IHttpResult -{ - public int StatusCode { get; set; } - - public IDictionary Headers { get; set; } - - public string Body { get; set; } - - public bool IsBase64Encoded { get; set; } - - static abstract TSelf Create( - int statusCode, - TResponse? bodyContent, - string? body, - IDictionary? headers, - bool isBase64Encoded - ); -} - -public static class BaseHttpResultExtensions -{ - extension(IHttpResult) - where THttpResult : IHttpResult - { - public static THttpResult StatusCode(int statusCode) => - THttpResult.Create( - statusCode, - null, - null, - new Dictionary(), - false - ); - - public static THttpResult Text(int statusCode, string body) => - THttpResult.Create( - statusCode, - null, - body, - new Dictionary { ["Content-Type"] = "text/plain; charset=utf-8" }, - false - ); - - public static THttpResult Json(int statusCode, T bodyContent) => - THttpResult.Create( - statusCode, - bodyContent, - null, - new Dictionary - { - ["Content-Type"] = "application/json; charset=utf-8", - }, - false - ); - } -} - public static class HttpResultExtensions { extension(IHttpResult) @@ -153,19 +96,3 @@ public static THttpResult InternalServerError(T bodyContent) => ); } } - -public static class UpdateHttpResultExtensions -{ - extension(THttpResult result) - where THttpResult : IHttpResult - { - public THttpResult AddHeader(string key, string value) - { - result.Headers[key] = value; - return result; - } - - public THttpResult AddContentType(string contentType) => - result.AddHeader("Content-Type", contentType); - } -} diff --git a/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs new file mode 100644 index 00000000..2970ed61 --- /dev/null +++ b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs @@ -0,0 +1,21 @@ +namespace MinimalLambda.Envelopes.ApiGateway; + +public interface IHttpResult : IResponseEnvelope + where TSelf : IHttpResult +{ + public int StatusCode { get; set; } + + public IDictionary Headers { get; set; } + + public string Body { get; set; } + + public bool IsBase64Encoded { get; set; } + + static abstract TSelf Create( + int statusCode, + TResponse? bodyContent, + string? body, + IDictionary? headers, + bool isBase64Encoded + ); +} diff --git a/src/Envelopes/MinimalLambda.Envelopes/MinimalLambda.Envelopes.csproj b/src/Envelopes/MinimalLambda.Envelopes/MinimalLambda.Envelopes.csproj index 5157ae4f..9e9ec9a8 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/MinimalLambda.Envelopes.csproj +++ b/src/Envelopes/MinimalLambda.Envelopes/MinimalLambda.Envelopes.csproj @@ -15,4 +15,10 @@ + + + + + + diff --git a/src/Envelopes/MinimalLambda.Envelopes/UpdateHttpResultExtensions.cs b/src/Envelopes/MinimalLambda.Envelopes/UpdateHttpResultExtensions.cs new file mode 100644 index 00000000..a3b07093 --- /dev/null +++ b/src/Envelopes/MinimalLambda.Envelopes/UpdateHttpResultExtensions.cs @@ -0,0 +1,17 @@ +namespace MinimalLambda.Envelopes.ApiGateway; + +public static class UpdateHttpResultExtensions +{ + extension(THttpResult result) + where THttpResult : IHttpResult + { + public THttpResult AddHeader(string key, string value) + { + result.Headers[key] = value; + return result; + } + + public THttpResult AddContentType(string contentType) => + result.AddHeader("Content-Type", contentType); + } +} From ec451363957c2b558513cc79c5bf71290b93af3b Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 19:48:48 -0500 Subject: [PATCH 16/38] refactor(dependencies): remove `Microsoft.AspNetCore.Http` from `Directory.Packages.props` - Removed unused `Microsoft.AspNetCore.Http` package (version `2.3.0`) for dependency cleanup. - Ensured all references to this package were unnecessary before removal. --- Directory.Packages.props | 1 - 1 file changed, 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c73722ce..5b33858e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,7 +13,6 @@ - From 826b01f580a2f35e264081208307fe8ceb75a826 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 20:12:16 -0500 Subject: [PATCH 17/38] feat(envelopes): introduce `AlbResult` and enhance HTTP response customization - Added new `AlbResult` class for Application Load Balancer response handling. - Implemented `Configure` method in `ApiGatewayResult` and `ApiGatewayV2Result` for customizations. - Updated `IHttpResult` interface with a new `Configure` method. - Added `MinimalLambda.Envelopes` project reference to `MinimalLambda.Envelopes.Alb`. - Improved examples with `Configure` usage for custom headers and multi-value headers. --- .../MinimalLambda.Example.Events/Program.cs | 8 ++- .../MinimalLambda.Envelopes.Alb/AlbResult.cs | 57 +++++++++++++++++++ .../MinimalLambda.Envelopes.Alb.csproj | 1 + .../ApiGatewayResult.cs | 9 ++- .../ApiGatewayV2Result.cs | 7 +++ .../MinimalLambda.Envelopes/IHttpResult.cs | 2 + 6 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs diff --git a/examples/MinimalLambda.Example.Events/Program.cs b/examples/MinimalLambda.Example.Events/Program.cs index 36d268d4..237a66f2 100644 --- a/examples/MinimalLambda.Example.Events/Program.cs +++ b/examples/MinimalLambda.Example.Events/Program.cs @@ -33,7 +33,13 @@ return ApiGatewayResult.InternalServerError(new BadErrorDetails("Bad error")); if (request.BodyContent.Name == "error") - return ApiGatewayResult.BadRequest(new ErrorDetails("bummer")); + return ApiGatewayResult + .BadRequest(new ErrorDetails("bummer")) + .Configure(response => + { + response.Headers.Add("X-Custom-Header", "Custom Value"); + response.MultiValueHeaders.Add("X-Custom-Header", ["Custom Value 2"]); + }); return ApiGatewayResult .Ok(new Response($"Hello {request.BodyContent?.Name}!", DateTime.UtcNow)) diff --git a/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs b/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs new file mode 100644 index 00000000..014bab83 --- /dev/null +++ b/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs @@ -0,0 +1,57 @@ +using System.Text.Json.Serialization; +using Amazon.Lambda.ApplicationLoadBalancerEvents; +using MinimalLambda.Envelopes.ApiGateway; +using MinimalLambda.Options; + +namespace MinimalLambda.Envelopes.Alb; + +public sealed class AlbResult : ApplicationLoadBalancerResponse, IHttpResult +{ + [JsonIgnore] + private readonly IResponseEnvelope? _inner; + + private AlbResult(ApplicationLoadBalancerResponse response) + { + _inner = response as IResponseEnvelope; + StatusCode = response.StatusCode; + StatusDescription = response.StatusDescription; + Headers = response.Headers; + MultiValueHeaders = response.MultiValueHeaders; + Body = response.Body; + IsBase64Encoded = response.IsBase64Encoded; + } + + /// + public void PackPayload(EnvelopeOptions options) + { + if (_inner is null) + return; + + _inner.PackPayload(options); + Body = ((ApplicationLoadBalancerResponse)_inner).Body; + } + + public AlbResult Configure(Action customizer) + { + customizer(this); + return this; + } + + public static AlbResult Create( + int statusCode, + T? bodyContent, + string? body, + IDictionary? headers, + bool isBase64Encoded + ) => + new( + new AlbResponseEnvelope + { + StatusCode = statusCode, + BodyContent = bodyContent, + Body = body ?? string.Empty, + Headers = headers, + IsBase64Encoded = isBase64Encoded, + } + ); +} diff --git a/src/Envelopes/MinimalLambda.Envelopes.Alb/MinimalLambda.Envelopes.Alb.csproj b/src/Envelopes/MinimalLambda.Envelopes.Alb/MinimalLambda.Envelopes.Alb.csproj index 2da41e1e..7e59109f 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.Alb/MinimalLambda.Envelopes.Alb.csproj +++ b/src/Envelopes/MinimalLambda.Envelopes.Alb/MinimalLambda.Envelopes.Alb.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs index cb80a6a3..9b247e42 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs @@ -12,8 +12,9 @@ public sealed class ApiGatewayResult : APIGatewayProxyResponse, IHttpResult customizer) + { + customizer(this); + return this; + } + public static ApiGatewayResult Create( int statusCode, T? bodyContent, diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs index 38d9e248..b7b24ea3 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs @@ -16,6 +16,7 @@ private ApiGatewayV2Result(APIGatewayHttpApiV2ProxyResponse response) _inner = response as IResponseEnvelope; StatusCode = response.StatusCode; Headers = response.Headers; + Cookies = response.Cookies; Body = response.Body; IsBase64Encoded = response.IsBase64Encoded; } @@ -30,6 +31,12 @@ public void PackPayload(EnvelopeOptions options) Body = ((APIGatewayHttpApiV2ProxyResponse)_inner).Body; } + public ApiGatewayV2Result Configure(Action customizer) + { + customizer(this); + return this; + } + public static ApiGatewayV2Result Create( int statusCode, T? bodyContent, diff --git a/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs index 2970ed61..70495916 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs @@ -18,4 +18,6 @@ static abstract TSelf Create( IDictionary? headers, bool isBase64Encoded ); + + public TSelf Configure(Action customizer); } From b97b555fee4ed4d61ded28c31de9812d7cb0af1e Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 20:16:09 -0500 Subject: [PATCH 18/38] refactor(envelopes): rename `Configure` method to `Customize` across result classes - Updated `Configure` to `Customize` in `ApiGatewayResult`, `ApiGatewayV2Result`, and `AlbResult`. - Adjusted `IHttpResult` interface to use `Customize` method instead of `Configure`. - Modified examples to reflect the method name change for better clarity and intent. --- examples/MinimalLambda.Example.Events/Program.cs | 4 ++-- src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs | 2 +- .../MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs | 2 +- .../MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs | 2 +- src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/MinimalLambda.Example.Events/Program.cs b/examples/MinimalLambda.Example.Events/Program.cs index 237a66f2..cbb83951 100644 --- a/examples/MinimalLambda.Example.Events/Program.cs +++ b/examples/MinimalLambda.Example.Events/Program.cs @@ -35,7 +35,7 @@ if (request.BodyContent.Name == "error") return ApiGatewayResult .BadRequest(new ErrorDetails("bummer")) - .Configure(response => + .Customize(response => { response.Headers.Add("X-Custom-Header", "Custom Value"); response.MultiValueHeaders.Add("X-Custom-Header", ["Custom Value 2"]); @@ -43,7 +43,7 @@ return ApiGatewayResult .Ok(new Response($"Hello {request.BodyContent?.Name}!", DateTime.UtcNow)) - .AddHeader("X-Custom-Header", "Custom Value"); + .Customize(result => result.Headers.Add("X-Custom-Header", "Custom Value")); } ); diff --git a/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs b/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs index 014bab83..864c0589 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs @@ -31,7 +31,7 @@ public void PackPayload(EnvelopeOptions options) Body = ((ApplicationLoadBalancerResponse)_inner).Body; } - public AlbResult Configure(Action customizer) + public AlbResult Customize(Action customizer) { customizer(this); return this; diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs index 9b247e42..27f52496 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs @@ -29,7 +29,7 @@ public void PackPayload(EnvelopeOptions options) Body = ((APIGatewayProxyResponse)_inner).Body; } - public ApiGatewayResult Configure(Action customizer) + public ApiGatewayResult Customize(Action customizer) { customizer(this); return this; diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs index b7b24ea3..1a0bccf3 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs @@ -31,7 +31,7 @@ public void PackPayload(EnvelopeOptions options) Body = ((APIGatewayHttpApiV2ProxyResponse)_inner).Body; } - public ApiGatewayV2Result Configure(Action customizer) + public ApiGatewayV2Result Customize(Action customizer) { customizer(this); return this; diff --git a/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs index 70495916..42a90bea 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs @@ -19,5 +19,5 @@ static abstract TSelf Create( bool isBase64Encoded ); - public TSelf Configure(Action customizer); + public TSelf Customize(Action customizer); } From deba0e83bf21e237be034c0deb4d340cdf31fe8d Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 20:16:44 -0500 Subject: [PATCH 19/38] refactor(envelopes): remove `UpdateHttpResultExtensions` for cleanup - Deleted `UpdateHttpResultExtensions.cs` as it is no longer used or required. - Simplified the codebase by removing unnecessary extension methods for headers and content types. --- .../UpdateHttpResultExtensions.cs | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 src/Envelopes/MinimalLambda.Envelopes/UpdateHttpResultExtensions.cs diff --git a/src/Envelopes/MinimalLambda.Envelopes/UpdateHttpResultExtensions.cs b/src/Envelopes/MinimalLambda.Envelopes/UpdateHttpResultExtensions.cs deleted file mode 100644 index a3b07093..00000000 --- a/src/Envelopes/MinimalLambda.Envelopes/UpdateHttpResultExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace MinimalLambda.Envelopes.ApiGateway; - -public static class UpdateHttpResultExtensions -{ - extension(THttpResult result) - where THttpResult : IHttpResult - { - public THttpResult AddHeader(string key, string value) - { - result.Headers[key] = value; - return result; - } - - public THttpResult AddContentType(string contentType) => - result.AddHeader("Content-Type", contentType); - } -} From 749ed49fb1e44e65475a35180303908692eaaca6 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 20:36:35 -0500 Subject: [PATCH 20/38] feat(envelopes): add detailed XML comments for HTTP result extensions - Added XML documentation to `HttpResultExtensions` for method clarity and usability. - Provided summaries, parameter descriptions, and return value details for all HTTP status code methods. - Updated `BaseHttpResultExtensions` with XML comments for factory methods. - Enhanced `IHttpResult` interface with comprehensive XML documentation for properties and methods. --- .../BaseHttpResultExtensions.cs | 13 +++++ .../HttpResultExtensions.cs | 47 +++++++++++++++++++ .../MinimalLambda.Envelopes/IHttpResult.cs | 30 ++++++++++-- 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs b/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs index 6ead6a3f..15a01174 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs +++ b/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs @@ -1,10 +1,14 @@ namespace MinimalLambda.Envelopes.ApiGateway; +/// Provides factory extension methods for creating HTTP results. public static class BaseHttpResultExtensions { extension(IHttpResult) where THttpResult : IHttpResult { + /// Creates an HTTP result with the specified status code. + /// The HTTP status code. + /// An HTTP result with the status code. public static THttpResult StatusCode(int statusCode) => THttpResult.Create( statusCode, @@ -14,6 +18,10 @@ public static THttpResult StatusCode(int statusCode) => false ); + /// Creates a text/plain HTTP result. + /// The HTTP status code. + /// The plain text response body. + /// An HTTP result with text/plain content type. public static THttpResult Text(int statusCode, string body) => THttpResult.Create( statusCode, @@ -23,6 +31,11 @@ public static THttpResult Text(int statusCode, string body) => false ); + /// Creates an application/json HTTP result. + /// The type of content to serialize. + /// The HTTP status code. + /// The content to serialize as JSON. + /// An HTTP result with application/json content type. public static THttpResult Json(int statusCode, T bodyContent) => THttpResult.Create( statusCode, diff --git a/src/Envelopes/MinimalLambda.Envelopes/HttpResultExtensions.cs b/src/Envelopes/MinimalLambda.Envelopes/HttpResultExtensions.cs index 44bd580c..7e47e115 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/HttpResultExtensions.cs +++ b/src/Envelopes/MinimalLambda.Envelopes/HttpResultExtensions.cs @@ -2,6 +2,7 @@ namespace MinimalLambda.Envelopes.ApiGateway; +/// Provides convenience extension methods for common HTTP status codes. public static class HttpResultExtensions { extension(IHttpResult) @@ -9,17 +10,29 @@ public static class HttpResultExtensions { // ── 200 Ok ─────────────────────────────────────────────────────────────────────── + /// Creates a 200 OK response with content. + /// The type of content to return. + /// The response content to serialize. + /// An HTTP 200 result with JSON content. public static THttpResult Ok(T bodyContent) => BaseHttpResultExtensions.Json(StatusCodes.Status200OK, bodyContent); + /// Creates a 200 OK response. + /// An HTTP 200 result. public static THttpResult Ok() => BaseHttpResultExtensions.StatusCode(StatusCodes.Status200OK); // ── 201 Created ────────────────────────────────────────────────────────────────── + /// Creates a 201 Created response. + /// An HTTP 201 result. public static THttpResult Created() => BaseHttpResultExtensions.StatusCode(StatusCodes.Status201Created); + /// Creates a 201 Created response with content. + /// The type of content to return. + /// The response content to serialize. + /// An HTTP 201 result with JSON content. public static THttpResult Created(T bodyContent) => BaseHttpResultExtensions.Json( StatusCodes.Status201Created, @@ -28,14 +41,22 @@ public static THttpResult Created(T bodyContent) => // ── 204 No Content ─────────────────────────────────────────────────────────────── + /// Creates a 204 No Content response. + /// An HTTP 204 result. public static THttpResult NoContent() => BaseHttpResultExtensions.StatusCode(StatusCodes.Status204NoContent); // ── 400 Bad Request ────────────────────────────────────────────────────────────── + /// Creates a 400 Bad Request response. + /// An HTTP 400 result. public static THttpResult BadRequest() => BaseHttpResultExtensions.StatusCode(StatusCodes.Status400BadRequest); + /// Creates a 400 Bad Request response with content. + /// The type of content to return. + /// The response content to serialize. + /// An HTTP 400 result with JSON content. public static THttpResult BadRequest(T bodyContent) => BaseHttpResultExtensions.Json( StatusCodes.Status400BadRequest, @@ -44,14 +65,22 @@ public static THttpResult BadRequest(T bodyContent) => // ── 401 Unauthorized ───────────────────────────────────────────────────────────── + /// Creates a 401 Unauthorized response. + /// An HTTP 401 result. public static THttpResult Unauthorized() => BaseHttpResultExtensions.StatusCode(StatusCodes.Status401Unauthorized); // ── 404 Not Found ──────────────────────────────────────────────────────────────── + /// Creates a 404 Not Found response. + /// An HTTP 404 result. public static THttpResult NotFound() => BaseHttpResultExtensions.StatusCode(StatusCodes.Status404NotFound); + /// Creates a 404 Not Found response with content. + /// The type of content to return. + /// The response content to serialize. + /// An HTTP 404 result with JSON content. public static THttpResult NotFound(T bodyContent) => BaseHttpResultExtensions.Json( StatusCodes.Status404NotFound, @@ -60,9 +89,15 @@ public static THttpResult NotFound(T bodyContent) => // ── 409 Conflict ───────────────────────────────────────────────────────────────── + /// Creates a 409 Conflict response. + /// An HTTP 409 result. public static THttpResult Conflict() => BaseHttpResultExtensions.StatusCode(StatusCodes.Status409Conflict); + /// Creates a 409 Conflict response with content. + /// The type of content to return. + /// The response content to serialize. + /// An HTTP 409 result with JSON content. public static THttpResult Conflict(T bodyContent) => BaseHttpResultExtensions.Json( StatusCodes.Status409Conflict, @@ -71,11 +106,17 @@ public static THttpResult Conflict(T bodyContent) => // ── 422 Unprocessable Entity ───────────────────────────────────────────────────── + /// Creates a 422 Unprocessable Entity response. + /// An HTTP 422 result. public static THttpResult UnprocessableEntity() => BaseHttpResultExtensions.StatusCode( StatusCodes.Status422UnprocessableEntity ); + /// Creates a 422 Unprocessable Entity response with content. + /// The type of content to return. + /// The response content to serialize. + /// An HTTP 422 result with JSON content. public static THttpResult UnprocessableEntity(T bodyContent) => BaseHttpResultExtensions.Json( StatusCodes.Status422UnprocessableEntity, @@ -84,11 +125,17 @@ public static THttpResult UnprocessableEntity(T bodyContent) => // ── 500 Internal Server Error ──────────────────────────────────────────────────── + /// Creates a 500 Internal Server Error response. + /// An HTTP 500 result. public static THttpResult InternalServerError() => BaseHttpResultExtensions.StatusCode( StatusCodes.Status500InternalServerError ); + /// Creates a 500 Internal Server Error response with content. + /// The type of content to return. + /// The response content to serialize. + /// An HTTP 500 result with JSON content. public static THttpResult InternalServerError(T bodyContent) => BaseHttpResultExtensions.Json( StatusCodes.Status500InternalServerError, diff --git a/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs index 42a90bea..c3a44244 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs @@ -1,16 +1,37 @@ namespace MinimalLambda.Envelopes.ApiGateway; +/// +/// Defines the contract for HTTP response results returned from ALB, API Gateway v1, and API +/// Gateway v2 Lambda integrations. +/// +/// The concrete implementing type for fluent method chaining. public interface IHttpResult : IResponseEnvelope where TSelf : IHttpResult { - public int StatusCode { get; set; } + /// Gets or sets the response body content. + public string Body { get; set; } + /// Gets or sets the HTTP response headers. public IDictionary Headers { get; set; } - public string Body { get; set; } - + /// Gets or sets whether the body is base64-encoded for binary content. public bool IsBase64Encoded { get; set; } + /// Gets or sets the HTTP status code. + public int StatusCode { get; set; } + + /// Creates a new HTTP result instance. + /// + /// Provide either for automatic serialization or + /// for pre-serialized content. + /// + /// The type of content being returned. + /// The HTTP status code. + /// The typed content to serialize into the body. + /// A pre-serialized body string. + /// Optional response headers. + /// Whether the body is base64-encoded. + /// A new HTTP result instance. static abstract TSelf Create( int statusCode, TResponse? bodyContent, @@ -19,5 +40,8 @@ static abstract TSelf Create( bool isBase64Encoded ); + /// Applies customizations to the result. + /// An action to customize the result properties. + /// The same instance for method chaining. public TSelf Customize(Action customizer); } From 96c5ec0076593f5efb331f18fddce8ab6db77429 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 20:40:00 -0500 Subject: [PATCH 21/38] feat(envelopes): add missing XML documentation for `Customize` and `Create` methods - Added `inheritdoc` XML documentation for `Customize` method in `ApiGatewayResult`, `ApiGatewayV2Result`, and `AlbResult`. - Applied `inheritdoc` XML documentation for `Create` method in all affected result classes. - Improved code clarity with consistent use of XML comments. --- src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs | 2 ++ .../MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs | 2 ++ .../MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs b/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs index 864c0589..16a24a46 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs @@ -31,12 +31,14 @@ public void PackPayload(EnvelopeOptions options) Body = ((ApplicationLoadBalancerResponse)_inner).Body; } + /// public AlbResult Customize(Action customizer) { customizer(this); return this; } + /// public static AlbResult Create( int statusCode, T? bodyContent, diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs index 27f52496..09d5e323 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs @@ -29,12 +29,14 @@ public void PackPayload(EnvelopeOptions options) Body = ((APIGatewayProxyResponse)_inner).Body; } + /// public ApiGatewayResult Customize(Action customizer) { customizer(this); return this; } + /// public static ApiGatewayResult Create( int statusCode, T? bodyContent, diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs index 1a0bccf3..a7207c96 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs @@ -31,12 +31,14 @@ public void PackPayload(EnvelopeOptions options) Body = ((APIGatewayHttpApiV2ProxyResponse)_inner).Body; } + /// public ApiGatewayV2Result Customize(Action customizer) { customizer(this); return this; } + /// public static ApiGatewayV2Result Create( int statusCode, T? bodyContent, From 6faef789f3fa8b980ac74a040ac9388ddb63ecc6 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 20:45:37 -0500 Subject: [PATCH 22/38] feat(envelopes): enhance XML documentation for AWS Lambda result classes - Added detailed XML summaries and remarks to `ApiGatewayResult`, `ApiGatewayV2Result`, and `AlbResult`. - Improved clarity by explaining usage scenarios for each result class. - Enhanced API documentation for better usability and maintainability. --- src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs | 9 +++++++++ .../ApiGatewayResult.cs | 9 +++++++++ .../ApiGatewayV2Result.cs | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs b/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs index 16a24a46..8738a1db 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs @@ -5,6 +5,15 @@ namespace MinimalLambda.Envelopes.Alb; +/// +/// Represents an HTTP response for AWS Lambda functions invoked by an Application Load +/// Balancer (ALB). +/// +/// +/// This class wraps and provides support for +/// response envelope customization through . Use this type when +/// returning responses from Lambda functions triggered by ALB target groups. +/// public sealed class AlbResult : ApplicationLoadBalancerResponse, IHttpResult { [JsonIgnore] diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs index 09d5e323..39383ba5 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs @@ -4,6 +4,15 @@ namespace MinimalLambda.Envelopes.ApiGateway; +/// +/// Represents an HTTP response for AWS Lambda functions invoked by Amazon API Gateway REST +/// API (v1). +/// +/// +/// This class wraps and provides support for response +/// envelope customization through . Use this type when returning +/// responses from Lambda proxy integrations with API Gateway REST APIs. +/// public sealed class ApiGatewayResult : APIGatewayProxyResponse, IHttpResult { [JsonIgnore] diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs index a7207c96..977c777d 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs @@ -4,6 +4,15 @@ namespace MinimalLambda.Envelopes.ApiGateway; +/// +/// Represents an HTTP response for AWS Lambda functions invoked by Amazon API Gateway HTTP +/// API (v2). +/// +/// +/// This class wraps and provides support for +/// response envelope customization through . Use this type when +/// returning responses from Lambda proxy integrations with API Gateway HTTP APIs. +/// public sealed class ApiGatewayV2Result : APIGatewayHttpApiV2ProxyResponse, IHttpResult From 16b549ace8296af702dc6a90c64a4bb45e76448c Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 20:59:17 -0500 Subject: [PATCH 23/38] refactor(envelopes): remove `Customize` method from result classes - Removed `Customize` method from `ApiGatewayResult`, `ApiGatewayV2Result`, and `AlbResult`. - Simplified `IHttpResult` interface by excluding `Customize` method. - Refactored `BaseHttpResultExtensions` to provide `Customize` functionality as an extension method. - Updated namespaces in all affected files for consistency with the project structure. --- .../MinimalLambda.Envelopes.Alb/AlbResult.cs | 8 -------- .../ApiGatewayResult.cs | 7 ------- .../ApiGatewayV2Result.cs | 7 ------- .../BaseHttpResultExtensions.cs | 17 +++++++++++++++-- .../HttpResultExtensions.cs | 2 +- .../MinimalLambda.Envelopes/IHttpResult.cs | 7 +------ 6 files changed, 17 insertions(+), 31 deletions(-) diff --git a/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs b/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs index 8738a1db..997297ff 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs @@ -1,6 +1,5 @@ using System.Text.Json.Serialization; using Amazon.Lambda.ApplicationLoadBalancerEvents; -using MinimalLambda.Envelopes.ApiGateway; using MinimalLambda.Options; namespace MinimalLambda.Envelopes.Alb; @@ -40,13 +39,6 @@ public void PackPayload(EnvelopeOptions options) Body = ((ApplicationLoadBalancerResponse)_inner).Body; } - /// - public AlbResult Customize(Action customizer) - { - customizer(this); - return this; - } - /// public static AlbResult Create( int statusCode, diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs index 39383ba5..016248d1 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs @@ -38,13 +38,6 @@ public void PackPayload(EnvelopeOptions options) Body = ((APIGatewayProxyResponse)_inner).Body; } - /// - public ApiGatewayResult Customize(Action customizer) - { - customizer(this); - return this; - } - /// public static ApiGatewayResult Create( int statusCode, diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs index 977c777d..6f8c13b8 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs @@ -40,13 +40,6 @@ public void PackPayload(EnvelopeOptions options) Body = ((APIGatewayHttpApiV2ProxyResponse)_inner).Body; } - /// - public ApiGatewayV2Result Customize(Action customizer) - { - customizer(this); - return this; - } - /// public static ApiGatewayV2Result Create( int statusCode, diff --git a/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs b/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs index 15a01174..5f9a3895 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs +++ b/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs @@ -1,9 +1,9 @@ -namespace MinimalLambda.Envelopes.ApiGateway; +namespace MinimalLambda.Envelopes; /// Provides factory extension methods for creating HTTP results. public static class BaseHttpResultExtensions { - extension(IHttpResult) + extension(IHttpResult result) where THttpResult : IHttpResult { /// Creates an HTTP result with the specified status code. @@ -48,4 +48,17 @@ public static THttpResult Json(int statusCode, T bodyContent) => false ); } + + extension(THttpResult result) + where THttpResult : IHttpResult + { + /// Applies customizations to the result. + /// An action to customize the result properties. + /// The same instance for method chaining. + public THttpResult Customize(Action customizer) + { + customizer(result); + return result; + } + } } diff --git a/src/Envelopes/MinimalLambda.Envelopes/HttpResultExtensions.cs b/src/Envelopes/MinimalLambda.Envelopes/HttpResultExtensions.cs index 7e47e115..19fd2879 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/HttpResultExtensions.cs +++ b/src/Envelopes/MinimalLambda.Envelopes/HttpResultExtensions.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Http; -namespace MinimalLambda.Envelopes.ApiGateway; +namespace MinimalLambda.Envelopes; /// Provides convenience extension methods for common HTTP status codes. public static class HttpResultExtensions diff --git a/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs index c3a44244..8bc419db 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs @@ -1,4 +1,4 @@ -namespace MinimalLambda.Envelopes.ApiGateway; +namespace MinimalLambda.Envelopes; /// /// Defines the contract for HTTP response results returned from ALB, API Gateway v1, and API @@ -39,9 +39,4 @@ static abstract TSelf Create( IDictionary? headers, bool isBase64Encoded ); - - /// Applies customizations to the result. - /// An action to customize the result properties. - /// The same instance for method chaining. - public TSelf Customize(Action customizer); } From 833856de1dbe0efeb1f596c437ee824a7981c9b5 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 21:00:03 -0500 Subject: [PATCH 24/38] feat(events): add `MinimalLambda.Envelopes` to `MinimalLambda.Example.Events` - Included `MinimalLambda.Envelopes` namespace in the example project for envelope handling. - Enhanced `examples/MinimalLambda.Example.Events/Program.cs` with additional import for functionality consistency. --- examples/MinimalLambda.Example.Events/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/MinimalLambda.Example.Events/Program.cs b/examples/MinimalLambda.Example.Events/Program.cs index cbb83951..61199221 100644 --- a/examples/MinimalLambda.Example.Events/Program.cs +++ b/examples/MinimalLambda.Example.Events/Program.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using MinimalLambda.Builder; +using MinimalLambda.Envelopes; using MinimalLambda.Envelopes.ApiGateway; var builder = LambdaApplication.CreateBuilder(); From f06580c22e2e8c2fb4662791b92f384bf354a17c Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 21:09:24 -0500 Subject: [PATCH 25/38] refactor(envelopes): simplify `Create` method by removing unused parameters - Removed the `body` parameter from all `Create` method overloads in result classes. - Updated `IHttpResult` interface and implementations to reflect the changes. - Refactored `BaseHttpResultExtensions` to align with streamlined method signatures. - Improved overall code readability and reduced unnecessary complexity. --- .../MinimalLambda.Envelopes.Alb/AlbResult.cs | 4 +- .../ApiGatewayResult.cs | 4 +- .../ApiGatewayV2Result.cs | 4 +- .../BaseHttpResultExtensions.cs | 27 ++++++------- .../MinimalLambda.Envelopes/IHttpResult.cs | 40 +++++++++++++++++-- 5 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs b/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs index 997297ff..243c6615 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs @@ -43,8 +43,7 @@ public void PackPayload(EnvelopeOptions options) public static AlbResult Create( int statusCode, T? bodyContent, - string? body, - IDictionary? headers, + IDictionary headers, bool isBase64Encoded ) => new( @@ -52,7 +51,6 @@ bool isBase64Encoded { StatusCode = statusCode, BodyContent = bodyContent, - Body = body ?? string.Empty, Headers = headers, IsBase64Encoded = isBase64Encoded, } diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs index 016248d1..0ae46ee2 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs @@ -42,8 +42,7 @@ public void PackPayload(EnvelopeOptions options) public static ApiGatewayResult Create( int statusCode, T? bodyContent, - string? body, - IDictionary? headers, + IDictionary headers, bool isBase64Encoded ) => new( @@ -51,7 +50,6 @@ bool isBase64Encoded { StatusCode = statusCode, BodyContent = bodyContent, - Body = body ?? string.Empty, Headers = headers, IsBase64Encoded = isBase64Encoded, } diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs index 6f8c13b8..ed5e6a7f 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs @@ -44,8 +44,7 @@ public void PackPayload(EnvelopeOptions options) public static ApiGatewayV2Result Create( int statusCode, T? bodyContent, - string? body, - IDictionary? headers, + IDictionary headers, bool isBase64Encoded ) => new( @@ -53,7 +52,6 @@ bool isBase64Encoded { StatusCode = statusCode, BodyContent = bodyContent, - Body = body ?? string.Empty, Headers = headers, IsBase64Encoded = isBase64Encoded, } diff --git a/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs b/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs index 5f9a3895..decf4fff 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs +++ b/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs @@ -10,26 +10,24 @@ public static class BaseHttpResultExtensions /// The HTTP status code. /// An HTTP result with the status code. public static THttpResult StatusCode(int statusCode) => - THttpResult.Create( - statusCode, - null, - null, - new Dictionary(), - false - ); + THttpResult.Create(statusCode, null, new Dictionary(), false); /// Creates a text/plain HTTP result. /// The HTTP status code. /// The plain text response body. /// An HTTP result with text/plain content type. public static THttpResult Text(int statusCode, string body) => - THttpResult.Create( - statusCode, - null, - body, - new Dictionary { ["Content-Type"] = "text/plain; charset=utf-8" }, - false - ); + THttpResult + .Create( + statusCode, + null, + new Dictionary + { + ["Content-Type"] = "text/plain; charset=utf-8", + }, + false + ) + .Customize(result => result.Body = body); /// Creates an application/json HTTP result. /// The type of content to serialize. @@ -40,7 +38,6 @@ public static THttpResult Json(int statusCode, T bodyContent) => THttpResult.Create( statusCode, bodyContent, - null, new Dictionary { ["Content-Type"] = "application/json; charset=utf-8", diff --git a/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs index 8bc419db..4c2c305a 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs @@ -28,15 +28,47 @@ public interface IHttpResult : IResponseEnvelope /// The type of content being returned. /// The HTTP status code. /// The typed content to serialize into the body. - /// A pre-serialized body string. - /// Optional response headers. + /// Response headers. /// Whether the body is base64-encoded. /// A new HTTP result instance. static abstract TSelf Create( int statusCode, TResponse? bodyContent, - string? body, - IDictionary? headers, + IDictionary headers, bool isBase64Encoded ); } + +public interface IHttpResult2 : IResponseEnvelope + where TSelf : IHttpResult2 +{ + public string Body { get; set; } + + public IDictionary Headers { get; set; } + + public bool IsBase64Encoded { get; set; } + + public int StatusCode { get; set; } + + public IResponseEnvelope ResponseEnvelope { get; set; } +} + +public static class MyExtension +{ + extension(IHttpResult) + where THttpResult : IHttpResult, new() + { + public static THttpResult Create( + int statusCode, + TResponse? bodyContent, + IDictionary headers, + bool isBase64Encoded + ) => + new() + { + StatusCode = statusCode, + Headers = headers, + IsBase64Encoded = isBase64Encoded, + }; + } +} From 5518f03ff0c4cb602a46d555a7d7ec0ecb99b5fd Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 21:09:51 -0500 Subject: [PATCH 26/38] refactor(envelopes): remove redundant `IHttpResult2` interface and unused extension methods - Deleted `IHttpResult2` as it was not referenced or utilized in the project. - Removed unused `MyExtension` class and its `Create` extension method. - Simplified `IHttpResult` by excluding unnecessary extensions and duplicate abstractions. --- .../MinimalLambda.Envelopes/IHttpResult.cs | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs index 4c2c305a..7b31894a 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs @@ -38,37 +38,3 @@ static abstract TSelf Create( bool isBase64Encoded ); } - -public interface IHttpResult2 : IResponseEnvelope - where TSelf : IHttpResult2 -{ - public string Body { get; set; } - - public IDictionary Headers { get; set; } - - public bool IsBase64Encoded { get; set; } - - public int StatusCode { get; set; } - - public IResponseEnvelope ResponseEnvelope { get; set; } -} - -public static class MyExtension -{ - extension(IHttpResult) - where THttpResult : IHttpResult, new() - { - public static THttpResult Create( - int statusCode, - TResponse? bodyContent, - IDictionary headers, - bool isBase64Encoded - ) => - new() - { - StatusCode = statusCode, - Headers = headers, - IsBase64Encoded = isBase64Encoded, - }; - } -} From bca9e726bf1ad745cde22125c397af2065d01506 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 21:19:32 -0500 Subject: [PATCH 27/38] feat(envelopes): update package description and add README file - Updated project file with a meaningful package description. - Added a comprehensive `README.md` for `MinimalLambda.Envelopes` package. - Included usage information, documentation links, and related package details. --- .../MinimalLambda.Envelopes.csproj | 2 +- .../MinimalLambda.Envelopes/README.md | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Envelopes/MinimalLambda.Envelopes/MinimalLambda.Envelopes.csproj b/src/Envelopes/MinimalLambda.Envelopes/MinimalLambda.Envelopes.csproj index 9e9ec9a8..64cb54c8 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/MinimalLambda.Envelopes.csproj +++ b/src/Envelopes/MinimalLambda.Envelopes/MinimalLambda.Envelopes.csproj @@ -9,7 +9,7 @@ true MinimalLambda.Envelopes - "" + Core components for MinimalLambda envelopes README.md diff --git a/src/Envelopes/MinimalLambda.Envelopes/README.md b/src/Envelopes/MinimalLambda.Envelopes/README.md index e69de29b..b5dede00 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/README.md +++ b/src/Envelopes/MinimalLambda.Envelopes/README.md @@ -0,0 +1,38 @@ +# MinimalLambda.Envelopes + +Shared infrastructure and abstractions for envelope packages. + +> 📚 **[View Full Documentation](https://j-d-ha.github.io/minimal-lambda/)** + +## Overview + +This package contains shared infrastructure and common abstractions used across envelope packages in the MinimalLambda framework. + +> [!NOTE] +> This is an infrastructure package automatically referenced by other envelope packages. + +## What's Included + +- `IHttpResult` interface and extension methods for HTTP response building + +## Other Packages + +Additional packages in the minimal-lambda framework for abstractions, observability, and event source handling. + +| Package | NuGet | Downloads | +|-------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [**MinimalLambda**](../../MinimalLambda/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda/) | +| [**MinimalLambda.Abstractions**](../../MinimalLambda.Abstractions/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions/) | +| [**MinimalLambda.OpenTelemetry**](../../MinimalLambda.OpenTelemetry/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry/) | +| [**MinimalLambda.Envelopes.Sqs**](../MinimalLambda.Envelopes.Sqs/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | +| [**MinimalLambda.Envelopes.ApiGateway**](../MinimalLambda.Envelopes.ApiGateway/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | +| [**MinimalLambda.Envelopes.Sns**](../MinimalLambda.Envelopes.Sns/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | +| [**MinimalLambda.Envelopes.Kinesis**](../MinimalLambda.Envelopes.Kinesis/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Kinesis.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Kinesis) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Kinesis.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Kinesis/) | +| [**MinimalLambda.Envelopes.KinesisFirehose**](../MinimalLambda.Envelopes.KinesisFirehose/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.KinesisFirehose.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.KinesisFirehose) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.KinesisFirehose.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.KinesisFirehose/) | +| [**MinimalLambda.Envelopes.Kafka**](../MinimalLambda.Envelopes.Kafka/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Kafka.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Kafka) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Kafka.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Kafka/) | +| [**MinimalLambda.Envelopes.CloudWatchLogs**](../MinimalLambda.Envelopes.CloudWatchLogs/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.CloudWatchLogs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.CloudWatchLogs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.CloudWatchLogs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.CloudWatchLogs/) | +| [**MinimalLambda.Envelopes.Alb**](../MinimalLambda.Envelopes.Alb/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Alb.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Alb) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Alb.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Alb/) | + +## License + +This project is licensed under the MIT License. See [LICENSE](../../LICENSE) for details. From afbaeab53b77f28fb380cbdd84b094490c1f95a8 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 21:42:57 -0500 Subject: [PATCH 28/38] feat(docs): add `MinimalLambda.Envelopes` package references to README files - Updated multiple README files to include references to the `MinimalLambda.Envelopes` package. - Added links to the NuGet package and download badges for consistency and accessibility. - Improved documentation to ensure comprehensive package visibility across projects. --- README.md | 1 + src/Envelopes/MinimalLambda.Envelopes.Alb/README.md | 1 + src/Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md | 1 + src/Envelopes/MinimalLambda.Envelopes.CloudWatchLogs/README.md | 1 + src/Envelopes/MinimalLambda.Envelopes.Kafka/README.md | 1 + src/Envelopes/MinimalLambda.Envelopes.Kinesis/README.md | 1 + src/Envelopes/MinimalLambda.Envelopes.KinesisFirehose/README.md | 1 + src/Envelopes/MinimalLambda.Envelopes.Sns/README.md | 1 + src/Envelopes/MinimalLambda.Envelopes.Sqs/README.md | 1 + src/MinimalLambda.Abstractions/README.md | 1 + src/MinimalLambda.OpenTelemetry/README.md | 1 + src/MinimalLambda.Testing/README.md | 1 + src/MinimalLambda/README.md | 1 + 13 files changed, 13 insertions(+) diff --git a/README.md b/README.md index 666eab07..57801514 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ including SQS, SNS, API Gateway, Kinesis, and more. | Package | NuGet | Downloads | |------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [**MinimalLambda.Envelopes**](./src/Envelopes/MinimalLambda.Envelopes/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | | [**MinimalLambda.Envelopes.Sqs**](./MinimalLambda.Envelopes.Sqs/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | | [**MinimalLambda.Envelopes.ApiGateway**](./MinimalLambda.Envelopes.ApiGateway/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | | [**MinimalLambda.Envelopes.Sns**](./MinimalLambda.Envelopes.Sns/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | diff --git a/src/Envelopes/MinimalLambda.Envelopes.Alb/README.md b/src/Envelopes/MinimalLambda.Envelopes.Alb/README.md index bceb4685..dea1d62e 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.Alb/README.md +++ b/src/Envelopes/MinimalLambda.Envelopes.Alb/README.md @@ -134,6 +134,7 @@ source handling. | [**MinimalLambda**](../../MinimalLambda/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda/) | | [**MinimalLambda.Abstractions**](../../MinimalLambda.Abstractions/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions/) | | [**MinimalLambda.OpenTelemetry**](../../MinimalLambda.OpenTelemetry/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry/) | +| [**MinimalLambda.Envelopes**](../MinimalLambda.Envelopes/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | | [**MinimalLambda.Envelopes.Sqs**](../MinimalLambda.Envelopes.Sqs/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | | [**MinimalLambda.Envelopes.ApiGateway**](../MinimalLambda.Envelopes.ApiGateway/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | | [**MinimalLambda.Envelopes.Sns**](../MinimalLambda.Envelopes.Sns/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md index 005090a0..26a870d1 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md @@ -138,6 +138,7 @@ source handling. | [**MinimalLambda**](../../MinimalLambda/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda/) | | [**MinimalLambda.Abstractions**](../../MinimalLambda.Abstractions/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions/) | | [**MinimalLambda.OpenTelemetry**](../../MinimalLambda.OpenTelemetry/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry/) | +| [**MinimalLambda.Envelopes**](../MinimalLambda.Envelopes/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | | [**MinimalLambda.Envelopes.Sqs**](../MinimalLambda.Envelopes.Sqs/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | | [**MinimalLambda.Envelopes.ApiGateway**](../MinimalLambda.Envelopes.ApiGateway/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | | [**MinimalLambda.Envelopes.Sns**](../MinimalLambda.Envelopes.Sns/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | diff --git a/src/Envelopes/MinimalLambda.Envelopes.CloudWatchLogs/README.md b/src/Envelopes/MinimalLambda.Envelopes.CloudWatchLogs/README.md index 2e007f85..ad7da84f 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.CloudWatchLogs/README.md +++ b/src/Envelopes/MinimalLambda.Envelopes.CloudWatchLogs/README.md @@ -127,6 +127,7 @@ source handling. | [**MinimalLambda**](../../MinimalLambda/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda/) | | [**MinimalLambda.Abstractions**](../../MinimalLambda.Abstractions/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions/) | | [**MinimalLambda.OpenTelemetry**](../../MinimalLambda.OpenTelemetry/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry/) | +| [**MinimalLambda.Envelopes**](../MinimalLambda.Envelopes/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | | [**MinimalLambda.Envelopes.Sqs**](../MinimalLambda.Envelopes.Sqs/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | | [**MinimalLambda.Envelopes.ApiGateway**](../MinimalLambda.Envelopes.ApiGateway/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | | [**MinimalLambda.Envelopes.Sns**](../MinimalLambda.Envelopes.Sns/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | diff --git a/src/Envelopes/MinimalLambda.Envelopes.Kafka/README.md b/src/Envelopes/MinimalLambda.Envelopes.Kafka/README.md index b4bded24..b06bc82c 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.Kafka/README.md +++ b/src/Envelopes/MinimalLambda.Envelopes.Kafka/README.md @@ -131,6 +131,7 @@ source handling. | [**MinimalLambda**](../../MinimalLambda/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda/) | | [**MinimalLambda.Abstractions**](../../MinimalLambda.Abstractions/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions/) | | [**MinimalLambda.OpenTelemetry**](../../MinimalLambda.OpenTelemetry/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry/) | +| [**MinimalLambda.Envelopes**](../MinimalLambda.Envelopes/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | | [**MinimalLambda.Envelopes.Sqs**](../MinimalLambda.Envelopes.Sqs/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | | [**MinimalLambda.Envelopes.ApiGateway**](../MinimalLambda.Envelopes.ApiGateway/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | | [**MinimalLambda.Envelopes.Sns**](../MinimalLambda.Envelopes.Sns/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | diff --git a/src/Envelopes/MinimalLambda.Envelopes.Kinesis/README.md b/src/Envelopes/MinimalLambda.Envelopes.Kinesis/README.md index a33b19be..a308e241 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.Kinesis/README.md +++ b/src/Envelopes/MinimalLambda.Envelopes.Kinesis/README.md @@ -122,6 +122,7 @@ source handling. | [**MinimalLambda**](../../MinimalLambda/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda/) | | [**MinimalLambda.Abstractions**](../../MinimalLambda.Abstractions/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions/) | | [**MinimalLambda.OpenTelemetry**](../../MinimalLambda.OpenTelemetry/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry/) | +| [**MinimalLambda.Envelopes**](../MinimalLambda.Envelopes/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | | [**MinimalLambda.Envelopes.Sqs**](../MinimalLambda.Envelopes.Sqs/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | | [**MinimalLambda.Envelopes.ApiGateway**](../MinimalLambda.Envelopes.ApiGateway/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | | [**MinimalLambda.Envelopes.Sns**](../MinimalLambda.Envelopes.Sns/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | diff --git a/src/Envelopes/MinimalLambda.Envelopes.KinesisFirehose/README.md b/src/Envelopes/MinimalLambda.Envelopes.KinesisFirehose/README.md index 1440b7a8..6f855bc2 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.KinesisFirehose/README.md +++ b/src/Envelopes/MinimalLambda.Envelopes.KinesisFirehose/README.md @@ -157,6 +157,7 @@ source handling. | [**MinimalLambda**](../../MinimalLambda/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda/) | | [**MinimalLambda.Abstractions**](../../MinimalLambda.Abstractions/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions/) | | [**MinimalLambda.OpenTelemetry**](../../MinimalLambda.OpenTelemetry/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry/) | +| [**MinimalLambda.Envelopes**](../MinimalLambda.Envelopes/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | | [**MinimalLambda.Envelopes.Sqs**](../MinimalLambda.Envelopes.Sqs/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | | [**MinimalLambda.Envelopes.ApiGateway**](../MinimalLambda.Envelopes.ApiGateway/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | | [**MinimalLambda.Envelopes.Sns**](../MinimalLambda.Envelopes.Sns/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | diff --git a/src/Envelopes/MinimalLambda.Envelopes.Sns/README.md b/src/Envelopes/MinimalLambda.Envelopes.Sns/README.md index bc9907a0..28cfb540 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.Sns/README.md +++ b/src/Envelopes/MinimalLambda.Envelopes.Sns/README.md @@ -109,6 +109,7 @@ source handling. | [**MinimalLambda**](../../MinimalLambda/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda/) | | [**MinimalLambda.Abstractions**](../../MinimalLambda.Abstractions/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions/) | | [**MinimalLambda.OpenTelemetry**](../../MinimalLambda.OpenTelemetry/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry/) | +| [**MinimalLambda.Envelopes**](../MinimalLambda.Envelopes/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | | [**MinimalLambda.Envelopes.Sqs**](../MinimalLambda.Envelopes.Sqs/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | | [**MinimalLambda.Envelopes.ApiGateway**](../MinimalLambda.Envelopes.ApiGateway/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | | [**MinimalLambda.Envelopes.Sns**](../MinimalLambda.Envelopes.Sns/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | diff --git a/src/Envelopes/MinimalLambda.Envelopes.Sqs/README.md b/src/Envelopes/MinimalLambda.Envelopes.Sqs/README.md index ecc47dfb..774535c0 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.Sqs/README.md +++ b/src/Envelopes/MinimalLambda.Envelopes.Sqs/README.md @@ -153,6 +153,7 @@ source handling. | [**MinimalLambda**](../../MinimalLambda/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.svg)](https://www.nuget.org/packages/MinimalLambda/) | | [**MinimalLambda.Abstractions**](../../MinimalLambda.Abstractions/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions/) | | [**MinimalLambda.OpenTelemetry**](../../MinimalLambda.OpenTelemetry/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry/) | +| [**MinimalLambda.Envelopes**](../MinimalLambda.Envelopes/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | | [**MinimalLambda.Envelopes.Sqs**](../MinimalLambda.Envelopes.Sqs/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | | [**MinimalLambda.Envelopes.ApiGateway**](../MinimalLambda.Envelopes.ApiGateway/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | | [**MinimalLambda.Envelopes.Sns**](../MinimalLambda.Envelopes.Sns/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | diff --git a/src/MinimalLambda.Abstractions/README.md b/src/MinimalLambda.Abstractions/README.md index 326a587d..b0cd0bc7 100644 --- a/src/MinimalLambda.Abstractions/README.md +++ b/src/MinimalLambda.Abstractions/README.md @@ -214,6 +214,7 @@ source handling. | [**MinimalLambda.Abstractions**](../MinimalLambda.Abstractions/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions/) | | [**MinimalLambda.OpenTelemetry**](../MinimalLambda.OpenTelemetry/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry/) | | [**MinimalLambda.Testing**](../MinimalLambda.Testing/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Testing.svg)](https://www.nuget.org/packages/MinimalLambda.Testing) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Testing.svg)](https://www.nuget.org/packages/MinimalLambda.Testing/) | +| [**MinimalLambda.Envelopes**](../Envelopes/MinimalLambda.Envelopes/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | | [**MinimalLambda.Envelopes.Sqs**](../Envelopes/MinimalLambda.Envelopes.Sqs/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | | [**MinimalLambda.Envelopes.ApiGateway**](../Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | | [**MinimalLambda.Envelopes.Sns**](../Envelopes/MinimalLambda.Envelopes.Sns/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | diff --git a/src/MinimalLambda.OpenTelemetry/README.md b/src/MinimalLambda.OpenTelemetry/README.md index 75028971..68aa3be4 100644 --- a/src/MinimalLambda.OpenTelemetry/README.md +++ b/src/MinimalLambda.OpenTelemetry/README.md @@ -252,6 +252,7 @@ source handling. | [**MinimalLambda.Abstractions**](../MinimalLambda.Abstractions/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions/) | | [**MinimalLambda.OpenTelemetry**](../MinimalLambda.OpenTelemetry/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry/) | | [**MinimalLambda.Testing**](../MinimalLambda.Testing/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Testing.svg)](https://www.nuget.org/packages/MinimalLambda.Testing) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Testing.svg)](https://www.nuget.org/packages/MinimalLambda.Testing/) | +| [**MinimalLambda.Envelopes**](../Envelopes/MinimalLambda.Envelopes/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | | [**MinimalLambda.Envelopes.Sqs**](../Envelopes/MinimalLambda.Envelopes.Sqs/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | | [**MinimalLambda.Envelopes.ApiGateway**](../Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | | [**MinimalLambda.Envelopes.Sns**](../Envelopes/MinimalLambda.Envelopes.Sns/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | diff --git a/src/MinimalLambda.Testing/README.md b/src/MinimalLambda.Testing/README.md index 9c9f299a..ca70a976 100644 --- a/src/MinimalLambda.Testing/README.md +++ b/src/MinimalLambda.Testing/README.md @@ -105,6 +105,7 @@ source handling. | [**MinimalLambda.Abstractions**](../MinimalLambda.Abstractions/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions/) | | [**MinimalLambda.OpenTelemetry**](../MinimalLambda.OpenTelemetry/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry/) | | [**MinimalLambda.Testing**](../MinimalLambda.Testing/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Testing.svg)](https://www.nuget.org/packages/MinimalLambda.Testing) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Testing.svg)](https://www.nuget.org/packages/MinimalLambda.Testing/) | +| [**MinimalLambda.Envelopes**](../Envelopes/MinimalLambda.Envelopes/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | | [**MinimalLambda.Envelopes.Sqs**](../Envelopes/MinimalLambda.Envelopes.Sqs/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | | [**MinimalLambda.Envelopes.ApiGateway**](../Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | | [**MinimalLambda.Envelopes.Sns**](../Envelopes/MinimalLambda.Envelopes.Sns/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | diff --git a/src/MinimalLambda/README.md b/src/MinimalLambda/README.md index 85e20c5f..0e2d0318 100644 --- a/src/MinimalLambda/README.md +++ b/src/MinimalLambda/README.md @@ -278,6 +278,7 @@ source handling. | [**MinimalLambda.Abstractions**](../MinimalLambda.Abstractions/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Abstractions.svg)](https://www.nuget.org/packages/MinimalLambda.Abstractions/) | | [**MinimalLambda.OpenTelemetry**](../MinimalLambda.OpenTelemetry/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.OpenTelemetry.svg)](https://www.nuget.org/packages/MinimalLambda.OpenTelemetry/) | | [**MinimalLambda.Testing**](../MinimalLambda.Testing/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Testing.svg)](https://www.nuget.org/packages/MinimalLambda.Testing) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Testing.svg)](https://www.nuget.org/packages/MinimalLambda.Testing/) | +| [**MinimalLambda.Envelopes**](../Envelopes/MinimalLambda.Envelopes/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | | [**MinimalLambda.Envelopes.Sqs**](../Envelopes/MinimalLambda.Envelopes.Sqs/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | | [**MinimalLambda.Envelopes.ApiGateway**](../Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | | [**MinimalLambda.Envelopes.Sns**](../Envelopes/MinimalLambda.Envelopes.Sns/README.md) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | From 229e581f3d98b069cd876f3592668187a1763305 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 22:01:28 -0500 Subject: [PATCH 29/38] feat(docs): add `AlbResult`, `ApiGatewayResult`, and `ApiGatewayV2Result` documentation to README - Updated `MinimalLambda.Envelopes.Alb` README with details on `AlbResult` class and its fluent API. - Updated `MinimalLambda.Envelopes.ApiGateway` README with details on `ApiGatewayResult` and `ApiGatewayV2Result` classes. - Highlighted usage scenarios, response methods, and customization options. - Added code samples and guidance for registering serializers with multiple result types. --- .../MinimalLambda.Envelopes.Alb/README.md | 46 +++++++++++++++ .../README.md | 58 +++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/src/Envelopes/MinimalLambda.Envelopes.Alb/README.md b/src/Envelopes/MinimalLambda.Envelopes.Alb/README.md index dea1d62e..559c213d 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.Alb/README.md +++ b/src/Envelopes/MinimalLambda.Envelopes.Alb/README.md @@ -20,6 +20,7 @@ serialization: |--------------------------|-----------------------------------|---------------------------------------------| | `AlbRequestEnvelope` | `ApplicationLoadBalancerRequest` | ALB requests with deserialized body content | | `AlbResponseEnvelope` | `ApplicationLoadBalancerResponse` | ALB responses with typed body content | +| `AlbResult` | `ApplicationLoadBalancerResponse` | ALB responses with fluent API builder | ## Quick Start @@ -60,6 +61,40 @@ internal record Request(string Name); internal record Response(string Message, DateTime TimestampUtc); ``` +## Response Builder API + +The `AlbResult` class provides a fluent API for building HTTP responses. **Key benefit**: Return +multiple strongly typed models from the same handler (e.g., success vs. error responses with +different types). + +```csharp +lambda.MapHandler(([Event] AlbRequestEnvelope request) => +{ + if (string.IsNullOrEmpty(request.BodyContent?.Name)) + return AlbResult.BadRequest(new ErrorResponse("Name is required")); + + return AlbResult.Ok(new SuccessResponse($"Hello {request.BodyContent.Name}!")); +}); +``` + +Available methods: `Ok()`, `Created()`, `NoContent()`, `BadRequest()`, `Unauthorized()`, +`NotFound()`, `Conflict()`, `UnprocessableEntity()`, `InternalServerError()`, `StatusCode(int)`, +`Text(int, string)`, `Json(int, T)`. + +All methods have overloads with and without body content. Use `.Customize()` for fluent header +customization. + +> [!NOTE] +> `AlbResult` uses `AlbResponseEnvelope` internally. + +## Choosing Between Envelopes and Results + +**Use `AlbResult`** when you need to return multiple strongly typed models from the same handler +(e.g., different success and error types). Provides convenient methods for common HTTP status codes. + +**Use `AlbResponseEnvelope` directly** when you need custom serialization (e.g., XML) or want to +extend envelope base classes for custom behavior. + ## Custom Envelopes To implement custom deserialization logic, extend the appropriate base class and override the @@ -107,6 +142,17 @@ When using .NET Native AOT, register all envelope and payload types in your `Jso internal partial class SerializerContext : JsonSerializerContext; ``` +**When using `AlbResult` with multiple return types**, register each type separately: + +```csharp +[JsonSerializable(typeof(AlbRequestEnvelope))] +[JsonSerializable(typeof(AlbResult))] +[JsonSerializable(typeof(Request))] +[JsonSerializable(typeof(SuccessResponse))] +[JsonSerializable(typeof(ErrorResponse))] +internal partial class SerializerContext : JsonSerializerContext; +``` + Register the serializer and configure envelope options to use the context: ```csharp diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md index 26a870d1..33a3a71c 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/README.md @@ -21,6 +21,8 @@ serialization: | `ApiGatewayResponseEnvelope` | `APIGatewayProxyResponse` | REST API, HTTP API payload format 1.0, or WebSocket API responses with typed body | | `ApiGatewayV2RequestEnvelope` | `APIGatewayHttpApiV2ProxyRequest` | HTTP API payload format 2.0 requests with deserialized body | | `ApiGatewayV2ResponseEnvelope` | `APIGatewayHttpApiV2ProxyResponse` | HTTP API payload format 2.0 responses with typed body | +| `ApiGatewayResult` | `APIGatewayProxyResponse` | REST/HTTP/WebSocket API responses with fluent API | +| `ApiGatewayV2Result` | `APIGatewayHttpApiV2ProxyResponse` | HTTP API v2 responses with fluent API | ## Quick Start @@ -64,6 +66,51 @@ internal record Response(string Message, DateTime TimestampUtc); For HTTP API v2, use `ApiGatewayV2RequestEnvelope` and `ApiGatewayV2ResponseEnvelope` in the same way. +## Response Builder API + +The `ApiGatewayResult` and `ApiGatewayV2Result` classes provide fluent APIs for building HTTP +responses. **Key benefit**: Return multiple strongly typed models from the same handler (e.g., +success vs. error responses with different types). + +```csharp +// REST API / HTTP API v1 / WebSocket +lambda.MapHandler(([Event] ApiGatewayRequestEnvelope request) => +{ + if (string.IsNullOrEmpty(request.BodyContent?.Name)) + return ApiGatewayResult.BadRequest(new ErrorResponse("Name is required")); + + return ApiGatewayResult.Ok(new SuccessResponse($"Hello {request.BodyContent.Name}!")); +}); + +// HTTP API v2 +lambda.MapHandler(([Event] ApiGatewayV2RequestEnvelope request) => +{ + if (string.IsNullOrEmpty(request.BodyContent?.Name)) + return ApiGatewayV2Result.BadRequest(new ErrorResponse("Name is required")); + + return ApiGatewayV2Result.Ok(new SuccessResponse($"Hello {request.BodyContent.Name}!")); +}); +``` + +Available methods: `Ok()`, `Created()`, `NoContent()`, `BadRequest()`, `Unauthorized()`, +`NotFound()`, `Conflict()`, `UnprocessableEntity()`, `InternalServerError()`, `StatusCode(int)`, +`Text(int, string)`, `Json(int, T)`. + +All methods have overloads with and without body content. Use `.Customize()` for fluent header +customization. + +> [!NOTE] +> Both result classes use their respective envelope classes internally. + +## Choosing Between Envelopes and Results + +**Use `ApiGatewayResult` / `ApiGatewayV2Result`** when you need to return multiple strongly typed +models from the same handler (e.g., different success and error types). Provides convenient methods +for common HTTP status codes. + +**Use envelope classes directly** when you need custom serialization (e.g., XML) or want to extend +envelope base classes for custom behavior. + ## Custom Envelopes To implement custom deserialization logic, extend the appropriate base class and override the @@ -111,6 +158,17 @@ When using .NET Native AOT, register all envelope and payload types in your `Jso internal partial class SerializerContext : JsonSerializerContext; ``` +**When using `ApiGatewayResult` / `ApiGatewayV2Result` with multiple return types**, register each type separately: + +```csharp +[JsonSerializable(typeof(ApiGatewayRequestEnvelope))] +[JsonSerializable(typeof(ApiGatewayResult))] +[JsonSerializable(typeof(Request))] +[JsonSerializable(typeof(SuccessResponse))] +[JsonSerializable(typeof(ErrorResponse))] +internal partial class SerializerContext : JsonSerializerContext; +``` + Register the serializer and configure envelope options to use the context: ```csharp From 4dabb1c45967ceac168c79669e93e82811e97baf Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 22:12:16 -0500 Subject: [PATCH 30/38] feat(docs): add detailed documentation for `MinimalLambda.Envelopes` package features - Included a comprehensive explanation of the `MinimalLambda.Envelopes` infrastructure package. - Added documentation for the Response Builder API with usage examples and supported result classes. - Detailed AOT serializer configuration with examples for registering envelope and result types. - Highlighted scenarios for using result classes vs. envelopes directly. --- docs/features/envelopes.md | 125 +++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/docs/features/envelopes.md b/docs/features/envelopes.md index 4ecbed5b..384a01b6 100644 --- a/docs/features/envelopes.md +++ b/docs/features/envelopes.md @@ -26,6 +26,7 @@ Install only the envelopes you need; each one lives in its own NuGet package. | Event Source | Package | NuGet | |---------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Infrastructure / Base | [MinimalLambda.Envelopes](https://github.com/j-d-ha/minimal-lambda/tree/main/src/Envelopes/MinimalLambda.Envelopes) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | | SQS | [MinimalLambda.Envelopes.Sqs](https://github.com/j-d-ha/minimal-lambda/tree/main/src/Envelopes/MinimalLambda.Envelopes.Sqs) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | | SNS | [MinimalLambda.Envelopes.Sns](https://github.com/j-d-ha/minimal-lambda/tree/main/src/Envelopes/MinimalLambda.Envelopes.Sns) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | | API Gateway / HTTP API | [MinimalLambda.Envelopes.ApiGateway](https://github.com/j-d-ha/minimal-lambda/tree/main/src/Envelopes/MinimalLambda.Envelopes.ApiGateway) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | @@ -35,6 +36,11 @@ Install only the envelopes you need; each one lives in its own NuGet package. | CloudWatch Logs | [MinimalLambda.Envelopes.CloudWatchLogs](https://github.com/j-d-ha/minimal-lambda/tree/main/src/Envelopes/MinimalLambda.Envelopes.CloudWatchLogs) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.CloudWatchLogs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.CloudWatchLogs) | | Application Load Balancer (ALB) | [MinimalLambda.Envelopes.Alb](https://github.com/j-d-ha/minimal-lambda/tree/main/src/Envelopes/MinimalLambda.Envelopes.Alb) | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Alb.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Alb) | +!!! note "Infrastructure Package" + `MinimalLambda.Envelopes` is automatically referenced by ALB and API Gateway packages. It provides + `IHttpResult` and extension methods for the response builder API. You don't need to install + it directly. + Each package ships with README examples in the repository if you need event-specific guidance. ## Quick Start @@ -75,6 +81,125 @@ await lambda.RunAsync(); internal sealed record OrderMessage(string OrderId, decimal Amount); ``` +## Response Builder API + +For HTTP-based event sources (API Gateway, ALB), result classes provide a fluent API for building +responses. **Key benefit**: Return multiple strongly typed models from the same handler—for example, +different success and error response types. + +```csharp title="API Gateway Example" linenums="1" +using MinimalLambda.Envelopes.ApiGateway; + +lambda.MapHandler(([Event] ApiGatewayRequestEnvelope request) => +{ + // Each return statement uses a different strongly typed model + if (string.IsNullOrEmpty(request.BodyContent?.Username)) + return ApiGatewayResult.BadRequest(new ValidationError("Username required")); + + if (!authService.Authenticate(request.BodyContent)) + return ApiGatewayResult.Unauthorized(new AuthError("Invalid credentials")); + + return ApiGatewayResult.Ok(new LoginSuccess(token, expiresAt)); +}); + +internal record LoginRequest(string Username, string Password); +internal record ValidationError(string Message); +internal record AuthError(string Message); +internal record LoginSuccess(string Token, DateTime ExpiresAt); +``` + +### Available Result Classes + +| Class | Package | Use Case | +|------------------------|--------------------------------------|---------------------------------------------| +| `AlbResult` | MinimalLambda.Envelopes.Alb | Application Load Balancer responses | +| `ApiGatewayResult` | MinimalLambda.Envelopes.ApiGateway | REST API / HTTP API v1 / WebSocket | +| `ApiGatewayV2Result` | MinimalLambda.Envelopes.ApiGateway | HTTP API v2 responses | + +Common methods: `Ok()`, `Created()`, `NoContent()`, `BadRequest()`, `Unauthorized()`, `NotFound()`, +`Conflict()`, `UnprocessableEntity()`, `InternalServerError()`, `StatusCode(int)`, `Text(int, +string)`, `Json(int, T)`. All methods have overloads with and without body content. + +### When to Use Results vs. Envelopes + +**Use result classes** when you need to return multiple strongly typed models from the same handler. +Provides convenient methods for common HTTP status codes. + +**Use envelope classes directly** when you need custom serialization (e.g., XML) or want to extend +envelope base classes for custom behavior. + +!!! tip "Complete API Reference" + For detailed method documentation, AOT configuration, and advanced usage, see the package README + files: + + - [ALB Package README](https://github.com/j-d-ha/minimal-lambda/tree/main/src/Envelopes/MinimalLambda.Envelopes.Alb) + - [API Gateway Package README](https://github.com/j-d-ha/minimal-lambda/tree/main/src/Envelopes/MinimalLambda.Envelopes.ApiGateway) + +!!! note + Result classes use their respective envelope classes internally (`AlbResponseEnvelope`, + `ApiGatewayResponseEnvelope`, etc.). They're a convenience layer over the envelope + infrastructure. + +## AOT Support + +When using .NET Native AOT, register all envelope and payload types in your `JsonSerializerContext`. + +!!! tip "Register Both Envelope and Payload Types" + You must register **both** the envelope type (e.g., `ApiGatewayRequestEnvelope`) + **and** the inner payload type (e.g., `LoginRequest`). The envelope wraps the AWS event + structure, while the payload is your business type inside the envelope. + +### Basic Envelope Setup + +```csharp title="Program.cs" linenums="1" +using System.Text.Json.Serialization; + +[JsonSerializable(typeof(ApiGatewayRequestEnvelope))] // Envelope wrapper +[JsonSerializable(typeof(ApiGatewayResponseEnvelope))] // Envelope wrapper +[JsonSerializable(typeof(LoginRequest))] // Inner payload type +[JsonSerializable(typeof(LoginSuccess))] // Inner payload type +internal partial class SerializerContext : JsonSerializerContext; +``` + +### Result Classes with Multiple Return Types + +When using result classes (`AlbResult`, `ApiGatewayResult`, `ApiGatewayV2Result`), register each +response type separately: + +```csharp title="Program.cs" linenums="1" +using System.Text.Json.Serialization; + +[JsonSerializable(typeof(ApiGatewayRequestEnvelope))] +[JsonSerializable(typeof(ApiGatewayResult))] +[JsonSerializable(typeof(LoginRequest))] +[JsonSerializable(typeof(ValidationError))] +[JsonSerializable(typeof(AuthError))] +[JsonSerializable(typeof(LoginSuccess))] +internal partial class SerializerContext : JsonSerializerContext; +``` + +### Registering the Serializer Context + +Register the serializer and configure envelope options to use the context: + +```csharp title="Program.cs" linenums="1" +var builder = LambdaApplication.CreateBuilder(); + +builder.Services.AddLambdaSerializerWithContext(); + +builder.Services.ConfigureEnvelopeOptions(options => +{ + options.JsonOptions.TypeInfoResolver = SerializerContext.Default; +}); +``` + +!!! important "Why Register in Two Places?" + The context must be registered as the type resolver for **both** the envelope options and the + Lambda serializer because deserialization happens at different steps: + + 1. **Lambda serializer** deserializes the raw AWS event (e.g., API Gateway event structure) + 2. **Envelope options** deserialize the envelope content into your payload types + ## Custom Serialization & EnvelopeOptions All envelope packages respect the global `EnvelopeOptions` configuration. Call From ebba9bea3ad5305d14f33b4736d8cb5b48fd933c Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 22:16:41 -0500 Subject: [PATCH 31/38] feat(docs): update installation guide and add `MinimalLambda.Envelopes` references - Added `MinimalLambda.Envelopes` to the installation guide table with usage scenarios. - Updated example code to use the streamlined `ApiGatewayResult.Ok` response builder. - Included NuGet package badge and download count for `MinimalLambda.Envelopes` in the index. --- docs/getting-started/installation.md | 1 + docs/index.md | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 9e5c4465..3cfb7288 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -190,6 +190,7 @@ Envelope packages provide type-safe, strongly-typed event handling for specific | Package | Event Source | When to Use | |----------------------------------------------|---------------------------|-------------------------------| +| **MinimalLambda.Envelopes** | Infrastructure | HTTP response builders (auto-referenced) | | **MinimalLambda.Envelopes.Sqs** | Amazon SQS | Processing SQS queue messages | | **MinimalLambda.Envelopes.Sns** | Amazon SNS | Handling SNS notifications | | **MinimalLambda.Envelopes.ApiGateway** | API Gateway | Building REST/HTTP APIs | diff --git a/docs/index.md b/docs/index.md index 69aedfed..a566c3ef 100644 --- a/docs/index.md +++ b/docs/index.md @@ -95,12 +95,7 @@ already know, while still embracing Lambda’s execution model. var greeting = await service.GreetAsync(request.BodyContent.Name, cancellationToken); // ✅ Type-safe response - automatic JSON serialization - return new ApiGatewayResponseEnvelope - { - BodyContent = new GreetingResponse(greeting, DateTime.UtcNow), - StatusCode = 200, - Headers = new Dictionary { ["Content-Type"] = "application/json" }, - }; + return ApiGatewayResult.Ok(new GreetingResponse(greeting, DateTime.UtcNow)); } ); @@ -211,6 +206,7 @@ deserialization. | Package | Description | NuGet | Downloads | |----------------------------------------------------------------------------------------|-------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **MinimalLambda.Envelopes** | Infrastructure package for HTTP response builders | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes/) | | **MinimalLambda.Envelopes.Sqs** | Simple Queue Service events with typed message bodies | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sqs.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sqs/) | | **MinimalLambda.Envelopes.Sns** | Simple Notification Service messages | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.Sns.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.Sns/) | | **MinimalLambda.Envelopes.ApiGateway** | REST, HTTP, and WebSocket APIs | [![NuGet](https://img.shields.io/nuget/v/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway) | [![Downloads](https://img.shields.io/nuget/dt/MinimalLambda.Envelopes.ApiGateway.svg)](https://www.nuget.org/packages/MinimalLambda.Envelopes.ApiGateway/) | From 1535b3918487e1a183fea00ca27c4f29af12046d Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Sun, 14 Dec 2025 22:17:16 -0500 Subject: [PATCH 32/38] chore(build): bump version to 2.0.0-beta.7 - Updated `Directory.Build.props` to reflect the new version 2.0.0-beta.7. --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index bf4cdeab..78b44622 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 2.0.0-beta.6 + 2.0.0-beta.7 MIT From fb1a9c87c634d29e081e9fc14451c9cea74df170 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Mon, 15 Dec 2025 06:51:22 -0500 Subject: [PATCH 33/38] feat(tests): add unit tests for `BaseHttpResultExtensions` methods - Added tests for `StatusCode`, `Text`, and `Json` methods to verify result creation behavior. - Verified default headers, content types, and response body generation in tests. - Implemented a test for `Customize` extension to ensure property modification and chaining. - Utilized `AutoFixture` for generating test payloads and validated serialized responses. chore(project): update test project settings and language version - Updated `MinimalLambda.Envelopes.UnitTests.csproj` to use `LangVersion=preview`. --- .../BaseHttpResultExtensions.cs | 2 +- .../BaseHttpResultExtensionsTests.cs | 91 +++++++++++++++++++ .../MinimalLambda.Envelopes.UnitTests.csproj | 1 + 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 tests/MinimalLambda.Envelopes.UnitTests/BaseHttpResultExtensionsTests.cs diff --git a/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs b/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs index decf4fff..5c7cb09c 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs +++ b/src/Envelopes/MinimalLambda.Envelopes/BaseHttpResultExtensions.cs @@ -3,7 +3,7 @@ namespace MinimalLambda.Envelopes; /// Provides factory extension methods for creating HTTP results. public static class BaseHttpResultExtensions { - extension(IHttpResult result) + extension(IHttpResult) where THttpResult : IHttpResult { /// Creates an HTTP result with the specified status code. diff --git a/tests/MinimalLambda.Envelopes.UnitTests/BaseHttpResultExtensionsTests.cs b/tests/MinimalLambda.Envelopes.UnitTests/BaseHttpResultExtensionsTests.cs new file mode 100644 index 00000000..59d19adb --- /dev/null +++ b/tests/MinimalLambda.Envelopes.UnitTests/BaseHttpResultExtensionsTests.cs @@ -0,0 +1,91 @@ +using AutoFixture; +using AwesomeAssertions; +using JetBrains.Annotations; +using MinimalLambda.Envelopes.Alb; +using MinimalLambda.Options; +using Xunit; + +namespace MinimalLambda.Envelopes.UnitTests; + +[TestSubject(typeof(BaseHttpResultExtensions))] +public class BaseHttpResultExtensionsTests +{ + private readonly Fixture _fixture = new(); + + [Fact] + public void StatusCode_CreatesResultWithStatusCodeAndDefaults() + { + // Arrange + var statusCode = 404; + + // Act + var result = AlbResult.StatusCode(statusCode); + + // Assert + result.StatusCode.Should().Be(statusCode); + result.Headers.Should().NotBeNull(); + result.Headers.Should().BeEmpty(); + result.Body.Should().BeNull(); + result.IsBase64Encoded.Should().BeFalse(); + } + + [Fact] + public void Text_CreatesResultWithTextPlainContentType() + { + // Arrange + var statusCode = 200; + var body = "Hello, World!"; + + // Act + var result = AlbResult.Text(statusCode, body); + + // Assert + result.StatusCode.Should().Be(statusCode); + result.Body.Should().Be(body); + result.Headers.Should().ContainKey("Content-Type"); + result.Headers["Content-Type"].Should().Be("text/plain; charset=utf-8"); + } + + [Fact] + public void Json_CreatesResultWithApplicationJsonContentType() + { + // Arrange + var statusCode = 201; + var payload = _fixture.Create(); + var options = new EnvelopeOptions(); + + // Act + var result = AlbResult.Json(statusCode, payload); + result.PackPayload(options); + + // Assert + result.StatusCode.Should().Be(statusCode); + result.Headers.Should().ContainKey("Content-Type"); + result.Headers["Content-Type"].Should().Be("application/json; charset=utf-8"); + result.Body.Should().NotBeNull(); + result.Body.Should().Contain(payload.Name); + result.Body.Should().Contain(payload.Value.ToString()); + } + + [Fact] + public void Customize_ModifiesPropertiesAndReturnsInstanceForChaining() + { + // Arrange + var result = AlbResult.StatusCode(200); + var customDescription = "Custom Status"; + var customHeader = "X-Custom-Header"; + + // Act + var customizedResult = result + .Customize(r => r.StatusDescription = customDescription) + .Customize(r => r.Headers[customHeader] = "CustomValue"); + + // Assert + customizedResult.Should().BeSameAs(result); + result.StatusDescription.Should().Be(customDescription); + result.Headers.Should().ContainKey(customHeader); + result.Headers[customHeader].Should().Be("CustomValue"); + } + + private record TestPayload(string Name, int Value); +} diff --git a/tests/MinimalLambda.Envelopes.UnitTests/MinimalLambda.Envelopes.UnitTests.csproj b/tests/MinimalLambda.Envelopes.UnitTests/MinimalLambda.Envelopes.UnitTests.csproj index e6b4c2bf..12380ea6 100644 --- a/tests/MinimalLambda.Envelopes.UnitTests/MinimalLambda.Envelopes.UnitTests.csproj +++ b/tests/MinimalLambda.Envelopes.UnitTests/MinimalLambda.Envelopes.UnitTests.csproj @@ -1,6 +1,7 @@  net8.0;net9.0;net10.0 + preview enable enable false From 453cf46db08fa4d88b389e1f9d34413bd803580b Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Mon, 15 Dec 2025 06:55:39 -0500 Subject: [PATCH 34/38] feat(tests): add unit tests for `HttpResultExtensions` methods - Implemented tests for various result types: `Ok`, `Created`, `NoContent`, `BadRequest`, and more. - Verified response behavior, including status codes, headers, and JSON body content. - Utilized `AutoFixture` for generating test payloads to ensure test repeatability. - Ensured compatibility with the `AlbResult` response builder and `EnvelopeOptions`. --- .../HttpResultExtensionsTests.cs | 248 ++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 tests/MinimalLambda.Envelopes.UnitTests/HttpResultExtensionsTests.cs diff --git a/tests/MinimalLambda.Envelopes.UnitTests/HttpResultExtensionsTests.cs b/tests/MinimalLambda.Envelopes.UnitTests/HttpResultExtensionsTests.cs new file mode 100644 index 00000000..6af1d583 --- /dev/null +++ b/tests/MinimalLambda.Envelopes.UnitTests/HttpResultExtensionsTests.cs @@ -0,0 +1,248 @@ +using AutoFixture; +using AwesomeAssertions; +using JetBrains.Annotations; +using MinimalLambda.Envelopes.Alb; +using MinimalLambda.Options; +using Xunit; + +namespace MinimalLambda.Envelopes.UnitTests; + +[TestSubject(typeof(HttpResultExtensions))] +public class HttpResultExtensionsTests +{ + private readonly Fixture _fixture = new(); + + [Fact] + public void Ok_ReturnsStatus200() + { + // Act + var result = AlbResult.Ok(); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(200); + } + + [Fact] + public void Ok_WithBodyContent_ReturnsStatus200WithJson() + { + // Arrange + var payload = _fixture.Create(); + var options = new EnvelopeOptions(); + + // Act + var result = AlbResult.Ok(payload); + result.PackPayload(options); + + // Assert + result.StatusCode.Should().Be(200); + result.Headers.Should().ContainKey("Content-Type"); + result.Headers["Content-Type"].Should().Be("application/json; charset=utf-8"); + result.Body.Should().NotBeNull(); + result.Body.Should().Contain(payload.Name); + } + + [Fact] + public void Created_ReturnsStatus201() + { + // Act + var result = AlbResult.Created(); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(201); + } + + [Fact] + public void Created_WithBodyContent_ReturnsStatus201WithJson() + { + // Arrange + var payload = _fixture.Create(); + var options = new EnvelopeOptions(); + + // Act + var result = AlbResult.Created(payload); + result.PackPayload(options); + + // Assert + result.StatusCode.Should().Be(201); + result.Headers.Should().ContainKey("Content-Type"); + result.Headers["Content-Type"].Should().Be("application/json; charset=utf-8"); + result.Body.Should().NotBeNull(); + result.Body.Should().Contain(payload.Name); + } + + [Fact] + public void NoContent_ReturnsStatus204() + { + // Act + var result = AlbResult.NoContent(); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(204); + } + + [Fact] + public void BadRequest_ReturnsStatus400() + { + // Act + var result = AlbResult.BadRequest(); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(400); + } + + [Fact] + public void BadRequest_WithBodyContent_ReturnsStatus400WithJson() + { + // Arrange + var payload = _fixture.Create(); + var options = new EnvelopeOptions(); + + // Act + var result = AlbResult.BadRequest(payload); + result.PackPayload(options); + + // Assert + result.StatusCode.Should().Be(400); + result.Headers.Should().ContainKey("Content-Type"); + result.Headers["Content-Type"].Should().Be("application/json; charset=utf-8"); + result.Body.Should().NotBeNull(); + result.Body.Should().Contain(payload.Name); + } + + [Fact] + public void Unauthorized_ReturnsStatus401() + { + // Act + var result = AlbResult.Unauthorized(); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(401); + } + + [Fact] + public void NotFound_ReturnsStatus404() + { + // Act + var result = AlbResult.NotFound(); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(404); + } + + [Fact] + public void NotFound_WithBodyContent_ReturnsStatus404WithJson() + { + // Arrange + var payload = _fixture.Create(); + var options = new EnvelopeOptions(); + + // Act + var result = AlbResult.NotFound(payload); + result.PackPayload(options); + + // Assert + result.StatusCode.Should().Be(404); + result.Headers.Should().ContainKey("Content-Type"); + result.Headers["Content-Type"].Should().Be("application/json; charset=utf-8"); + result.Body.Should().NotBeNull(); + result.Body.Should().Contain(payload.Name); + } + + [Fact] + public void Conflict_ReturnsStatus409() + { + // Act + var result = AlbResult.Conflict(); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(409); + } + + [Fact] + public void Conflict_WithBodyContent_ReturnsStatus409WithJson() + { + // Arrange + var payload = _fixture.Create(); + var options = new EnvelopeOptions(); + + // Act + var result = AlbResult.Conflict(payload); + result.PackPayload(options); + + // Assert + result.StatusCode.Should().Be(409); + result.Headers.Should().ContainKey("Content-Type"); + result.Headers["Content-Type"].Should().Be("application/json; charset=utf-8"); + result.Body.Should().NotBeNull(); + result.Body.Should().Contain(payload.Name); + } + + [Fact] + public void UnprocessableEntity_ReturnsStatus422() + { + // Act + var result = AlbResult.UnprocessableEntity(); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(422); + } + + [Fact] + public void UnprocessableEntity_WithBodyContent_ReturnsStatus422WithJson() + { + // Arrange + var payload = _fixture.Create(); + var options = new EnvelopeOptions(); + + // Act + var result = AlbResult.UnprocessableEntity(payload); + result.PackPayload(options); + + // Assert + result.StatusCode.Should().Be(422); + result.Headers.Should().ContainKey("Content-Type"); + result.Headers["Content-Type"].Should().Be("application/json; charset=utf-8"); + result.Body.Should().NotBeNull(); + result.Body.Should().Contain(payload.Name); + } + + [Fact] + public void InternalServerError_ReturnsStatus500() + { + // Act + var result = AlbResult.InternalServerError(); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(500); + } + + [Fact] + public void InternalServerError_WithBodyContent_ReturnsStatus500WithJson() + { + // Arrange + var payload = _fixture.Create(); + var options = new EnvelopeOptions(); + + // Act + var result = AlbResult.InternalServerError(payload); + result.PackPayload(options); + + // Assert + result.StatusCode.Should().Be(500); + result.Headers.Should().ContainKey("Content-Type"); + result.Headers["Content-Type"].Should().Be("application/json; charset=utf-8"); + result.Body.Should().NotBeNull(); + result.Body.Should().Contain(payload.Name); + } + + private record TestPayload(string Name, int Value); +} From 37f33243130570c91a6950d978f9c1605706d403 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Mon, 15 Dec 2025 07:12:09 -0500 Subject: [PATCH 35/38] feat(envelopes): added more tests --- .../AlbResultTests.cs | 84 ++++++++++++++++ .../ApiGatewayResultTests.cs | 89 +++++++++++++++++ .../ApiGatewayV2ResultTests.cs | 99 +++++++++++++++++++ 3 files changed, 272 insertions(+) create mode 100644 tests/MinimalLambda.Envelopes.UnitTests/AlbResultTests.cs create mode 100644 tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayResultTests.cs create mode 100644 tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayV2ResultTests.cs diff --git a/tests/MinimalLambda.Envelopes.UnitTests/AlbResultTests.cs b/tests/MinimalLambda.Envelopes.UnitTests/AlbResultTests.cs new file mode 100644 index 00000000..aebaa0f7 --- /dev/null +++ b/tests/MinimalLambda.Envelopes.UnitTests/AlbResultTests.cs @@ -0,0 +1,84 @@ +using System.Text.Json; +using Amazon.Lambda.ApplicationLoadBalancerEvents; +using AutoFixture; +using AwesomeAssertions; +using JetBrains.Annotations; +using MinimalLambda.Envelopes.Alb; +using MinimalLambda.Options; +using Xunit; + +namespace MinimalLambda.Envelopes.UnitTests; + +[TestSubject(typeof(AlbResult))] +public class AlbResultTests +{ + private readonly Fixture _fixture = new(); + + [Fact] + public void Create_WithBodyContent_SetsPropertiesCorrectly() + { + // Arrange + var statusCode = 200; + var payload = _fixture.Create(); + var headers = new Dictionary { ["X-Custom"] = "Value" }; + var isBase64Encoded = true; + + // Act + var result = AlbResult.Create(statusCode, payload, headers, isBase64Encoded); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(statusCode); + result.Headers.Should().ContainKey("X-Custom"); + result.Headers["X-Custom"].Should().Be("Value"); + result.IsBase64Encoded.Should().Be(isBase64Encoded); + result.Body.Should().BeNull(); + } + + [Fact] + public void PackPayload_SerializesBodyContent() + { + // Arrange + var payload = _fixture.Create(); + var options = new EnvelopeOptions(); + var result = AlbResult.Create(200, payload, new Dictionary(), false); + + // Act + result.PackPayload(options); + + // Assert + result.Body.Should().NotBeNull(); + var deserialized = JsonSerializer.Deserialize(result.Body); + deserialized.Should().NotBeNull(); + deserialized!.Name.Should().Be(payload.Name); + deserialized.Value.Should().Be(payload.Value); + } + + [Fact] + public void AlbResult_InheritsFromApplicationLoadBalancerResponse() + { + // Arrange + var result = AlbResult.Create(200, "test", new Dictionary(), false); + + // Act & Assert + result.Should().BeAssignableTo(); + } + + [Fact] + public void Create_WithNullBodyContent_HandlesNull() + { + // Arrange + var options = new EnvelopeOptions(); + + // Act + var result = AlbResult.Create(204, null, new Dictionary(), false); + result.PackPayload(options); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(204); + result.Body.Should().Be("null"); + } + + private record TestPayload(string Name, int Value); +} diff --git a/tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayResultTests.cs b/tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayResultTests.cs new file mode 100644 index 00000000..4567b47f --- /dev/null +++ b/tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayResultTests.cs @@ -0,0 +1,89 @@ +using System.Text.Json; +using Amazon.Lambda.APIGatewayEvents; +using AutoFixture; +using AwesomeAssertions; +using JetBrains.Annotations; +using MinimalLambda.Envelopes.ApiGateway; +using MinimalLambda.Options; +using Xunit; + +namespace MinimalLambda.Envelopes.UnitTests; + +[TestSubject(typeof(ApiGatewayResult))] +public class ApiGatewayResultTests +{ + private readonly Fixture _fixture = new(); + + [Fact] + public void Create_WithBodyContent_SetsPropertiesCorrectly() + { + // Arrange + var statusCode = 200; + var payload = _fixture.Create(); + var headers = new Dictionary { ["X-Custom"] = "Value" }; + var isBase64Encoded = true; + + // Act + var result = ApiGatewayResult.Create(statusCode, payload, headers, isBase64Encoded); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(statusCode); + result.Headers.Should().ContainKey("X-Custom"); + result.Headers["X-Custom"].Should().Be("Value"); + result.IsBase64Encoded.Should().Be(isBase64Encoded); + result.Body.Should().BeNull(); + } + + [Fact] + public void PackPayload_SerializesBodyContent() + { + // Arrange + var payload = _fixture.Create(); + var options = new EnvelopeOptions(); + var result = ApiGatewayResult.Create(200, payload, new Dictionary(), false); + + // Act + result.PackPayload(options); + + // Assert + result.Body.Should().NotBeNull(); + var deserialized = JsonSerializer.Deserialize(result.Body); + deserialized.Should().NotBeNull(); + deserialized!.Name.Should().Be(payload.Name); + deserialized.Value.Should().Be(payload.Value); + } + + [Fact] + public void ApiGatewayResult_InheritsFromAPIGatewayProxyResponse() + { + // Arrange + var result = ApiGatewayResult.Create(200, "test", new Dictionary(), false); + + // Act & Assert + result.Should().BeAssignableTo(); + } + + [Fact] + public void Create_WithNullBodyContent_HandlesNull() + { + // Arrange + var options = new EnvelopeOptions(); + + // Act + var result = ApiGatewayResult.Create( + 204, + null, + new Dictionary(), + false + ); + result.PackPayload(options); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(204); + result.Body.Should().Be("null"); + } + + private record TestPayload(string Name, int Value); +} diff --git a/tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayV2ResultTests.cs b/tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayV2ResultTests.cs new file mode 100644 index 00000000..a0312f1b --- /dev/null +++ b/tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayV2ResultTests.cs @@ -0,0 +1,99 @@ +using System.Text.Json; +using Amazon.Lambda.APIGatewayEvents; +using AutoFixture; +using AwesomeAssertions; +using JetBrains.Annotations; +using MinimalLambda.Envelopes.ApiGateway; +using MinimalLambda.Options; +using Xunit; + +namespace MinimalLambda.Envelopes.UnitTests; + +[TestSubject(typeof(ApiGatewayV2Result))] +public class ApiGatewayV2ResultTests +{ + private readonly Fixture _fixture = new(); + + [Fact] + public void Create_WithBodyContent_SetsPropertiesCorrectly() + { + // Arrange + var statusCode = 200; + var payload = _fixture.Create(); + var headers = new Dictionary { ["X-Custom"] = "Value" }; + var isBase64Encoded = true; + + // Act + var result = ApiGatewayV2Result.Create(statusCode, payload, headers, isBase64Encoded); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(statusCode); + result.Headers.Should().ContainKey("X-Custom"); + result.Headers["X-Custom"].Should().Be("Value"); + result.IsBase64Encoded.Should().Be(isBase64Encoded); + result.Body.Should().BeNull(); + } + + [Fact] + public void PackPayload_SerializesBodyContent() + { + // Arrange + var payload = _fixture.Create(); + var options = new EnvelopeOptions(); + var result = ApiGatewayV2Result.Create( + 200, + payload, + new Dictionary(), + false + ); + + // Act + result.PackPayload(options); + + // Assert + result.Body.Should().NotBeNull(); + var deserialized = JsonSerializer.Deserialize(result.Body); + deserialized.Should().NotBeNull(); + deserialized!.Name.Should().Be(payload.Name); + deserialized.Value.Should().Be(payload.Value); + } + + [Fact] + public void ApiGatewayV2Result_InheritsFromAPIGatewayHttpApiV2ProxyResponse() + { + // Arrange + var result = ApiGatewayV2Result.Create( + 200, + "test", + new Dictionary(), + false + ); + + // Act & Assert + result.Should().BeAssignableTo(); + } + + [Fact] + public void Create_WithNullBodyContent_HandlesNull() + { + // Arrange + var options = new EnvelopeOptions(); + + // Act + var result = ApiGatewayV2Result.Create( + 204, + null, + new Dictionary(), + false + ); + result.PackPayload(options); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(204); + result.Body.Should().Be("null"); + } + + private record TestPayload(string Name, int Value); +} From c305d825475952c1f5cba8f02e8e36fed5aacce2 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Mon, 15 Dec 2025 07:29:55 -0500 Subject: [PATCH 36/38] feat(tests): add unit tests for new `HttpResultExtensions` methods - Implemented tests for `Accepted`, `MovedPermanently`, `Found`, and `Forbidden` result types. - Verified response behavior, including status codes, headers, and JSON body content. - Added support for testing results with and without body content, including `Location` header checks. - Utilized `AutoFixture` for generating test payloads and validated JSON serialization behavior. --- .../HttpResultExtensions.cs | 64 ++++++++++ .../HttpResultExtensionsTests.cs | 112 ++++++++++++++++++ 2 files changed, 176 insertions(+) diff --git a/src/Envelopes/MinimalLambda.Envelopes/HttpResultExtensions.cs b/src/Envelopes/MinimalLambda.Envelopes/HttpResultExtensions.cs index 19fd2879..ad815d59 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/HttpResultExtensions.cs +++ b/src/Envelopes/MinimalLambda.Envelopes/HttpResultExtensions.cs @@ -39,6 +39,23 @@ public static THttpResult Created(T bodyContent) => bodyContent ); + // ── 202 Accepted ───────────────────────────────────────────────────────────────── + + /// Creates a 202 Accepted response. + /// An HTTP 202 result. + public static THttpResult Accepted() => + BaseHttpResultExtensions.StatusCode(StatusCodes.Status202Accepted); + + /// Creates a 202 Accepted response with content. + /// The type of content to return. + /// The response content to serialize. + /// An HTTP 202 result with JSON content. + public static THttpResult Accepted(T bodyContent) => + BaseHttpResultExtensions.Json( + StatusCodes.Status202Accepted, + bodyContent + ); + // ── 204 No Content ─────────────────────────────────────────────────────────────── /// Creates a 204 No Content response. @@ -46,6 +63,36 @@ public static THttpResult Created(T bodyContent) => public static THttpResult NoContent() => BaseHttpResultExtensions.StatusCode(StatusCodes.Status204NoContent); + // ── 301 Moved Permanently ──────────────────────────────────────────────────────── + + /// Creates a 301 Moved Permanently response. + /// An HTTP 301 result. + public static THttpResult MovedPermanently() => + BaseHttpResultExtensions.StatusCode(StatusCodes.Status301MovedPermanently); + + /// Creates a 301 Moved Permanently response with a location. + /// The URI of the redirect target. + /// An HTTP 301 result with Location header. + public static THttpResult MovedPermanently(string location) => + BaseHttpResultExtensions + .StatusCode(StatusCodes.Status301MovedPermanently) + .Customize(result => result.Headers["Location"] = location); + + // ── 302 Found ──────────────────────────────────────────────────────────────────── + + /// Creates a 302 Found response. + /// An HTTP 302 result. + public static THttpResult Found() => + BaseHttpResultExtensions.StatusCode(StatusCodes.Status302Found); + + /// Creates a 302 Found response with a location. + /// The URI of the redirect target. + /// An HTTP 302 result with Location header. + public static THttpResult Found(string location) => + BaseHttpResultExtensions + .StatusCode(StatusCodes.Status302Found) + .Customize(result => result.Headers["Location"] = location); + // ── 400 Bad Request ────────────────────────────────────────────────────────────── /// Creates a 400 Bad Request response. @@ -70,6 +117,23 @@ public static THttpResult BadRequest(T bodyContent) => public static THttpResult Unauthorized() => BaseHttpResultExtensions.StatusCode(StatusCodes.Status401Unauthorized); + // ── 403 Forbidden ──────────────────────────────────────────────────────────────── + + /// Creates a 403 Forbidden response. + /// An HTTP 403 result. + public static THttpResult Forbidden() => + BaseHttpResultExtensions.StatusCode(StatusCodes.Status403Forbidden); + + /// Creates a 403 Forbidden response with content. + /// The type of content to return. + /// The response content to serialize. + /// An HTTP 403 result with JSON content. + public static THttpResult Forbidden(T bodyContent) => + BaseHttpResultExtensions.Json( + StatusCodes.Status403Forbidden, + bodyContent + ); + // ── 404 Not Found ──────────────────────────────────────────────────────────────── /// Creates a 404 Not Found response. diff --git a/tests/MinimalLambda.Envelopes.UnitTests/HttpResultExtensionsTests.cs b/tests/MinimalLambda.Envelopes.UnitTests/HttpResultExtensionsTests.cs index 6af1d583..7ba06a2d 100644 --- a/tests/MinimalLambda.Envelopes.UnitTests/HttpResultExtensionsTests.cs +++ b/tests/MinimalLambda.Envelopes.UnitTests/HttpResultExtensionsTests.cs @@ -72,6 +72,36 @@ public void Created_WithBodyContent_ReturnsStatus201WithJson() result.Body.Should().Contain(payload.Name); } + [Fact] + public void Accepted_ReturnsStatus202() + { + // Act + var result = AlbResult.Accepted(); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(202); + } + + [Fact] + public void Accepted_WithBodyContent_ReturnsStatus202WithJson() + { + // Arrange + var payload = _fixture.Create(); + var options = new EnvelopeOptions(); + + // Act + var result = AlbResult.Accepted(payload); + result.PackPayload(options); + + // Assert + result.StatusCode.Should().Be(202); + result.Headers.Should().ContainKey("Content-Type"); + result.Headers["Content-Type"].Should().Be("application/json; charset=utf-8"); + result.Body.Should().NotBeNull(); + result.Body.Should().Contain(payload.Name); + } + [Fact] public void NoContent_ReturnsStatus204() { @@ -83,6 +113,58 @@ public void NoContent_ReturnsStatus204() result.StatusCode.Should().Be(204); } + [Fact] + public void MovedPermanently_ReturnsStatus301() + { + // Act + var result = AlbResult.MovedPermanently(); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(301); + } + + [Fact] + public void MovedPermanently_WithLocation_ReturnsStatus301WithLocationHeader() + { + // Arrange + var location = "https://example.com/new-location"; + + // Act + var result = AlbResult.MovedPermanently(location); + + // Assert + result.StatusCode.Should().Be(301); + result.Headers.Should().ContainKey("Location"); + result.Headers["Location"].Should().Be(location); + } + + [Fact] + public void Found_ReturnsStatus302() + { + // Act + var result = AlbResult.Found(); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(302); + } + + [Fact] + public void Found_WithLocation_ReturnsStatus302WithLocationHeader() + { + // Arrange + var location = "https://example.com/temporary-location"; + + // Act + var result = AlbResult.Found(location); + + // Assert + result.StatusCode.Should().Be(302); + result.Headers.Should().ContainKey("Location"); + result.Headers["Location"].Should().Be(location); + } + [Fact] public void BadRequest_ReturnsStatus400() { @@ -124,6 +206,36 @@ public void Unauthorized_ReturnsStatus401() result.StatusCode.Should().Be(401); } + [Fact] + public void Forbidden_ReturnsStatus403() + { + // Act + var result = AlbResult.Forbidden(); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(403); + } + + [Fact] + public void Forbidden_WithBodyContent_ReturnsStatus403WithJson() + { + // Arrange + var payload = _fixture.Create(); + var options = new EnvelopeOptions(); + + // Act + var result = AlbResult.Forbidden(payload); + result.PackPayload(options); + + // Assert + result.StatusCode.Should().Be(403); + result.Headers.Should().ContainKey("Content-Type"); + result.Headers["Content-Type"].Should().Be("application/json; charset=utf-8"); + result.Body.Should().NotBeNull(); + result.Body.Should().Contain(payload.Name); + } + [Fact] public void NotFound_ReturnsStatus404() { From 8d3fae77cdc3bff15911d40feed7691553d53641 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Mon, 15 Dec 2025 08:43:24 -0500 Subject: [PATCH 37/38] feat(envelopes): add `Create` methods for result creation from envelopes - Added `Create` methods to `ApiGatewayResult`, `ApiGatewayV2Result`, and `AlbResult` classes. - Simplified wrapping response envelopes into result objects with type parameter support. - Improved API documentation with XML comments for new methods. feat(tests): add unit tests for `Create` methods in result classes - Implemented unit tests for `ApiGatewayResult.Create`, `ApiGatewayV2Result.Create`, and `AlbResult.Create`. - Verified correct status codes, headers, and payload handling with different envelopes. - Ensured `IsBase64Encoded` property and header presence are properly validated in tests. --- .../MinimalLambda.Envelopes.Alb/AlbResult.cs | 6 +++++ .../ApiGatewayResult.cs | 7 ++++++ .../ApiGatewayV2Result.cs | 7 ++++++ .../AlbResultTests.cs | 24 +++++++++++++++++++ .../ApiGatewayResultTests.cs | 24 +++++++++++++++++++ .../ApiGatewayV2ResultTests.cs | 24 +++++++++++++++++++ 6 files changed, 92 insertions(+) diff --git a/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs b/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs index 243c6615..c0196dd8 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.Alb/AlbResult.cs @@ -55,4 +55,10 @@ bool isBase64Encoded IsBase64Encoded = isBase64Encoded, } ); + + /// Creates an ALB result from an existing response envelope. + /// The type of content in the envelope's body. + /// The response envelope to wrap. + /// An wrapping the envelope. + public static AlbResult Create(AlbResponseEnvelope envelope) => new(envelope); } diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs index 0ae46ee2..41c17323 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayResult.cs @@ -54,4 +54,11 @@ bool isBase64Encoded IsBase64Encoded = isBase64Encoded, } ); + + /// Creates an API Gateway result from an existing response envelope. + /// The type of content in the envelope's body. + /// The response envelope to wrap. + /// An wrapping the envelope. + public static ApiGatewayResult Create(ApiGatewayResponseEnvelope envelope) => + new(envelope); } diff --git a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs index ed5e6a7f..278850eb 100644 --- a/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs +++ b/src/Envelopes/MinimalLambda.Envelopes.ApiGateway/ApiGatewayV2Result.cs @@ -56,4 +56,11 @@ bool isBase64Encoded IsBase64Encoded = isBase64Encoded, } ); + + /// Creates an API Gateway v2 result from an existing response envelope. + /// The type of content in the envelope's body. + /// The response envelope to wrap. + /// An wrapping the envelope. + public static ApiGatewayV2Result Create(ApiGatewayV2ResponseEnvelope envelope) => + new(envelope); } diff --git a/tests/MinimalLambda.Envelopes.UnitTests/AlbResultTests.cs b/tests/MinimalLambda.Envelopes.UnitTests/AlbResultTests.cs index aebaa0f7..61df315e 100644 --- a/tests/MinimalLambda.Envelopes.UnitTests/AlbResultTests.cs +++ b/tests/MinimalLambda.Envelopes.UnitTests/AlbResultTests.cs @@ -80,5 +80,29 @@ public void Create_WithNullBodyContent_HandlesNull() result.Body.Should().Be("null"); } + [Fact] + public void Create_WithEnvelope_CreatesResultCorrectly() + { + // Arrange + var payload = _fixture.Create(); + var envelope = new AlbResponseEnvelope + { + StatusCode = 201, + BodyContent = payload, + Headers = new Dictionary { ["X-Test"] = "Value" }, + IsBase64Encoded = false, + }; + + // Act + var result = AlbResult.Create(envelope); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(201); + result.Headers.Should().ContainKey("X-Test"); + result.Headers["X-Test"].Should().Be("Value"); + result.IsBase64Encoded.Should().Be(false); + } + private record TestPayload(string Name, int Value); } diff --git a/tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayResultTests.cs b/tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayResultTests.cs index 4567b47f..f8cd0b3e 100644 --- a/tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayResultTests.cs +++ b/tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayResultTests.cs @@ -85,5 +85,29 @@ public void Create_WithNullBodyContent_HandlesNull() result.Body.Should().Be("null"); } + [Fact] + public void Create_WithEnvelope_CreatesResultCorrectly() + { + // Arrange + var payload = _fixture.Create(); + var envelope = new ApiGatewayResponseEnvelope + { + StatusCode = 201, + BodyContent = payload, + Headers = new Dictionary { ["X-Test"] = "Value" }, + IsBase64Encoded = false, + }; + + // Act + var result = ApiGatewayResult.Create(envelope); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(201); + result.Headers.Should().ContainKey("X-Test"); + result.Headers["X-Test"].Should().Be("Value"); + result.IsBase64Encoded.Should().Be(false); + } + private record TestPayload(string Name, int Value); } diff --git a/tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayV2ResultTests.cs b/tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayV2ResultTests.cs index a0312f1b..c5d1a51b 100644 --- a/tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayV2ResultTests.cs +++ b/tests/MinimalLambda.Envelopes.UnitTests/ApiGatewayV2ResultTests.cs @@ -95,5 +95,29 @@ public void Create_WithNullBodyContent_HandlesNull() result.Body.Should().Be("null"); } + [Fact] + public void Create_WithEnvelope_CreatesResultCorrectly() + { + // Arrange + var payload = _fixture.Create(); + var envelope = new ApiGatewayV2ResponseEnvelope + { + StatusCode = 201, + BodyContent = payload, + Headers = new Dictionary { ["X-Test"] = "Value" }, + IsBase64Encoded = false, + }; + + // Act + var result = ApiGatewayV2Result.Create(envelope); + + // Assert + result.Should().NotBeNull(); + result.StatusCode.Should().Be(201); + result.Headers.Should().ContainKey("X-Test"); + result.Headers["X-Test"].Should().Be("Value"); + result.IsBase64Encoded.Should().Be(false); + } + private record TestPayload(string Name, int Value); } From bfc0aa644b3755c871b4a7769e91f95ab61f1009 Mon Sep 17 00:00:00 2001 From: Jonas Ha Date: Mon, 15 Dec 2025 09:29:41 -0500 Subject: [PATCH 38/38] feat(envelopes): update XML docs for `IHttpResult.Create` method - Simplified method summary to clarify strongly typed body usage. - Removed redundant remarks for clearer documentation format. --- src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs index 7b31894a..075042a3 100644 --- a/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs +++ b/src/Envelopes/MinimalLambda.Envelopes/IHttpResult.cs @@ -20,11 +20,7 @@ public interface IHttpResult : IResponseEnvelope /// Gets or sets the HTTP status code. public int StatusCode { get; set; } - /// Creates a new HTTP result instance. - /// - /// Provide either for automatic serialization or - /// for pre-serialized content. - /// + /// Creates a new HTTP result instance with a strongly typed body. /// The type of content being returned. /// The HTTP status code. /// The typed content to serialize into the body.