Skip to content

Commit ba2ec6b

Browse files
committed
Fix failing test when run in parallel, respect naming convention as set in SerializerSettings for properties in swagger doc
1 parent ec2e678 commit ba2ec6b

6 files changed

+107
-91
lines changed
Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Text.Json;
45
using Humanizer;
56
using JsonApiDotNetCore.Configuration;
67
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Documents;
@@ -13,42 +14,41 @@ internal sealed class JsonApiSchemaIdSelector
1314
{
1415
private static readonly IDictionary<Type, string> OpenTypeToSchemaTemplateMap = new Dictionary<Type, string>
1516
{
16-
[typeof(ResourcePostRequestDocument<>)] = "###-post-request-document",
17-
[typeof(ResourcePatchRequestDocument<>)] = "###-patch-request-document",
18-
[typeof(ResourceObjectInPostRequest<>)] = "###-data-in-post-request",
19-
[typeof(AttributesInPostRequest<>)] = "###-attributes-in-post-request",
20-
[typeof(RelationshipsInPostRequest<>)] = "###-relationships-in-post-request",
21-
[typeof(ResourceObjectInPatchRequest<>)] = "###-data-in-patch-request",
22-
[typeof(AttributesInPatchRequest<>)] = "###-attributes-in-patch-request",
23-
[typeof(RelationshipsInPatchRequest<>)] = "###-relationships-in-patch-request",
24-
[typeof(ToOneRelationshipInRequest<>)] = "to-one-###-in-request",
25-
[typeof(NullableToOneRelationshipInRequest<>)] = "nullable-to-one-###-in-request",
26-
[typeof(ToManyRelationshipInRequest<>)] = "to-many-###-in-request",
27-
[typeof(PrimaryResourceResponseDocument<>)] = "###-primary-response-document",
28-
[typeof(SecondaryResourceResponseDocument<>)] = "###-secondary-response-document",
29-
[typeof(NullableSecondaryResourceResponseDocument<>)] = "nullable-###-secondary-response-document",
30-
[typeof(ResourceCollectionResponseDocument<>)] = "###-collection-response-document",
31-
[typeof(ResourceIdentifierResponseDocument<>)] = "###-identifier-response-document",
32-
[typeof(NullableResourceIdentifierResponseDocument<>)] = "nullable-###-identifier-response-document",
33-
[typeof(ResourceIdentifierCollectionResponseDocument<>)] = "###-identifier-collection-response-document",
34-
[typeof(ToOneRelationshipInResponse<>)] = "to-one-###-in-response",
35-
[typeof(NullableToOneRelationshipInResponse<>)] = "nullable-to-one-###-in-response",
36-
[typeof(ToManyRelationshipInResponse<>)] = "to-many-###-in-response",
37-
[typeof(ResourceObjectInResponse<>)] = "###-data-in-response",
38-
[typeof(AttributesInResponse<>)] = "###-attributes-in-response",
39-
[typeof(RelationshipsInResponse<>)] = "###-relationships-in-response",
40-
[typeof(ResourceIdentifierObject<>)] = "###-identifier"
17+
[typeof(ResourcePostRequestDocument<>)] = "[ResourceName] Post Request Document",
18+
[typeof(ResourcePatchRequestDocument<>)] = "[ResourceName] Patch Request Document",
19+
[typeof(ResourceObjectInPostRequest<>)] = "[ResourceName] Data In Post Request",
20+
[typeof(AttributesInPostRequest<>)] = "[ResourceName] Attributes In Post Request",
21+
[typeof(RelationshipsInPostRequest<>)] = "[ResourceName] Relationships In Post Request",
22+
[typeof(ResourceObjectInPatchRequest<>)] = "[ResourceName] Data In Patch Request",
23+
[typeof(AttributesInPatchRequest<>)] = "[ResourceName] Attributes In Patch Request",
24+
[typeof(RelationshipsInPatchRequest<>)] = "[ResourceName] Relationships In Patch Request",
25+
[typeof(ToOneRelationshipInRequest<>)] = "To One [ResourceName] In Request",
26+
[typeof(NullableToOneRelationshipInRequest<>)] = "Nullable To One [ResourceName] In Request",
27+
[typeof(ToManyRelationshipInRequest<>)] = "To Many [ResourceName] In Request",
28+
[typeof(PrimaryResourceResponseDocument<>)] = "[ResourceName] Primary Response Document",
29+
[typeof(SecondaryResourceResponseDocument<>)] = "[ResourceName] Secondary Response Document",
30+
[typeof(NullableSecondaryResourceResponseDocument<>)] = "Nullable [ResourceName] Secondary Response Document",
31+
[typeof(ResourceCollectionResponseDocument<>)] = "[ResourceName] Collection Response Document",
32+
[typeof(ResourceIdentifierResponseDocument<>)] = "[ResourceName] Identifier Response Document",
33+
[typeof(NullableResourceIdentifierResponseDocument<>)] = "Nullable [ResourceName] Identifier Response Document",
34+
[typeof(ResourceIdentifierCollectionResponseDocument<>)] = "[ResourceName] Identifier Collection Response Document",
35+
[typeof(ToOneRelationshipInResponse<>)] = "To One [ResourceName] In Response",
36+
[typeof(NullableToOneRelationshipInResponse<>)] = "Nullable To One [ResourceName] In Response",
37+
[typeof(ToManyRelationshipInResponse<>)] = "To Many [ResourceName] In Response",
38+
[typeof(ResourceObjectInResponse<>)] = "[ResourceName] Data In Response",
39+
[typeof(AttributesInResponse<>)] = "[ResourceName] Attributes In Response",
40+
[typeof(RelationshipsInResponse<>)] = "[ResourceName] Relationships In Response",
41+
[typeof(ResourceIdentifierObject<>)] = "[ResourceName] Identifier"
4142
};
4243

43-
private readonly ResourceNameFormatter _formatter;
44+
private readonly JsonNamingPolicy? _namingPolicy;
4445
private readonly IResourceGraph _resourceGraph;
4546

46-
public JsonApiSchemaIdSelector(ResourceNameFormatter formatter, IResourceGraph resourceGraph)
47+
public JsonApiSchemaIdSelector(JsonNamingPolicy? namingPolicy, IResourceGraph resourceGraph)
4748
{
48-
ArgumentGuard.NotNull(formatter, nameof(formatter));
4949
ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
5050

51-
_formatter = formatter;
51+
_namingPolicy = namingPolicy;
5252
_resourceGraph = resourceGraph;
5353
}
5454

@@ -65,15 +65,24 @@ public string GetSchemaId(Type type)
6565

6666
if (type.IsConstructedGenericType && OpenTypeToSchemaTemplateMap.ContainsKey(type.GetGenericTypeDefinition()))
6767
{
68+
string pascalCaseSchemaIdTemplate = OpenTypeToSchemaTemplateMap[type.GetGenericTypeDefinition()];
6869
Type resourceClrType = type.GetGenericArguments().First();
69-
string resourceName = _formatter.FormatResourceName(resourceClrType).Singularize();
7070

71-
string template = OpenTypeToSchemaTemplateMap[type.GetGenericTypeDefinition()];
72-
return template.Replace("###", resourceName);
71+
// @formatter:wrap_chained_method_calls chop_always
72+
// @formatter:keep_existing_linebreaks true
73+
74+
string pascalCaseSchemaId = pascalCaseSchemaIdTemplate
75+
.Replace("[ResourceName]", resourceClrType.Name)
76+
.Replace(" ", "");
77+
78+
// @formatter:keep_existing_linebreaks restore
79+
// @formatter:wrap_chained_method_calls restore
80+
81+
return _namingPolicy != null ? _namingPolicy.ConvertName(pascalCaseSchemaId) : pascalCaseSchemaId;
7382
}
7483

7584
// Used for a fixed set of types, such as jsonapi-object, links-in-many-resource-document etc.
76-
return _formatter.FormatResourceName(type).Singularize();
85+
return _namingPolicy != null ? _namingPolicy.ConvertName(type.Name) : type.Name;
7786
}
7887
}
7988
}

src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,14 @@ private static void AddSwaggerGenerator(IServiceScope scope, IServiceCollection
5959
var resourceGraph = scope.ServiceProvider.GetRequiredService<IResourceGraph>();
6060
var jsonApiOptions = scope.ServiceProvider.GetRequiredService<IJsonApiOptions>();
6161
JsonNamingPolicy? namingPolicy = jsonApiOptions.SerializerOptions.PropertyNamingPolicy;
62-
ResourceNameFormatter resourceNameFormatter = new(namingPolicy);
6362

6463
AddSchemaGenerator(services);
6564

6665
services.AddSwaggerGen(swaggerGenOptions =>
6766
{
6867
swaggerGenOptions.SupportNonNullableReferenceTypes();
6968
SetOperationInfo(swaggerGenOptions, controllerResourceMapping, namingPolicy);
70-
SetSchemaIdSelector(swaggerGenOptions, resourceGraph, resourceNameFormatter);
69+
SetSchemaIdSelector(swaggerGenOptions, resourceGraph, namingPolicy);
7170
swaggerGenOptions.DocumentFilter<EndpointOrderingFilter>();
7271

7372
setupSwaggerGenAction?.Invoke(swaggerGenOptions);
@@ -105,9 +104,9 @@ private static IList<string> GetOperationTags(ApiDescription description, IContr
105104
};
106105
}
107106

108-
private static void SetSchemaIdSelector(SwaggerGenOptions swaggerGenOptions, IResourceGraph resourceGraph, ResourceNameFormatter resourceNameFormatter)
107+
private static void SetSchemaIdSelector(SwaggerGenOptions swaggerGenOptions, IResourceGraph resourceGraph, JsonNamingPolicy? namingPolicy)
109108
{
110-
JsonApiSchemaIdSelector jsonApiObjectSchemaSelector = new(resourceNameFormatter, resourceGraph);
109+
JsonApiSchemaIdSelector jsonApiObjectSchemaSelector = new(namingPolicy, resourceGraph);
111110

112111
swaggerGenOptions.CustomSchemaIds(type => jsonApiObjectSchemaSelector.GetSchemaId(type));
113112
}

src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public JsonApiSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IResourceG
4040
ArgumentGuard.NotNull(options, nameof(options));
4141

4242
_defaultSchemaGenerator = defaultSchemaGenerator;
43-
_nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(_schemaRepositoryAccessor);
43+
_nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(_schemaRepositoryAccessor, options.SerializerOptions.PropertyNamingPolicy);
4444
_resourceObjectSchemaGenerator = new ResourceObjectSchemaGenerator(defaultSchemaGenerator, resourceGraph, options, _schemaRepositoryAccessor);
4545
}
4646

Lines changed: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Text.Json;
34
using Microsoft.OpenApi.Models;
45

56
namespace JsonApiDotNetCore.OpenApi.SwaggerComponents
67
{
78
internal sealed class NullableReferenceSchemaGenerator
89
{
10+
private const string PascalCaseNullableSchemaReferenceId = "NullValue";
11+
private readonly string _nullableSchemaReferenceId;
12+
913
private static readonly NullableReferenceSchemaStrategy NullableReferenceStrategy =
1014
Enum.Parse<NullableReferenceSchemaStrategy>(NullableReferenceSchemaStrategy.Implicit.ToString());
1115

12-
private static OpenApiSchema? _referenceSchemaForNullValue;
16+
private static OpenApiSchema? _referenceSchemaForExplicitNullValue;
1317
private readonly ISchemaRepositoryAccessor _schemaRepositoryAccessor;
1418

15-
public NullableReferenceSchemaGenerator(ISchemaRepositoryAccessor schemaRepositoryAccessor)
19+
public NullableReferenceSchemaGenerator(ISchemaRepositoryAccessor schemaRepositoryAccessor, JsonNamingPolicy? namingPolicy)
1620
{
1721
ArgumentGuard.NotNull(schemaRepositoryAccessor, nameof(schemaRepositoryAccessor));
1822

1923
_schemaRepositoryAccessor = schemaRepositoryAccessor;
24+
_nullableSchemaReferenceId = namingPolicy != null ? namingPolicy.ConvertName(PascalCaseNullableSchemaReferenceId) : PascalCaseNullableSchemaReferenceId;
2025
}
2126

2227
public OpenApiSchema GenerateSchema(OpenApiSchema referenceSchema)
@@ -28,20 +33,13 @@ public OpenApiSchema GenerateSchema(OpenApiSchema referenceSchema)
2833
OneOf = new List<OpenApiSchema>
2934
{
3035
referenceSchema,
31-
GetNullableReferenceSchema()
36+
NullableReferenceStrategy == NullableReferenceSchemaStrategy.Explicit ? GetExplicitNullSchema() : GetImplicitNullSchema()
3237
}
3338
};
3439
}
3540

36-
private OpenApiSchema GetNullableReferenceSchema()
37-
{
38-
return NullableReferenceStrategy == NullableReferenceSchemaStrategy.Explicit
39-
? GetNullableReferenceSchemaUsingExplicitNullType()
40-
: GetNullableReferenceSchemaUsingImplicitNullType();
41-
}
42-
4341
// This approach is supported in OAS starting from v3.1. See https://github.com/OAI/OpenAPI-Specification/issues/1368#issuecomment-580103688
44-
private static OpenApiSchema GetNullableReferenceSchemaUsingExplicitNullType()
42+
private static OpenApiSchema GetExplicitNullSchema()
4543
{
4644
return new OpenApiSchema
4745
{
@@ -50,48 +48,58 @@ private static OpenApiSchema GetNullableReferenceSchemaUsingExplicitNullType()
5048
}
5149

5250
// This approach is supported starting from OAS v3.0. See https://github.com/OAI/OpenAPI-Specification/issues/1368#issuecomment-487314681
53-
private OpenApiSchema GetNullableReferenceSchemaUsingImplicitNullType()
51+
private OpenApiSchema GetImplicitNullSchema()
5452
{
55-
if (_referenceSchemaForNullValue != null)
53+
EnsureFullSchemaForNullValueExists();
54+
55+
return _referenceSchemaForExplicitNullValue ??= new OpenApiSchema
5656
{
57-
return _referenceSchemaForNullValue;
58-
}
57+
Reference = new OpenApiReference
58+
{
59+
Id = _nullableSchemaReferenceId,
60+
Type = ReferenceType.Schema
61+
}
62+
};
63+
}
5964

60-
var fullSchemaForNullValue = new OpenApiSchema
65+
private void EnsureFullSchemaForNullValueExists()
66+
{
67+
if (!_schemaRepositoryAccessor.Current.Schemas.ContainsKey(_nullableSchemaReferenceId))
6168
{
62-
Nullable = true,
63-
Not = new OpenApiSchema
69+
var fullSchemaForNullValue = new OpenApiSchema
6470
{
65-
AnyOf = new List<OpenApiSchema>
71+
Nullable = true,
72+
Not = new OpenApiSchema
6673
{
67-
new()
74+
AnyOf = new List<OpenApiSchema>
6875
{
69-
Type = "string"
76+
new()
77+
{
78+
Type = "string"
79+
},
80+
new()
81+
{
82+
Type = "number"
83+
},
84+
new()
85+
{
86+
Type = "boolean"
87+
},
88+
new()
89+
{
90+
Type = "object"
91+
},
92+
new()
93+
{
94+
Type = "array"
95+
}
7096
},
71-
new()
72-
{
73-
Type = "number"
74-
},
75-
new()
76-
{
77-
Type = "boolean"
78-
},
79-
new()
80-
{
81-
Type = "object"
82-
},
83-
new()
84-
{
85-
Type = "array"
86-
}
87-
},
88-
Items = new OpenApiSchema()
89-
}
90-
};
97+
Items = new OpenApiSchema()
98+
}
99+
};
91100

92-
_referenceSchemaForNullValue = _schemaRepositoryAccessor.Current.AddDefinition("null-value", fullSchemaForNullValue);
93-
94-
return _referenceSchemaForNullValue;
101+
_schemaRepositoryAccessor.Current.AddDefinition(_nullableSchemaReferenceId, fullSchemaForNullValue);
102+
}
95103
}
96104
}
97105
}

src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.ComponentModel.DataAnnotations;
44
using System.Linq;
55
using System.Reflection;
6+
using System.Text.Json;
67
using JsonApiDotNetCore.OpenApi.JsonApiObjects;
78
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;
89
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
@@ -14,8 +15,6 @@ namespace JsonApiDotNetCore.OpenApi.SwaggerComponents
1415
{
1516
internal sealed class ResourceFieldObjectSchemaBuilder
1617
{
17-
private static readonly SchemaRepository ResourceSchemaRepository = new();
18-
1918
private static readonly Type[] RelationshipInResponseOpenTypes =
2019
{
2120
typeof(ToOneRelationshipInResponse<>),
@@ -27,11 +26,12 @@ internal sealed class ResourceFieldObjectSchemaBuilder
2726
private readonly ISchemaRepositoryAccessor _schemaRepositoryAccessor;
2827
private readonly SchemaGenerator _defaultSchemaGenerator;
2928
private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator;
29+
private readonly SchemaRepository _resourceSchemaRepository = new();
3030
private readonly NullableReferenceSchemaGenerator _nullableReferenceSchemaGenerator;
3131
private readonly IDictionary<string, OpenApiSchema> _schemasForResourceFields;
3232

3333
public ResourceFieldObjectSchemaBuilder(ResourceTypeInfo resourceTypeInfo, ISchemaRepositoryAccessor schemaRepositoryAccessor,
34-
SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator)
34+
SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, JsonNamingPolicy? namingPolicy)
3535
{
3636
ArgumentGuard.NotNull(resourceTypeInfo, nameof(resourceTypeInfo));
3737
ArgumentGuard.NotNull(schemaRepositoryAccessor, nameof(schemaRepositoryAccessor));
@@ -43,18 +43,18 @@ public ResourceFieldObjectSchemaBuilder(ResourceTypeInfo resourceTypeInfo, ISche
4343
_defaultSchemaGenerator = defaultSchemaGenerator;
4444
_resourceTypeSchemaGenerator = resourceTypeSchemaGenerator;
4545

46-
_nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(schemaRepositoryAccessor);
46+
_nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(schemaRepositoryAccessor, namingPolicy);
4747
_schemasForResourceFields = GetFieldSchemas();
4848
}
4949

5050
private IDictionary<string, OpenApiSchema> GetFieldSchemas()
5151
{
52-
if (!ResourceSchemaRepository.TryLookupByType(_resourceTypeInfo.ResourceType.ClrType, out OpenApiSchema referenceSchemaForResource))
52+
if (!_resourceSchemaRepository.TryLookupByType(_resourceTypeInfo.ResourceType.ClrType, out OpenApiSchema referenceSchemaForResource))
5353
{
54-
referenceSchemaForResource = _defaultSchemaGenerator.GenerateSchema(_resourceTypeInfo.ResourceType.ClrType, ResourceSchemaRepository);
54+
referenceSchemaForResource = _defaultSchemaGenerator.GenerateSchema(_resourceTypeInfo.ResourceType.ClrType, _resourceSchemaRepository);
5555
}
5656

57-
OpenApiSchema fullSchemaForResource = ResourceSchemaRepository.Schemas[referenceSchemaForResource.Reference.Id];
57+
OpenApiSchema fullSchemaForResource = _resourceSchemaRepository.Schemas[referenceSchemaForResource.Reference.Id];
5858
return fullSchemaForResource.Properties;
5959
}
6060

@@ -99,7 +99,7 @@ private void AddAttributeSchemaToResourceObject(AttrAttribute attribute, OpenApi
9999

100100
private void ExposeSchema(OpenApiReference openApiReference, Type typeRepresentedBySchema)
101101
{
102-
OpenApiSchema fullSchema = ResourceSchemaRepository.Schemas[openApiReference.Id];
102+
OpenApiSchema fullSchema = _resourceSchemaRepository.Schemas[openApiReference.Id];
103103
_schemaRepositoryAccessor.Current.AddDefinition(openApiReference.Id, fullSchema);
104104
_schemaRepositoryAccessor.Current.RegisterType(typeRepresentedBySchema, openApiReference.Id);
105105
}

0 commit comments

Comments
 (0)