Skip to content

Commit 4c45295

Browse files
committed
Address PR comments
1 parent 3278f14 commit 4c45295

File tree

28 files changed

+2351
-3144
lines changed

28 files changed

+2351
-3144
lines changed

src/Examples/JsonApiDotNetCoreExample/GeneratedSwagger/JsonApiDotNetCoreExample.json

Lines changed: 240 additions & 348 deletions
Large diffs are not rendered by default.

src/Examples/JsonApiDotNetCoreExampleClient/ExampleApiClient.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using JetBrains.Annotations;
22
using JsonApiDotNetCore.OpenApi.Client;
33
using Newtonsoft.Json;
4+
using NSwag;
5+
using NSwag.CodeGeneration;
46

57
namespace JsonApiDotNetCoreExampleClient;
68

@@ -16,3 +18,11 @@ partial void UpdateJsonSerializerSettings(JsonSerializerSettings settings)
1618
#endif
1719
}
1820
}
21+
22+
public class TestParameterNameGenerator : IParameterNameGenerator
23+
{
24+
public string Generate(OpenApiParameter parameter, IEnumerable<OpenApiParameter> allParameters)
25+
{
26+
throw new NotImplementedException("TTEESSST");
27+
}
28+
}

src/Examples/JsonApiDotNetCoreExampleClient/JsonApiDotNetCoreExampleClient.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@
2222
<PrivateAssets>all</PrivateAssets>
2323
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2424
</PackageReference>
25+
<PackageReference Include="NSwag.CodeGeneration" Version="14.0.3" />
2526
</ItemGroup>
2627

2728
<ItemGroup>
2829
<OpenApiReference Include="..\JsonApiDotNetCoreExample\GeneratedSwagger\JsonApiDotNetCoreExample.json" CodeGenerator="NSwagCSharp" ClassName="ExampleApiClient">
29-
<Options>/GenerateExceptionClasses:false /WrapResponses:true /ResponseClass:JsonApiResponse /GenerateOptionalParameters:true /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client.Exceptions</Options>
30+
<Options>/GenerateExceptionClasses:false /WrapResponses:true /GenerateResponseClasses:false /ResponseClass:ApiResponse /GenerateOptionalParameters:true /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client /ParameterNameGenerator:JsonApiDotNetCoreExampleClient.TestParameterNameGenerator</Options>
3031
</OpenApiReference>
3132
</ItemGroup>
3233
</Project>

src/Examples/JsonApiDotNetCoreExampleClient/Program.cs

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1+
using System.Net;
12
using JsonApiDotNetCore.OpenApi.Client;
2-
using JsonApiDotNetCore.OpenApi.Client.Exceptions;
33
using JsonApiDotNetCoreExampleClient;
4-
using Microsoft.AspNetCore.Http;
54
using Microsoft.Net.Http.Headers;
65

76
#if DEBUG
@@ -15,19 +14,17 @@
1514

1615
var apiClient = new ExampleApiClient(httpClient);
1716

18-
JsonApiResponse<PersonCollectionResponseDocument> getResponse = await GetPersonCollectionAsync(apiClient);
17+
ApiResponse<PersonCollectionResponseDocument?> getResponse1 = await GetPersonCollectionAsync(apiClient, null);
18+
ApiResponse<PersonCollectionResponseDocument?> getResponse2 = await GetPersonCollectionAsync(apiClient, getResponse1.Headers[HeaderNames.ETag].First());
1919

20-
try
21-
{
22-
getResponse = await GetPersonCollectionAsync(apiClient, getResponse.Headers[HeaderNames.ETag].First());
23-
}
24-
catch (ApiException exception) when (exception.StatusCode == StatusCodes.Status304NotModified)
20+
if (getResponse2.StatusCode == (int)HttpStatusCode.NotModified)
2521
{
22+
// getResponse2.Result is null.
2623
}
2724

28-
foreach (PersonDataInResponse person in getResponse.Result.Data)
25+
foreach (PersonDataInResponse person in getResponse1.Result!.Data)
2926
{
30-
PrintPerson(person, getResponse.Result.Included);
27+
PrintPerson(person, getResponse1.Result.Included);
3128
}
3229

3330
var patchRequest = new PersonPatchRequestDocument
@@ -52,17 +49,15 @@
5249
Console.WriteLine("Press any key to close.");
5350
Console.ReadKey();
5451

55-
#pragma warning disable AV1553
56-
static Task<JsonApiResponse<PersonCollectionResponseDocument>> GetPersonCollectionAsync(ExampleApiClient apiClient, string? ifNoneMatch = null)
57-
#pragma warning restore AV1553
52+
static Task<ApiResponse<PersonCollectionResponseDocument?>> GetPersonCollectionAsync(ExampleApiClient apiClient, string? ifNoneMatch)
5853
{
59-
return apiClient.GetPersonCollectionAsync(new Dictionary<string, string?>
54+
return ApiResponse.TranslateAsync(() => apiClient.GetPersonCollectionAsync(new Dictionary<string, string?>
6055
{
6156
["filter"] = "has(assignedTodoItems)",
6257
["sort"] = "-lastName",
6358
["page[size]"] = "5",
6459
["include"] = "assignedTodoItems.tags"
65-
}, ifNoneMatch);
60+
}, ifNoneMatch));
6661
}
6762

6863
static void PrintPerson(PersonDataInResponse person, ICollection<DataInResponse> includes)

src/JsonApiDotNetCore.OpenApi.Client/Exceptions/ApiException.cs renamed to src/JsonApiDotNetCore.OpenApi.Client/ApiException.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// We cannot rely on generating ApiException as soon as we are generating multiple clients, see https://github.com/RicoSuter/NSwag/issues/2839#issuecomment-776647377.
44
// Instead, we configure NSwag to point to the exception below in the generated code.
55

6-
namespace JsonApiDotNetCore.OpenApi.Client.Exceptions;
6+
namespace JsonApiDotNetCore.OpenApi.Client;
77

88
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
99
public class ApiException(string message, int statusCode, string? response, IReadOnlyDictionary<string, IEnumerable<string>> headers, Exception? innerException)

src/JsonApiDotNetCore.OpenApi.Client/ApiResponse.cs

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1+
using System.Net;
12
using JetBrains.Annotations;
2-
using JsonApiDotNetCore.OpenApi.Client.Exceptions;
3-
4-
#pragma warning disable AV1008 // Class should not be static
53

64
namespace JsonApiDotNetCore.OpenApi.Client;
75

86
[PublicAPI]
9-
public static class ApiResponse
7+
public class ApiResponse
108
{
9+
public int StatusCode { get; private set; }
10+
11+
public IReadOnlyDictionary<string, IEnumerable<string>> Headers { get; private set; }
12+
13+
public ApiResponse(int statusCode, IReadOnlyDictionary<string, IEnumerable<string>> headers)
14+
{
15+
StatusCode = statusCode;
16+
Headers = headers;
17+
}
18+
1119
public static async Task<TResponse?> TranslateAsync<TResponse>(Func<Task<TResponse>> operation)
1220
where TResponse : class
1321
{
@@ -17,7 +25,7 @@ public static class ApiResponse
1725
{
1826
return await operation().ConfigureAwait(false);
1927
}
20-
catch (ApiException exception) when (exception.StatusCode == 204)
28+
catch (ApiException exception) when (exception.StatusCode is (int)HttpStatusCode.NoContent or (int)HttpStatusCode.NotModified)
2129
{
2230
// Workaround for https://github.com/RicoSuter/NSwag/issues/2499
2331
return null;
@@ -32,9 +40,52 @@ public static async Task TranslateAsync(Func<Task> operation)
3240
{
3341
await operation().ConfigureAwait(false);
3442
}
35-
catch (ApiException exception) when (exception.StatusCode == 204)
43+
catch (ApiException exception) when (exception.StatusCode is (int)HttpStatusCode.NoContent or (int)HttpStatusCode.NotModified)
44+
{
45+
// Workaround for https://github.com/RicoSuter/NSwag/issues/2499
46+
}
47+
}
48+
49+
public static async Task<ApiResponse<TResult?>> TranslateAsync<TResult>(Func<Task<ApiResponse<TResult>>> operation)
50+
where TResult : class
51+
{
52+
ArgumentGuard.NotNull(operation);
53+
54+
try
55+
{
56+
return (await operation().ConfigureAwait(false))!;
57+
}
58+
catch (ApiException exception) when (exception.StatusCode is (int)HttpStatusCode.NoContent or (int)HttpStatusCode.NotModified)
59+
{
60+
// Workaround for https://github.com/RicoSuter/NSwag/issues/2499
61+
return new ApiResponse<TResult?>(exception.StatusCode, exception.Headers, null);
62+
}
63+
}
64+
65+
public static async Task<ApiResponse> TranslateAsync(Func<Task<ApiResponse>> operation)
66+
{
67+
ArgumentGuard.NotNull(operation);
68+
69+
try
70+
{
71+
return await operation().ConfigureAwait(false);
72+
}
73+
catch (ApiException exception) when (exception.StatusCode is (int)HttpStatusCode.NoContent or (int)HttpStatusCode.NotModified)
3674
{
3775
// Workaround for https://github.com/RicoSuter/NSwag/issues/2499
76+
return new ApiResponse(exception.StatusCode, exception.Headers);
3877
}
3978
}
4079
}
80+
81+
[PublicAPI]
82+
public class ApiResponse<TResult> : ApiResponse
83+
{
84+
public TResult Result { get; private set; }
85+
86+
public ApiResponse(int statusCode, IReadOnlyDictionary<string, IEnumerable<string>> headers, TResult result)
87+
: base(statusCode, headers)
88+
{
89+
Result = result;
90+
}
91+
}

src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiOperationDocumentationFilter.cs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ private static void ApplyGetPrimary(OpenApiOperation operation, ResourceType res
182182
}
183183

184184
AddQueryStringParameters(operation, false);
185-
AddHeaderParameterIfNoneMatch(operation);
185+
AddRequestHeaderIfNoneMatch(operation);
186186
SetResponseDescription(operation.Responses, HttpStatusCode.BadRequest, TextQueryStringBad);
187187
}
188188
else if (operation.Parameters.Count == 1)
@@ -210,7 +210,7 @@ private static void ApplyGetPrimary(OpenApiOperation operation, ResourceType res
210210

211211
SetParameterDescription(operation.Parameters[0], $"The identifier of the {singularName} to retrieve.");
212212
AddQueryStringParameters(operation, false);
213-
AddHeaderParameterIfNoneMatch(operation);
213+
AddRequestHeaderIfNoneMatch(operation);
214214
SetResponseDescription(operation.Responses, HttpStatusCode.BadRequest, TextQueryStringBad);
215215
SetResponseDescription(operation.Responses, HttpStatusCode.NotFound, $"The {singularName} does not exist.");
216216
}
@@ -313,7 +313,7 @@ relationship is HasOneAttribute
313313

314314
SetParameterDescription(operation.Parameters[0], $"The identifier of the {singularLeftName} whose related {rightName} to retrieve.");
315315
AddQueryStringParameters(operation, false);
316-
AddHeaderParameterIfNoneMatch(operation);
316+
AddRequestHeaderIfNoneMatch(operation);
317317
SetResponseDescription(operation.Responses, HttpStatusCode.BadRequest, TextQueryStringBad);
318318
SetResponseDescription(operation.Responses, HttpStatusCode.NotFound, $"The {singularLeftName} does not exist.");
319319
}
@@ -355,7 +355,7 @@ relationship is HasOneAttribute
355355

356356
SetParameterDescription(operation.Parameters[0], $"The identifier of the {singularLeftName} whose related {singularRightName} {ident} to retrieve.");
357357
AddQueryStringParameters(operation, true);
358-
AddHeaderParameterIfNoneMatch(operation);
358+
AddRequestHeaderIfNoneMatch(operation);
359359
SetResponseDescription(operation.Responses, HttpStatusCode.BadRequest, TextQueryStringBad);
360360
SetResponseDescription(operation.Responses, HttpStatusCode.NotFound, $"The {singularLeftName} does not exist.");
361361
}
@@ -470,13 +470,12 @@ private static void SetResponseHeaderETag(OpenApiResponses responses, HttpStatus
470470

471471
response.Headers[HeaderNames.ETag] = new OpenApiHeader
472472
{
473-
Description = "ETag identifying the version of the fetched resource.",
473+
Description = "A fingerprint of the HTTP response, which can be used in an If-None-Match header to only fetch changes.",
474474
Required = true,
475475
Schema = new OpenApiSchema
476476
{
477477
Type = "string"
478-
},
479-
Example = new OpenApiString("\"33a64df551425fcc55e4d42a148795d9f25f89d4\"")
478+
}
480479
};
481480
}
482481

@@ -486,7 +485,7 @@ private static void SetResponseHeaderContentLength(OpenApiResponses responses, H
486485

487486
response.Headers[HeaderNames.ContentLength] = new OpenApiHeader
488487
{
489-
Description = "Size of the response body in bytes",
488+
Description = "Size of the HTTP response body, in bytes.",
490489
Required = true,
491490
Schema = new OpenApiSchema
492491
{
@@ -502,11 +501,11 @@ private static void SetResponseHeaderLocation(OpenApiResponses responses, HttpSt
502501

503502
response.Headers[HeaderNames.Location] = new OpenApiHeader
504503
{
505-
Description = "Location of the newly created resource.",
504+
Description = "The URL at which the newly created JSON:API resource can be retrieved.",
506505
Required = true,
507506
Schema = new OpenApiSchema
508507
{
509-
Type = "string"
508+
Type = "uri"
510509
}
511510
};
512511
}
@@ -555,20 +554,18 @@ private static void AddQueryStringParameters(OpenApiOperation operation, bool is
555554
});
556555
}
557556

558-
private static void AddHeaderParameterIfNoneMatch(OpenApiOperation operation)
557+
private static void AddRequestHeaderIfNoneMatch(OpenApiOperation operation)
559558
{
560559
operation.Parameters.Add(new OpenApiParameter
561560
{
562561
In = ParameterLocation.Header,
563562
Name = "If-None-Match",
564-
Description = "ETag identifying the version of the requested resource.",
565-
Required = false,
563+
Description = "A list of ETags, resulting in HTTP status 304 without a body, if one of them matches the current fingerprint.",
566564
Schema = new OpenApiSchema
567565
{
568566
Type = "string",
569567
Nullable = true
570-
},
571-
Example = new OpenApiString("\"33a64df551425fcc55e4d42a148795d9f25f89d4\"")
568+
}
572569
});
573570
}
574571
}

test/OpenApiClientTests/LegacyClient/RequestTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public async Task Getting_resource_collection_produces_expected_request()
2323
IOpenApiClient apiClient = new OpenApiClient(wrapper.HttpClient);
2424

2525
// Act
26-
_ = await ApiResponse.TranslateAsync(() => apiClient.GetFlightCollectionAsync());
26+
_ = await ApiResponse.TranslateAsync(() => apiClient.GetFlightCollectionAsync(null, null));
2727

2828
// Assert
2929
wrapper.Request.ShouldNotBeNull();
@@ -43,7 +43,7 @@ public async Task Getting_resource_produces_expected_request()
4343
IOpenApiClient apiClient = new OpenApiClient(wrapper.HttpClient);
4444

4545
// Act
46-
_ = await ApiResponse.TranslateAsync(() => apiClient.GetFlightAsync(flightId));
46+
_ = await ApiResponse.TranslateAsync(() => apiClient.GetFlightAsync(flightId, null, null));
4747

4848
// Assert
4949
wrapper.Request.ShouldNotBeNull();
@@ -269,7 +269,7 @@ public async Task Getting_secondary_resource_produces_expected_request()
269269
IOpenApiClient apiClient = new OpenApiClient(wrapper.HttpClient);
270270

271271
// Act
272-
_ = await ApiResponse.TranslateAsync(() => apiClient.GetFlightPurserAsync(flightId));
272+
_ = await ApiResponse.TranslateAsync(() => apiClient.GetFlightPurserAsync(flightId, null, null));
273273

274274
// Assert
275275
wrapper.Request.ShouldNotBeNull();
@@ -289,7 +289,7 @@ public async Task Getting_secondary_resources_produces_expected_request()
289289
IOpenApiClient apiClient = new OpenApiClient(wrapper.HttpClient);
290290

291291
// Act
292-
_ = await ApiResponse.TranslateAsync(() => apiClient.GetFlightCabinCrewMembersAsync(flightId));
292+
_ = await ApiResponse.TranslateAsync(() => apiClient.GetFlightCabinCrewMembersAsync(flightId, null, null));
293293

294294
// Assert
295295
wrapper.Request.ShouldNotBeNull();
@@ -309,7 +309,7 @@ public async Task Getting_ToOne_relationship_produces_expected_request()
309309
IOpenApiClient apiClient = new OpenApiClient(wrapper.HttpClient);
310310

311311
// Act
312-
_ = await ApiResponse.TranslateAsync(() => apiClient.GetFlightPurserRelationshipAsync(flightId));
312+
_ = await ApiResponse.TranslateAsync(() => apiClient.GetFlightPurserRelationshipAsync(flightId, null, null));
313313

314314
// Assert
315315
wrapper.Request.ShouldNotBeNull();
@@ -368,7 +368,7 @@ public async Task Getting_ToMany_relationship_produces_expected_request()
368368
IOpenApiClient apiClient = new OpenApiClient(wrapper.HttpClient);
369369

370370
// Act
371-
_ = await ApiResponse.TranslateAsync(() => apiClient.GetFlightCabinCrewMembersRelationshipAsync(flightId));
371+
_ = await ApiResponse.TranslateAsync(() => apiClient.GetFlightCabinCrewMembersRelationshipAsync(flightId, null, null));
372372

373373
// Assert
374374
wrapper.Request.ShouldNotBeNull();

0 commit comments

Comments
 (0)