Skip to content

Commit 8bab51a

Browse files
committed
Change discriminator type in dataInResponse from string to ResourceType enum
1 parent ddb5e10 commit 8bab51a

File tree

41 files changed

+548
-159
lines changed

Some content is hidden

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

41 files changed

+548
-159
lines changed

src/Examples/JsonApiDotNetCoreExample/GeneratedSwagger/JsonApiDotNetCoreExample.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5554,8 +5554,11 @@
55545554
"type": "object",
55555555
"properties": {
55565556
"type": {
5557-
"minLength": 1,
5558-
"type": "string"
5557+
"allOf": [
5558+
{
5559+
"$ref": "#/components/schemas/resourceType"
5560+
}
5561+
]
55595562
},
55605563
"meta": {
55615564
"allOf": [
@@ -6864,6 +6867,14 @@
68646867
},
68656868
"additionalProperties": false
68666869
},
6870+
"resourceType": {
6871+
"enum": [
6872+
"people",
6873+
"tags",
6874+
"todoItems"
6875+
],
6876+
"type": "string"
6877+
},
68676878
"tagAttributesInResponse": {
68686879
"type": "object",
68696880
"properties": {

src/Examples/OpenApiKiotaClientExample/GeneratedCode/Models/DataInResponse.cs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,11 @@ public partial class DataInResponse : IBackedModel, IParsable
3232
}
3333
#endif
3434
/// <summary>The type property</summary>
35-
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
36-
#nullable enable
37-
public string? Type
38-
{
39-
get { return BackingStore?.Get<string?>("type"); }
40-
set { BackingStore?.Set("type", value); }
41-
}
42-
#nullable restore
43-
#else
44-
public string Type
35+
public global::OpenApiKiotaClientExample.GeneratedCode.Models.ResourceType? Type
4536
{
46-
get { return BackingStore?.Get<string>("type"); }
37+
get { return BackingStore?.Get<global::OpenApiKiotaClientExample.GeneratedCode.Models.ResourceType?>("type"); }
4738
set { BackingStore?.Set("type", value); }
4839
}
49-
#endif
5040
/// <summary>
5141
/// Instantiates a new <see cref="global::OpenApiKiotaClientExample.GeneratedCode.Models.DataInResponse"/> and sets the default values.
5242
/// </summary>
@@ -80,7 +70,7 @@ public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
8070
return new Dictionary<string, Action<IParseNode>>
8171
{
8272
{ "meta", n => { Meta = n.GetObjectValue<global::OpenApiKiotaClientExample.GeneratedCode.Models.Meta>(global::OpenApiKiotaClientExample.GeneratedCode.Models.Meta.CreateFromDiscriminatorValue); } },
83-
{ "type", n => { Type = n.GetStringValue(); } },
73+
{ "type", n => { Type = n.GetEnumValue<global::OpenApiKiotaClientExample.GeneratedCode.Models.ResourceType>(); } },
8474
};
8575
}
8676
/// <summary>
@@ -91,7 +81,7 @@ public virtual void Serialize(ISerializationWriter writer)
9181
{
9282
_ = writer ?? throw new ArgumentNullException(nameof(writer));
9383
writer.WriteObjectValue<global::OpenApiKiotaClientExample.GeneratedCode.Models.Meta>("meta", Meta);
94-
writer.WriteStringValue("type", Type);
84+
writer.WriteEnumValue<global::OpenApiKiotaClientExample.GeneratedCode.Models.ResourceType>("type", Type);
9585
}
9686
}
9787
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// <auto-generated/>
2+
using System.Runtime.Serialization;
3+
using System;
4+
namespace OpenApiKiotaClientExample.GeneratedCode.Models
5+
{
6+
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
7+
#pragma warning disable CS1591
8+
public enum ResourceType
9+
#pragma warning restore CS1591
10+
{
11+
[EnumMember(Value = "people")]
12+
#pragma warning disable CS1591
13+
People,
14+
#pragma warning restore CS1591
15+
[EnumMember(Value = "tags")]
16+
#pragma warning disable CS1591
17+
Tags,
18+
#pragma warning restore CS1591
19+
[EnumMember(Value = "todoItems")]
20+
#pragma warning disable CS1591
21+
TodoItems,
22+
#pragma warning restore CS1591
23+
}
24+
}

src/JsonApiDotNetCore.OpenApi.Swashbuckle/ConfigureSwaggerGenOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public void Configure(SwaggerGenOptions options)
5959

6060
options.DocumentFilter<ServerDocumentFilter>();
6161
options.DocumentFilter<EndpointOrderingFilter>();
62+
options.DocumentFilter<StringEnumOrderingFilter>();
6263
options.OperationFilter<DocumentationOpenApiOperationFilter>();
6364
options.DocumentFilter<UnusedComponentSchemaCleaner>();
6465
}

src/JsonApiDotNetCore.OpenApi.Swashbuckle/JsonApiSchemaIdSelector.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,9 @@ private string ApplySchemaTemplate(string schemaTemplate, ResourceType? resource
115115
{
116116
string schemaId = schemaTemplate;
117117

118-
if (resourceType != null)
119-
{
120-
schemaId = schemaId.Replace("[ResourceName]", resourceType.PublicName.Singularize()).Pascalize();
121-
}
118+
schemaId = resourceType != null
119+
? schemaId.Replace("[ResourceName]", resourceType.PublicName.Singularize()).Pascalize()
120+
: schemaId.Replace("[ResourceName]", "$$$").Pascalize().Replace("$$$", string.Empty);
122121

123122
if (relationshipName != null)
124123
{
@@ -136,10 +135,8 @@ private string ApplySchemaTemplate(string schemaTemplate, ResourceType? resource
136135
return namingPolicy != null ? namingPolicy.ConvertName(pascalCaseSchemaId) : pascalCaseSchemaId;
137136
}
138137

139-
public string GetResourceTypeSchemaId(ResourceType resourceType)
138+
public string GetResourceTypeSchemaId(ResourceType? resourceType)
140139
{
141-
ArgumentGuard.NotNull(resourceType);
142-
143140
return ApplySchemaTemplate(ResourceTypeSchemaIdTemplate, resourceType, null, null);
144141
}
145142

src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/AbstractResourceDataSchemaGenerator.cs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public OpenApiSchema GenerateSchema(SchemaRepository schemaRepository)
3535
return referenceSchema;
3636
}
3737

38+
OpenApiSchema referenceSchemaForResourceType = GenerateEmptyResourceTypeSchema(schemaRepository);
3839
OpenApiSchema referenceSchemaForMeta = _metaSchemaGenerator.GenerateSchema(schemaRepository);
3940

4041
var fullSchema = new OpenApiSchema
@@ -45,8 +46,7 @@ public OpenApiSchema GenerateSchema(SchemaRepository schemaRepository)
4546
{
4647
[JsonApiPropertyName.Type] = new()
4748
{
48-
MinLength = 1,
49-
Type = "string"
49+
AllOf = [referenceSchemaForResourceType]
5050
},
5151
[referenceSchemaForMeta.Reference.Id] = referenceSchemaForMeta.WrapInExtendedSchema()
5252
},
@@ -70,6 +70,21 @@ public OpenApiSchema GenerateSchema(SchemaRepository schemaRepository)
7070
return referenceSchema;
7171
}
7272

73+
private OpenApiSchema GenerateEmptyResourceTypeSchema(SchemaRepository schemaRepository)
74+
{
75+
var fullSchema = new OpenApiSchema
76+
{
77+
Type = "string",
78+
Extensions =
79+
{
80+
[StringEnumOrderingFilter.RequiresSortKey] = new OpenApiBoolean(true)
81+
}
82+
};
83+
84+
string resourceTypeSchemaId = _schemaIdSelector.GetResourceTypeSchemaId(null);
85+
return schemaRepository.AddDefinition(resourceTypeSchemaId, fullSchema);
86+
}
87+
7388
public void MapDiscriminator(Type resourceDataConstructedType, OpenApiSchema referenceSchemaForResourceData, SchemaRepository schemaRepository)
7489
{
7590
ArgumentGuard.NotNull(resourceDataConstructedType);
@@ -86,9 +101,20 @@ public void MapDiscriminator(Type resourceDataConstructedType, OpenApiSchema ref
86101
}
87102

88103
OpenApiSchema fullSchemaForAbstractResourceData = schemaRepository.Schemas[referenceSchemaForAbstractResourceData.Reference.Id];
104+
string dataSchemaId = referenceSchemaForResourceData.Reference.ReferenceV3;
105+
string publicName = resourceTypeInfo.ResourceType.PublicName;
89106

90-
fullSchemaForAbstractResourceData.Discriminator.Mapping[resourceTypeInfo.ResourceType.PublicName] =
91-
referenceSchemaForResourceData.Reference.ReferenceV3;
107+
if (fullSchemaForAbstractResourceData.Discriminator.Mapping.TryAdd(publicName, dataSchemaId))
108+
{
109+
MapResourceType(publicName, schemaRepository);
110+
}
92111
}
93112
}
113+
114+
private void MapResourceType(string publicName, SchemaRepository schemaRepository)
115+
{
116+
string schemaId = _schemaIdSelector.GetResourceTypeSchemaId(null);
117+
OpenApiSchema fullSchema = schemaRepository.Schemas[schemaId];
118+
fullSchema.Enum.Add(new OpenApiString(publicName));
119+
}
94120
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using JetBrains.Annotations;
2+
using Microsoft.OpenApi.Any;
3+
using Microsoft.OpenApi.Interfaces;
4+
using Microsoft.OpenApi.Models;
5+
using Microsoft.OpenApi.Services;
6+
using Swashbuckle.AspNetCore.SwaggerGen;
7+
8+
namespace JsonApiDotNetCore.OpenApi.Swashbuckle.SwaggerComponents;
9+
10+
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
11+
internal sealed class StringEnumOrderingFilter : IDocumentFilter
12+
{
13+
internal const string RequiresSortKey = "x-requires-sort";
14+
15+
public void Apply(OpenApiDocument document, DocumentFilterContext context)
16+
{
17+
ArgumentGuard.NotNull(document);
18+
ArgumentGuard.NotNull(context);
19+
20+
var visitor = new OpenApiEnumVisitor();
21+
var walker = new OpenApiWalker(visitor);
22+
walker.Walk(document);
23+
}
24+
25+
private sealed class OpenApiEnumVisitor : OpenApiVisitorBase
26+
{
27+
public override void Visit(OpenApiSchema schema)
28+
{
29+
if (schema.Enum.Count > 0)
30+
{
31+
if (HasSortAnnotation(schema))
32+
{
33+
if (schema.Enum.Count > 1)
34+
{
35+
OrderEnumMembers(schema);
36+
}
37+
}
38+
39+
schema.Extensions.Remove(RequiresSortKey);
40+
}
41+
}
42+
43+
private static bool HasSortAnnotation(OpenApiSchema schema)
44+
{
45+
// Order our own enums, but don't touch enums from user-defined resource attributes.
46+
return schema.Extensions.TryGetValue(RequiresSortKey, out IOpenApiExtension? extension) && extension is OpenApiBoolean { Value: true };
47+
}
48+
49+
private static void OrderEnumMembers(OpenApiSchema schema)
50+
{
51+
List<IOpenApiAny> ordered = schema.Enum.OfType<OpenApiString>().OrderBy(openApiString => openApiString.Value).Cast<IOpenApiAny>().ToList();
52+
53+
if (ordered.Count != schema.Enum.Count)
54+
{
55+
throw new UnreachableCodeException();
56+
}
57+
58+
schema.Enum = ordered;
59+
}
60+
}
61+
}

test/OpenApiKiotaEndToEndTests/AtomicOperations/GeneratedCode/Models/DataInResponse.cs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,11 @@ public partial class DataInResponse : IBackedModel, IParsable
3232
}
3333
#endif
3434
/// <summary>The type property</summary>
35-
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
36-
#nullable enable
37-
public string? Type
38-
{
39-
get { return BackingStore?.Get<string?>("type"); }
40-
set { BackingStore?.Set("type", value); }
41-
}
42-
#nullable restore
43-
#else
44-
public string Type
35+
public global::OpenApiKiotaEndToEndTests.AtomicOperations.GeneratedCode.Models.ResourceType? Type
4536
{
46-
get { return BackingStore?.Get<string>("type"); }
37+
get { return BackingStore?.Get<global::OpenApiKiotaEndToEndTests.AtomicOperations.GeneratedCode.Models.ResourceType?>("type"); }
4738
set { BackingStore?.Set("type", value); }
4839
}
49-
#endif
5040
/// <summary>
5141
/// Instantiates a new <see cref="global::OpenApiKiotaEndToEndTests.AtomicOperations.GeneratedCode.Models.DataInResponse"/> and sets the default values.
5242
/// </summary>
@@ -81,7 +71,7 @@ public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
8171
return new Dictionary<string, Action<IParseNode>>
8272
{
8373
{ "meta", n => { Meta = n.GetObjectValue<global::OpenApiKiotaEndToEndTests.AtomicOperations.GeneratedCode.Models.Meta>(global::OpenApiKiotaEndToEndTests.AtomicOperations.GeneratedCode.Models.Meta.CreateFromDiscriminatorValue); } },
84-
{ "type", n => { Type = n.GetStringValue(); } },
74+
{ "type", n => { Type = n.GetEnumValue<global::OpenApiKiotaEndToEndTests.AtomicOperations.GeneratedCode.Models.ResourceType>(); } },
8575
};
8676
}
8777
/// <summary>
@@ -92,7 +82,7 @@ public virtual void Serialize(ISerializationWriter writer)
9282
{
9383
_ = writer ?? throw new ArgumentNullException(nameof(writer));
9484
writer.WriteObjectValue<global::OpenApiKiotaEndToEndTests.AtomicOperations.GeneratedCode.Models.Meta>("meta", Meta);
95-
writer.WriteStringValue("type", Type);
85+
writer.WriteEnumValue<global::OpenApiKiotaEndToEndTests.AtomicOperations.GeneratedCode.Models.ResourceType>("type", Type);
9686
}
9787
}
9888
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// <auto-generated/>
2+
using System.Runtime.Serialization;
3+
using System;
4+
namespace OpenApiKiotaEndToEndTests.AtomicOperations.GeneratedCode.Models
5+
{
6+
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
7+
#pragma warning disable CS1591
8+
public enum ResourceType
9+
#pragma warning restore CS1591
10+
{
11+
[EnumMember(Value = "courses")]
12+
#pragma warning disable CS1591
13+
Courses,
14+
#pragma warning restore CS1591
15+
[EnumMember(Value = "enrollments")]
16+
#pragma warning disable CS1591
17+
Enrollments,
18+
#pragma warning restore CS1591
19+
[EnumMember(Value = "students")]
20+
#pragma warning disable CS1591
21+
Students,
22+
#pragma warning restore CS1591
23+
[EnumMember(Value = "teachers")]
24+
#pragma warning disable CS1591
25+
Teachers,
26+
#pragma warning restore CS1591
27+
}
28+
}

test/OpenApiKiotaEndToEndTests/ClientIdGenerationModes/GeneratedCode/Models/DataInResponse.cs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,11 @@ public partial class DataInResponse : IBackedModel, IParsable
3232
}
3333
#endif
3434
/// <summary>The type property</summary>
35-
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
36-
#nullable enable
37-
public string? Type
38-
{
39-
get { return BackingStore?.Get<string?>("type"); }
40-
set { BackingStore?.Set("type", value); }
41-
}
42-
#nullable restore
43-
#else
44-
public string Type
35+
public global::OpenApiKiotaEndToEndTests.ClientIdGenerationModes.GeneratedCode.Models.ResourceType? Type
4536
{
46-
get { return BackingStore?.Get<string>("type"); }
37+
get { return BackingStore?.Get<global::OpenApiKiotaEndToEndTests.ClientIdGenerationModes.GeneratedCode.Models.ResourceType?>("type"); }
4738
set { BackingStore?.Set("type", value); }
4839
}
49-
#endif
5040
/// <summary>
5141
/// Instantiates a new <see cref="global::OpenApiKiotaEndToEndTests.ClientIdGenerationModes.GeneratedCode.Models.DataInResponse"/> and sets the default values.
5242
/// </summary>
@@ -80,7 +70,7 @@ public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
8070
return new Dictionary<string, Action<IParseNode>>
8171
{
8272
{ "meta", n => { Meta = n.GetObjectValue<global::OpenApiKiotaEndToEndTests.ClientIdGenerationModes.GeneratedCode.Models.Meta>(global::OpenApiKiotaEndToEndTests.ClientIdGenerationModes.GeneratedCode.Models.Meta.CreateFromDiscriminatorValue); } },
83-
{ "type", n => { Type = n.GetStringValue(); } },
73+
{ "type", n => { Type = n.GetEnumValue<global::OpenApiKiotaEndToEndTests.ClientIdGenerationModes.GeneratedCode.Models.ResourceType>(); } },
8474
};
8575
}
8676
/// <summary>
@@ -91,7 +81,7 @@ public virtual void Serialize(ISerializationWriter writer)
9181
{
9282
_ = writer ?? throw new ArgumentNullException(nameof(writer));
9383
writer.WriteObjectValue<global::OpenApiKiotaEndToEndTests.ClientIdGenerationModes.GeneratedCode.Models.Meta>("meta", Meta);
94-
writer.WriteStringValue("type", Type);
84+
writer.WriteEnumValue<global::OpenApiKiotaEndToEndTests.ClientIdGenerationModes.GeneratedCode.Models.ResourceType>("type", Type);
9585
}
9686
}
9787
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// <auto-generated/>
2+
using System.Runtime.Serialization;
3+
using System;
4+
namespace OpenApiKiotaEndToEndTests.ClientIdGenerationModes.GeneratedCode.Models
5+
{
6+
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
7+
#pragma warning disable CS1591
8+
public enum ResourceType
9+
#pragma warning restore CS1591
10+
{
11+
[EnumMember(Value = "games")]
12+
#pragma warning disable CS1591
13+
Games,
14+
#pragma warning restore CS1591
15+
[EnumMember(Value = "playerGroups")]
16+
#pragma warning disable CS1591
17+
PlayerGroups,
18+
#pragma warning restore CS1591
19+
[EnumMember(Value = "players")]
20+
#pragma warning disable CS1591
21+
Players,
22+
#pragma warning restore CS1591
23+
}
24+
}

0 commit comments

Comments
 (0)