Skip to content

Add AWSLambda0145 diagnostic for unregistered durable envelope types#2464

Open
GarrettBeatty wants to merge 1 commit into
devfrom
feature/durable-serializer-context-diagnostic
Open

Add AWSLambda0145 diagnostic for unregistered durable envelope types#2464
GarrettBeatty wants to merge 1 commit into
devfrom
feature/durable-serializer-context-diagnostic

Conversation

@GarrettBeatty

@GarrettBeatty GarrettBeatty commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a new build-time diagnostic AWSLambda0145 (Warning) to the Annotations source generator.

Motivation

With the Annotations model, a [DurableExecution] function's generated handler has the signature:

Task<DurableExecutionInvocationOutput> Handler(DurableExecutionInvocationInput input, ILambdaContext context)

Users never see DurableExecutionInvocationInput / DurableExecutionInvocationOutput in their own code — the generator introduces them. If the function registers the source-generator serializer (SourceGeneratorLambdaJsonSerializer<TContext>), the durable runtime (de)serializes that envelope through the user's JsonSerializerContext. Unlike the reflection-based DefaultLambdaJsonSerializer, a source-gen context only handles types explicitly registered via [JsonSerializable]. If the envelope types aren't registered, the function fails at invocation time with no build-time signal.

This diagnostic surfaces the problem at build time, consistent with the other durable diagnostics (AWSLambda0142–0144).

Behavior

Emitted (Warning) only when all hold:

  • the method has [DurableExecution], and
  • the registered serializer (method-level attribute, else assembly-level) is SourceGeneratorLambdaJsonSerializer<TContext>, and
  • TContext (walking its base contexts) does not register a given envelope type via [JsonSerializable(typeof(...))].

One warning per missing envelope type. Silent for DefaultLambdaJsonSerializer / any non-source-gen serializer, and skipped if the durable types can't be resolved (package not referenced — other diagnostics cover that).

Changes

  • TypeFullNames.cs: add SourceGeneratorLambdaJsonSerializer1andJsonSerializableAttribute` constants.
  • DiagnosticDescriptors.cs: add DurableExecutionMissingSerializableEnvelope (AWSLambda0145, Warning).
  • AnalyzerReleases.Unshipped.md: register the new rule.
  • LambdaFunctionValidator.cs: add ValidateDurableExecutionSerializerContext, called from ValidateDurableExecution.
  • Tests: DurableExecutionSerializerContextDiagnosticsTests — both registered (no diagnostic), one missing, both missing, and DefaultLambdaJsonSerializer (no diagnostic).
  • Added AutoVer changefile (Amazon.Lambda.Annotations, Minor).

Testing

  • dotnet build -c Release on the generator: clean.
  • All 50 DurableExecution* generator tests pass (net10.0), including the 4 new ones.

When a [DurableExecution] function registers the source-generator serializer
(SourceGeneratorLambdaJsonSerializer<TContext>), the durable invocation envelope
types DurableExecutionInvocationInput and DurableExecutionInvocationOutput must
be registered on the JsonSerializerContext with [JsonSerializable]. Unlike the
reflection-based DefaultLambdaJsonSerializer, the context only serializes types
explicitly registered, so a missing registration fails only at invocation time.
The generator now emits AWSLambda0145 (Warning) at build time when either
envelope type is missing.
@GarrettBeatty

Copy link
Copy Markdown
Contributor Author

created a test program

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Demo project for AWSLambda0145 (commit 5602af60).
//
// WHAT TO LOOK FOR IN VISUAL STUDIO:
//   Build this project (or just let the IDE analyze it). You should see, in the
//   Error List, TWO warnings pointing at the [LambdaFunction] method below:
//
//     AWSLambda0145: The [DurableExecution] function uses
//       SourceGeneratorLambdaJsonSerializer<DurableDiagnosticDemo.MyJsonContext>, but
//       DurableDiagnosticDemo.MyJsonContext does not register
//       Amazon.Lambda.DurableExecution.DurableExecutionInvocationInput with a
//       [JsonSerializable] attribute. ...
//
//     AWSLambda0145: ... does not register ...DurableExecutionInvocationOutput ...
//
// TO MAKE THE WARNINGS DISAPPEAR:
//   Uncomment the two [JsonSerializable(...)] attributes marked "FIX" on MyJsonContext
//   below, then rebuild. Both warnings go away. Re-comment them to see the warnings
//   come back — that's the diagnostic doing its job.

using System.Text.Json.Serialization;
using Amazon.Lambda.Annotations;
using Amazon.Lambda.Core;
using Amazon.Lambda.DurableExecution;
using Amazon.Lambda.RuntimeSupport;
using Amazon.Lambda.Serialization.SystemTextJson;

// Register the SOURCE-GENERATOR serializer (not the reflection-based DefaultLambdaJsonSerializer).
// This is the trigger for AWSLambda0145: the durable runtime serializes the invocation envelope
// through this JsonSerializerContext, and the context only handles types explicitly registered
// with [JsonSerializable].
[assembly: LambdaSerializer(typeof(SourceGeneratorLambdaJsonSerializer<DurableDiagnosticDemo.MyJsonContext>))]

namespace DurableDiagnosticDemo;

// The user's JsonSerializerContext.
//
// The order request/response types are registered so the workflow payload (de)serializes,
// but the durable ENVELOPE types are intentionally NOT registered — that's what fires the warning.
[JsonSerializable(typeof(OrderRequest))]
[JsonSerializable(typeof(string))]
// ---- FIX: uncomment these two lines to register the durable envelope types and clear AWSLambda0145 ----
// [JsonSerializable(typeof(DurableExecutionInvocationInput))]
// [JsonSerializable(typeof(DurableExecutionInvocationOutput))]
// -------------------------------------------------------------------------------------------------------
public partial class MyJsonContext : JsonSerializerContext
{
}

public record OrderRequest(string OrderId, decimal TotalAmount);

public class Function
{
    // Executable entry point — not exercised by the diagnostic, just here so the project is a
    // realistic durable Lambda. The [LambdaFunction] method below is what the generator analyzes.
    public static Task Main(string[] args) => Task.CompletedTask;

    [LambdaFunction]
    [DurableExecution(executionTimeout: 300)]
    public Task<string> Run(OrderRequest order, IDurableContext context)
    {
        return Task.FromResult($"Fulfilled {order.OrderId}");
    }
}

this shows diagnostic. then when we uncomment

// [JsonSerializable(typeof(DurableExecutionInvocationInput))]
// [JsonSerializable(typeof(DurableExecutionInvocationOutput))]

it goes away

@GarrettBeatty GarrettBeatty marked this pull request as ready for review July 8, 2026 14:34
@GarrettBeatty GarrettBeatty requested review from a team as code owners July 8, 2026 14:34
@GarrettBeatty GarrettBeatty requested review from normj and philasmar July 8, 2026 14:34
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diagnostic message suggests code that may not compile without a using directive:

In LambdaFunctionValidator.cs:139, the {1} argument passed to the diagnostic is the full metadata name (Amazon.Lambda.DurableExecution.DurableExecutionInvocationInput). The
message format says:

▎ "add [JsonSerializable(typeof({1}))] to {0}"

If the user doesn't have using Amazon.Lambda.DurableExecution; in the file where their context is declared, copy-pasting this won't compile. The {0} argument (context name) uses ToDisplayString() which produces the natural C# name.

Suggestion: Use envelopeTypeSymbol.ToDisplayString() for {1} as well (which would produce the fully-qualified C# form like global::Amazon.Lambda.DurableExecution.DurableExecutionInvocationInput), or just use the short type name (envelopeTypeSymbol.Name) since users with [DurableExecution] will almost certainly have the using directive already.

@GarrettBeatty GarrettBeatty Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right now it looks like

[JsonSerializable(typeof(Amazon.Lambda.DurableExecution.DurableExecutionInvocationInput))]

i dont really like this option

[JsonSerializable(typeof(global::Amazon.Lambda.DurableExecution.DurableExecutionInvocationInput))]

and envelopeTypeSymbol.Name would be

[JsonSerializable(typeof(DurableExecutionInvocationInput))] 

I think the current way is the best no? because if they copy and paste it, their IDE should theoretically auto import the using statement anyway

@GarrettBeatty GarrettBeatty requested a review from philasmar July 8, 2026 18:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants