Skip to content

Commit 196ac0d

Browse files
committed
More comprehensive SchemaReferenceId generator
1 parent 35cb74d commit 196ac0d

File tree

7 files changed

+92
-20
lines changed

7 files changed

+92
-20
lines changed

API/Controller/Devices/DevicesController.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ public sealed partial class DevicesController
2525
/// <response code="200">All devices for the current user</response>
2626
[HttpGet]
2727
[MapToApiVersion("1")]
28-
[ProducesResponseType<LegacyDataResponse<Models.Response.ResponseDevice[]>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
28+
[ProducesResponseType<LegacyDataResponse<Models.Response.DeviceResponse[]>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
2929
public IActionResult ListDevices()
3030
{
3131
var devices = _db.Devices
3232
.Where(x => x.OwnerId == CurrentUser.Id)
33-
.Select(x => new Models.Response.ResponseDevice
33+
.Select(x => new Models.Response.DeviceResponse
3434
{
3535
Id = x.Id,
3636
Name = x.Name,
@@ -47,7 +47,7 @@ public IActionResult ListDevices()
4747
/// <param name="deviceId"></param>
4848
/// <response code="200">The device</response>
4949
[HttpGet("{deviceId}")]
50-
[ProducesResponseType<LegacyDataResponse<Models.Response.ResponseDeviceWithToken>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
50+
[ProducesResponseType<LegacyDataResponse<Models.Response.DeviceWithTokenResponse>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
5151
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound
5252
[MapToApiVersion("1")]
5353
public async Task<IActionResult> GetDeviceById([FromRoute] Guid deviceId)
@@ -56,7 +56,7 @@ public async Task<IActionResult> GetDeviceById([FromRoute] Guid deviceId)
5656

5757

5858
var device = await _db.Devices.Where(x => x.OwnerId == CurrentUser.Id && x.Id == deviceId)
59-
.Select(x => new Models.Response.ResponseDeviceWithToken
59+
.Select(x => new Models.Response.DeviceWithTokenResponse
6060
{
6161
Id = x.Id,
6262
Name = x.Name,

API/Controller/Shockers/ListShockers.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ public sealed partial class ShockerController
1515
/// <response code="200">The shockers were successfully retrieved.</response>
1616
[HttpGet("own")]
1717
[MapToApiVersion("1")]
18-
[ProducesResponseType<LegacyDataResponse<ResponseDeviceWithShockers[]>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
18+
[ProducesResponseType<LegacyDataResponse<DeviceWithShockersResponse[]>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
1919
public IActionResult ListShockers()
2020
{
2121
var shockers = _db.Devices
2222
.Where(x => x.OwnerId == CurrentUser.Id)
23-
.OrderBy(x => x.CreatedAt).Select(x => new ResponseDeviceWithShockers
23+
.OrderBy(x => x.CreatedAt).Select(x => new DeviceWithShockersResponse
2424
{
2525
Id = x.Id,
2626
Name = x.Name,
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace OpenShock.API.Models.Response;
22

3-
public class ResponseDevice
3+
public class DeviceResponse
44
{
55
public required Guid Id { get; init; }
66
public required string Name { get; init; }
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace OpenShock.API.Models.Response;
22

3-
public sealed class ResponseDeviceWithShockers : ResponseDevice
3+
public sealed class DeviceWithShockersResponse : DeviceResponse
44
{
55
public required ShockerResponse[] Shockers { get; init; }
66
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace OpenShock.API.Models.Response;
22

3-
public sealed class ResponseDeviceWithToken : ResponseDevice
3+
public sealed class DeviceWithTokenResponse : DeviceResponse
44
{
55
public required string? Token { get; init; }
66
}

Common/OpenAPI/OpenApiExtensions.cs

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,77 @@
1-
using Asp.Versioning.ApiExplorer;
1+
using System.Text;
2+
using Asp.Versioning.ApiExplorer;
23
using Microsoft.OpenApi;
4+
using OpenShock.Common.Models;
5+
using OpenShock.Common.Utils;
36

47
namespace OpenShock.Common.OpenAPI;
58

69
public static class OpenApiExtensions
710
{
11+
private static string RemoveResponseSuffix(string name)
12+
{
13+
string? value;
14+
if (StringUtils.TryRemoveSuffix(name, "LegacyResponse", out value)) return value;
15+
if (StringUtils.TryRemoveSuffix(name, "Response", out value)) return value;
16+
return name;
17+
}
18+
19+
private static string GetCleanName(Type type)
20+
{
21+
if (type.IsEnum || Type.GetTypeCode(type) is not TypeCode.Object)
22+
{
23+
return type.Name;
24+
}
25+
26+
// Handle arrays
27+
if (type.IsArray)
28+
{
29+
return RemoveResponseSuffix(GetCleanName(type.GetElementType()!)) + "Array";
30+
}
31+
32+
var isGeneric = type.IsGenericType;
33+
if (!isGeneric)
34+
{
35+
if (type == typeof(DateOnly)) return "Date";
36+
if (type == typeof(DateTimeOffset)) return "DateTimeWithoutTimeZone";
37+
38+
return type.Name;
39+
}
40+
41+
var genericTypeDef = type.GetGenericTypeDefinition();
42+
if (genericTypeDef == typeof(List<>) ||
43+
genericTypeDef == typeof(IList<>) ||
44+
genericTypeDef == typeof(HashSet<>) ||
45+
genericTypeDef == typeof(IEnumerable<>) ||
46+
genericTypeDef == typeof(IAsyncEnumerable<>))
47+
{
48+
return RemoveResponseSuffix(GetCleanName(type.GetGenericArguments()[0])) + "Array";
49+
}
50+
51+
if (genericTypeDef == typeof(Dictionary<,>))
52+
{
53+
return $"DictionaryOf{GetCleanName(type.GetGenericArguments()[0])}And{GetCleanName(type.GetGenericArguments()[1])}";
54+
}
55+
56+
57+
if (genericTypeDef == typeof(LegacyDataResponse<>))
58+
{
59+
return RemoveResponseSuffix(GetCleanName(type.GetGenericArguments()[0])) + "LegacyResponse";
60+
}
61+
62+
// Handle generic types
63+
var genericArgs = string.Join("And", type.GetGenericArguments().Select(GetCleanName));
64+
65+
var name = type.Name.AsSpan();
66+
var backtickIndex = name.IndexOf('`');
67+
if (backtickIndex > 0)
68+
{
69+
name = name[..backtickIndex];
70+
}
71+
72+
return $"{name}Of{genericArgs}";
73+
}
74+
875
public static IServiceCollection AddOpenApiExt<TProgram>(this WebApplicationBuilder builder) where TProgram : class
976
{
1077
var assembly = typeof(TProgram).Assembly;
@@ -30,15 +97,7 @@ public static IServiceCollection AddOpenApiExt<TProgram>(this WebApplicationBuil
3097
options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_1;
3198
options.AddDocumentTransformer(DocumentDefaults.GetDocumentTransformer(
3299
version: description.ApiVersion.ToString()));
33-
options.CreateSchemaReferenceId = (type) =>
34-
{
35-
var defaultName = type.Type.Name;
36-
var cleanName = defaultName
37-
.Replace("[]", "Array")
38-
.Replace("`1", "Of")
39-
.Replace("`2", "OfTwo");
40-
return cleanName;
41-
};
100+
options.CreateSchemaReferenceId = type => GetCleanName(type.Type);
42101
options.AddOperationTransformer((operation, context, cancellationToken) =>
43102
{
44103
var actionDescriptor = context.Description.ActionDescriptor;

Common/Utils/StringUtils.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
1-
namespace OpenShock.Common.Utils;
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace OpenShock.Common.Utils;
24

35
public static class StringUtils
46
{
57
public static string Truncate(this string input, int maxLength)
68
{
79
return input.Length <= maxLength ? input : input[..maxLength];
810
}
11+
public static bool TryRemoveSuffix(string str, string suffix, [NotNullWhen(true)] out string? value)
12+
{
13+
if (!str.EndsWith(suffix))
14+
{
15+
value = null;
16+
return false;
17+
}
18+
19+
value = str[..^suffix.Length];
20+
return true;
21+
}
922
}

0 commit comments

Comments
 (0)