Skip to content

Commit a771afa

Browse files
authored
Merge pull request #1705 from json-api-dotnet/improve-coverage
Improve code coverage by skipping unreachable code
2 parents fb78af3 + 45696f6 commit a771afa

14 files changed

+261
-190
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Diagnostics;
2+
using System.Diagnostics.CodeAnalysis;
3+
4+
namespace JsonApiDotNetCore.OpenApi.Swashbuckle;
5+
6+
#pragma warning disable AV1008 // Class should not be static
7+
8+
internal static class ConsistencyGuard
9+
{
10+
[ExcludeFromCodeCoverage]
11+
public static void ThrowIf([DoesNotReturnIf(true)] bool condition)
12+
{
13+
if (condition)
14+
{
15+
throw new UnreachableException();
16+
}
17+
}
18+
}

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

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Diagnostics;
21
using System.Reflection;
32
using JsonApiDotNetCore.Errors;
43
using JsonApiDotNetCore.Middleware;
@@ -115,18 +114,20 @@ private static List<ActionDescriptor> AddJsonApiMetadataToAction(ActionDescripto
115114

116115
private static void UpdateProducesResponseTypeAttribute(ActionDescriptor endpoint, Type responseDocumentType)
117116
{
117+
ProducesResponseTypeAttribute? attribute = null;
118+
118119
if (ProducesJsonApiResponseDocument(endpoint))
119120
{
120121
var producesResponse = endpoint.GetFilterMetadata<ProducesResponseTypeAttribute>();
121122

122123
if (producesResponse != null)
123124
{
124-
producesResponse.Type = responseDocumentType;
125-
return;
125+
attribute = producesResponse;
126126
}
127127
}
128128

129-
throw new UnreachableException();
129+
ConsistencyGuard.ThrowIf(attribute == null);
130+
attribute.Type = responseDocumentType;
130131
}
131132

132133
private static bool ProducesJsonApiResponseDocument(ActionDescriptor endpoint)

Diff for: src/JsonApiDotNetCore.OpenApi.Swashbuckle/JsonApiMetadata/JsonApiEndpointMetadataProvider.cs

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Diagnostics;
21
using System.Reflection;
32
using JsonApiDotNetCore.Configuration;
43
using JsonApiDotNetCore.Controllers;
@@ -43,11 +42,7 @@ public JsonApiEndpointMetadataContainer Get(MethodInfo controllerAction)
4342
}
4443

4544
ResourceType? primaryResourceType = _controllerResourceMapping.GetResourceTypeForController(controllerAction.ReflectedType);
46-
47-
if (primaryResourceType == null)
48-
{
49-
throw new UnreachableException();
50-
}
45+
ConsistencyGuard.ThrowIf(primaryResourceType == null);
5146

5247
IJsonApiRequestMetadata? requestMetadata = GetRequestMetadata(endpoint, primaryResourceType);
5348
IJsonApiResponseMetadata? responseMetadata = GetResponseMetadata(endpoint, primaryResourceType);

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

+106-48
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Diagnostics;
21
using System.Net;
32
using System.Reflection;
43
using JsonApiDotNetCore.Configuration;
@@ -97,20 +96,56 @@ private static bool IsEndpointAvailable(JsonApiEndpoints endpoint, ResourceType
9796

9897
// For an overridden JSON:API action method in a partial class to show up, it's flag must be turned on in [Resource].
9998
// Otherwise, it is considered to be an action method that throws because the endpoint is unavailable.
100-
return endpoint switch
101-
{
102-
JsonApiEndpoints.GetCollection => availableEndpoints.HasFlag(JsonApiEndpoints.GetCollection),
103-
JsonApiEndpoints.GetSingle => availableEndpoints.HasFlag(JsonApiEndpoints.GetSingle),
104-
JsonApiEndpoints.GetSecondary => availableEndpoints.HasFlag(JsonApiEndpoints.GetSecondary),
105-
JsonApiEndpoints.GetRelationship => availableEndpoints.HasFlag(JsonApiEndpoints.GetRelationship),
106-
JsonApiEndpoints.Post => availableEndpoints.HasFlag(JsonApiEndpoints.Post),
107-
JsonApiEndpoints.PostRelationship => availableEndpoints.HasFlag(JsonApiEndpoints.PostRelationship),
108-
JsonApiEndpoints.Patch => availableEndpoints.HasFlag(JsonApiEndpoints.Patch),
109-
JsonApiEndpoints.PatchRelationship => availableEndpoints.HasFlag(JsonApiEndpoints.PatchRelationship),
110-
JsonApiEndpoints.Delete => availableEndpoints.HasFlag(JsonApiEndpoints.Delete),
111-
JsonApiEndpoints.DeleteRelationship => availableEndpoints.HasFlag(JsonApiEndpoints.DeleteRelationship),
112-
_ => throw new UnreachableException()
113-
};
99+
return IncludesEndpoint(endpoint, availableEndpoints);
100+
}
101+
102+
private static bool IncludesEndpoint(JsonApiEndpoints endpoint, JsonApiEndpoints availableEndpoints)
103+
{
104+
bool? isIncluded = null;
105+
106+
if (endpoint == JsonApiEndpoints.GetCollection)
107+
{
108+
isIncluded = availableEndpoints.HasFlag(JsonApiEndpoints.GetCollection);
109+
}
110+
else if (endpoint == JsonApiEndpoints.GetSingle)
111+
{
112+
isIncluded = availableEndpoints.HasFlag(JsonApiEndpoints.GetSingle);
113+
}
114+
else if (endpoint == JsonApiEndpoints.GetSecondary)
115+
{
116+
isIncluded = availableEndpoints.HasFlag(JsonApiEndpoints.GetSecondary);
117+
}
118+
else if (endpoint == JsonApiEndpoints.GetRelationship)
119+
{
120+
isIncluded = availableEndpoints.HasFlag(JsonApiEndpoints.GetRelationship);
121+
}
122+
else if (endpoint == JsonApiEndpoints.Post)
123+
{
124+
isIncluded = availableEndpoints.HasFlag(JsonApiEndpoints.Post);
125+
}
126+
else if (endpoint == JsonApiEndpoints.PostRelationship)
127+
{
128+
isIncluded = availableEndpoints.HasFlag(JsonApiEndpoints.PostRelationship);
129+
}
130+
else if (endpoint == JsonApiEndpoints.Patch)
131+
{
132+
isIncluded = availableEndpoints.HasFlag(JsonApiEndpoints.Patch);
133+
}
134+
else if (endpoint == JsonApiEndpoints.PatchRelationship)
135+
{
136+
isIncluded = availableEndpoints.HasFlag(JsonApiEndpoints.PatchRelationship);
137+
}
138+
else if (endpoint == JsonApiEndpoints.Delete)
139+
{
140+
isIncluded = availableEndpoints.HasFlag(JsonApiEndpoints.Delete);
141+
}
142+
else if (endpoint == JsonApiEndpoints.DeleteRelationship)
143+
{
144+
isIncluded = availableEndpoints.HasFlag(JsonApiEndpoints.DeleteRelationship);
145+
}
146+
147+
ConsistencyGuard.ThrowIf(isIncluded == null);
148+
return isIncluded.Value;
114149
}
115150

116151
private static JsonApiEndpoints GetGeneratedControllerEndpoints(ResourceType resourceType)
@@ -158,29 +193,40 @@ private static HttpStatusCode[] GetSuccessStatusCodesForEndpoint(JsonApiEndpoint
158193
];
159194
}
160195

161-
return endpoint.Value switch
196+
HttpStatusCode[]? statusCodes = null;
197+
198+
if (endpoint.Value is JsonApiEndpoints.GetCollection or JsonApiEndpoints.GetSingle or JsonApiEndpoints.GetSecondary or JsonApiEndpoints.GetRelationship)
162199
{
163-
JsonApiEndpoints.GetCollection or JsonApiEndpoints.GetSingle or JsonApiEndpoints.GetSecondary or JsonApiEndpoints.GetRelationship =>
200+
statusCodes =
164201
[
165202
HttpStatusCode.OK,
166203
HttpStatusCode.NotModified
167-
],
168-
JsonApiEndpoints.Post =>
204+
];
205+
}
206+
else if (endpoint.Value == JsonApiEndpoints.Post)
207+
{
208+
statusCodes =
169209
[
170210
HttpStatusCode.Created,
171211
HttpStatusCode.NoContent
172-
],
173-
JsonApiEndpoints.Patch =>
212+
];
213+
}
214+
else if (endpoint.Value == JsonApiEndpoints.Patch)
215+
{
216+
statusCodes =
174217
[
175218
HttpStatusCode.OK,
176219
HttpStatusCode.NoContent
177-
],
178-
JsonApiEndpoints.Delete or JsonApiEndpoints.PostRelationship or JsonApiEndpoints.PatchRelationship or JsonApiEndpoints.DeleteRelationship =>
179-
[
180-
HttpStatusCode.NoContent
181-
],
182-
_ => throw new UnreachableException()
183-
};
220+
];
221+
}
222+
else if (endpoint.Value is JsonApiEndpoints.Delete or JsonApiEndpoints.PostRelationship or JsonApiEndpoints.PatchRelationship or
223+
JsonApiEndpoints.DeleteRelationship)
224+
{
225+
statusCodes = [HttpStatusCode.NoContent];
226+
}
227+
228+
ConsistencyGuard.ThrowIf(statusCodes == null);
229+
return statusCodes;
184230
}
185231

186232
private HttpStatusCode[] GetErrorStatusCodesForEndpoint(JsonApiEndpointWrapper endpoint, ResourceType? resourceType)
@@ -200,46 +246,58 @@ private HttpStatusCode[] GetErrorStatusCodesForEndpoint(JsonApiEndpointWrapper e
200246
// Condition doesn't apply to atomic operations, because Forbidden is also used when an operation is not accessible.
201247
ClientIdGenerationMode clientIdGeneration = resourceType?.ClientIdGeneration ?? _options.ClientIdGeneration;
202248

203-
return endpoint.Value switch
249+
HttpStatusCode[]? statusCodes = null;
250+
251+
if (endpoint.Value == JsonApiEndpoints.GetCollection)
252+
{
253+
statusCodes = [HttpStatusCode.BadRequest];
254+
}
255+
else if (endpoint.Value is JsonApiEndpoints.GetSingle or JsonApiEndpoints.GetSecondary or JsonApiEndpoints.GetRelationship)
204256
{
205-
JsonApiEndpoints.GetCollection => [HttpStatusCode.BadRequest],
206-
JsonApiEndpoints.GetSingle or JsonApiEndpoints.GetSecondary or JsonApiEndpoints.GetRelationship =>
257+
statusCodes =
207258
[
208259
HttpStatusCode.BadRequest,
209260
HttpStatusCode.NotFound
210-
],
211-
JsonApiEndpoints.Post when clientIdGeneration == ClientIdGenerationMode.Forbidden =>
261+
];
262+
}
263+
else if (endpoint.Value == JsonApiEndpoints.Post && clientIdGeneration == ClientIdGenerationMode.Forbidden)
264+
{
265+
statusCodes =
212266
[
213267
HttpStatusCode.BadRequest,
214268
HttpStatusCode.Forbidden,
215269
HttpStatusCode.NotFound,
216270
HttpStatusCode.Conflict,
217271
HttpStatusCode.UnprocessableEntity
218-
],
219-
JsonApiEndpoints.Post =>
220-
[
221-
HttpStatusCode.BadRequest,
222-
HttpStatusCode.NotFound,
223-
HttpStatusCode.Conflict,
224-
HttpStatusCode.UnprocessableEntity
225-
],
226-
JsonApiEndpoints.Patch =>
272+
];
273+
}
274+
else if (endpoint.Value is JsonApiEndpoints.Post or JsonApiEndpoints.Patch)
275+
{
276+
statusCodes =
227277
[
228278
HttpStatusCode.BadRequest,
229279
HttpStatusCode.NotFound,
230280
HttpStatusCode.Conflict,
231281
HttpStatusCode.UnprocessableEntity
232-
],
233-
JsonApiEndpoints.Delete => [HttpStatusCode.NotFound],
234-
JsonApiEndpoints.PostRelationship or JsonApiEndpoints.PatchRelationship or JsonApiEndpoints.DeleteRelationship =>
282+
];
283+
}
284+
else if (endpoint.Value == JsonApiEndpoints.Delete)
285+
{
286+
statusCodes = [HttpStatusCode.NotFound];
287+
}
288+
else if (endpoint.Value is JsonApiEndpoints.PostRelationship or JsonApiEndpoints.PatchRelationship or JsonApiEndpoints.DeleteRelationship)
289+
{
290+
statusCodes =
235291
[
236292
HttpStatusCode.BadRequest,
237293
HttpStatusCode.NotFound,
238294
HttpStatusCode.Conflict,
239295
HttpStatusCode.UnprocessableEntity
240-
],
241-
_ => throw new UnreachableException()
242-
};
296+
];
297+
}
298+
299+
ConsistencyGuard.ThrowIf(statusCodes == null);
300+
return statusCodes;
243301
}
244302

245303
private void SetRequestMetadata(ActionModel action, JsonApiEndpointWrapper endpoint)

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

+4-16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Diagnostics;
21
using System.Reflection;
32
using System.Text.Json;
43
using Humanizer;
@@ -64,23 +63,14 @@ public string GetOpenApiOperationId(ApiDescription endpoint)
6463
private static string GetTemplate(ApiDescription endpoint)
6564
{
6665
Type bodyType = GetBodyType(endpoint);
67-
68-
if (!SchemaOpenTypeToOpenApiOperationIdTemplateMap.TryGetValue(bodyType, out string? template))
69-
{
70-
throw new UnreachableException();
71-
}
72-
66+
ConsistencyGuard.ThrowIf(!SchemaOpenTypeToOpenApiOperationIdTemplateMap.TryGetValue(bodyType, out string? template));
7367
return template;
7468
}
7569

7670
private static Type GetBodyType(ApiDescription endpoint)
7771
{
7872
var producesResponseTypeAttribute = endpoint.ActionDescriptor.GetFilterMetadata<ProducesResponseTypeAttribute>();
79-
80-
if (producesResponseTypeAttribute == null)
81-
{
82-
throw new UnreachableException();
83-
}
73+
ConsistencyGuard.ThrowIf(producesResponseTypeAttribute == null);
8474

8575
ControllerParameterDescriptor? requestBodyDescriptor = endpoint.ActionDescriptor.GetBodyParameterDescriptor();
8676
Type bodyType = (requestBodyDescriptor?.ParameterType ?? producesResponseTypeAttribute.Type).ConstructedToOpenType();
@@ -95,10 +85,8 @@ private static Type GetBodyType(ApiDescription endpoint)
9585

9686
private string ApplyTemplate(string openApiOperationIdTemplate, ResourceType? resourceType, ApiDescription endpoint)
9787
{
98-
if (endpoint.RelativePath == null || endpoint.HttpMethod == null)
99-
{
100-
throw new UnreachableException();
101-
}
88+
ConsistencyGuard.ThrowIf(endpoint.RelativePath == null);
89+
ConsistencyGuard.ThrowIf(endpoint.HttpMethod == null);
10290

10391
string method = endpoint.HttpMethod.ToLowerInvariant();
10492
string relationshipName = openApiOperationIdTemplate.Contains("[RelationshipName]") ? endpoint.RelativePath.Split('/').Last() : string.Empty;

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

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Diagnostics;
21
using Microsoft.OpenApi.Models;
32

43
namespace JsonApiDotNetCore.OpenApi.Swashbuckle;
@@ -20,10 +19,7 @@ public static void ReorderProperties(this OpenApiSchema fullSchema, IEnumerable<
2019
}
2120
}
2221

23-
if (fullSchema.Properties.Count != propertiesInOrder.Count)
24-
{
25-
throw new UnreachableException();
26-
}
22+
ConsistencyGuard.ThrowIf(fullSchema.Properties.Count != propertiesInOrder.Count);
2723

2824
fullSchema.Properties = propertiesInOrder;
2925
}

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

+2-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Diagnostics;
21
using System.Reflection;
32
using JsonApiDotNetCore.Configuration;
43
using JsonApiDotNetCore.OpenApi.Swashbuckle.JsonApiObjects.ResourceObjects;
@@ -64,11 +63,7 @@ public OpenApiSchema GenerateSchema(Type dataContainerSchemaType, ResourceType r
6463
private static Type GetElementTypeOfDataProperty(Type dataContainerConstructedType, ResourceType resourceType)
6564
{
6665
PropertyInfo? dataProperty = dataContainerConstructedType.GetProperty("Data");
67-
68-
if (dataProperty == null)
69-
{
70-
throw new UnreachableException();
71-
}
66+
ConsistencyGuard.ThrowIf(dataProperty == null);
7267

7368
Type innerPropertyType = dataProperty.PropertyType.ConstructedToOpenType().IsAssignableTo(typeof(ICollection<>))
7469
? dataProperty.PropertyType.GenericTypeArguments[0]
@@ -79,10 +74,7 @@ private static Type GetElementTypeOfDataProperty(Type dataContainerConstructedTy
7974
return typeof(DataInResponse<>).MakeGenericType(resourceType.ClrType);
8075
}
8176

82-
if (!innerPropertyType.IsGenericType)
83-
{
84-
throw new UnreachableException();
85-
}
77+
ConsistencyGuard.ThrowIf(!innerPropertyType.IsGenericType);
8678

8779
return innerPropertyType;
8880
}

0 commit comments

Comments
 (0)