Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Better support for open generic when mapping types #2704

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

harvzor
Copy link

@harvzor harvzor commented Sep 8, 2023

Issues

This helps with these issues:

The problem

The issue I'm trying to solve is that there's no way to generically solve all mappings for a type that uses open generics. We can currently do something like this:

services.AddSwaggerGen(c =>
{
    c.MapType(typeof(GenericType<string>), () => new OpenApiSchema { Type = "string" });
    c.MapType(typeof(GenericType<int>), () => new OpenApiSchema { Type = "number" });
    // and handle every other type that GenericType could be...
}

But writing an exhaustive list is impossible, especially when you need to support custom objects.

The solution

Provide the user with a MappingContext so they can dynamically handle different types:

c.MapType(typeof(GenericType<>), mappingContext =>
{
    var type = mappingContext.UnderlyingType.GenericTypeArguments[0];

    return mappingContext.SchemaGenerator.GenerateSchema(type, mappingContext.SchemaRepository);
});

The MappingContext contains these useful properties:

public interface IMappingContext
{
    ISchemaGenerator SchemaGenerator { get; }
    SchemaRepository SchemaRepository { get; }
    Type UnderlyingType { get; }
}

@harvzor harvzor changed the title Better support for open generic when mapping types WIP: Better support for open generic when mapping types Sep 8, 2023
Comment on lines +198 to +223
/// <summary>
/// Provide a custom mapping, for a given type, to the Swagger-flavored JSONSchema
/// </summary>
/// <param name="swaggerGenOptions"></param>
/// <param name="type">System type</param>
/// <param name="schemaFactory">A factory method that generates Schema's for the provided type</param>
public static void MapType(
this SwaggerGenOptions swaggerGenOptions,
Type type,
Func<MappingContext, OpenApiSchema> schemaFactory)
{
swaggerGenOptions.SchemaGeneratorOptions.CustomTypeMappings.Add(type, schemaFactory);
}

/// <summary>
/// Provide a custom mapping, for a given type, to the Swagger-flavored JSONSchema
/// </summary>
/// <typeparam name="T">System type</typeparam>
/// <param name="swaggerGenOptions"></param>
/// <param name="schemaFactory">A factory method that generates Schema's for the provided type</param>
public static void MapType<T>(
this SwaggerGenOptions swaggerGenOptions,
Func<MappingContext, OpenApiSchema> schemaFactory)
{
swaggerGenOptions.MapType(typeof(T), schemaFactory);
}
Copy link
Author

Choose a reason for hiding this comment

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

I've kept the old MapType extension methods so that existing usages of this method won't become broken as I'm now providing the MappingContext.

However, if anyone is using swaggerGenOptions.SchemaGeneratorOptions.CustomTypeMappings directly instead of these helper methods, they will need to update their code.

Comment on lines +198 to +223
/// <summary>
/// Provide a custom mapping, for a given type, to the Swagger-flavored JSONSchema
/// </summary>
/// <param name="swaggerGenOptions"></param>
/// <param name="type">System type</param>
/// <param name="schemaFactory">A factory method that generates Schema's for the provided type</param>
public static void MapType(
this SwaggerGenOptions swaggerGenOptions,
Type type,
Func<MappingContext, OpenApiSchema> schemaFactory)
{
swaggerGenOptions.SchemaGeneratorOptions.CustomTypeMappings.Add(type, schemaFactory);
}

/// <summary>
/// Provide a custom mapping, for a given type, to the Swagger-flavored JSONSchema
/// </summary>
/// <typeparam name="T">System type</typeparam>
/// <param name="swaggerGenOptions"></param>
/// <param name="schemaFactory">A factory method that generates Schema's for the provided type</param>
public static void MapType<T>(
this SwaggerGenOptions swaggerGenOptions,
Func<MappingContext, OpenApiSchema> schemaFactory)
{
swaggerGenOptions.MapType(typeof(T), schemaFactory);
}
Copy link
Author

Choose a reason for hiding this comment

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

Need to add docs for the MappingContext

Comment on lines +4 to +25
namespace Basic.Controllers
{
/// <summary>
/// Summary for GenericsController
/// </summary>
[Route("/generics")]
[Produces("application/json")]
public class GenericsController
{
[HttpPost(Name = "CreateString")]
public string CreateString([FromBody] GenericType<string> genericString)
{
throw new NotImplementedException();
}

[HttpPost(Name = "CreateDateTime")]
public string CreateDateTime([FromBody] GenericType<DateTime> genericObject)
{
throw new NotImplementedException();
}
}
}
Copy link
Author

Choose a reason for hiding this comment

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

Not sure if this is the best place to put this

But it at least needs to be integration tested

throw new NotImplementedException();
}
}
}
Copy link
Author

Choose a reason for hiding this comment

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

Add a test for more complex custom objects such as:

public class Foo
{
    public string Bar { get; set; }
}

@martincostello
Copy link
Collaborator

Thanks for contributing - if you'd like to continue with this pull request, please rebase against the default branch to pick up our new CI.

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.

None yet

2 participants