Skip to content

Commit 4635800

Browse files
authored
Backport various fixes and enhancements from other work in progress (#1685)
1 parent e8e3093 commit 4635800

File tree

12 files changed

+149
-89
lines changed

12 files changed

+149
-89
lines changed

Diff for: src/JsonApiDotNetCore.OpenApi.Client.NSwag/JsonApiDotNetCore.OpenApi.Client.NSwag.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<None Include="..\..\package-icon.png" Visible="false" Pack="True" PackagePath="" />
2828
<None Include="..\..\PackageReadme.md" Visible="false" Pack="True" PackagePath="" />
2929
<None Include="Build\*.props" Pack="True" PackagePath="build" />
30+
<None Include="Templates\*.liquid" Pack="True" PackagePath="Templates" />
3031
</ItemGroup>
3132

3233
<ItemGroup>

Diff for: src/JsonApiDotNetCore.OpenApi.Swashbuckle/ConfigureSwaggerGenOptions.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public ConfigureSwaggerGenOptions(OpenApiOperationIdSelector operationIdSelector
4545

4646
public void Configure(SwaggerGenOptions options)
4747
{
48+
ArgumentNullException.ThrowIfNull(options);
49+
4850
options.SupportNonNullableReferenceTypes();
4951
options.UseAllOfToExtendReferenceSchemas();
5052

@@ -57,10 +59,10 @@ public void Configure(SwaggerGenOptions options)
5759
options.CustomOperationIds(_operationIdSelector.GetOpenApiOperationId);
5860
options.CustomSchemaIds(_schemaIdSelector.GetSchemaId);
5961

62+
options.OperationFilter<DocumentationOpenApiOperationFilter>();
6063
options.DocumentFilter<ServerDocumentFilter>();
6164
options.DocumentFilter<EndpointOrderingFilter>();
6265
options.DocumentFilter<StringEnumOrderingFilter>();
63-
options.OperationFilter<DocumentationOpenApiOperationFilter>();
6466
options.DocumentFilter<UnusedComponentSchemaCleaner>();
6567
}
6668

Diff for: src/JsonApiDotNetCore.OpenApi.Swashbuckle/JsonApiActionDescriptorCollectionProvider.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ .. AddJsonApiMetadataToAction(endpoint, endpointMetadataContainer.ResponseMetada
5858

5959
if (replacementDescriptorsForEndpoint.Count > 0)
6060
{
61-
newDescriptors.InsertRange(newDescriptors.IndexOf(endpoint) - 1, replacementDescriptorsForEndpoint);
61+
newDescriptors.InsertRange(newDescriptors.IndexOf(endpoint), replacementDescriptorsForEndpoint);
6262
newDescriptors.Remove(endpoint);
6363
}
6464
}

Diff for: src/JsonApiDotNetCore.OpenApi.Swashbuckle/OpenApiEndpointConvention.cs

+5
Original file line numberDiff line numberDiff line change
@@ -280,5 +280,10 @@ public static JsonApiEndpointWrapper FromActionModel(ActionModel actionModel)
280280
JsonApiEndpoints endpoint = EndpointResolver.Instance.GetEndpoint(actionModel.ActionMethod);
281281
return new JsonApiEndpointWrapper(false, endpoint);
282282
}
283+
284+
public override string ToString()
285+
{
286+
return IsAtomicOperationsEndpoint ? "PostOperations" : Value.ToString();
287+
}
283288
}
284289
}

Diff for: src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Bodies/BodySchemaGenerator.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,19 @@ public OpenApiSchema GenerateSchema(Type bodyType, SchemaRepository schemaReposi
4545

4646
_linksVisibilitySchemaGenerator.UpdateSchemaForTopLevel(bodyType, fullSchema, schemaRepository);
4747

48-
SetJsonApiVersion(fullSchema);
48+
SetJsonApiVersion(fullSchema, schemaRepository);
4949

5050
return referenceSchema;
5151
}
5252

5353
protected abstract OpenApiSchema GenerateBodySchema(Type bodyType, SchemaRepository schemaRepository);
5454

55-
private void SetJsonApiVersion(OpenApiSchema fullSchema)
55+
private void SetJsonApiVersion(OpenApiSchema fullSchema, SchemaRepository schemaRepository)
5656
{
5757
if (fullSchema.Properties.ContainsKey(JsonApiPropertyName.Jsonapi) && !_options.IncludeJsonApiVersion)
5858
{
5959
fullSchema.Properties.Remove(JsonApiPropertyName.Jsonapi);
60+
schemaRepository.Schemas.Remove(JsonApiPropertyName.Jsonapi);
6061
}
6162
}
6263
}

Diff for: src/JsonApiDotNetCore.OpenApi.Swashbuckle/SchemaGenerators/Components/LinksVisibilitySchemaGenerator.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,17 @@ public void UpdateSchemaForRelationship(Type modelType, OpenApiSchema fullSchema
110110
private void UpdateLinksProperty(OpenApiSchema fullSchemaForLinksContainer, LinkTypes visibleLinkTypes, LinkTypes possibleLinkTypes,
111111
SchemaRepository schemaRepository)
112112
{
113+
OpenApiSchema referenceSchemaForLinks = fullSchemaForLinksContainer.Properties[JsonApiPropertyName.Links].UnwrapLastExtendedSchema();
114+
113115
if ((visibleLinkTypes & possibleLinkTypes) == 0)
114116
{
115117
fullSchemaForLinksContainer.Required.Remove(JsonApiPropertyName.Links);
116118
fullSchemaForLinksContainer.Properties.Remove(JsonApiPropertyName.Links);
119+
120+
schemaRepository.Schemas.Remove(referenceSchemaForLinks.Reference.Id);
117121
}
118122
else if (visibleLinkTypes != possibleLinkTypes)
119123
{
120-
OpenApiSchema referenceSchemaForLinks = fullSchemaForLinksContainer.Properties[JsonApiPropertyName.Links].UnwrapLastExtendedSchema();
121124
string linksSchemaId = referenceSchemaForLinks.Reference.Id;
122125

123126
if (schemaRepository.Schemas.TryGetValue(linksSchemaId, out OpenApiSchema? fullSchemaForLinks))

Diff for: src/JsonApiDotNetCore.OpenApi.Swashbuckle/SwaggerComponents/StringEnumOrderingFilter.cs

+5-8
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,15 @@ private sealed class OpenApiEnumVisitor : OpenApiVisitorBase
2727
{
2828
public override void Visit(OpenApiSchema schema)
2929
{
30-
if (schema.Enum.Count > 0)
30+
if (HasSortAnnotation(schema))
3131
{
32-
if (HasSortAnnotation(schema))
32+
if (schema.Enum.Count > 1)
3333
{
34-
if (schema.Enum.Count > 1)
35-
{
36-
OrderEnumMembers(schema);
37-
}
34+
OrderEnumMembers(schema);
3835
}
39-
40-
schema.Extensions.Remove(RequiresSortKey);
4136
}
37+
38+
schema.Extensions.Remove(RequiresSortKey);
4239
}
4340

4441
private static bool HasSortAnnotation(OpenApiSchema schema)

Diff for: test/OpenApiTests/AtomicOperations/OperationsTests.cs

+14
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ public OperationsTests(OpenApiTestContext<OpenApiStartup<OperationsDbContext>, O
2929
[Fact]
3030
public async Task Operations_endpoint_is_exposed()
3131
{
32+
// Act
3233
JsonElement document = await _testContext.GetSwaggerDocumentAsync();
3334

35+
// Assert
3436
document.Should().ContainPath("paths./operations.post").Should().BeJson("""
3537
{
3638
"tags": [
@@ -125,8 +127,10 @@ public async Task Operations_endpoint_is_exposed()
125127
[Fact]
126128
public async Task Operations_request_component_schemas_are_exposed()
127129
{
130+
// Act
128131
JsonElement document = await _testContext.GetSwaggerDocumentAsync();
129132

133+
// Assert
130134
document.Should().ContainPath("components.schemas").With(schemasElement =>
131135
{
132136
schemasElement.Should().ContainPath("operationsRequestDocument").Should().BeJson("""
@@ -241,8 +245,10 @@ public async Task Operations_request_component_schemas_are_exposed()
241245
[Fact]
242246
public async Task Operations_response_component_schemas_are_exposed()
243247
{
248+
// Act
244249
JsonElement document = await _testContext.GetSwaggerDocumentAsync();
245250

251+
// Assert
246252
document.Should().ContainPath("components.schemas").With(schemasElement =>
247253
{
248254
schemasElement.Should().ContainPath("operationsResponseDocument").Should().BeJson("""
@@ -314,8 +320,10 @@ public async Task Operations_response_component_schemas_are_exposed()
314320
[Fact]
315321
public async Task Course_operation_component_schemas_are_exposed()
316322
{
323+
// Act
317324
JsonElement document = await _testContext.GetSwaggerDocumentAsync();
318325

326+
// Assert
319327
document.Should().ContainPath("components.schemas").With(schemasElement =>
320328
{
321329
// resource operations
@@ -782,8 +790,10 @@ public async Task Course_operation_component_schemas_are_exposed()
782790
[Fact]
783791
public async Task Student_operation_component_schemas_are_exposed()
784792
{
793+
// Act
785794
JsonElement document = await _testContext.GetSwaggerDocumentAsync();
786795

796+
// Assert
787797
document.Should().ContainPath("components.schemas").With(schemasElement =>
788798
{
789799
// resource operations
@@ -1342,8 +1352,10 @@ public async Task Student_operation_component_schemas_are_exposed()
13421352
[Fact]
13431353
public async Task Teacher_operation_component_schemas_are_exposed()
13441354
{
1355+
// Act
13451356
JsonElement document = await _testContext.GetSwaggerDocumentAsync();
13461357

1358+
// Assert
13471359
document.Should().ContainPath("components.schemas").With(schemasElement =>
13481360
{
13491361
// resource operations
@@ -1846,8 +1858,10 @@ public async Task Teacher_operation_component_schemas_are_exposed()
18461858
[Fact]
18471859
public async Task Enrollment_operation_component_schemas_are_exposed()
18481860
{
1861+
// Act
18491862
JsonElement document = await _testContext.GetSwaggerDocumentAsync();
18501863

1864+
// Assert
18511865
document.Should().ContainPath("components.schemas").With(schemasElement =>
18521866
{
18531867
// resource operations

Diff for: test/OpenApiTests/JsonPathBuilder.cs

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System.Collections.ObjectModel;
2+
using JsonApiDotNetCore.Configuration;
3+
using JsonApiDotNetCore.Controllers;
4+
using JsonApiDotNetCore.Resources.Annotations;
5+
6+
#pragma warning disable AV1008 // Class should not be static
7+
8+
namespace OpenApiTests;
9+
10+
internal static class JsonPathBuilder
11+
{
12+
public static readonly IReadOnlyCollection<JsonApiEndpoints> KnownEndpoints =
13+
[
14+
JsonApiEndpoints.GetCollection,
15+
JsonApiEndpoints.GetSingle,
16+
JsonApiEndpoints.GetSecondary,
17+
JsonApiEndpoints.GetRelationship,
18+
JsonApiEndpoints.Post,
19+
JsonApiEndpoints.PostRelationship,
20+
JsonApiEndpoints.Patch,
21+
JsonApiEndpoints.PatchRelationship,
22+
JsonApiEndpoints.Delete,
23+
JsonApiEndpoints.DeleteRelationship
24+
];
25+
26+
public static IReadOnlyDictionary<JsonApiEndpoints, ReadOnlyCollection<string>> GetEndpointPaths(ResourceType resourceType)
27+
{
28+
var endpointToPathMap = new Dictionary<JsonApiEndpoints, List<string>>
29+
{
30+
[JsonApiEndpoints.GetCollection] =
31+
[
32+
$"paths./{resourceType.PublicName}.get",
33+
$"paths./{resourceType.PublicName}.head"
34+
],
35+
[JsonApiEndpoints.GetSingle] =
36+
[
37+
$"paths./{resourceType.PublicName}/{{id}}.get",
38+
$"paths./{resourceType.PublicName}/{{id}}.head"
39+
],
40+
[JsonApiEndpoints.GetSecondary] = [],
41+
[JsonApiEndpoints.GetRelationship] = [],
42+
[JsonApiEndpoints.Post] = [$"paths./{resourceType.PublicName}.post"],
43+
[JsonApiEndpoints.PostRelationship] = [],
44+
[JsonApiEndpoints.Patch] = [$"paths./{resourceType.PublicName}/{{id}}.patch"],
45+
[JsonApiEndpoints.PatchRelationship] = [],
46+
[JsonApiEndpoints.Delete] = [$"paths./{resourceType.PublicName}/{{id}}.delete"],
47+
[JsonApiEndpoints.DeleteRelationship] = []
48+
};
49+
50+
foreach (RelationshipAttribute relationship in resourceType.Relationships)
51+
{
52+
endpointToPathMap[JsonApiEndpoints.GetSecondary].AddRange([
53+
$"paths./{resourceType.PublicName}/{{id}}/{relationship.PublicName}.get",
54+
$"paths./{resourceType.PublicName}/{{id}}/{relationship.PublicName}.head"
55+
]);
56+
57+
endpointToPathMap[JsonApiEndpoints.GetRelationship].AddRange([
58+
$"paths./{resourceType.PublicName}/{{id}}/relationships/{relationship.PublicName}.get",
59+
$"paths./{resourceType.PublicName}/{{id}}/relationships/{relationship.PublicName}.head"
60+
]);
61+
62+
endpointToPathMap[JsonApiEndpoints.PatchRelationship].Add($"paths./{resourceType.PublicName}/{{id}}/relationships/{relationship.PublicName}.patch");
63+
64+
if (relationship is HasManyAttribute)
65+
{
66+
endpointToPathMap[JsonApiEndpoints.PostRelationship].Add(
67+
$"paths./{resourceType.PublicName}/{{id}}/relationships/{relationship.PublicName}.post");
68+
69+
endpointToPathMap[JsonApiEndpoints.DeleteRelationship].Add(
70+
$"paths./{resourceType.PublicName}/{{id}}/relationships/{relationship.PublicName}.delete");
71+
}
72+
}
73+
74+
return endpointToPathMap.ToDictionary(pair => pair.Key, pair => pair.Value.AsReadOnly()).AsReadOnly();
75+
}
76+
}

Diff for: test/OpenApiTests/QueryStrings/QueryStringTests.cs

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public async Task Endpoints_have_query_string_parameter(string endpointPath)
3939
// Act
4040
JsonElement document = await _testContext.GetSwaggerDocumentAsync();
4141

42+
// Assert
4243
document.Should().ContainPath($"paths.{endpointPath}").With(verbElement =>
4344
{
4445
verbElement.Should().ContainPath("parameters").With(parametersElement =>
@@ -76,6 +77,7 @@ public async Task Endpoints_do_not_have_query_string_parameter(string endpointPa
7677
// Act
7778
JsonElement document = await _testContext.GetSwaggerDocumentAsync();
7879

80+
// Assert
7981
document.Should().ContainPath($"paths.{endpointPath}").With(verbElement =>
8082
{
8183
verbElement.Should().ContainPath("parameters").With(parametersElement =>

0 commit comments

Comments
 (0)