From 86c0ab02b5b2099b461a09e7f4f4ce58941205a7 Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Mon, 22 Apr 2024 19:55:00 +0200 Subject: [PATCH] [backend/api] Switch to a shared JsonSerializerOptions object instead of explicitly specifying json property names via attributes --- .../Renderers/UserProfileRenderer.cs | 2 +- .../Core/Extensions/ServiceExtensions.cs | 17 ++++- Iceshrimp.Frontend/Core/Services/ApiClient.cs | 11 ++-- .../Configuration/JsonSerialization.cs | 13 ++++ Iceshrimp.Shared/Iceshrimp.Shared.csproj | 4 ++ Iceshrimp.Shared/Schemas/AuthRequest.cs | 12 ++-- Iceshrimp.Shared/Schemas/AuthResponse.cs | 20 ++---- Iceshrimp.Shared/Schemas/DriveFileResponse.cs | 16 ++--- Iceshrimp.Shared/Schemas/ErrorResponse.cs | 10 +-- Iceshrimp.Shared/Schemas/InviteResponse.cs | 4 +- Iceshrimp.Shared/Schemas/NoteCreateRequest.cs | 14 ++-- Iceshrimp.Shared/Schemas/NoteResponse.cs | 66 +++++++++---------- .../Schemas/NotificationResponse.cs | 14 ++-- .../Schemas/UpdateDriveFileRequest.cs | 8 +-- .../Schemas/UserProfileResponse.cs | 22 +++---- Iceshrimp.Shared/Schemas/UserResponse.cs | 16 ++--- Iceshrimp.Shared/Schemas/ValueResponse.cs | 4 +- 17 files changed, 127 insertions(+), 126 deletions(-) create mode 100644 Iceshrimp.Shared/Configuration/JsonSerialization.cs diff --git a/Iceshrimp.Backend/Controllers/Renderers/UserProfileRenderer.cs b/Iceshrimp.Backend/Controllers/Renderers/UserProfileRenderer.cs index 9b75c85d..b359a85c 100644 --- a/Iceshrimp.Backend/Controllers/Renderers/UserProfileRenderer.cs +++ b/Iceshrimp.Backend/Controllers/Renderers/UserProfileRenderer.cs @@ -32,7 +32,7 @@ public class UserProfileRenderer(DatabaseContext db) { Name = p.Name, Value = p.Value, - IsVerified = p.IsVerified + Verified = p.IsVerified }); return new UserProfileResponse diff --git a/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs b/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs index 038edccb..d3235485 100644 --- a/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs @@ -10,6 +10,7 @@ using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion; using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Services; using Iceshrimp.Backend.Hubs.Authentication; +using Iceshrimp.Shared.Configuration; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; @@ -105,6 +106,20 @@ public static class ServiceExtensions .ConfigureWithValidation(configuration, "Storage") .ConfigureWithValidation(configuration, "Storage:Local") .ConfigureWithValidation(configuration, "Storage:ObjectStorage"); + + services.Configure(options => + { + options.SerializerOptions.PropertyNamingPolicy = JsonSerialization.Options.PropertyNamingPolicy; + foreach (var converter in JsonSerialization.Options.Converters) + options.SerializerOptions.Converters.Add(converter); + }); + + services.Configure(options => + { + options.JsonSerializerOptions.PropertyNamingPolicy = JsonSerialization.Options.PropertyNamingPolicy; + foreach (var converter in JsonSerialization.Options.Converters) + options.JsonSerializerOptions.Converters.Add(converter); + }); } private static IServiceCollection ConfigureWithValidation( @@ -260,7 +275,7 @@ public static class ServiceExtensions services.AddAuthentication(options => { options.AddScheme("HubAuthenticationScheme", null); - + // Add a stub authentication handler to bypass strange ASP.NET Core >=7.0 defaults // Ref: https://github.com/dotnet/aspnetcore/issues/44661 options.AddScheme("StubAuthenticationHandler", null); diff --git a/Iceshrimp.Frontend/Core/Services/ApiClient.cs b/Iceshrimp.Frontend/Core/Services/ApiClient.cs index ca335228..5c1edf1c 100644 --- a/Iceshrimp.Frontend/Core/Services/ApiClient.cs +++ b/Iceshrimp.Frontend/Core/Services/ApiClient.cs @@ -4,6 +4,7 @@ using System.Net.Mime; using System.Text; using System.Text.Json; using Iceshrimp.Frontend.Core.Miscellaneous; +using Iceshrimp.Shared.Configuration; using Iceshrimp.Shared.Schemas; using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Http; @@ -22,7 +23,7 @@ internal class ApiClient(HttpClient client) if (res.IsSuccessStatusCode) return; - var error = await res.Content.ReadFromJsonAsync(); + var error = await res.Content.ReadFromJsonAsync(JsonSerialization.Options); if (error == null) throw new Exception("Deserialized API error was null"); throw new ApiException(error); @@ -61,13 +62,13 @@ internal class ApiClient(HttpClient client) if (res.IsSuccessStatusCode) { - var deserialized = await res.Content.ReadFromJsonAsync(); + var deserialized = await res.Content.ReadFromJsonAsync(JsonSerialization.Options); if (deserialized == null) throw new Exception("Deserialized API response was null"); return (deserialized, null); } - var error = await res.Content.ReadFromJsonAsync(); + var error = await res.Content.ReadFromJsonAsync(JsonSerialization.Options); if (error == null) throw new Exception("Deserialized API error was null"); return (null, error); @@ -102,8 +103,8 @@ internal class ApiClient(HttpClient client) } else if (data is not null) { - request.Content = new StringContent(JsonSerializer.Serialize(data), Encoding.UTF8, - MediaTypeNames.Application.Json); + request.Content = new StringContent(JsonSerializer.Serialize(data, JsonSerialization.Options), + Encoding.UTF8, MediaTypeNames.Application.Json); } return await client.SendAsync(request); diff --git a/Iceshrimp.Shared/Configuration/JsonSerialization.cs b/Iceshrimp.Shared/Configuration/JsonSerialization.cs new file mode 100644 index 00000000..723b3c14 --- /dev/null +++ b/Iceshrimp.Shared/Configuration/JsonSerialization.cs @@ -0,0 +1,13 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Iceshrimp.Shared.Configuration; + +public static class JsonSerialization +{ + public static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = { new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower) } + }; +} \ No newline at end of file diff --git a/Iceshrimp.Shared/Iceshrimp.Shared.csproj b/Iceshrimp.Shared/Iceshrimp.Shared.csproj index 8f69a1a8..f2048211 100644 --- a/Iceshrimp.Shared/Iceshrimp.Shared.csproj +++ b/Iceshrimp.Shared/Iceshrimp.Shared.csproj @@ -9,5 +9,9 @@ embedded + + + + diff --git a/Iceshrimp.Shared/Schemas/AuthRequest.cs b/Iceshrimp.Shared/Schemas/AuthRequest.cs index 35b82658..d1cd88be 100644 --- a/Iceshrimp.Shared/Schemas/AuthRequest.cs +++ b/Iceshrimp.Shared/Schemas/AuthRequest.cs @@ -1,20 +1,18 @@ -using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; - namespace Iceshrimp.Shared.Schemas; public class AuthRequest { - [J("username")] public required string Username { get; set; } - [J("password")] public required string Password { get; set; } + public required string Username { get; set; } + public required string Password { get; set; } } public class RegistrationRequest : AuthRequest { - [J("invite")] public string? Invite { get; set; } + public string? Invite { get; set; } } public class ChangePasswordRequest { - [J("old_password")] public required string OldPassword { get; set; } - [J("new_password")] public required string NewPassword { get; set; } + public required string OldPassword { get; set; } + public required string NewPassword { get; set; } } \ No newline at end of file diff --git a/Iceshrimp.Shared/Schemas/AuthResponse.cs b/Iceshrimp.Shared/Schemas/AuthResponse.cs index ec11bdd9..2fa63989 100644 --- a/Iceshrimp.Shared/Schemas/AuthResponse.cs +++ b/Iceshrimp.Shared/Schemas/AuthResponse.cs @@ -1,23 +1,15 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; -using JE = System.Runtime.Serialization.EnumMemberAttribute; - namespace Iceshrimp.Shared.Schemas; -public class AuthStatusConverter() : JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower); - -[JsonConverter(typeof(AuthStatusConverter))] public enum AuthStatusEnum { - [JE(Value = "guest")] Guest, - [JE(Value = "authenticated")] Authenticated, - [JE(Value = "2fa")] TwoFactor + Guest, + Authenticated, + TwoFactor } public class AuthResponse { - [J("status")] public required AuthStatusEnum Status { get; set; } - [J("user")] public UserResponse? User { get; set; } - [J("token")] public string? Token { get; set; } + public required AuthStatusEnum Status { get; set; } + public UserResponse? User { get; set; } + public string? Token { get; set; } } \ No newline at end of file diff --git a/Iceshrimp.Shared/Schemas/DriveFileResponse.cs b/Iceshrimp.Shared/Schemas/DriveFileResponse.cs index d59aa3e2..7d4796e6 100644 --- a/Iceshrimp.Shared/Schemas/DriveFileResponse.cs +++ b/Iceshrimp.Shared/Schemas/DriveFileResponse.cs @@ -1,14 +1,12 @@ -using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; - namespace Iceshrimp.Shared.Schemas; public class DriveFileResponse { - [J("id")] public required string Id { get; set; } - [J("url")] public required string Url { get; set; } - [J("thumbnailUrl")] public required string ThumbnailUrl { get; set; } - [J("filename")] public required string Filename { get; set; } - [J("contentType")] public required string ContentType { get; set; } - [J("sensitive")] public required bool Sensitive { get; set; } - [J("description")] public required string? Description { get; set; } + public required string Id { get; set; } + public required string Url { get; set; } + public required string ThumbnailUrl { get; set; } + public required string Filename { get; set; } + public required string ContentType { get; set; } + public required bool Sensitive { get; set; } + public required string? Description { get; set; } } \ No newline at end of file diff --git a/Iceshrimp.Shared/Schemas/ErrorResponse.cs b/Iceshrimp.Shared/Schemas/ErrorResponse.cs index d1fe69e4..ecd6deaa 100644 --- a/Iceshrimp.Shared/Schemas/ErrorResponse.cs +++ b/Iceshrimp.Shared/Schemas/ErrorResponse.cs @@ -1,25 +1,21 @@ using System.Text.Json.Serialization; -using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; using JI = System.Text.Json.Serialization.JsonIgnoreAttribute; namespace Iceshrimp.Shared.Schemas; public class ErrorResponse { - [J("statusCode")] public required int StatusCode { get; set; } - [J("error")] public required string Error { get; set; } + public required int StatusCode { get; set; } + public required string Error { get; set; } - [J("message")] [JI(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Message { get; set; } - [J("details")] [JI(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Details { get; set; } - [J("source")] [JI(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Source { get; set; } - [J("requestId")] public required string RequestId { get; set; } + public required string RequestId { get; set; } } \ No newline at end of file diff --git a/Iceshrimp.Shared/Schemas/InviteResponse.cs b/Iceshrimp.Shared/Schemas/InviteResponse.cs index 5b3db508..99e98d34 100644 --- a/Iceshrimp.Shared/Schemas/InviteResponse.cs +++ b/Iceshrimp.Shared/Schemas/InviteResponse.cs @@ -1,8 +1,6 @@ -using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; - namespace Iceshrimp.Shared.Schemas; public class InviteResponse { - [J("code")] public required string Code { get; set; } + public required string Code { get; set; } } \ No newline at end of file diff --git a/Iceshrimp.Shared/Schemas/NoteCreateRequest.cs b/Iceshrimp.Shared/Schemas/NoteCreateRequest.cs index f15796b8..ee5ef901 100644 --- a/Iceshrimp.Shared/Schemas/NoteCreateRequest.cs +++ b/Iceshrimp.Shared/Schemas/NoteCreateRequest.cs @@ -1,13 +1,11 @@ -using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; - namespace Iceshrimp.Shared.Schemas; public class NoteCreateRequest { - [J("text")] public required string Text { get; set; } - [J("cw")] public string? Cw { get; set; } - [J("replyId")] public string? ReplyId { get; set; } - [J("renoteId")] public string? RenoteId { get; set; } - [J("mediaIds")] public List? MediaIds { get; set; } - [J("visibility")] public required NoteVisibility Visibility { get; set; } + public required string Text { get; set; } + public string? Cw { get; set; } + public string? ReplyId { get; set; } + public string? RenoteId { get; set; } + public List? MediaIds { get; set; } + public required NoteVisibility Visibility { get; set; } } \ No newline at end of file diff --git a/Iceshrimp.Shared/Schemas/NoteResponse.cs b/Iceshrimp.Shared/Schemas/NoteResponse.cs index 7f60755a..ae10e613 100644 --- a/Iceshrimp.Shared/Schemas/NoteResponse.cs +++ b/Iceshrimp.Shared/Schemas/NoteResponse.cs @@ -1,22 +1,20 @@ using System.Text.Json.Serialization; -using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; using JI = System.Text.Json.Serialization.JsonIgnoreAttribute; namespace Iceshrimp.Shared.Schemas; public class NoteResponse : NoteWithQuote, ICloneable { - [J("reply")] public NoteBase? Reply { get; set; } - [J("replyId")] public string? ReplyId { get; set; } - [J("renote")] public NoteWithQuote? Renote { get; set; } - [J("renoteId")] public string? RenoteId { get; set; } - [J("filtered")] public NoteFilteredSchema? Filtered { get; set; } + public NoteBase? Reply { get; set; } + public string? ReplyId { get; set; } + public NoteWithQuote? Renote { get; set; } + public string? RenoteId { get; set; } + public NoteFilteredSchema? Filtered { get; set; } // The properties below are only necessary for building a descendants tree [JI] public NoteResponse? Parent; [JI(Condition = JsonIgnoreCondition.WhenWritingNull)] - [J("descendants")] public List? Descendants { get; set; } public object Clone() => MemberwiseClone(); @@ -24,49 +22,49 @@ public class NoteResponse : NoteWithQuote, ICloneable public class NoteWithQuote : NoteBase { - [J("quote")] public NoteBase? Quote { get; set; } - [J("quoteId")] public string? QuoteId { get; set; } + public NoteBase? Quote { get; set; } + public string? QuoteId { get; set; } } public class NoteBase { - [J("id")] public required string Id { get; set; } - [J("createdAt")] public required string CreatedAt { get; set; } - [J("text")] public required string? Text { get; set; } - [J("cw")] public required string? Cw { get; set; } - [J("visibility")] public required NoteVisibility Visibility { get; set; } - [J("liked")] public required bool Liked { get; set; } - [J("likes")] public required int Likes { get; set; } - [J("renotes")] public required int Renotes { get; set; } - [J("replies")] public required int Replies { get; set; } - [J("user")] public required UserResponse User { get; set; } - [J("attachments")] public required List Attachments { get; set; } - [J("reactions")] public required List Reactions { get; set; } + public required string Id { get; set; } + public required string CreatedAt { get; set; } + public required string? Text { get; set; } + public required string? Cw { get; set; } + public required NoteVisibility Visibility { get; set; } + public required bool Liked { get; set; } + public required int Likes { get; set; } + public required int Renotes { get; set; } + public required int Replies { get; set; } + public required UserResponse User { get; set; } + public required List Attachments { get; set; } + public required List Reactions { get; set; } } public class NoteAttachment { - [JI] public required string Id; - [J("url")] public required string Url { get; set; } - [J("thumbnailUrl")] public required string ThumbnailUrl { get; set; } - [J("blurhash")] public required string? Blurhash { get; set; } - [J("alt")] public required string? AltText { get; set; } + [JI] public required string Id; + public required string Url { get; set; } + public required string ThumbnailUrl { get; set; } + public required string? Blurhash { get; set; } + public required string? AltText { get; set; } } public class NoteReactionSchema { - [JI] public required string NoteId; - [J("name")] public required string Name { get; set; } - [J("count")] public required int Count { get; set; } - [J("reacted")] public required bool Reacted { get; set; } - [J("url")] public required string? Url { get; set; } + [JI] public required string NoteId; + public required string Name { get; set; } + public required int Count { get; set; } + public required bool Reacted { get; set; } + public required string? Url { get; set; } } public class NoteFilteredSchema { - [J("filterId")] public required long Id { get; set; } - [J("keyword")] public required string Keyword { get; set; } - [J("drop")] public required bool Hide { get; set; } + public required long Id { get; set; } + public required string Keyword { get; set; } + public required bool Hide { get; set; } } public enum NoteVisibility diff --git a/Iceshrimp.Shared/Schemas/NotificationResponse.cs b/Iceshrimp.Shared/Schemas/NotificationResponse.cs index 780f7749..6a86ea10 100644 --- a/Iceshrimp.Shared/Schemas/NotificationResponse.cs +++ b/Iceshrimp.Shared/Schemas/NotificationResponse.cs @@ -1,13 +1,11 @@ -using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; - namespace Iceshrimp.Shared.Schemas; public class NotificationResponse { - [J("id")] public required string Id { get; set; } - [J("type")] public required string Type { get; set; } - [J("read")] public required bool Read { get; set; } - [J("createdAt")] public required string CreatedAt { get; set; } - [J("note")] public NoteResponse? Note { get; set; } - [J("user")] public UserResponse? User { get; set; } + public required string Id { get; set; } + public required string Type { get; set; } + public required bool Read { get; set; } + public required string CreatedAt { get; set; } + public NoteResponse? Note { get; set; } + public UserResponse? User { get; set; } } \ No newline at end of file diff --git a/Iceshrimp.Shared/Schemas/UpdateDriveFileRequest.cs b/Iceshrimp.Shared/Schemas/UpdateDriveFileRequest.cs index d92ce78a..6e258160 100644 --- a/Iceshrimp.Shared/Schemas/UpdateDriveFileRequest.cs +++ b/Iceshrimp.Shared/Schemas/UpdateDriveFileRequest.cs @@ -1,10 +1,8 @@ -using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; - namespace Iceshrimp.Shared.Schemas; public class UpdateDriveFileRequest { - [J("filename")] public string? Filename { get; set; } - [J("sensitive")] public bool? Sensitive { get; set; } - [J("description")] public string? Description { get; set; } + public string? Filename { get; set; } + public bool? Sensitive { get; set; } + public string? Description { get; set; } } \ No newline at end of file diff --git a/Iceshrimp.Shared/Schemas/UserProfileResponse.cs b/Iceshrimp.Shared/Schemas/UserProfileResponse.cs index 289048cc..f886dd31 100644 --- a/Iceshrimp.Shared/Schemas/UserProfileResponse.cs +++ b/Iceshrimp.Shared/Schemas/UserProfileResponse.cs @@ -1,21 +1,19 @@ -using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; - namespace Iceshrimp.Shared.Schemas; public class UserProfileResponse { - [J("id")] public required string Id { get; set; } - [J("birthday")] public required string? Birthday { get; set; } - [J("location")] public required string? Location { get; set; } - [J("fields")] public required List? Fields { get; set; } - [J("bio")] public required string? Bio { get; set; } - [J("followers")] public required int? Followers { get; set; } - [J("following")] public required int? Following { get; set; } + public required string Id { get; set; } + public required string? Birthday { get; set; } + public required string? Location { get; set; } + public required List? Fields { get; set; } + public required string? Bio { get; set; } + public required int? Followers { get; set; } + public required int? Following { get; set; } } public class UserProfileField { - [J("name")] public required string Name { get; set; } - [J("value")] public required string Value { get; set; } - [J("verified")] public bool? IsVerified { get; set; } + public required string Name { get; set; } + public required string Value { get; set; } + public bool? Verified { get; set; } } \ No newline at end of file diff --git a/Iceshrimp.Shared/Schemas/UserResponse.cs b/Iceshrimp.Shared/Schemas/UserResponse.cs index 630bc55a..d95009a6 100644 --- a/Iceshrimp.Shared/Schemas/UserResponse.cs +++ b/Iceshrimp.Shared/Schemas/UserResponse.cs @@ -1,14 +1,12 @@ -using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; - namespace Iceshrimp.Shared.Schemas; public class UserResponse { - [J("id")] public required string Id { get; set; } - [J("username")] public required string Username { get; set; } - [J("displayName")] public required string? DisplayName { get; set; } - [J("avatarUrl")] public required string? AvatarUrl { get; set; } - [J("bannerUrl")] public required string? BannerUrl { get; set; } - [J("instanceName")] public required string? InstanceName { get; set; } - [J("instanceIconUrl")] public required string? InstanceIconUrl { get; set; } + public required string Id { get; set; } + public required string Username { get; set; } + public required string? DisplayName { get; set; } + public required string? AvatarUrl { get; set; } + public required string? BannerUrl { get; set; } + public required string? InstanceName { get; set; } + public required string? InstanceIconUrl { get; set; } } \ No newline at end of file diff --git a/Iceshrimp.Shared/Schemas/ValueResponse.cs b/Iceshrimp.Shared/Schemas/ValueResponse.cs index 276503b5..a48dfa03 100644 --- a/Iceshrimp.Shared/Schemas/ValueResponse.cs +++ b/Iceshrimp.Shared/Schemas/ValueResponse.cs @@ -1,8 +1,6 @@ -using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; - namespace Iceshrimp.Shared.Schemas; public class ValueResponse(long count) { - [J("value")] public long Value { get; set; } = count; + public long Value { get; set; } = count; } \ No newline at end of file