Skip to content

Commit 2e847ab

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

20 files changed

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

40-
private readonly ResourceNameFormatter _formatter;
41+
private readonly JsonNamingPolicy? _namingPolicy;
4142
private readonly IResourceGraph _resourceGraph;
4243

43-
public JsonApiSchemaIdSelector(ResourceNameFormatter formatter, IResourceGraph resourceGraph)
44+
public JsonApiSchemaIdSelector(JsonNamingPolicy? namingPolicy, IResourceGraph resourceGraph)
4445
{
45-
ArgumentGuard.NotNull(formatter, nameof(formatter));
4646
ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph));
4747

48-
_formatter = formatter;
48+
_namingPolicy = namingPolicy;
4949
_resourceGraph = resourceGraph;
5050
}
5151

@@ -62,14 +62,21 @@ public string GetSchemaId(Type type)
6262

6363
if (type.IsConstructedGenericType && OpenTypeToSchemaTemplateMap.ContainsKey(type.GetGenericTypeDefinition()))
6464
{
65+
Type openType = type.GetGenericTypeDefinition();
6566
Type resourceClrType = type.GetGenericArguments().First();
66-
string resourceName = _formatter.FormatResourceName(resourceClrType).Singularize();
67+
resourceType = _resourceGraph.FindResourceType(resourceClrType);
6768

68-
string template = OpenTypeToSchemaTemplateMap[type.GetGenericTypeDefinition()];
69-
return template.Replace("###", resourceName);
69+
if (resourceType == null)
70+
{
71+
throw new UnreachableCodeException();
72+
}
73+
74+
string pascalCaseSchemaId = OpenTypeToSchemaTemplateMap[openType].Replace("[ResourceName]", resourceType.PublicName.Singularize()).Pascalize();
75+
76+
return _namingPolicy != null ? _namingPolicy.ConvertName(pascalCaseSchemaId) : pascalCaseSchemaId;
7077
}
7178

72-
// Used for a fixed set of types, such as jsonapi-object, links-in-many-resource-document etc.
73-
return _formatter.FormatResourceName(type).Singularize();
79+
// Used for a fixed set of types, such as JsonApiObject, LinksInResourceCollectionDocument etc.
80+
return _namingPolicy != null ? _namingPolicy.ConvertName(type.Name) : type.Name;
7481
}
7582
}

src/JsonApiDotNetCore.OpenApi/ServiceCollectionExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ private static void AddCustomApiExplorer(IServiceCollection services, IMvcCoreBu
4141
var apiDescriptionProviders = provider.GetRequiredService<IEnumerable<IApiDescriptionProvider>>();
4242

4343
JsonApiActionDescriptorCollectionProvider descriptorCollectionProviderWrapper = new(controllerResourceMapping, actionDescriptorCollectionProvider);
44+
4445
return new ApiDescriptionGroupCollectionProvider(descriptorCollectionProviderWrapper, apiDescriptionProviders);
4546
});
4647

@@ -55,15 +56,14 @@ private static void AddSwaggerGenerator(IServiceScope scope, IServiceCollection
5556
var resourceGraph = scope.ServiceProvider.GetRequiredService<IResourceGraph>();
5657
var jsonApiOptions = scope.ServiceProvider.GetRequiredService<IJsonApiOptions>();
5758
JsonNamingPolicy? namingPolicy = jsonApiOptions.SerializerOptions.PropertyNamingPolicy;
58-
ResourceNameFormatter resourceNameFormatter = new(namingPolicy);
5959

6060
AddSchemaGenerator(services);
6161

6262
services.AddSwaggerGen(swaggerGenOptions =>
6363
{
6464
swaggerGenOptions.SupportNonNullableReferenceTypes();
6565
SetOperationInfo(swaggerGenOptions, controllerResourceMapping, namingPolicy);
66-
SetSchemaIdSelector(swaggerGenOptions, resourceGraph, resourceNameFormatter);
66+
SetSchemaIdSelector(swaggerGenOptions, resourceGraph, namingPolicy);
6767
swaggerGenOptions.DocumentFilter<EndpointOrderingFilter>();
6868

6969
setupSwaggerGenAction?.Invoke(swaggerGenOptions);
@@ -101,9 +101,9 @@ private static IList<string> GetOperationTags(ApiDescription description, IContr
101101
};
102102
}
103103

104-
private static void SetSchemaIdSelector(SwaggerGenOptions swaggerGenOptions, IResourceGraph resourceGraph, ResourceNameFormatter resourceNameFormatter)
104+
private static void SetSchemaIdSelector(SwaggerGenOptions swaggerGenOptions, IResourceGraph resourceGraph, JsonNamingPolicy? namingPolicy)
105105
{
106-
JsonApiSchemaIdSelector jsonApiObjectSchemaSelector = new(resourceNameFormatter, resourceGraph);
106+
JsonApiSchemaIdSelector jsonApiObjectSchemaSelector = new(namingPolicy, resourceGraph);
107107

108108
swaggerGenOptions.CustomSchemaIds(type => jsonApiObjectSchemaSelector.GetSchemaId(type));
109109
}

src/JsonApiDotNetCore.OpenApi/SwaggerComponents/JsonApiSchemaGenerator.cs

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

4040
_defaultSchemaGenerator = defaultSchemaGenerator;
41-
_nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(_schemaRepositoryAccessor);
41+
_nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(_schemaRepositoryAccessor, options.SerializerOptions.PropertyNamingPolicy);
4242
_resourceObjectSchemaGenerator = new ResourceObjectSchemaGenerator(defaultSchemaGenerator, resourceGraph, options, _schemaRepositoryAccessor);
4343
}
4444

Lines changed: 55 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
1+
using System.Text.Json;
12
using Microsoft.OpenApi.Models;
23

34
namespace JsonApiDotNetCore.OpenApi.SwaggerComponents;
45

56
internal sealed class NullableReferenceSchemaGenerator
67
{
7-
private static readonly NullableReferenceSchemaStrategy NullableReferenceStrategy =
8+
private const string PascalCaseNullableSchemaReferenceId = "NullValue";
9+
10+
private readonly NullableReferenceSchemaStrategy _nullableReferenceStrategy =
811
Enum.Parse<NullableReferenceSchemaStrategy>(NullableReferenceSchemaStrategy.Implicit.ToString());
912

10-
private static OpenApiSchema? _referenceSchemaForNullValue;
13+
private readonly string _nullableSchemaReferenceId;
1114
private readonly ISchemaRepositoryAccessor _schemaRepositoryAccessor;
1215

13-
public NullableReferenceSchemaGenerator(ISchemaRepositoryAccessor schemaRepositoryAccessor)
16+
private OpenApiSchema? _referenceSchemaForExplicitNullValue;
17+
18+
public NullableReferenceSchemaGenerator(ISchemaRepositoryAccessor schemaRepositoryAccessor, JsonNamingPolicy? namingPolicy)
1419
{
1520
ArgumentGuard.NotNull(schemaRepositoryAccessor, nameof(schemaRepositoryAccessor));
1621

1722
_schemaRepositoryAccessor = schemaRepositoryAccessor;
23+
24+
_nullableSchemaReferenceId = namingPolicy != null ? namingPolicy.ConvertName(PascalCaseNullableSchemaReferenceId) : PascalCaseNullableSchemaReferenceId;
1825
}
1926

2027
public OpenApiSchema GenerateSchema(OpenApiSchema referenceSchema)
@@ -26,20 +33,13 @@ public OpenApiSchema GenerateSchema(OpenApiSchema referenceSchema)
2633
OneOf = new List<OpenApiSchema>
2734
{
2835
referenceSchema,
29-
GetNullableReferenceSchema()
36+
_nullableReferenceStrategy == NullableReferenceSchemaStrategy.Explicit ? GetExplicitNullSchema() : GetImplicitNullSchema()
3037
}
3138
};
3239
}
3340

34-
private OpenApiSchema GetNullableReferenceSchema()
35-
{
36-
return NullableReferenceStrategy == NullableReferenceSchemaStrategy.Explicit
37-
? GetNullableReferenceSchemaUsingExplicitNullType()
38-
: GetNullableReferenceSchemaUsingImplicitNullType();
39-
}
40-
4141
// This approach is supported in OAS starting from v3.1. See https://github.com/OAI/OpenAPI-Specification/issues/1368#issuecomment-580103688
42-
private static OpenApiSchema GetNullableReferenceSchemaUsingExplicitNullType()
42+
private static OpenApiSchema GetExplicitNullSchema()
4343
{
4444
return new OpenApiSchema
4545
{
@@ -48,47 +48,57 @@ private static OpenApiSchema GetNullableReferenceSchemaUsingExplicitNullType()
4848
}
4949

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

58-
var fullSchemaForNullValue = new OpenApiSchema
65+
private void EnsureFullSchemaForNullValueExists()
66+
{
67+
if (!_schemaRepositoryAccessor.Current.Schemas.ContainsKey(_nullableSchemaReferenceId))
5968
{
60-
Nullable = true,
61-
Not = new OpenApiSchema
69+
var fullSchemaForNullValue = new OpenApiSchema
6270
{
63-
AnyOf = new List<OpenApiSchema>
71+
Nullable = true,
72+
Not = new OpenApiSchema
6473
{
65-
new()
66-
{
67-
Type = "string"
68-
},
69-
new()
70-
{
71-
Type = "number"
72-
},
73-
new()
74-
{
75-
Type = "boolean"
76-
},
77-
new()
74+
AnyOf = new List<OpenApiSchema>
7875
{
79-
Type = "object"
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+
}
8096
},
81-
new()
82-
{
83-
Type = "array"
84-
}
85-
},
86-
Items = new OpenApiSchema()
87-
}
88-
};
89-
90-
_referenceSchemaForNullValue = _schemaRepositoryAccessor.Current.AddDefinition("null-value", fullSchemaForNullValue);
97+
Items = new OpenApiSchema()
98+
}
99+
};
91100

92-
return _referenceSchemaForNullValue;
101+
_schemaRepositoryAccessor.Current.AddDefinition(_nullableSchemaReferenceId, fullSchemaForNullValue);
102+
}
93103
}
94104
}

src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceFieldObjectSchemaBuilder.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.ComponentModel.DataAnnotations;
22
using System.Reflection;
3+
using System.Text.Json;
34
using JsonApiDotNetCore.OpenApi.JsonApiObjects;
45
using JsonApiDotNetCore.OpenApi.JsonApiObjects.Relationships;
56
using JsonApiDotNetCore.OpenApi.JsonApiObjects.ResourceObjects;
@@ -11,8 +12,6 @@ namespace JsonApiDotNetCore.OpenApi.SwaggerComponents;
1112

1213
internal sealed class ResourceFieldObjectSchemaBuilder
1314
{
14-
private static readonly SchemaRepository ResourceSchemaRepository = new();
15-
1615
private static readonly Type[] RelationshipInResponseOpenTypes =
1716
{
1817
typeof(ToOneRelationshipInResponse<>),
@@ -24,11 +23,12 @@ internal sealed class ResourceFieldObjectSchemaBuilder
2423
private readonly ISchemaRepositoryAccessor _schemaRepositoryAccessor;
2524
private readonly SchemaGenerator _defaultSchemaGenerator;
2625
private readonly ResourceTypeSchemaGenerator _resourceTypeSchemaGenerator;
26+
private readonly SchemaRepository _resourceSchemaRepository = new();
2727
private readonly NullableReferenceSchemaGenerator _nullableReferenceSchemaGenerator;
2828
private readonly IDictionary<string, OpenApiSchema> _schemasForResourceFields;
2929

3030
public ResourceFieldObjectSchemaBuilder(ResourceTypeInfo resourceTypeInfo, ISchemaRepositoryAccessor schemaRepositoryAccessor,
31-
SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator)
31+
SchemaGenerator defaultSchemaGenerator, ResourceTypeSchemaGenerator resourceTypeSchemaGenerator, JsonNamingPolicy? namingPolicy)
3232
{
3333
ArgumentGuard.NotNull(resourceTypeInfo, nameof(resourceTypeInfo));
3434
ArgumentGuard.NotNull(schemaRepositoryAccessor, nameof(schemaRepositoryAccessor));
@@ -40,18 +40,18 @@ public ResourceFieldObjectSchemaBuilder(ResourceTypeInfo resourceTypeInfo, ISche
4040
_defaultSchemaGenerator = defaultSchemaGenerator;
4141
_resourceTypeSchemaGenerator = resourceTypeSchemaGenerator;
4242

43-
_nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(schemaRepositoryAccessor);
43+
_nullableReferenceSchemaGenerator = new NullableReferenceSchemaGenerator(schemaRepositoryAccessor, namingPolicy);
4444
_schemasForResourceFields = GetFieldSchemas();
4545
}
4646

4747
private IDictionary<string, OpenApiSchema> GetFieldSchemas()
4848
{
49-
if (!ResourceSchemaRepository.TryLookupByType(_resourceTypeInfo.ResourceType.ClrType, out OpenApiSchema referenceSchemaForResource))
49+
if (!_resourceSchemaRepository.TryLookupByType(_resourceTypeInfo.ResourceType.ClrType, out OpenApiSchema referenceSchemaForResource))
5050
{
51-
referenceSchemaForResource = _defaultSchemaGenerator.GenerateSchema(_resourceTypeInfo.ResourceType.ClrType, ResourceSchemaRepository);
51+
referenceSchemaForResource = _defaultSchemaGenerator.GenerateSchema(_resourceTypeInfo.ResourceType.ClrType, _resourceSchemaRepository);
5252
}
5353

54-
OpenApiSchema fullSchemaForResource = ResourceSchemaRepository.Schemas[referenceSchemaForResource.Reference.Id];
54+
OpenApiSchema fullSchemaForResource = _resourceSchemaRepository.Schemas[referenceSchemaForResource.Reference.Id];
5555
return fullSchemaForResource.Properties;
5656
}
5757

@@ -96,7 +96,7 @@ private void AddAttributeSchemaToResourceObject(AttrAttribute attribute, OpenApi
9696

9797
private void ExposeSchema(OpenApiReference openApiReference, Type typeRepresentedBySchema)
9898
{
99-
OpenApiSchema fullSchema = ResourceSchemaRepository.Schemas[openApiReference.Id];
99+
OpenApiSchema fullSchema = _resourceSchemaRepository.Schemas[openApiReference.Id];
100100
_schemaRepositoryAccessor.Current.AddDefinition(openApiReference.Id, fullSchema);
101101
_schemaRepositoryAccessor.Current.RegisterType(typeRepresentedBySchema, openApiReference.Id);
102102
}

src/JsonApiDotNetCore.OpenApi/SwaggerComponents/ResourceObjectSchemaGenerator.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ public ResourceObjectSchemaGenerator(SchemaGenerator defaultSchemaGenerator, IRe
2626
_resourceGraph = resourceGraph;
2727
_schemaRepositoryAccessor = schemaRepositoryAccessor;
2828

29-
_resourceTypeSchemaGenerator = new ResourceTypeSchemaGenerator(schemaRepositoryAccessor, resourceGraph);
29+
_resourceTypeSchemaGenerator = new ResourceTypeSchemaGenerator(schemaRepositoryAccessor, resourceGraph, options.SerializerOptions.PropertyNamingPolicy);
30+
3031
_allowClientGeneratedIds = options.AllowClientGeneratedIds;
3132

3233
_resourceFieldObjectSchemaBuilderFactory = resourceTypeInfo => new ResourceFieldObjectSchemaBuilder(resourceTypeInfo, schemaRepositoryAccessor,
33-
defaultSchemaGenerator, _resourceTypeSchemaGenerator);
34+
defaultSchemaGenerator, _resourceTypeSchemaGenerator, options.SerializerOptions.PropertyNamingPolicy);
3435
}
3536

3637
public OpenApiSchema GenerateSchema(Type resourceObjectType)

0 commit comments

Comments
 (0)