[backend/api] Switch to a shared JsonSerializerOptions object instead of explicitly specifying json property names via attributes

This commit is contained in:
Laura Hausmann 2024-04-22 19:55:00 +02:00
parent 675ec23a3c
commit 86c0ab02b5
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
17 changed files with 127 additions and 126 deletions

View file

@ -32,7 +32,7 @@ public class UserProfileRenderer(DatabaseContext db)
{ {
Name = p.Name, Name = p.Name,
Value = p.Value, Value = p.Value,
IsVerified = p.IsVerified Verified = p.IsVerified
}); });
return new UserProfileResponse return new UserProfileResponse

View file

@ -10,6 +10,7 @@ using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Services; using Iceshrimp.Backend.Core.Services;
using Iceshrimp.Backend.Hubs.Authentication; using Iceshrimp.Backend.Hubs.Authentication;
using Iceshrimp.Shared.Configuration;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption;
@ -105,6 +106,20 @@ public static class ServiceExtensions
.ConfigureWithValidation<Config.StorageSection>(configuration, "Storage") .ConfigureWithValidation<Config.StorageSection>(configuration, "Storage")
.ConfigureWithValidation<Config.LocalStorageSection>(configuration, "Storage:Local") .ConfigureWithValidation<Config.LocalStorageSection>(configuration, "Storage:Local")
.ConfigureWithValidation<Config.ObjectStorageSection>(configuration, "Storage:ObjectStorage"); .ConfigureWithValidation<Config.ObjectStorageSection>(configuration, "Storage:ObjectStorage");
services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(options =>
{
options.SerializerOptions.PropertyNamingPolicy = JsonSerialization.Options.PropertyNamingPolicy;
foreach (var converter in JsonSerialization.Options.Converters)
options.SerializerOptions.Converters.Add(converter);
});
services.Configure<Microsoft.AspNetCore.Mvc.JsonOptions>(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = JsonSerialization.Options.PropertyNamingPolicy;
foreach (var converter in JsonSerialization.Options.Converters)
options.JsonSerializerOptions.Converters.Add(converter);
});
} }
private static IServiceCollection ConfigureWithValidation<T>( private static IServiceCollection ConfigureWithValidation<T>(

View file

@ -4,6 +4,7 @@ using System.Net.Mime;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using Iceshrimp.Frontend.Core.Miscellaneous; using Iceshrimp.Frontend.Core.Miscellaneous;
using Iceshrimp.Shared.Configuration;
using Iceshrimp.Shared.Schemas; using Iceshrimp.Shared.Schemas;
using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -22,7 +23,7 @@ internal class ApiClient(HttpClient client)
if (res.IsSuccessStatusCode) if (res.IsSuccessStatusCode)
return; return;
var error = await res.Content.ReadFromJsonAsync<ErrorResponse>(); var error = await res.Content.ReadFromJsonAsync<ErrorResponse>(JsonSerialization.Options);
if (error == null) if (error == null)
throw new Exception("Deserialized API error was null"); throw new Exception("Deserialized API error was null");
throw new ApiException(error); throw new ApiException(error);
@ -61,13 +62,13 @@ internal class ApiClient(HttpClient client)
if (res.IsSuccessStatusCode) if (res.IsSuccessStatusCode)
{ {
var deserialized = await res.Content.ReadFromJsonAsync<T>(); var deserialized = await res.Content.ReadFromJsonAsync<T>(JsonSerialization.Options);
if (deserialized == null) if (deserialized == null)
throw new Exception("Deserialized API response was null"); throw new Exception("Deserialized API response was null");
return (deserialized, null); return (deserialized, null);
} }
var error = await res.Content.ReadFromJsonAsync<ErrorResponse>(); var error = await res.Content.ReadFromJsonAsync<ErrorResponse>(JsonSerialization.Options);
if (error == null) if (error == null)
throw new Exception("Deserialized API error was null"); throw new Exception("Deserialized API error was null");
return (null, error); return (null, error);
@ -102,8 +103,8 @@ internal class ApiClient(HttpClient client)
} }
else if (data is not null) else if (data is not null)
{ {
request.Content = new StringContent(JsonSerializer.Serialize(data), Encoding.UTF8, request.Content = new StringContent(JsonSerializer.Serialize(data, JsonSerialization.Options),
MediaTypeNames.Application.Json); Encoding.UTF8, MediaTypeNames.Application.Json);
} }
return await client.SendAsync(request); return await client.SendAsync(request);

View file

@ -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) }
};
}

View file

@ -10,4 +10,8 @@
<DebugType>embedded</DebugType> <DebugType>embedded</DebugType>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
</ItemGroup>
</Project> </Project>

View file

@ -1,20 +1,18 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Shared.Schemas; namespace Iceshrimp.Shared.Schemas;
public class AuthRequest public class AuthRequest
{ {
[J("username")] public required string Username { get; set; } public required string Username { get; set; }
[J("password")] public required string Password { get; set; } public required string Password { get; set; }
} }
public class RegistrationRequest : AuthRequest public class RegistrationRequest : AuthRequest
{ {
[J("invite")] public string? Invite { get; set; } public string? Invite { get; set; }
} }
public class ChangePasswordRequest public class ChangePasswordRequest
{ {
[J("old_password")] public required string OldPassword { get; set; } public required string OldPassword { get; set; }
[J("new_password")] public required string NewPassword { get; set; } public required string NewPassword { get; set; }
} }

View file

@ -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; namespace Iceshrimp.Shared.Schemas;
public class AuthStatusConverter() : JsonStringEnumConverter<AuthStatusEnum>(JsonNamingPolicy.SnakeCaseLower);
[JsonConverter(typeof(AuthStatusConverter))]
public enum AuthStatusEnum public enum AuthStatusEnum
{ {
[JE(Value = "guest")] Guest, Guest,
[JE(Value = "authenticated")] Authenticated, Authenticated,
[JE(Value = "2fa")] TwoFactor TwoFactor
} }
public class AuthResponse public class AuthResponse
{ {
[J("status")] public required AuthStatusEnum Status { get; set; } public required AuthStatusEnum Status { get; set; }
[J("user")] public UserResponse? User { get; set; } public UserResponse? User { get; set; }
[J("token")] public string? Token { get; set; } public string? Token { get; set; }
} }

View file

@ -1,14 +1,12 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Shared.Schemas; namespace Iceshrimp.Shared.Schemas;
public class DriveFileResponse public class DriveFileResponse
{ {
[J("id")] public required string Id { get; set; } public required string Id { get; set; }
[J("url")] public required string Url { get; set; } public required string Url { get; set; }
[J("thumbnailUrl")] public required string ThumbnailUrl { get; set; } public required string ThumbnailUrl { get; set; }
[J("filename")] public required string Filename { get; set; } public required string Filename { get; set; }
[J("contentType")] public required string ContentType { get; set; } public required string ContentType { get; set; }
[J("sensitive")] public required bool Sensitive { get; set; } public required bool Sensitive { get; set; }
[J("description")] public required string? Description { get; set; } public required string? Description { get; set; }
} }

View file

@ -1,25 +1,21 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
using JI = System.Text.Json.Serialization.JsonIgnoreAttribute; using JI = System.Text.Json.Serialization.JsonIgnoreAttribute;
namespace Iceshrimp.Shared.Schemas; namespace Iceshrimp.Shared.Schemas;
public class ErrorResponse public class ErrorResponse
{ {
[J("statusCode")] public required int StatusCode { get; set; } public required int StatusCode { get; set; }
[J("error")] public required string Error { get; set; } public required string Error { get; set; }
[J("message")]
[JI(Condition = JsonIgnoreCondition.WhenWritingNull)] [JI(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Message { get; set; } public string? Message { get; set; }
[J("details")]
[JI(Condition = JsonIgnoreCondition.WhenWritingNull)] [JI(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Details { get; set; } public string? Details { get; set; }
[J("source")]
[JI(Condition = JsonIgnoreCondition.WhenWritingNull)] [JI(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Source { get; set; } public string? Source { get; set; }
[J("requestId")] public required string RequestId { get; set; } public required string RequestId { get; set; }
} }

View file

@ -1,8 +1,6 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Shared.Schemas; namespace Iceshrimp.Shared.Schemas;
public class InviteResponse public class InviteResponse
{ {
[J("code")] public required string Code { get; set; } public required string Code { get; set; }
} }

View file

@ -1,13 +1,11 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Shared.Schemas; namespace Iceshrimp.Shared.Schemas;
public class NoteCreateRequest public class NoteCreateRequest
{ {
[J("text")] public required string Text { get; set; } public required string Text { get; set; }
[J("cw")] public string? Cw { get; set; } public string? Cw { get; set; }
[J("replyId")] public string? ReplyId { get; set; } public string? ReplyId { get; set; }
[J("renoteId")] public string? RenoteId { get; set; } public string? RenoteId { get; set; }
[J("mediaIds")] public List<string>? MediaIds { get; set; } public List<string>? MediaIds { get; set; }
[J("visibility")] public required NoteVisibility Visibility { get; set; } public required NoteVisibility Visibility { get; set; }
} }

View file

@ -1,22 +1,20 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
using JI = System.Text.Json.Serialization.JsonIgnoreAttribute; using JI = System.Text.Json.Serialization.JsonIgnoreAttribute;
namespace Iceshrimp.Shared.Schemas; namespace Iceshrimp.Shared.Schemas;
public class NoteResponse : NoteWithQuote, ICloneable public class NoteResponse : NoteWithQuote, ICloneable
{ {
[J("reply")] public NoteBase? Reply { get; set; } public NoteBase? Reply { get; set; }
[J("replyId")] public string? ReplyId { get; set; } public string? ReplyId { get; set; }
[J("renote")] public NoteWithQuote? Renote { get; set; } public NoteWithQuote? Renote { get; set; }
[J("renoteId")] public string? RenoteId { get; set; } public string? RenoteId { get; set; }
[J("filtered")] public NoteFilteredSchema? Filtered { get; set; } public NoteFilteredSchema? Filtered { get; set; }
// The properties below are only necessary for building a descendants tree // The properties below are only necessary for building a descendants tree
[JI] public NoteResponse? Parent; [JI] public NoteResponse? Parent;
[JI(Condition = JsonIgnoreCondition.WhenWritingNull)] [JI(Condition = JsonIgnoreCondition.WhenWritingNull)]
[J("descendants")]
public List<NoteResponse>? Descendants { get; set; } public List<NoteResponse>? Descendants { get; set; }
public object Clone() => MemberwiseClone(); public object Clone() => MemberwiseClone();
@ -24,49 +22,49 @@ public class NoteResponse : NoteWithQuote, ICloneable
public class NoteWithQuote : NoteBase public class NoteWithQuote : NoteBase
{ {
[J("quote")] public NoteBase? Quote { get; set; } public NoteBase? Quote { get; set; }
[J("quoteId")] public string? QuoteId { get; set; } public string? QuoteId { get; set; }
} }
public class NoteBase public class NoteBase
{ {
[J("id")] public required string Id { get; set; } public required string Id { get; set; }
[J("createdAt")] public required string CreatedAt { get; set; } public required string CreatedAt { get; set; }
[J("text")] public required string? Text { get; set; } public required string? Text { get; set; }
[J("cw")] public required string? Cw { get; set; } public required string? Cw { get; set; }
[J("visibility")] public required NoteVisibility Visibility { get; set; } public required NoteVisibility Visibility { get; set; }
[J("liked")] public required bool Liked { get; set; } public required bool Liked { get; set; }
[J("likes")] public required int Likes { get; set; } public required int Likes { get; set; }
[J("renotes")] public required int Renotes { get; set; } public required int Renotes { get; set; }
[J("replies")] public required int Replies { get; set; } public required int Replies { get; set; }
[J("user")] public required UserResponse User { get; set; } public required UserResponse User { get; set; }
[J("attachments")] public required List<NoteAttachment> Attachments { get; set; } public required List<NoteAttachment> Attachments { get; set; }
[J("reactions")] public required List<NoteReactionSchema> Reactions { get; set; } public required List<NoteReactionSchema> Reactions { get; set; }
} }
public class NoteAttachment public class NoteAttachment
{ {
[JI] public required string Id; [JI] public required string Id;
[J("url")] public required string Url { get; set; } public required string Url { get; set; }
[J("thumbnailUrl")] public required string ThumbnailUrl { get; set; } public required string ThumbnailUrl { get; set; }
[J("blurhash")] public required string? Blurhash { get; set; } public required string? Blurhash { get; set; }
[J("alt")] public required string? AltText { get; set; } public required string? AltText { get; set; }
} }
public class NoteReactionSchema public class NoteReactionSchema
{ {
[JI] public required string NoteId; [JI] public required string NoteId;
[J("name")] public required string Name { get; set; } public required string Name { get; set; }
[J("count")] public required int Count { get; set; } public required int Count { get; set; }
[J("reacted")] public required bool Reacted { get; set; } public required bool Reacted { get; set; }
[J("url")] public required string? Url { get; set; } public required string? Url { get; set; }
} }
public class NoteFilteredSchema public class NoteFilteredSchema
{ {
[J("filterId")] public required long Id { get; set; } public required long Id { get; set; }
[J("keyword")] public required string Keyword { get; set; } public required string Keyword { get; set; }
[J("drop")] public required bool Hide { get; set; } public required bool Hide { get; set; }
} }
public enum NoteVisibility public enum NoteVisibility

View file

@ -1,13 +1,11 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Shared.Schemas; namespace Iceshrimp.Shared.Schemas;
public class NotificationResponse public class NotificationResponse
{ {
[J("id")] public required string Id { get; set; } public required string Id { get; set; }
[J("type")] public required string Type { get; set; } public required string Type { get; set; }
[J("read")] public required bool Read { get; set; } public required bool Read { get; set; }
[J("createdAt")] public required string CreatedAt { get; set; } public required string CreatedAt { get; set; }
[J("note")] public NoteResponse? Note { get; set; } public NoteResponse? Note { get; set; }
[J("user")] public UserResponse? User { get; set; } public UserResponse? User { get; set; }
} }

View file

@ -1,10 +1,8 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Shared.Schemas; namespace Iceshrimp.Shared.Schemas;
public class UpdateDriveFileRequest public class UpdateDriveFileRequest
{ {
[J("filename")] public string? Filename { get; set; } public string? Filename { get; set; }
[J("sensitive")] public bool? Sensitive { get; set; } public bool? Sensitive { get; set; }
[J("description")] public string? Description { get; set; } public string? Description { get; set; }
} }

View file

@ -1,21 +1,19 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Shared.Schemas; namespace Iceshrimp.Shared.Schemas;
public class UserProfileResponse public class UserProfileResponse
{ {
[J("id")] public required string Id { get; set; } public required string Id { get; set; }
[J("birthday")] public required string? Birthday { get; set; } public required string? Birthday { get; set; }
[J("location")] public required string? Location { get; set; } public required string? Location { get; set; }
[J("fields")] public required List<UserProfileField>? Fields { get; set; } public required List<UserProfileField>? Fields { get; set; }
[J("bio")] public required string? Bio { get; set; } public required string? Bio { get; set; }
[J("followers")] public required int? Followers { get; set; } public required int? Followers { get; set; }
[J("following")] public required int? Following { get; set; } public required int? Following { get; set; }
} }
public class UserProfileField public class UserProfileField
{ {
[J("name")] public required string Name { get; set; } public required string Name { get; set; }
[J("value")] public required string Value { get; set; } public required string Value { get; set; }
[J("verified")] public bool? IsVerified { get; set; } public bool? Verified { get; set; }
} }

View file

@ -1,14 +1,12 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Shared.Schemas; namespace Iceshrimp.Shared.Schemas;
public class UserResponse public class UserResponse
{ {
[J("id")] public required string Id { get; set; } public required string Id { get; set; }
[J("username")] public required string Username { get; set; } public required string Username { get; set; }
[J("displayName")] public required string? DisplayName { get; set; } public required string? DisplayName { get; set; }
[J("avatarUrl")] public required string? AvatarUrl { get; set; } public required string? AvatarUrl { get; set; }
[J("bannerUrl")] public required string? BannerUrl { get; set; } public required string? BannerUrl { get; set; }
[J("instanceName")] public required string? InstanceName { get; set; } public required string? InstanceName { get; set; }
[J("instanceIconUrl")] public required string? InstanceIconUrl { get; set; } public required string? InstanceIconUrl { get; set; }
} }

View file

@ -1,8 +1,6 @@
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace Iceshrimp.Shared.Schemas; namespace Iceshrimp.Shared.Schemas;
public class ValueResponse(long count) public class ValueResponse(long count)
{ {
[J("value")] public long Value { get; set; } = count; public long Value { get; set; } = count;
} }