[backend/api] Switch to a shared JsonSerializerOptions object instead of explicitly specifying json property names via attributes
All checks were successful
/ unit-tests (push) Successful in 25s
/ build-and-push (push) Successful in 1m40s

This commit is contained in:
Laura Hausmann 2024-04-22 19:55:00 +02:00
parent 675ec23a3c
commit 86c0ab02b5
Signed by: zotan
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,
Value = p.Value,
IsVerified = p.IsVerified
Verified = p.IsVerified
});
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.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<Config.StorageSection>(configuration, "Storage")
.ConfigureWithValidation<Config.LocalStorageSection>(configuration, "Storage:Local")
.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>(
@ -260,7 +275,7 @@ public static class ServiceExtensions
services.AddAuthentication(options =>
{
options.AddScheme<HubAuthenticationHandler>("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<IAuthenticationHandler>("StubAuthenticationHandler", null);

View file

@ -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<ErrorResponse>();
var error = await res.Content.ReadFromJsonAsync<ErrorResponse>(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<T>();
var deserialized = await res.Content.ReadFromJsonAsync<T>(JsonSerialization.Options);
if (deserialized == null)
throw new Exception("Deserialized API response was null");
return (deserialized, null);
}
var error = await res.Content.ReadFromJsonAsync<ErrorResponse>();
var error = await res.Content.ReadFromJsonAsync<ErrorResponse>(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);

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

@ -9,5 +9,9 @@
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugType>embedded</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
</ItemGroup>
</Project>

View file

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

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;
public class AuthStatusConverter() : JsonStringEnumConverter<AuthStatusEnum>(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; }
}

View file

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

View file

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

View file

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

View file

@ -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<string>? 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<string>? MediaIds { get; set; }
public required NoteVisibility Visibility { get; set; }
}

View file

@ -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<NoteResponse>? 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<NoteAttachment> Attachments { get; set; }
[J("reactions")] public required List<NoteReactionSchema> 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<NoteAttachment> Attachments { get; set; }
public required List<NoteReactionSchema> 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

View file

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

View file

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

View file

@ -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<UserProfileField>? 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<UserProfileField>? 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; }
}

View file

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

View file

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