Skip to content

Commit be92580

Browse files
authored
(#118) Added explicit type exports for NSwag generation. (#120)
* (#118) Added new NSwag schema processing for base typing * (#118) Updated expected swagger.json
1 parent 99ebbf5 commit be92580

File tree

7 files changed

+161
-3
lines changed

7 files changed

+161
-3
lines changed

samples/datasync-server/Sample.Datasync.Server.sln

+17
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Datasync.S
1515
EndProject
1616
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Datasync.Server.EntityFrameworkCore", "..\..\src\CommunityToolkit.Datasync.Server.EntityFrameworkCore\CommunityToolkit.Datasync.Server.EntityFrameworkCore.csproj", "{2086DD5C-C7C1-4957-B667-847C5FEE832C}"
1717
EndProject
18+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Datasync.Server.NSwag", "..\..\src\CommunityToolkit.Datasync.Server.NSwag\CommunityToolkit.Datasync.Server.NSwag.csproj", "{3DD18C86-10C3-490B-A7A6-C2B83C8A3B29}"
19+
EndProject
20+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Datasync.Server.Swashbuckle", "..\..\src\CommunityToolkit.Datasync.Server.Swashbuckle\CommunityToolkit.Datasync.Server.Swashbuckle.csproj", "{FEE92211-3A57-420D-8A76-77AD23B015B6}"
21+
EndProject
1822
Global
1923
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2024
Debug|Any CPU = Debug|Any CPU
@@ -37,6 +41,14 @@ Global
3741
{2086DD5C-C7C1-4957-B667-847C5FEE832C}.Debug|Any CPU.Build.0 = Debug|Any CPU
3842
{2086DD5C-C7C1-4957-B667-847C5FEE832C}.Release|Any CPU.ActiveCfg = Release|Any CPU
3943
{2086DD5C-C7C1-4957-B667-847C5FEE832C}.Release|Any CPU.Build.0 = Release|Any CPU
44+
{3DD18C86-10C3-490B-A7A6-C2B83C8A3B29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45+
{3DD18C86-10C3-490B-A7A6-C2B83C8A3B29}.Debug|Any CPU.Build.0 = Debug|Any CPU
46+
{3DD18C86-10C3-490B-A7A6-C2B83C8A3B29}.Release|Any CPU.ActiveCfg = Release|Any CPU
47+
{3DD18C86-10C3-490B-A7A6-C2B83C8A3B29}.Release|Any CPU.Build.0 = Release|Any CPU
48+
{FEE92211-3A57-420D-8A76-77AD23B015B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49+
{FEE92211-3A57-420D-8A76-77AD23B015B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
50+
{FEE92211-3A57-420D-8A76-77AD23B015B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
51+
{FEE92211-3A57-420D-8A76-77AD23B015B6}.Release|Any CPU.Build.0 = Release|Any CPU
4052
EndGlobalSection
4153
GlobalSection(SolutionProperties) = preSolution
4254
HideSolutionNode = FALSE
@@ -46,5 +58,10 @@ Global
4658
{54E9A0B2-A0B0-4CB1-8FAD-11DB9E4535A6} = {95358590-6440-469A-8A6A-6ACC47F52966}
4759
{DEC37ED1-B52A-4287-8F63-8210328AFF70} = {95358590-6440-469A-8A6A-6ACC47F52966}
4860
{2086DD5C-C7C1-4957-B667-847C5FEE832C} = {95358590-6440-469A-8A6A-6ACC47F52966}
61+
{3DD18C86-10C3-490B-A7A6-C2B83C8A3B29} = {95358590-6440-469A-8A6A-6ACC47F52966}
62+
{FEE92211-3A57-420D-8A76-77AD23B015B6} = {95358590-6440-469A-8A6A-6ACC47F52966}
63+
EndGlobalSection
64+
GlobalSection(ExtensibilityGlobals) = postSolution
65+
SolutionGuid = {B0373E78-5E78-44A4-A907-798EAC85F597}
4966
EndGlobalSection
5067
EndGlobal

samples/datasync-server/src/Sample.Datasync.Server/Program.cs

+27
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// See the LICENSE file in the project root for more information.
44

55
using CommunityToolkit.Datasync.Server;
6+
using CommunityToolkit.Datasync.Server.NSwag;
7+
using CommunityToolkit.Datasync.Server.Swashbuckle;
68
using Microsoft.EntityFrameworkCore;
79
using Sample.Datasync.Server.Db;
810

@@ -11,10 +13,24 @@
1113
string connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
1214
?? throw new ApplicationException("DefaultConnection is not set");
1315

16+
string? swaggerDriver = builder.Configuration["Swagger:Driver"];
17+
bool nswagEnabled = swaggerDriver?.Equals("NSwag", StringComparison.InvariantCultureIgnoreCase) == true;
18+
bool swashbuckleEnabled = swaggerDriver?.Equals("Swashbuckle", StringComparison.InvariantCultureIgnoreCase) == true;
19+
1420
builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(connectionString));
1521
builder.Services.AddDatasyncServices();
1622
builder.Services.AddControllers();
1723

24+
if (nswagEnabled)
25+
{
26+
_ = builder.Services.AddOpenApiDocument(options => options.AddDatasyncProcessor());
27+
}
28+
29+
if (swashbuckleEnabled)
30+
{
31+
_ = builder.Services.AddSwaggerGen(options => options.AddDatasyncControllers());
32+
}
33+
1834
WebApplication app = builder.Build();
1935

2036
// Initialize the database
@@ -25,6 +41,17 @@
2541
}
2642

2743
app.UseHttpsRedirection();
44+
45+
if (nswagEnabled)
46+
{
47+
_ = app.UseOpenApi().UseSwaggerUI();
48+
}
49+
50+
if (swashbuckleEnabled)
51+
{
52+
_ = app.UseSwagger().UseSwaggerUI();
53+
}
54+
2855
app.UseAuthorization();
2956
app.MapControllers();
3057

samples/datasync-server/src/Sample.Datasync.Server/Sample.Datasync.Server.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
<ItemGroup>
2525
<ProjectReference Include="..\..\..\..\src\CommunityToolkit.Datasync.Server.Abstractions\CommunityToolkit.Datasync.Server.Abstractions.csproj" />
2626
<ProjectReference Include="..\..\..\..\src\CommunityToolkit.Datasync.Server.EntityFrameworkCore\CommunityToolkit.Datasync.Server.EntityFrameworkCore.csproj" />
27+
<ProjectReference Include="..\..\..\..\src\CommunityToolkit.Datasync.Server.NSwag\CommunityToolkit.Datasync.Server.NSwag.csproj" />
28+
<ProjectReference Include="..\..\..\..\src\CommunityToolkit.Datasync.Server.Swashbuckle\CommunityToolkit.Datasync.Server.Swashbuckle.csproj" />
2729
<ProjectReference Include="..\..\..\..\src\CommunityToolkit.Datasync.Server\CommunityToolkit.Datasync.Server.csproj" />
2830
</ItemGroup>
2931

samples/datasync-server/src/Sample.Datasync.Server/appsettings.Development.json

+3
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,8 @@
44
"Default": "Information",
55
"Microsoft.AspNetCore": "Warning"
66
}
7+
},
8+
"Swagger": {
9+
"Driver": "NSwag"
710
}
811
}

src/CommunityToolkit.Datasync.Server.NSwag/DatasyncOperationProcessor.cs

+40
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ private static void ProcessDatasyncOperation(OperationProcessorContext context)
6565
string path = context.OperationDescription.Path;
6666
Type entityType = GetTableEntityType(context.ControllerType);
6767
JsonSchema entitySchemaRef = GetEntityReference(context, entityType);
68+
AddMissingSchemaProperties(entitySchemaRef.Reference);
6869

6970
if (method.Equals("DELETE", StringComparison.InvariantCultureIgnoreCase))
7071
{
@@ -91,20 +92,59 @@ private static void ProcessDatasyncOperation(OperationProcessorContext context)
9192
if (method.Equals("POST", StringComparison.InvariantCultureIgnoreCase))
9293
{
9394
operation.AddConditionalRequestSupport(entitySchemaRef, true);
95+
operation.TryAddConsumes("application/json");
96+
operation.Parameters.Add(new OpenApiParameter { Schema = entitySchemaRef, Kind = OpenApiParameterKind.Body });
9497
operation.SetResponse(HttpStatusCode.Created, entitySchemaRef);
9598
operation.SetResponse(HttpStatusCode.BadRequest);
9699
}
97100

98101
if (method.Equals("PUT", StringComparison.InvariantCultureIgnoreCase))
99102
{
100103
operation.AddConditionalRequestSupport(entitySchemaRef);
104+
operation.TryAddConsumes("application/json");
105+
operation.Parameters.Add(new OpenApiParameter { Schema = entitySchemaRef, Kind = OpenApiParameterKind.Body });
101106
operation.SetResponse(HttpStatusCode.OK, entitySchemaRef);
102107
operation.SetResponse(HttpStatusCode.BadRequest);
103108
operation.SetResponse(HttpStatusCode.NotFound);
104109
operation.SetResponse(HttpStatusCode.Gone);
105110
}
106111
}
107112

113+
private static void AddMissingSchemaProperties(JsonSchema? schema)
114+
{
115+
if (schema is null)
116+
{
117+
return;
118+
}
119+
120+
if (schema.Properties.ContainsKey("id") && schema.Properties.ContainsKey("updatedAt") && schema.Properties.ContainsKey("version"))
121+
{
122+
// Nothing to do - the correct properties are already in the schma.
123+
return;
124+
}
125+
126+
_ = schema.Properties.TryAdd("id", new JsonSchemaProperty
127+
{
128+
Type = JsonObjectType.String,
129+
Description = "The globally unique ID for the entity",
130+
IsRequired = true
131+
});
132+
_ = schema.Properties.TryAdd("updatedAt", new JsonSchemaProperty
133+
{
134+
Type = JsonObjectType.String,
135+
Description = "The ISO-8601 date/time string describing the last time the entity was updated with ms accuracy.",
136+
IsRequired = false
137+
});
138+
_ = schema.Properties.TryAdd("version", new JsonSchemaProperty
139+
{
140+
Type = JsonObjectType.String,
141+
Description = "An opaque string that changes whenever the entity changes.",
142+
IsRequired = false
143+
});
144+
145+
return;
146+
}
147+
108148
/// <summary>
109149
/// Either reads or generates the required entity type schema.
110150
/// </summary>

tests/CommunityToolkit.Datasync.Server.NSwag.Test/NSwag_Tests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public async Task NSwag_GeneratesSwagger()
2828

2929
// There is an x-generator field that is library specific and completely irrelevant
3030
// to the comparison, so this line will remove it for comparison purposes.
31-
Regex generatorRegex = new("\"x-generator\": \"[^\\\"]+\",");
31+
Regex generatorRegex = new("\"x-generator\": \"[^\\\"]+\",[\r\n]+");
3232
actualContent = generatorRegex.Replace(actualContent, "", 1);
3333
expectedContent = generatorRegex.Replace(expectedContent, "", 1);
3434

tests/CommunityToolkit.Datasync.Server.NSwag.Test/swagger.json

+71-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
2-
3-
"openapi": "3.0.0",
2+
"openapi": "3.0.0",
43
"info": {
54
"title": "My Title",
65
"version": "1.0.0"
@@ -223,6 +222,15 @@
223222
}
224223
}
225224
],
225+
"requestBody": {
226+
"content": {
227+
"application/json": {
228+
"schema": {
229+
"$ref": "#/components/schemas/KitchenSink"
230+
}
231+
}
232+
}
233+
},
226234
"responses": {
227235
"201": {
228236
"description": "Created",
@@ -576,6 +584,15 @@
576584
}
577585
}
578586
],
587+
"requestBody": {
588+
"content": {
589+
"application/json": {
590+
"schema": {
591+
"$ref": "#/components/schemas/KitchenSink"
592+
}
593+
}
594+
}
595+
},
579596
"responses": {
580597
"200": {
581598
"description": "OK",
@@ -660,6 +677,15 @@
660677
}
661678
}
662679
],
680+
"requestBody": {
681+
"content": {
682+
"application/json": {
683+
"schema": {
684+
"$ref": "#/components/schemas/TodoItem"
685+
}
686+
}
687+
}
688+
},
663689
"responses": {
664690
"201": {
665691
"description": "Created",
@@ -1013,6 +1039,15 @@
10131039
}
10141040
}
10151041
],
1042+
"requestBody": {
1043+
"content": {
1044+
"application/json": {
1045+
"schema": {
1046+
"$ref": "#/components/schemas/TodoItem"
1047+
}
1048+
}
1049+
}
1050+
},
10161051
"responses": {
10171052
"200": {
10181053
"description": "OK",
@@ -1085,6 +1120,23 @@
10851120
"schemas": {
10861121
"KitchenSink": {
10871122
"title": "KitchenSink",
1123+
"required": [
1124+
"id"
1125+
],
1126+
"properties": {
1127+
"id": {
1128+
"type": "string",
1129+
"description": "The globally unique ID for the entity"
1130+
},
1131+
"updatedAt": {
1132+
"type": "string",
1133+
"description": "The ISO-8601 date/time string describing the last time the entity was updated with ms accuracy."
1134+
},
1135+
"version": {
1136+
"type": "string",
1137+
"description": "An opaque string that changes whenever the entity changes."
1138+
}
1139+
},
10881140
"definitions": {
10891141
"KitchenSinkState": {
10901142
"type": "integer",
@@ -1246,6 +1298,23 @@
12461298
},
12471299
"TodoItem": {
12481300
"title": "TodoItem",
1301+
"required": [
1302+
"id"
1303+
],
1304+
"properties": {
1305+
"id": {
1306+
"type": "string",
1307+
"description": "The globally unique ID for the entity"
1308+
},
1309+
"updatedAt": {
1310+
"type": "string",
1311+
"description": "The ISO-8601 date/time string describing the last time the entity was updated with ms accuracy."
1312+
},
1313+
"version": {
1314+
"type": "string",
1315+
"description": "An opaque string that changes whenever the entity changes."
1316+
}
1317+
},
12491318
"definitions": {
12501319
"EntityTableData": {
12511320
"allOf": [

0 commit comments

Comments
 (0)