Vogen icon indicating copy to clipboard operation
Vogen copied to clipboard

feat: generate OpenApiOptions extensions for ASP.NET Core OpenAPI (#704)

Open amyboose opened this issue 10 months ago • 3 comments

✨ Add code generation support for Microsoft.AspNetCore.OpenApi.OpenApiOptions

This PR implements issue #704: enabling automatic OpenAPI schema mapping for value objects via source generator.


✅ What’s included

  • New feature: Generates a static method MapVogenTypesIn{AssemblyName}(this OpenApiOptions) in a VogenOpenApiExtensions class.
  • 🧠 The method registers all Vogen value objects into OpenApiOptions.AddSchemaTransformer, mapping their underlying types (e.g., int, Guid) to OpenAPI schemas (e.g., type: integer, format: int32).
  • 🧪 Verified compatibility with both nullable and non-nullable value objects.
  • 🛠️ Integrated into VogenGenerator with compile-time detection of Microsoft.AspNetCore.OpenApi presence.
  • ✅ All new tests pass. Current tests fail not due to my fault, checked on main branch too.

🧪 How to use

  1. Add this line in your assembly:

    [assembly: VogenDefaults(OpenApiSchemaCustomizations = OpenApiSchemaCustomizations.GenerateOpenApiMappingExtensionMethod)]
    
    
  2. In your Program.cs or Startup.cs:

    options.MapVogenTypesInMyAssembly(); // Assembly name will vary
    
    

Example of generated code:

public static class VogenOpenApiExtensions
{
     public static global::Microsoft.AspNetCore.OpenApi.OpenApiOptions MapVogenTypesInMyNotes_Contracts(this global::Microsoft.AspNetCore.OpenApi.OpenApiOptions options)
     {
		options.AddSchemaTransformer((schema, context, cancellationToken) =>
		{
			if (context.JsonTypeInfo.Type == typeof(global::MyNotes.Contracts.Notes.Requests.TestId))
			{
				schema.Type = "integer";
				schema.Format = "int32";
			}

			if (context.JsonTypeInfo.Type == typeof(global::System.Nullable<MyNotes.Contracts.Notes.Requests.TestId>))
			{
				schema.Type = "integer";
				schema.Format = "int32";
				schema.Nullable = true;
			}

			return global::System.Threading.Tasks.Task.CompletedTask;
		});

		return options;
     }
}

amyboose avatar Jun 12 '25 11:06 amyboose

Thanks for the PR @amyboose - much appreciated! It looks great. It seems some of the tests are failing though. Happy to help fix if you need me.

SteveDunn avatar Jun 19 '25 12:06 SteveDunn

I fixed some of the tests.

But I couldn't handle the snapshots. It looks like the snapshots are wrong. For example test Instance_names_can_have_reserved_keywords generates:

    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")]

while verified snapshot:

        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Vogen", "1.0.0.0")]

And StructGenerator contains:

        string GenerateCode() => $@"

{Util.WriteStartNamespace(item.FullNamespace)}
    [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] 
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{Util.GenerateYourAssemblyName()}"", ""{Util.GenerateYourAssemblyVersion()}"")]
    {Util.GenerateAnyConversionAttributes(tds, item)}

There is a problem with indents and I don’t understand why I generate an indent that is shorter in length than in verified snapshots.

I also noticed that somehow the version in GeneratedCodeAttribute in shapshots is magically always 1.0.0.0. I couldn't solve this problem in the usual way.

But I also suggest using an explicit indication to ignore the MinVer version in RunSnapshots.ps1 using -p:MinVerSkip=true:

if($reset)
{
    WriteStage("... resetting snapshots before running them ...")
    exec { & dotnet build Vogen.sln -c Release -p:MinVerSkip=true -p Thorough=true -p ResetSnapshots=true --no-restore --verbosity $verbosity}
}
else
{
    WriteStage("... running snapshots ...")
    exec { & dotnet build Vogen.sln -c Release -p:MinVerSkip=true -p Thorough=true --no-restore --verbosity $verbosity}
}

The Vogen.dll library always took the current version 8.0.0.0 from me. But it would be good if it ignored MinVer within the test environment. Something like:

<PropertyGroup>
  <MinVerSkip Condition=" '$(MinVerSkip)' == '' and '$(MINVER_SKIP)' != '' ">$(MINVER_SKIP)</MinVerSkip>
</PropertyGroup>

As a result, I was able to make some of the tests work, but I couldn't cope with snapshots. So, please help in this regard. It would also be good if MivVer would automatically work correctly both in the IDE and in scripts.

amyboose avatar Jun 21 '25 06:06 amyboose

Hi, thanks for looking at the fixes. One of the issues is my fault: there's a 'scrubber' for the snapshots that turns 8.0.0.0 into 1.0.0.0. Every time I do a major version number change, I forget to update this! Apologies for that. The indentation is irrelevant; Vogen formats the output (but I've seen been told by the Roslyn team that it shouldn't, so I'll probably change that at some point). I've just checked out the PR and I'm now resetting the snapshots. Everything should be OK then, so I'll add to this PR and then get it merged. Thanks again for your contribution! Much appreciated.

SteveDunn avatar Jun 22 '25 21:06 SteveDunn

LGTM! Thank you!

SteveDunn avatar Jun 23 '25 04:06 SteveDunn