Skip to content

Commit 57c04b9

Browse files
committed
[Swashbuckle] Added "VarNamesFromDotnetIdentifiers" as a new option for SmartEnumSchemaExtension
1 parent dd3ebbf commit 57c04b9

File tree

51 files changed

+888
-53
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+888
-53
lines changed

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumCodeGenerator.cs

+26-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,32 @@ private void GenerateEnum(CancellationToken cancellationToken)
109109
KeyType = typeof(").AppendTypeFullyQualified(_state.KeyMember).Append(@"),
110110
ValidationErrorType = typeof(").AppendTypeFullyQualified(_state.ValidationError).Append(@"),
111111
IsValidatable = ").Append(_state.Settings.IsValidatable ? "true" : "false").Append(@",
112-
GetItems = () => global::System.Linq.Enumerable.Select(").AppendTypeFullyQualified(_state).Append(@".Items, i => (object)i),
112+
Items = new global::System.Lazy<global::System.Collections.Generic.IReadOnlyList<global::Thinktecture.Internal.SmartEnumItemMetadata>>(
113+
() => new global::System.Collections.Generic.List<global::Thinktecture.Internal.SmartEnumItemMetadata>(
114+
global::System.Linq.Enumerable.Select(").AppendTypeFullyQualified(_state).Append(@".Items, (item, index) =>
115+
{
116+
string identifier = index switch
117+
{");
118+
119+
for (var i = 0; i < _state.Items.Count; i++)
120+
{
121+
var item = _state.Items[i];
122+
123+
_sb.Append(@"
124+
").Append(i).Append(" => \"").Append(item.Name).Append("\", ");
125+
}
126+
127+
_sb.Append(@"
128+
_ => throw new global::System.ArgumentOutOfRangeException($""Unknown item at index {index}."")
129+
};
130+
131+
return new global::Thinktecture.Internal.SmartEnumItemMetadata
132+
{
133+
Key = item.").Append(_state.KeyMember.Name).Append(@",
134+
Item = item,
135+
Identifier = identifier
136+
};
137+
})).AsReadOnly()),
113138
ConvertToKey = static ").AppendTypeFullyQualified(_state.KeyMember).Append(" (").AppendTypeFullyQualified(_state).Append(" item) => item.").Append(_state.KeyMember.Name).Append(@",
114139
ConvertToKeyExpression = static ").AppendTypeFullyQualified(_state.KeyMember).Append(" (").AppendTypeFullyQualified(_state).Append(" item) => item.").Append(_state.KeyMember.Name).Append(@",
115140
GetKey = static object (object item) => ((").AppendTypeFullyQualified(_state).Append(")item).").Append(_state.KeyMember.Name).Append(@",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace Thinktecture.Swashbuckle;
2+
3+
/// <summary>
4+
/// Represents a Smart Enum item with information needed for OpenAPI schema generation.
5+
/// </summary>
6+
public interface ISmartEnumItem
7+
{
8+
/// <summary>
9+
/// The key of the Smart Enum item.
10+
/// </summary>
11+
object Key { get; }
12+
13+
/// <summary>
14+
/// The Smart Enum item.
15+
/// </summary>
16+
object Item { get; }
17+
18+
/// <summary>
19+
/// The .NET identifier of the Smart Enum item.
20+
/// </summary>
21+
string Identifier { get; }
22+
}

src/Thinktecture.Runtime.Extensions.Swashbuckle/Swashbuckle/ISmartEnumSchemaExtension.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ public interface ISmartEnumSchemaExtension
1414
/// <param name="schema">The OpenAPI schema to which extensions will be added.</param>
1515
/// <param name="context">The schema filter context containing type information.</param>
1616
/// <param name="items">The collection of Smart Enum items.</param>
17-
void Apply(OpenApiSchema schema, SchemaFilterContext context, IEnumerable<object> items);
17+
void Apply(OpenApiSchema schema, SchemaFilterContext context, IReadOnlyList<ISmartEnumItem> items);
1818
}

src/Thinktecture.Runtime.Extensions.Swashbuckle/Swashbuckle/Internal/SmartEnums/NoSmartEnumSchemaExtension.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Thinktecture.Swashbuckle.Internal.SmartEnums;
1212
public class NoSmartEnumSchemaExtension : ISmartEnumSchemaExtension
1313
{
1414
/// <inheritdoc />
15-
public void Apply(OpenApiSchema schema, SchemaFilterContext context, IEnumerable<object> items)
15+
public void Apply(OpenApiSchema schema, SchemaFilterContext context, IReadOnlyList<ISmartEnumItem> items)
1616
{
1717
}
1818
}

src/Thinktecture.Runtime.Extensions.Swashbuckle/Swashbuckle/Internal/SmartEnums/SmartEnumItem.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ namespace Thinktecture.Swashbuckle.Internal.SmartEnums;
88
/// any release. You should only use it directly in your code with extreme caution and knowing that
99
/// doing so can result in application failures when updating to a new Thinktecture.Runtime.Extensions release.
1010
/// </summary>
11-
public record SmartEnumItem(object Item, IOpenApiAny OpenApiValue);
11+
public record SmartEnumItem(object Key, object Item, string Identifier, IOpenApiAny OpenApiValue)
12+
: ISmartEnumItem;

src/Thinktecture.Runtime.Extensions.Swashbuckle/Swashbuckle/Internal/SmartEnums/SmartEnumSchemaFilterBase.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@ public void Apply(OpenApiSchema schema, SchemaFilterContext context, Metadata.Ke
4646
schema.Properties.Clear();
4747
schema.Required.Clear();
4848

49-
var items = GetItems(metadata.Type, metadata.KeyType, metadata.GetItems());
49+
var items = GetItems(metadata.Type, metadata.KeyType, metadata.Items.Value);
5050

5151
SetItems(schema, items);
5252

5353
var keySchema = context.SchemaGenerator.GenerateSchema(metadata.KeyType, context.SchemaRepository);
5454
CopyPropertiesFromKeyTypeSchema(schema, context, keySchema);
5555

56-
_extension.Apply(schema, context, metadata.GetItems());
56+
_extension.Apply(schema, context, items);
5757
}
5858

5959
/// <summary>
@@ -75,16 +75,16 @@ public void Apply(OpenApiSchema schema, SchemaFilterContext context, Metadata.Ke
7575
protected virtual IReadOnlyList<SmartEnumItem> GetItems(
7676
Type type,
7777
Type keyType,
78-
IEnumerable<object> items)
78+
IReadOnlyList<SmartEnumItemMetadata> items)
7979
{
8080
var (valueFactory, keySelector) = DetermineValueFactoryAndKeySelector(type, keyType);
8181

8282
return items.Select(item =>
8383
{
84-
var key = keySelector(item);
84+
var key = keySelector(item.Item);
8585
var value = valueFactory.CreateOpenApiValue(key);
8686

87-
return new SmartEnumItem(item, value);
87+
return new SmartEnumItem(item.Key, item.Item, item.Identifier, value);
8888
})
8989
.ToList();
9090
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using Microsoft.Extensions.Logging;
2+
using Microsoft.OpenApi.Any;
3+
using Microsoft.OpenApi.Models;
4+
using Swashbuckle.AspNetCore.SwaggerGen;
5+
6+
namespace Thinktecture.Swashbuckle.Internal.SmartEnums;
7+
8+
/// <summary>
9+
/// This is an internal API that supports the Thinktecture.Runtime.Extensions infrastructure and not subject to
10+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
11+
/// any release. You should only use it directly in your code with extreme caution and knowing that
12+
/// doing so can result in application failures when updating to a new Thinktecture.Runtime.Extensions release.
13+
/// </summary>
14+
public class VarNamesFromDotnetIdentifiersSchemaExtension : ISmartEnumSchemaExtension
15+
{
16+
private readonly ILogger<VarNamesFromStringRepresentationSchemaExtension> _logger;
17+
private readonly string _extensionName;
18+
19+
/// <summary>
20+
/// This is an internal API that supports the Thinktecture.Runtime.Extensions infrastructure and not subject to
21+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
22+
/// any release. You should only use it directly in your code with extreme caution and knowing that
23+
/// doing so can result in application failures when updating to a new Thinktecture.Runtime.Extensions release.
24+
/// </summary>
25+
public VarNamesFromDotnetIdentifiersSchemaExtension(
26+
ILogger<VarNamesFromStringRepresentationSchemaExtension> logger,
27+
string extensionName = "x-enum-varnames")
28+
{
29+
_logger = logger;
30+
_extensionName = extensionName;
31+
}
32+
33+
/// <inheritdoc />
34+
public void Apply(OpenApiSchema schema, SchemaFilterContext context, IReadOnlyList<ISmartEnumItem> items)
35+
{
36+
var names = new OpenApiArray();
37+
names.AddRange(items.Select(item => new OpenApiString(item.Identifier.ToString())));
38+
39+
schema.Extensions[_extensionName] = names;
40+
}
41+
}

src/Thinktecture.Runtime.Extensions.Swashbuckle/Swashbuckle/Internal/SmartEnums/VarNamesFromStringRepresentationSchemaExtension.cs

+8-5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ namespace Thinktecture.Swashbuckle.Internal.SmartEnums;
1414
public class VarNamesFromStringRepresentationSchemaExtension : ISmartEnumSchemaExtension
1515
{
1616
private readonly ILogger<VarNamesFromStringRepresentationSchemaExtension> _logger;
17+
private readonly string _extensionName;
1718

1819
/// <summary>
1920
/// This is an internal API that supports the Thinktecture.Runtime.Extensions infrastructure and not subject to
@@ -22,15 +23,17 @@ public class VarNamesFromStringRepresentationSchemaExtension : ISmartEnumSchemaE
2223
/// doing so can result in application failures when updating to a new Thinktecture.Runtime.Extensions release.
2324
/// </summary>
2425
public VarNamesFromStringRepresentationSchemaExtension(
25-
ILogger<VarNamesFromStringRepresentationSchemaExtension> logger)
26+
ILogger<VarNamesFromStringRepresentationSchemaExtension> logger,
27+
string extensionName = "x-enum-varnames")
2628
{
2729
_logger = logger;
30+
_extensionName = extensionName;
2831
}
2932

3033
/// <inheritdoc />
31-
public void Apply(OpenApiSchema schema, SchemaFilterContext context, IEnumerable<object> items)
34+
public void Apply(OpenApiSchema schema, SchemaFilterContext context, IReadOnlyList<ISmartEnumItem> items)
3235
{
33-
var duplicates = items.Select(i => i.ToString())
36+
var duplicates = items.Select(i => i.Item.ToString())
3437
.GroupBy(n => n, StringComparer.Ordinal)
3538
.Where(g => g.Count() > 1)
3639
.Select(g => g.Key)
@@ -46,8 +49,8 @@ public void Apply(OpenApiSchema schema, SchemaFilterContext context, IEnumerable
4649
}
4750

4851
var names = new OpenApiArray();
49-
names.AddRange(items.Select(item => new OpenApiString(item.ToString())));
52+
names.AddRange(items.Select(item => new OpenApiString(item.Item.ToString())));
5053

51-
schema.Extensions["x-enum-varnames"] = names;
54+
schema.Extensions[_extensionName] = names;
5255
}
5356
}

src/Thinktecture.Runtime.Extensions.Swashbuckle/Swashbuckle/SmartEnumSchemaExtension.cs

+7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ public partial class SmartEnumSchemaExtension
2323
nameof(VarNamesFromStringRepresentation),
2424
p => ActivatorUtilities.CreateInstance<VarNamesFromStringRepresentationSchemaExtension>(p));
2525

26+
/// <summary>
27+
/// Extends the schema with "x-enum-varnames" using the .NET identifiers of the items.
28+
/// </summary>
29+
public static readonly SmartEnumSchemaExtension VarNamesFromDotnetIdentifiers = new(
30+
nameof(VarNamesFromDotnetIdentifiers),
31+
p => ActivatorUtilities.CreateInstance<VarNamesFromDotnetIdentifiersSchemaExtension>(p));
32+
2633
/// <summary>
2734
/// Schema extension is resolved via dependency injection.
2835
/// </summary>

src/Thinktecture.Runtime.Extensions/Internal/Metadata.Keyed.SmartEnum.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public sealed class SmartEnum : Keyed
2222
/// <summary>
2323
/// A collection of items available in the current Smart Enum.
2424
/// </summary>
25-
public required Func<IEnumerable<object>> GetItems { get; init; }
25+
public required Lazy<IReadOnlyList<SmartEnumItemMetadata>> Items { get; init; }
2626

2727
/// <summary>
2828
/// Typed delegate for conversion of values of type <see cref="KeyType"/> to type <see cref="Type"/>.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace Thinktecture.Internal;
2+
3+
/// <summary>
4+
/// Contains metadata for a Smart Enum item.
5+
/// </summary>
6+
public sealed class SmartEnumItemMetadata
7+
{
8+
/// <summary>
9+
/// The key of the Smart Enum item.
10+
/// </summary>
11+
public required object Key { get; init; }
12+
13+
/// <summary>
14+
/// The Smart Enum item.
15+
/// </summary>
16+
public required object Item { get; init; }
17+
18+
/// <summary>
19+
/// The .NET identifier of the Smart Enum item.
20+
/// </summary>
21+
public required string Identifier { get; init; }
22+
}

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/SmartEnumSourceGeneratorTests.Should_change_conversion_from_key_Explicit0.verified.txt

+17-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,23 @@ namespace Thinktecture.Tests
1919
KeyType = typeof(string),
2020
ValidationErrorType = typeof(global::Thinktecture.ValidationError),
2121
IsValidatable = false,
22-
GetItems = () => global::System.Linq.Enumerable.Select(global::Thinktecture.Tests.TestEnum.Items, i => (object)i),
22+
Items = new global::System.Lazy<global::System.Collections.Generic.IReadOnlyList<global::Thinktecture.Internal.SmartEnumItemMetadata>>(
23+
() => new global::System.Collections.Generic.List<global::Thinktecture.Internal.SmartEnumItemMetadata>(
24+
global::System.Linq.Enumerable.Select(global::Thinktecture.Tests.TestEnum.Items, (item, index) =>
25+
{
26+
string identifier = index switch
27+
{
28+
0 => "Item1",
29+
_ => throw new global::System.ArgumentOutOfRangeException($"Unknown item at index {index}.")
30+
};
31+
32+
return new global::Thinktecture.Internal.SmartEnumItemMetadata
33+
{
34+
Key = item.Key,
35+
Item = item,
36+
Identifier = identifier
37+
};
38+
})).AsReadOnly()),
2339
ConvertToKey = static string (global::Thinktecture.Tests.TestEnum item) => item.Key,
2440
ConvertToKeyExpression = static string (global::Thinktecture.Tests.TestEnum item) => item.Key,
2541
GetKey = static object (object item) => ((global::Thinktecture.Tests.TestEnum)item).Key,

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/SmartEnumSourceGeneratorTests.Should_change_conversion_from_key_Implicit0.verified.txt

+17-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,23 @@ namespace Thinktecture.Tests
1919
KeyType = typeof(string),
2020
ValidationErrorType = typeof(global::Thinktecture.ValidationError),
2121
IsValidatable = false,
22-
GetItems = () => global::System.Linq.Enumerable.Select(global::Thinktecture.Tests.TestEnum.Items, i => (object)i),
22+
Items = new global::System.Lazy<global::System.Collections.Generic.IReadOnlyList<global::Thinktecture.Internal.SmartEnumItemMetadata>>(
23+
() => new global::System.Collections.Generic.List<global::Thinktecture.Internal.SmartEnumItemMetadata>(
24+
global::System.Linq.Enumerable.Select(global::Thinktecture.Tests.TestEnum.Items, (item, index) =>
25+
{
26+
string identifier = index switch
27+
{
28+
0 => "Item1",
29+
_ => throw new global::System.ArgumentOutOfRangeException($"Unknown item at index {index}.")
30+
};
31+
32+
return new global::Thinktecture.Internal.SmartEnumItemMetadata
33+
{
34+
Key = item.Key,
35+
Item = item,
36+
Identifier = identifier
37+
};
38+
})).AsReadOnly()),
2339
ConvertToKey = static string (global::Thinktecture.Tests.TestEnum item) => item.Key,
2440
ConvertToKeyExpression = static string (global::Thinktecture.Tests.TestEnum item) => item.Key,
2541
GetKey = static object (object item) => ((global::Thinktecture.Tests.TestEnum)item).Key,

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/SmartEnumSourceGeneratorTests.Should_change_conversion_from_key_None0.verified.txt

+17-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,23 @@ namespace Thinktecture.Tests
1919
KeyType = typeof(string),
2020
ValidationErrorType = typeof(global::Thinktecture.ValidationError),
2121
IsValidatable = false,
22-
GetItems = () => global::System.Linq.Enumerable.Select(global::Thinktecture.Tests.TestEnum.Items, i => (object)i),
22+
Items = new global::System.Lazy<global::System.Collections.Generic.IReadOnlyList<global::Thinktecture.Internal.SmartEnumItemMetadata>>(
23+
() => new global::System.Collections.Generic.List<global::Thinktecture.Internal.SmartEnumItemMetadata>(
24+
global::System.Linq.Enumerable.Select(global::Thinktecture.Tests.TestEnum.Items, (item, index) =>
25+
{
26+
string identifier = index switch
27+
{
28+
0 => "Item1",
29+
_ => throw new global::System.ArgumentOutOfRangeException($"Unknown item at index {index}.")
30+
};
31+
32+
return new global::Thinktecture.Internal.SmartEnumItemMetadata
33+
{
34+
Key = item.Key,
35+
Item = item,
36+
Identifier = identifier
37+
};
38+
})).AsReadOnly()),
2339
ConvertToKey = static string (global::Thinktecture.Tests.TestEnum item) => item.Key,
2440
ConvertToKeyExpression = static string (global::Thinktecture.Tests.TestEnum item) => item.Key,
2541
GetKey = static object (object item) => ((global::Thinktecture.Tests.TestEnum)item).Key,

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/SmartEnumSourceGeneratorTests.Should_change_conversion_to_key_Explicit0.verified.txt

+17-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,23 @@ namespace Thinktecture.Tests
1919
KeyType = typeof(string),
2020
ValidationErrorType = typeof(global::Thinktecture.ValidationError),
2121
IsValidatable = false,
22-
GetItems = () => global::System.Linq.Enumerable.Select(global::Thinktecture.Tests.TestEnum.Items, i => (object)i),
22+
Items = new global::System.Lazy<global::System.Collections.Generic.IReadOnlyList<global::Thinktecture.Internal.SmartEnumItemMetadata>>(
23+
() => new global::System.Collections.Generic.List<global::Thinktecture.Internal.SmartEnumItemMetadata>(
24+
global::System.Linq.Enumerable.Select(global::Thinktecture.Tests.TestEnum.Items, (item, index) =>
25+
{
26+
string identifier = index switch
27+
{
28+
0 => "Item1",
29+
_ => throw new global::System.ArgumentOutOfRangeException($"Unknown item at index {index}.")
30+
};
31+
32+
return new global::Thinktecture.Internal.SmartEnumItemMetadata
33+
{
34+
Key = item.Key,
35+
Item = item,
36+
Identifier = identifier
37+
};
38+
})).AsReadOnly()),
2339
ConvertToKey = static string (global::Thinktecture.Tests.TestEnum item) => item.Key,
2440
ConvertToKeyExpression = static string (global::Thinktecture.Tests.TestEnum item) => item.Key,
2541
GetKey = static object (object item) => ((global::Thinktecture.Tests.TestEnum)item).Key,

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/SmartEnumSourceGeneratorTests.Should_change_conversion_to_key_Implicit0.verified.txt

+17-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,23 @@ namespace Thinktecture.Tests
1919
KeyType = typeof(string),
2020
ValidationErrorType = typeof(global::Thinktecture.ValidationError),
2121
IsValidatable = false,
22-
GetItems = () => global::System.Linq.Enumerable.Select(global::Thinktecture.Tests.TestEnum.Items, i => (object)i),
22+
Items = new global::System.Lazy<global::System.Collections.Generic.IReadOnlyList<global::Thinktecture.Internal.SmartEnumItemMetadata>>(
23+
() => new global::System.Collections.Generic.List<global::Thinktecture.Internal.SmartEnumItemMetadata>(
24+
global::System.Linq.Enumerable.Select(global::Thinktecture.Tests.TestEnum.Items, (item, index) =>
25+
{
26+
string identifier = index switch
27+
{
28+
0 => "Item1",
29+
_ => throw new global::System.ArgumentOutOfRangeException($"Unknown item at index {index}.")
30+
};
31+
32+
return new global::Thinktecture.Internal.SmartEnumItemMetadata
33+
{
34+
Key = item.Key,
35+
Item = item,
36+
Identifier = identifier
37+
};
38+
})).AsReadOnly()),
2339
ConvertToKey = static string (global::Thinktecture.Tests.TestEnum item) => item.Key,
2440
ConvertToKeyExpression = static string (global::Thinktecture.Tests.TestEnum item) => item.Key,
2541
GetKey = static object (object item) => ((global::Thinktecture.Tests.TestEnum)item).Key,

0 commit comments

Comments
 (0)