Summary
Implement support for handlers that can return multiple different response types within the same envelope flavor, similar to ASP.NET Core Minimal API's Results<TResult1, TResult2, ...> pattern. This would allow a single handler to return different typed responses (e.g., success vs. error) while maintaining the same base envelope type.
Motivation
Currently, handlers must specify a single response envelope type. However, many real-world scenarios require returning different response types from the same handler (e.g., Ok<T> vs BadRequest<TError>). The workaround requires creating wrapper types manually, which adds boilerplate and reduces type safety.
Proposed Solution
Introduce a Response<TResult1, TResult2, ...> type and envelope-specific variants (e.g., ApiGatewayResponse<TResult1, TResult2>) that:
- Accept multiple generic type parameters for different response body types
- Use implicit operators to convert from each specific envelope type
- Delegate serialization to the active result envelope
- Maintain the same base envelope type across all variants
Example Usage
```csharp
lambda.MapHandler(
Response<ApiGatewayResponseEnvelope, ApiGatewayResponseEnvelope> (
[Event] ApiGatewayRequestEnvelope request,
ILogger logger
) =>
{
logger.LogInformation("In Handler. Payload: {Payload}", request.Body);
if (request.BodyContent == null)
return ApiGatewayResponse.BadRequest(new ErrorDetails("bummer"));
return ApiGatewayResponse.Ok(
new Response($"Hello {request.BodyContent?.Name}!", DateTime.UtcNow)
);
}
);
```
Implementation Pattern
```csharp
public class Response<TResult1, TResult2> : IResponseEnvelope
where TResult1 : IResponseEnvelope
where TResult2 : IResponseEnvelope
{
private Response(IResponseEnvelope activeResult)
{
Result = activeResult;
}
public IResponseEnvelope Result { get; }
public static implicit operator Response<TResult1, TResult2>(TResult1 result) => new(result);
public static implicit operator Response<TResult1, TResult2>(TResult2 result) => new(result);
public void PackPayload(EnvelopeOptions options) => Result.PackPayload(options);
}
public class ApiGatewayResponse<TResult1, TResult2> : IResponseEnvelope
{
private ApiGatewayResponse(IResponseEnvelope activeResult)
{
Result = activeResult;
}
public IResponseEnvelope Result { get; }
public static implicit operator ApiGatewayResponse<TResult1, TResult2>(
ApiGatewayResponseEnvelope<TResult1> result
) => new(result);
public static implicit operator ApiGatewayResponse<TResult1, TResult2>(
ApiGatewayResponseEnvelope<TResult2> result
) => new(result);
public void PackPayload(EnvelopeOptions options) => Result.PackPayload(options);
}
```
Requirements
Design Considerations
- Type Constraints: All type parameters must implement
IResponseEnvelope to ensure proper serialization
- Envelope Consistency: All variants within a single
Response<...> must share the same base envelope type (e.g., all ApiGatewayResponseEnvelope<T>)
- Serialization: The wrapper delegates
PackPayload to the active result, maintaining correct envelope structure
- Discovery: The Lambda host infrastructure must recognize these wrapper types and properly unwrap/serialize them
Alternative Approaches Considered
- Single polymorphic type: Would lose compile-time type safety
- Manual wrapper per handler: Current workaround, adds boilerplate
- OneOf-style discriminated union: More complex, doesn't match ASP.NET Core patterns
Related
This aligns with ASP.NET Core Minimal API's Results<TResult1, TResult2, ...> pattern, providing familiar semantics for developers.
Summary
Implement support for handlers that can return multiple different response types within the same envelope flavor, similar to ASP.NET Core Minimal API's
Results<TResult1, TResult2, ...>pattern. This would allow a single handler to return different typed responses (e.g., success vs. error) while maintaining the same base envelope type.Motivation
Currently, handlers must specify a single response envelope type. However, many real-world scenarios require returning different response types from the same handler (e.g.,
Ok<T>vsBadRequest<TError>). The workaround requires creating wrapper types manually, which adds boilerplate and reduces type safety.Proposed Solution
Introduce a
Response<TResult1, TResult2, ...>type and envelope-specific variants (e.g.,ApiGatewayResponse<TResult1, TResult2>) that:Example Usage
```csharp
lambda.MapHandler(
Response<ApiGatewayResponseEnvelope, ApiGatewayResponseEnvelope> (
[Event] ApiGatewayRequestEnvelope request,
ILogger logger
) =>
{
logger.LogInformation("In Handler. Payload: {Payload}", request.Body);
);
```
Implementation Pattern
```csharp
public class Response<TResult1, TResult2> : IResponseEnvelope
where TResult1 : IResponseEnvelope
where TResult2 : IResponseEnvelope
{
private Response(IResponseEnvelope activeResult)
{
Result = activeResult;
}
}
public class ApiGatewayResponse<TResult1, TResult2> : IResponseEnvelope
{
private ApiGatewayResponse(IResponseEnvelope activeResult)
{
Result = activeResult;
}
}
```
Requirements
ApiGatewayResponse<T1, T2, ...>SqsResponse<T1, T2, ...>(if applicable)SnsResponse<T1, T2, ...>(if applicable)Response<T1, T2, ...>for custom envelopesDesign Considerations
IResponseEnvelopeto ensure proper serializationResponse<...>must share the same base envelope type (e.g., allApiGatewayResponseEnvelope<T>)PackPayloadto the active result, maintaining correct envelope structureAlternative Approaches Considered
Related
This aligns with ASP.NET Core Minimal API's
Results<TResult1, TResult2, ...>pattern, providing familiar semantics for developers.