-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
base: master
Are you sure you want to change the base?
Changes from all commits
618c8cc
182fe99
0294b8f
6ff5c45
ea2332a
902500e
f513aa1
fb5e9bb
8662c63
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -179,7 +179,7 @@ public static void AddServer(this SwaggerGenOptions swaggerGenOptions, OpenApiSe | |||||
Type type, | ||||||
Func<OpenApiSchema> schemaFactory) | ||||||
{ | ||||||
swaggerGenOptions.SchemaGeneratorOptions.CustomTypeMappings.Add(type, schemaFactory); | ||||||
swaggerGenOptions.MapType(type, _ => schemaFactory()); | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
|
@@ -195,6 +195,33 @@ public static void AddServer(this SwaggerGenOptions swaggerGenOptions, OpenApiSe | |||||
swaggerGenOptions.MapType(typeof(T), schemaFactory); | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
/// Provide a custom mapping, for a given type, to the Swagger-flavored JSONSchema | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless "JSONSchema" is a specific term I've not heard before:
Suggested change
|
||||||
/// </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> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
public static void MapType( | ||||||
this SwaggerGenOptions swaggerGenOptions, | ||||||
Type type, | ||||||
Func<IMappingContext, OpenApiSchema> schemaFactory) | ||||||
{ | ||||||
swaggerGenOptions.SchemaGeneratorOptions.CustomTypeMappings.Add(type, schemaFactory); | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
/// Provide a custom mapping, for a given type, to the Swagger-flavored JSONSchema | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
/// </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<IMappingContext, OpenApiSchema> schemaFactory) | ||||||
{ | ||||||
swaggerGenOptions.MapType(typeof(T), schemaFactory); | ||||||
} | ||||||
|
||||||
/// <summary> | ||||||
/// Generate inline schema definitions (as opposed to referencing a shared definition) for enum parameters and properties | ||||||
/// </summary> | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,15 +9,15 @@ public class SchemaGeneratorOptions | |
{ | ||
public SchemaGeneratorOptions() | ||
{ | ||
CustomTypeMappings = new Dictionary<Type, Func<OpenApiSchema>>(); | ||
CustomTypeMappings = new Dictionary<Type, Func<MappingContext, OpenApiSchema>>(); | ||
SchemaIdSelector = DefaultSchemaIdSelector; | ||
SubTypesSelector = DefaultSubTypesSelector; | ||
DiscriminatorNameSelector = DefaultDiscriminatorNameSelector; | ||
DiscriminatorValueSelector = DefaultDiscriminatorValueSelector; | ||
SchemaFilters = new List<ISchemaFilter>(); | ||
} | ||
|
||
public IDictionary<Type, Func<OpenApiSchema>> CustomTypeMappings { get; set; } | ||
public IDictionary<Type, Func<MappingContext, OpenApiSchema>> CustomTypeMappings { get; set; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a binary-breaking change, so if we took it as-is this would have to be part of v7. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would get released sooner if there were a way to implement this without breaking changes. |
||
|
||
public bool UseInlineDefinitionsForEnums { get; set; } | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using System; | ||
|
||
namespace Swashbuckle.AspNetCore.SwaggerGen; | ||
|
||
public interface IMappingContext | ||
{ | ||
ISchemaGenerator SchemaGenerator { get; } | ||
SchemaRepository SchemaRepository { get; } | ||
|
||
/// <summary> | ||
/// Actual runtime type that's being mapped. | ||
/// </summary> | ||
Type UnderlyingType { get;} | ||
} | ||
|
||
public class MappingContext( | ||
ISchemaGenerator schemaGenerator, | ||
SchemaRepository schemaRepository, | ||
Type underlyingType) | ||
: IMappingContext | ||
{ | ||
public ISchemaGenerator SchemaGenerator { get; } = schemaGenerator; | ||
public SchemaRepository SchemaRepository { get; } = schemaRepository; | ||
public Type UnderlyingType { get; } = underlyingType; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -478,7 +478,7 @@ public void GenerateSchema_SetsDefault_IfParameterHasDefaultValueAttribute() | |
string expectedSchemaType) | ||
{ | ||
var subject = Subject( | ||
configureGenerator: c => c.CustomTypeMappings.Add(mappingType, () => new OpenApiSchema { Type = "string" }) | ||
configureGenerator: c => c.CustomTypeMappings.Add(mappingType, _ => new OpenApiSchema { Type = "string" }) | ||
); | ||
var schema = subject.GenerateSchema(type, new SchemaRepository()); | ||
|
||
|
@@ -979,6 +979,57 @@ public void GenerateSchema_GeneratesSchema_IfParameterHasTypeConstraints() | |
Assert.Equal("integer", schema.Type); | ||
} | ||
|
||
[Theory] | ||
[InlineData(typeof(GenericType<string>), "string")] | ||
[InlineData(typeof(GenericType<int>), "number")] | ||
public void GenerateSchema_GeneratesSchemaWithCorrectType_IfTypeTakenFromUnderlyingType(Type genericType, string expectedType) | ||
{ | ||
var subject = Subject( | ||
configureGenerator: c => c.CustomTypeMappings.Add(genericType, mappingContext => | ||
{ | ||
Assert.Equal(genericType, mappingContext.UnderlyingType); | ||
Assert.NotNull(mappingContext.SchemaGenerator); | ||
Assert.NotNull(mappingContext.SchemaRepository); | ||
|
||
var genericTypeArgument = mappingContext.UnderlyingType.GenericTypeArguments.First(); | ||
|
||
if (genericTypeArgument == typeof(string)) | ||
return new OpenApiSchema { Type = "string" }; | ||
if (genericTypeArgument == typeof(int)) | ||
return new OpenApiSchema { Type = "number" }; | ||
|
||
throw new NotImplementedException(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be better to fail with an assertion related to what unexpected value was received. |
||
}) | ||
); | ||
var schema = subject.GenerateSchema(genericType, new SchemaRepository()); | ||
|
||
Assert.Equal(expectedType, schema.Type); | ||
Assert.Empty(schema.Properties); | ||
} | ||
|
||
[Fact] | ||
public void GenerateSchema_GeneratesSchemaWithCorrectReference_IfSchemaIsGeneratedForGenericTypeArgument() | ||
{ | ||
var genericType = typeof(GenericType<ComplexType>); | ||
|
||
var subject = Subject( | ||
configureGenerator: c => c.CustomTypeMappings.Add(genericType, mappingContext => | ||
{ | ||
Assert.Equal(genericType, mappingContext.UnderlyingType); | ||
Assert.NotNull(mappingContext.SchemaGenerator); | ||
Assert.NotNull(mappingContext.SchemaRepository); | ||
|
||
var genericTypeArgument = mappingContext.UnderlyingType.GenericTypeArguments.First(); | ||
|
||
return mappingContext.SchemaGenerator.GenerateSchema(genericTypeArgument, mappingContext.SchemaRepository); | ||
}) | ||
); | ||
var schema = subject.GenerateSchema(genericType, new SchemaRepository()); | ||
|
||
Assert.Equal("#/components/schemas/ComplexType", schema.Reference.ReferenceV3); | ||
Assert.Empty(schema.Properties); | ||
} | ||
|
||
private static SchemaGenerator Subject( | ||
Action<SchemaGeneratorOptions> configureGenerator = null, | ||
Action<JsonSerializerOptions> configureSerializer = null) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using System; | ||
using Microsoft.AspNetCore.Mvc; | ||
|
||
namespace Basic.Controllers | ||
{ | ||
/// <summary> | ||
/// Summary for GenericsController | ||
/// </summary> | ||
[Route("/generics")] | ||
[Produces("application/json")] | ||
public class GenericsController | ||
{ | ||
[HttpPost("CreateString")] | ||
public string CreateString([FromBody] GenericType<string> genericString) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
|
||
[HttpPost("CreateDateTime")] | ||
public string CreateDateTime([FromBody] GenericType<DateTime> genericObject) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a test for more complex custom objects such as:
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace Basic | ||
{ | ||
public class GenericType<T> | ||
{ | ||
public T Property1 { get; set; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ | |
using Microsoft.OpenApi.Models; | ||
using Microsoft.AspNetCore.Localization; | ||
using Basic.Swagger; | ||
using Swashbuckle.AspNetCore.SwaggerGen; | ||
|
||
namespace Basic | ||
{ | ||
|
@@ -55,6 +56,13 @@ public void ConfigureServices(IServiceCollection services) | |
c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "Basic.xml")); | ||
|
||
c.EnableAnnotations(); | ||
|
||
c.MapType(typeof(GenericType<>), mappingContext => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test a generic type with more than one type parameter? |
||
{ | ||
var type = mappingContext.UnderlyingType.GenericTypeArguments[0]; | ||
|
||
return mappingContext.SchemaGenerator.GenerateSchema(type, mappingContext.SchemaRepository); | ||
}); | ||
}); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to make sure the version is correct when this is released