-
-
Notifications
You must be signed in to change notification settings - Fork 162
Expand file tree
/
Copy pathOpenApiOperationIdSelector.cs
More file actions
109 lines (89 loc) · 5.09 KB
/
OpenApiOperationIdSelector.cs
File metadata and controls
109 lines (89 loc) · 5.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
using System.Reflection;
using System.Text.Json;
using Humanizer;
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Middleware;
using JsonApiDotNetCore.OpenApi.Swashbuckle.JsonApiObjects.Documents;
using JsonApiDotNetCore.OpenApi.Swashbuckle.JsonApiObjects.Relationships;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
namespace JsonApiDotNetCore.OpenApi.Swashbuckle;
internal sealed class OpenApiOperationIdSelector
{
private const string ResourceIdTemplate = "[Method] [PrimaryResourceName]";
private const string ResourceCollectionIdTemplate = $"{ResourceIdTemplate} Collection";
private const string SecondaryResourceIdTemplate = $"{ResourceIdTemplate} [RelationshipName]";
private const string RelationshipIdTemplate = $"{SecondaryResourceIdTemplate} Relationship";
private const string AtomicOperationsIdTemplate = "[Method] Operations";
private static readonly Dictionary<Type, string> SchemaOpenTypeToOpenApiOperationIdTemplateMap = new()
{
[typeof(CollectionResponseDocument<>)] = ResourceCollectionIdTemplate,
[typeof(PrimaryResponseDocument<>)] = ResourceIdTemplate,
[typeof(CreateRequestDocument<>)] = ResourceIdTemplate,
[typeof(UpdateRequestDocument<>)] = ResourceIdTemplate,
[typeof(void)] = ResourceIdTemplate,
[typeof(SecondaryResponseDocument<>)] = SecondaryResourceIdTemplate,
[typeof(NullableSecondaryResponseDocument<>)] = SecondaryResourceIdTemplate,
[typeof(IdentifierCollectionResponseDocument<>)] = RelationshipIdTemplate,
[typeof(IdentifierResponseDocument<>)] = RelationshipIdTemplate,
[typeof(NullableIdentifierResponseDocument<>)] = RelationshipIdTemplate,
[typeof(ToOneInRequest<>)] = RelationshipIdTemplate,
[typeof(NullableToOneInRequest<>)] = RelationshipIdTemplate,
[typeof(ToManyInRequest<>)] = RelationshipIdTemplate,
[typeof(OperationsRequestDocument)] = AtomicOperationsIdTemplate
};
private readonly IControllerResourceMapping _controllerResourceMapping;
private readonly IJsonApiOptions _options;
public OpenApiOperationIdSelector(IControllerResourceMapping controllerResourceMapping, IJsonApiOptions options)
{
ArgumentNullException.ThrowIfNull(controllerResourceMapping);
ArgumentNullException.ThrowIfNull(options);
_controllerResourceMapping = controllerResourceMapping;
_options = options;
}
public string GetOpenApiOperationId(ApiDescription endpoint)
{
ArgumentNullException.ThrowIfNull(endpoint);
MethodInfo actionMethod = endpoint.ActionDescriptor.GetActionMethod();
ResourceType? primaryResourceType = _controllerResourceMapping.GetResourceTypeForController(actionMethod.ReflectedType);
string template = GetTemplate(endpoint);
return ApplyTemplate(template, primaryResourceType, endpoint);
}
private static string GetTemplate(ApiDescription endpoint)
{
Type bodyType = GetBodyType(endpoint);
ConsistencyGuard.ThrowIf(!SchemaOpenTypeToOpenApiOperationIdTemplateMap.TryGetValue(bodyType, out string? template));
return template;
}
private static Type GetBodyType(ApiDescription endpoint)
{
var producesResponseTypeAttribute = endpoint.ActionDescriptor.GetFilterMetadata<ProducesResponseTypeAttribute>();
ConsistencyGuard.ThrowIf(producesResponseTypeAttribute == null);
ControllerParameterDescriptor? requestBodyDescriptor = endpoint.ActionDescriptor.GetBodyParameterDescriptor();
Type bodyType = (requestBodyDescriptor?.ParameterType ?? producesResponseTypeAttribute.Type).ConstructedToOpenType();
if (bodyType == typeof(CollectionResponseDocument<>) && endpoint.ParameterDescriptions.Count > 0)
{
bodyType = typeof(SecondaryResponseDocument<>);
}
return bodyType;
}
private string ApplyTemplate(string openApiOperationIdTemplate, ResourceType? resourceType, ApiDescription endpoint)
{
ConsistencyGuard.ThrowIf(endpoint.RelativePath == null);
ConsistencyGuard.ThrowIf(endpoint.HttpMethod == null);
string method = endpoint.HttpMethod.ToLowerInvariant();
string relationshipName = openApiOperationIdTemplate.Contains("[RelationshipName]") ? endpoint.RelativePath.Split('/').Last() : string.Empty;
// @formatter:wrap_chained_method_calls chop_always
// @formatter:wrap_before_first_method_call true
string pascalCaseOpenApiOperationId = openApiOperationIdTemplate
.Replace("[Method]", method)
.Replace("[PrimaryResourceName]", resourceType?.PublicName.Singularize())
.Replace("[RelationshipName]", relationshipName)
.Pascalize();
// @formatter:wrap_before_first_method_call true restore
// @formatter:wrap_chained_method_calls restore
JsonNamingPolicy? namingPolicy = _options.SerializerOptions.PropertyNamingPolicy;
return namingPolicy != null ? namingPolicy.ConvertName(pascalCaseOpenApiOperationId) : pascalCaseOpenApiOperationId;
}
}