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

Improve polymorphism & inheritance behavior incl. more flexible config #1766

Merged
merged 1 commit into from
Aug 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ artifacts/
Thumbs.db
test/WebSites/CliExample/wwwroot/api-docs/v1/*.json
test/WebSites/CliExampleWithFactory/wwwroot/api-docs/v1/*.json
test/WebSites/NswagClientExample/NSwagClient/
*ncrunch*
15 changes: 15 additions & 0 deletions Swashbuckle.AspNetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CliExampleWithFactory", "te
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Swashbuckle.AspNetCore.TestSupport", "test\Swashbuckle.AspNetCore.TestSupport\Swashbuckle.AspNetCore.TestSupport.csproj", "{6E3C0128-931F-4438-AEDD-984E0E2B2C7B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NSwagClientExample", "test\WebSites\NswagClientExample\NSwagClientExample.csproj", "{9840E751-5845-431C-943B-C75CAE596A45}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -459,6 +461,18 @@ Global
{6E3C0128-931F-4438-AEDD-984E0E2B2C7B}.Release|x64.Build.0 = Release|Any CPU
{6E3C0128-931F-4438-AEDD-984E0E2B2C7B}.Release|x86.ActiveCfg = Release|Any CPU
{6E3C0128-931F-4438-AEDD-984E0E2B2C7B}.Release|x86.Build.0 = Release|Any CPU
{9840E751-5845-431C-943B-C75CAE596A45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9840E751-5845-431C-943B-C75CAE596A45}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9840E751-5845-431C-943B-C75CAE596A45}.Debug|x64.ActiveCfg = Debug|Any CPU
{9840E751-5845-431C-943B-C75CAE596A45}.Debug|x64.Build.0 = Debug|Any CPU
{9840E751-5845-431C-943B-C75CAE596A45}.Debug|x86.ActiveCfg = Debug|Any CPU
{9840E751-5845-431C-943B-C75CAE596A45}.Debug|x86.Build.0 = Debug|Any CPU
{9840E751-5845-431C-943B-C75CAE596A45}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9840E751-5845-431C-943B-C75CAE596A45}.Release|Any CPU.Build.0 = Release|Any CPU
{9840E751-5845-431C-943B-C75CAE596A45}.Release|x64.ActiveCfg = Release|Any CPU
{9840E751-5845-431C-943B-C75CAE596A45}.Release|x64.Build.0 = Release|Any CPU
{9840E751-5845-431C-943B-C75CAE596A45}.Release|x86.ActiveCfg = Release|Any CPU
{9840E751-5845-431C-943B-C75CAE596A45}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -495,6 +509,7 @@ Global
{605AA0D3-2AA7-46B4-811B-85DC1B1A3901} = {1669F896-133C-4996-B58C-E7CDA299ADFF}
{322813C4-0458-4F98-965D-568AD2C819CE} = {245144DE-BC89-4822-B044-020458BFECC0}
{6E3C0128-931F-4438-AEDD-984E0E2B2C7B} = {1669F896-133C-4996-B58C-E7CDA299ADFF}
{9840E751-5845-431C-943B-C75CAE596A45} = {245144DE-BC89-4822-B044-020458BFECC0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {36FC6A67-247D-4149-8EDD-79FFD1A75F51}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ public static void EnableAnnotations(this SwaggerGenOptions options, bool enable
options.DocumentFilter<AnnotationsDocumentFilter>();

if (enableSubTypeAnnotations)
options.GeneratePolymorphicSchemas(AnnotationsSubTypeResolver, AnnotationsDiscriminatorSelector);
{
options.UseOneOfForPolymorphism(AnnotationsDiscriminatorSelector);
options.DetectSubTypesUsing(AnnotationsSubTypeResolver);
options.UseAllOfForInheritance();
}
}

private static IEnumerable<Type> AnnotationsSubTypeResolver(Type type)
Expand All @@ -42,7 +46,7 @@ private static string AnnotationsDiscriminatorSelector(Type type)
{
return type.GetCustomAttributes(false)
.OfType<SwaggerSubTypesAttribute>()
.FirstOrDefault()?.Discriminator ?? "$type";
.FirstOrDefault()?.Discriminator;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,23 @@ public NewtonsoftDataContractResolver(SchemaGeneratorOptions generatorOptions, J

public DataContract GetDataContractForType(Type type)
{
var jsonContract = _contractResolver.ResolveContract(type.IsNullable(out Type innerType) ? innerType : type);
if (type.IsAssignableTo(typeof(JToken)))
{
return DataContract.ForDynamic(underlyingType: type);
}

var jsonContract = _contractResolver.ResolveContract(type);

if (jsonContract is JsonPrimitiveContract && !jsonContract.UnderlyingType.IsEnum)
{
var primitiveTypeAndFormat = PrimitiveTypesAndFormats.ContainsKey(jsonContract.UnderlyingType)
? PrimitiveTypesAndFormats[jsonContract.UnderlyingType]
: Tuple.Create(DataType.String, (string)null);

return new DataContract(
return DataContract.ForPrimitive(
underlyingType: jsonContract.UnderlyingType,
dataType: primitiveTypeAndFormat.Item1,
format: primitiveTypeAndFormat.Item2,
underlyingType: jsonContract.UnderlyingType);
dataFormat: primitiveTypeAndFormat.Item2);
}

if (jsonContract is JsonPrimitiveContract && jsonContract.UnderlyingType.IsEnum)
Expand All @@ -48,74 +53,52 @@ public DataContract GetDataContractForType(Type type)
? PrimitiveTypesAndFormats[typeof(string)]
: PrimitiveTypesAndFormats[jsonContract.UnderlyingType.GetEnumUnderlyingType()];

return new DataContract(
dataType: primitiveTypeAndFormat.Item1,
format: primitiveTypeAndFormat.Item2,
return DataContract.ForPrimitive(
underlyingType: jsonContract.UnderlyingType,
dataType: primitiveTypeAndFormat.Item1,
dataFormat: primitiveTypeAndFormat.Item2,
enumValues: enumValues);
}

if (jsonContract is JsonArrayContract jsonArrayContract)
{
return DataContract.ForArray(
underlyingType: jsonArrayContract.UnderlyingType,
itemType: jsonArrayContract.CollectionItemType ?? typeof(object));
}

if (jsonContract is JsonDictionaryContract jsonDictionaryContract)
{
var keyType = jsonDictionaryContract.DictionaryKeyType ?? typeof(object);
var valueType = jsonDictionaryContract.DictionaryValueType ?? typeof(object);

IEnumerable<string> keys = null;

if (keyType.IsEnum)
{
// This is a special case where we can include named properties based on the enum values
var enumValues = GetDataContractForType(keyType).EnumValues;
// This is a special case where we know the possible key values
var enumValues = GetSerializedEnumValuesFor(_contractResolver.ResolveContract(keyType));

var propertyNames = enumValues.Any(value => value is string)
keys = enumValues.Any(value => value is string)
? enumValues.Cast<string>()
: keyType.GetEnumNames();

return new DataContract(
dataType: DataType.Object,
underlyingType: jsonDictionaryContract.UnderlyingType,
properties: propertyNames.Select(name => new DataProperty(name, valueType)));
}

return new DataContract(
dataType: DataType.Object,
return DataContract.ForDictionary(
underlyingType: jsonDictionaryContract.UnderlyingType,
additionalPropertiesType: valueType);
}

if (jsonContract is JsonArrayContract jsonArrayContract)
{
return new DataContract(
dataType: DataType.Array,
underlyingType: jsonArrayContract.UnderlyingType,
arrayItemType: jsonArrayContract.CollectionItemType ?? typeof(object));
valueType: valueType,
keys: keys);
}

if (jsonContract is JsonObjectContract jsonObjectContract)
{
return new DataContract(
dataType: DataType.Object,
return DataContract.ForObject(
underlyingType: jsonObjectContract.UnderlyingType,
properties: GetDataPropertiesFor(jsonObjectContract),
additionalPropertiesType: jsonObjectContract.ExtensionDataValueType);
}

if (jsonContract.UnderlyingType == typeof(JArray))
{
return new DataContract(
dataType: DataType.Array,
underlyingType: jsonContract.UnderlyingType,
arrayItemType: typeof(JToken));
}

if (jsonContract.UnderlyingType == typeof(JObject))
{
return new DataContract(
dataType: DataType.Object,
underlyingType: jsonContract.UnderlyingType);
extensionDataType: jsonObjectContract.ExtensionDataValueType);
}

return new DataContract(
dataType: DataType.Unknown,
underlyingType: jsonContract.UnderlyingType);
return DataContract.ForDynamic(underlyingType: type);
}

private IEnumerable<object> GetSerializedEnumValuesFor(JsonContract jsonContract)
Expand Down Expand Up @@ -165,11 +148,6 @@ private string GetConvertedEnumName(string enumName, bool hasSpecifiedName, Stri

private IEnumerable<DataProperty> GetDataPropertiesFor(JsonObjectContract jsonObjectContract)
{
if (jsonObjectContract.UnderlyingType == typeof(object))
{
return null;
}

var dataProperties = new List<DataProperty>();

foreach (var jsonProperty in jsonObjectContract.Properties)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ private void DeepCopy(SchemaGeneratorOptions source, SchemaGeneratorOptions targ
target.CustomTypeMappings = new Dictionary<Type, Func<OpenApiSchema>>(source.CustomTypeMappings);
target.SchemaIdSelector = source.SchemaIdSelector;
target.IgnoreObsoleteProperties = source.IgnoreObsoleteProperties;
target.GeneratePolymorphicSchemas = source.GeneratePolymorphicSchemas;
target.UseOneOfForPolymorphism = source.UseOneOfForPolymorphism;
target.UseAllOfForInheritance = source.UseAllOfForInheritance;
target.SubTypesResolver = source.SubTypesResolver;
target.DiscriminatorSelector = source.DiscriminatorSelector;
target.UseAllOfToExtendReferenceSchemas = source.UseAllOfToExtendReferenceSchemas;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,6 @@ public static void IgnoreObsoleteActions(this SwaggerGenOptions swaggerGenOption
swaggerGenOptions.SwaggerGeneratorOptions.ConflictingActionsResolver = resolver;
}

[Obsolete("If the serializer is configured for string enums (e.g. StringEnumConverter) Swashbuckle will reflect that automatically")]
public static void DescribeAllEnumsAsStrings(this SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.SchemaGeneratorOptions.DescribeAllEnumsAsStrings = true;
}

[Obsolete("If the serializer is configured for (camel-cased) string enums (e.g. StringEnumConverter) Swashbuckle will reflect that automatically")]
public static void DescribeStringEnumsInCamelCase(this SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.SchemaGeneratorOptions.DescribeStringEnumsInCamelCase = true;
}

/// <summary>
/// Provide a custom strategy for assigning "operationId" to operations
/// </summary>
Expand All @@ -79,6 +67,7 @@ public static void DescribeStringEnumsInCamelCase(this SwaggerGenOptions swagger
swaggerGenOptions.SwaggerGeneratorOptions.OperationIdSelector = operationIdSelector;
}


/// <summary>
/// Provide a custom strategy for assigning a default "tag" to operations
/// </summary>
Expand Down Expand Up @@ -216,41 +205,62 @@ public static void IgnoreObsoleteProperties(this SwaggerGenOptions swaggerGenOpt
}

/// <summary>
/// Generate polymorphic schemas (i.e. "oneOf") based on discovered subtypes
/// Generate inline schema definitions (as opposed to referencing a shared definition) for enum parameters and properties
/// </summary>
/// <param name="swaggerGenOptions"></param>
public static void UseInlineDefinitionsForEnums(this SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.SchemaGeneratorOptions.UseInlineDefinitionsForEnums = true;
}

/// <summary>
/// Extend reference schemas (using the allOf construct) so that contextual metadata can be applied to all parameter and property schemas
/// </summary>
/// <param name="swaggerGenOptions"></param>
public static void UseAllOfToExtendReferenceSchemas(this SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.SchemaGeneratorOptions.UseAllOfToExtendReferenceSchemas = true;
}

/// <summary>
/// Enables polymorphic schema generation. If enabled, request and response schemas will contain the oneOf
/// construct to describe sub types as a set of alternative schemas.
/// </summary>
/// <param name="swaggerGenOptions"></param>
/// <param name="subTypesResolver"></param>
/// <param name="discriminatorSelector"></param>
public static void GeneratePolymorphicSchemas(
public static void UseOneOfForPolymorphism(
this SwaggerGenOptions swaggerGenOptions,
Func<Type, IEnumerable<Type>> subTypesResolver = null,
Func<Type, string> discriminatorSelector = null)
{
swaggerGenOptions.SchemaGeneratorOptions.GeneratePolymorphicSchemas = true;

if (subTypesResolver != null)
swaggerGenOptions.SchemaGeneratorOptions.SubTypesResolver = subTypesResolver;
swaggerGenOptions.SchemaGeneratorOptions.UseOneOfForPolymorphism = true;

if (discriminatorSelector != null)
swaggerGenOptions.SchemaGeneratorOptions.DiscriminatorSelector = discriminatorSelector;
}

/// <summary>
/// Extend reference schemas (using the allOf construct) so that contextual metadata can be applied to all parameter and property schemas
/// Enables composite schema generation. If enabled, sub-class schemas will contain the allOf construct to
/// incorporate properties from the base class instead of defining those properties inline.
/// </summary>
/// <param name="swaggerGenOptions"></param>
public static void UseAllOfToExtendReferenceSchemas(this SwaggerGenOptions swaggerGenOptions)
public static void UseAllOfForInheritance(this SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.SchemaGeneratorOptions.UseAllOfToExtendReferenceSchemas = true;
swaggerGenOptions.SchemaGeneratorOptions.UseAllOfForInheritance = true;
}

/// <summary>
/// Generate inline schema definitions (as opposed to referencing a shared definition) for enum parameters and properties
/// To support polymorphism and inheritance behavior, Swashbuckle needs to detect the "known" sub types for a given base type.
/// That is, the sub types explicitly exposed by your API. By default, this will be any sub types in the same assembly as the base type.
/// To override this, you can provide a custom resolver function. This setting is only applicable when used in conjunction with
/// the UseOneOfForPolymorphism or UseAllOfForInheritance settings.
/// </summary>
/// <param name="swaggerGenOptions"></param>
public static void UseInlineDefinitionsForEnums(this SwaggerGenOptions swaggerGenOptions)
/// <param name="resolver"></param>
public static void DetectSubTypesUsing(
this SwaggerGenOptions swaggerGenOptions,
Func<Type, IEnumerable<Type>> resolver)
{
swaggerGenOptions.SchemaGeneratorOptions.UseInlineDefinitionsForEnums = true;
swaggerGenOptions.SchemaGeneratorOptions.SubTypesResolver = resolver;
}

/// <summary>
Expand Down Expand Up @@ -383,5 +393,39 @@ public static void UseInlineDefinitionsForEnums(this SwaggerGenOptions swaggerGe
{
swaggerGenOptions.IncludeXmlComments(() => new XPathDocument(filePath), includeControllerXmlComments);
}


[Obsolete("If the serializer is configured for string enums (e.g. StringEnumConverter) Swashbuckle will reflect that automatically")]
public static void DescribeAllEnumsAsStrings(this SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.SchemaGeneratorOptions.DescribeAllEnumsAsStrings = true;
}

[Obsolete("If the serializer is configured for (camel-cased) string enums (e.g. StringEnumConverter) Swashbuckle will reflect that automatically")]
public static void DescribeStringEnumsInCamelCase(this SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.SchemaGeneratorOptions.DescribeStringEnumsInCamelCase = true;
}

/// <summary>
/// Generate polymorphic schemas (i.e. "oneOf") based on discovered subtypes.
/// Deprecated: Use the \"UseOneOfForPolymorphism\" and \"UseAllOfForInheritance\" settings instead
/// </summary>
/// <param name="swaggerGenOptions"></param>
/// <param name="subTypesResolver"></param>
/// <param name="discriminatorSelector"></param>
[Obsolete("You can use \"UseOneOfForPolymorphism\", \"UseAllOfForInheritance\" and \"DetectSubTypesUsing\" to configure equivalant behavior")]
public static void GeneratePolymorphicSchemas(
this SwaggerGenOptions swaggerGenOptions,
Func<Type, IEnumerable<Type>> subTypesResolver = null,
Func<Type, string> discriminatorSelector = null)
{
swaggerGenOptions.UseOneOfForPolymorphism(discriminatorSelector);

if (subTypesResolver != null)
swaggerGenOptions.DetectSubTypesUsing(subTypesResolver);

swaggerGenOptions.UseAllOfForInheritance();
}
}
}