diff --git a/PluralKit.API/Controllers/v1/MemberController.cs b/PluralKit.API/Controllers/v1/MemberController.cs index 8ca35626..5b7f5512 100644 --- a/PluralKit.API/Controllers/v1/MemberController.cs +++ b/PluralKit.API/Controllers/v1/MemberController.cs @@ -58,21 +58,21 @@ namespace PluralKit.API await using var tx = await conn.BeginTransactionAsync(); var member = await _repo.CreateMember(systemId, properties.Value("name"), conn); - MemberPatch patch; - try - { - patch = MemberPatch.FromJSON(properties); - patch.AssertIsValid(); - } - catch (FieldTooLongError e) + var patch = MemberPatch.FromJSON(properties); + + patch.AssertIsValid(); + if (patch.Errors.Count > 0) { await tx.RollbackAsync(); - return BadRequest(e.Message); - } - catch (ValidationError e) - { - await tx.RollbackAsync(); - return BadRequest($"Request field '{e.Message}' is invalid."); + + var err = patch.Errors[0]; + if (err is FieldTooLongError) + return BadRequest($"Field {err.Key} is too long " + + $"({(err as FieldTooLongError).ActualLength} > {(err as FieldTooLongError).MaxLength})."); + else if (err.Text != null) + return BadRequest(err.Text); + else + return BadRequest($"Field {err.Key} is invalid."); } member = await _repo.UpdateMember(member.Id, patch, conn); @@ -90,19 +90,19 @@ namespace PluralKit.API var res = await _auth.AuthorizeAsync(User, member, "EditMember"); if (!res.Succeeded) return Unauthorized($"Member '{hid}' is not part of your system."); - MemberPatch patch; - try + var patch = MemberPatch.FromJSON(changes); + + patch.AssertIsValid(); + if (patch.Errors.Count > 0) { - patch = MemberPatch.FromJSON(changes); - patch.AssertIsValid(); - } - catch (FieldTooLongError e) - { - return BadRequest(e.Message); - } - catch (ValidationError e) - { - return BadRequest($"Request field '{e.Message}' is invalid."); + var err = patch.Errors[0]; + if (err is FieldTooLongError) + return BadRequest($"Field {err.Key} is too long " + + $"({(err as FieldTooLongError).ActualLength} > {(err as FieldTooLongError).MaxLength})."); + else if (err.Text != null) + return BadRequest(err.Text); + else + return BadRequest($"Field {err.Key} is invalid."); } var newMember = await _repo.UpdateMember(member.Id, patch); diff --git a/PluralKit.API/Controllers/v1/SystemController.cs b/PluralKit.API/Controllers/v1/SystemController.cs index f59f0670..437d4994 100644 --- a/PluralKit.API/Controllers/v1/SystemController.cs +++ b/PluralKit.API/Controllers/v1/SystemController.cs @@ -133,19 +133,17 @@ namespace PluralKit.API { var system = await _repo.GetSystem(User.CurrentSystem()); - SystemPatch patch; - try + var patch = SystemPatch.FromJSON(changes); + + patch.AssertIsValid(); + if (patch.Errors.Count > 0) { - patch = SystemPatch.FromJSON(changes); - patch.AssertIsValid(); - } - catch (FieldTooLongError e) - { - return BadRequest(e.Message); - } - catch (ValidationError e) - { - return BadRequest($"Request field '{e.Message}' is invalid."); + var err = patch.Errors[0]; + if (err is FieldTooLongError) + return BadRequest($"Field {err.Key} is too long " + + $"({(err as FieldTooLongError).ActualLength} > {(err as FieldTooLongError).MaxLength})."); + + return BadRequest($"Field {err.Key} is invalid."); } system = await _repo.UpdateSystem(system!.Id, patch); diff --git a/PluralKit.API/Controllers/v2/GuildControllerV2.cs b/PluralKit.API/Controllers/v2/GuildControllerV2.cs index e3abd0de..6c098eff 100644 --- a/PluralKit.API/Controllers/v2/GuildControllerV2.cs +++ b/PluralKit.API/Controllers/v2/GuildControllerV2.cs @@ -55,17 +55,11 @@ namespace PluralKit.API else memberId = settings.AutoproxyMember; - SystemGuildPatch patch = null; - try - { - patch = SystemGuildPatch.FromJson(data, memberId); - patch.AssertIsValid(); - } - catch (ValidationError e) - { - // todo - return BadRequest(e.Message); - } + var patch = SystemGuildPatch.FromJson(data, memberId); + + patch.AssertIsValid(); + if (patch.Errors.Count > 0) + throw new ModelParseError(patch.Errors); // this is less than great, but at least it's legible if (patch.AutoproxyMember.Value == null) @@ -116,17 +110,11 @@ namespace PluralKit.API if (settings == null) throw APIErrors.MemberGuildNotFound; - MemberGuildPatch patch = null; - try - { - patch = MemberGuildPatch.FromJson(data); - patch.AssertIsValid(); - } - catch (ValidationError e) - { - // todo - return BadRequest(e.Message); - } + var patch = MemberGuildPatch.FromJson(data); + + patch.AssertIsValid(); + if (patch.Errors.Count > 0) + throw new ModelParseError(patch.Errors); var newSettings = await _repo.UpdateMemberGuild(member.Id, guild_id, patch); return Ok(newSettings.ToJson()); diff --git a/PluralKit.API/Errors.cs b/PluralKit.API/Errors.cs index 39ee1122..a2cfe8bb 100644 --- a/PluralKit.API/Errors.cs +++ b/PluralKit.API/Errors.cs @@ -1,7 +1,10 @@ using System; +using System.Collections.Generic; using Newtonsoft.Json.Linq; +using PluralKit.Core; + namespace PluralKit.API { public class PKError: Exception @@ -25,15 +28,49 @@ namespace PluralKit.API public class ModelParseError: PKError { - public ModelParseError() : base(400, 40001, "Error parsing JSON model") + private IEnumerable _errors { get; init; } + public ModelParseError(IEnumerable errors) : base(400, 40001, "Error parsing JSON model") { - // todo + _errors = errors; } public new JObject ToJson() { var j = base.ToJson(); + var e = new JObject(); + foreach (var err in _errors) + { + var o = new JObject(); + + if (err is FieldTooLongError fe) + { + o.Add("message", $"Field {err.Key} is too long."); + o.Add("actual_length", fe.ActualLength); + o.Add("max_length", fe.MaxLength); + } + else if (err.Text != null) + o.Add("message", err.Text); + else + o.Add("message", $"Field {err.Key} is invalid."); + + if (e[err.Key] != null) + { + if (e[err.Key].Type == JTokenType.Object) + { + var current = e[err.Key]; + e.Remove(err.Key); + e.Add(err.Key, new JArray()); + (e[err.Key] as JArray).Add(current); + } + + (e[err.Key] as JArray).Add(o); + } + else + e.Add(err.Key, o); + } + + j.Add("errors", e); return j; } } diff --git a/PluralKit.API/Startup.cs b/PluralKit.API/Startup.cs index e3c70023..04e6b434 100644 --- a/PluralKit.API/Startup.cs +++ b/PluralKit.API/Startup.cs @@ -154,6 +154,15 @@ namespace PluralKit.API return; } + // for some reason, if we don't specifically cast to ModelParseError, it uses the base's ToJson method + if (exc.Error is ModelParseError fe) + { + ctx.Response.StatusCode = fe.ResponseCode; + await ctx.Response.WriteAsync(JsonConvert.SerializeObject(fe.ToJson())); + + return; + } + var err = (PKError)exc.Error; ctx.Response.StatusCode = err.ResponseCode; diff --git a/PluralKit.Core/Models/ModelTypes/Validation.cs b/PluralKit.Core/Models/ModelTypes/Validation.cs new file mode 100644 index 00000000..20e8e75d --- /dev/null +++ b/PluralKit.Core/Models/ModelTypes/Validation.cs @@ -0,0 +1,29 @@ +using System; + +using Newtonsoft.Json; + +namespace PluralKit.Core +{ + public class ValidationError + { + public string Key; + public string? Text; + public ValidationError(string key, string? text = null) + { + Key = key; + Text = text; + } + } + + public class FieldTooLongError: ValidationError + { + public int MaxLength; + public int ActualLength; + + public FieldTooLongError(string key, int maxLength, int actualLength) : base(key) + { + MaxLength = maxLength; + ActualLength = actualLength; + } + } +} \ No newline at end of file diff --git a/PluralKit.Core/Models/Patch/GroupPatch.cs b/PluralKit.Core/Models/Patch/GroupPatch.cs index 75db6773..293b3d66 100644 --- a/PluralKit.Core/Models/Patch/GroupPatch.cs +++ b/PluralKit.Core/Models/Patch/GroupPatch.cs @@ -38,7 +38,7 @@ namespace PluralKit.Core public new void AssertIsValid() { - if (Name.IsPresent) + if (Name.Value != null) AssertValid(Name.Value, "name", Limits.MaxGroupNameLength); if (DisplayName.Value != null) AssertValid(DisplayName.Value, "display_name", Limits.MaxGroupNameLength); @@ -59,10 +59,13 @@ namespace PluralKit.Core { var patch = new GroupPatch(); - if (o.ContainsKey("name") && o["name"].Type == JTokenType.Null) - throw new ValidationError("Group name can not be set to null."); + if (o.ContainsKey("name")) + { + patch.Name = o.Value("name").NullIfEmpty(); + if (patch.Name.Value == null) + patch.Errors.Add(new ValidationError("name", "Group name can not be set to null.")); + } - if (o.ContainsKey("name")) patch.Name = o.Value("name"); if (o.ContainsKey("display_name")) patch.DisplayName = o.Value("display_name").NullIfEmpty(); if (o.ContainsKey("description")) patch.Description = o.Value("description").NullIfEmpty(); if (o.ContainsKey("icon")) patch.Icon = o.Value("icon").NullIfEmpty(); @@ -74,16 +77,16 @@ namespace PluralKit.Core var privacy = o.Value("privacy"); if (privacy.ContainsKey("description_privacy")) - patch.DescriptionPrivacy = privacy.ParsePrivacy("description_privacy"); + patch.DescriptionPrivacy = patch.ParsePrivacy(privacy, "description_privacy"); if (privacy.ContainsKey("icon_privacy")) - patch.IconPrivacy = privacy.ParsePrivacy("icon_privacy"); + patch.IconPrivacy = patch.ParsePrivacy(privacy, "icon_privacy"); if (privacy.ContainsKey("list_privacy")) - patch.ListPrivacy = privacy.ParsePrivacy("list_privacy"); + patch.ListPrivacy = patch.ParsePrivacy(privacy, "list_privacy"); if (privacy.ContainsKey("visibility")) - patch.Visibility = privacy.ParsePrivacy("visibility"); + patch.Visibility = patch.ParsePrivacy(privacy, "visibility"); } return patch; diff --git a/PluralKit.Core/Models/Patch/MemberPatch.cs b/PluralKit.Core/Models/Patch/MemberPatch.cs index d18c8772..edf47289 100644 --- a/PluralKit.Core/Models/Patch/MemberPatch.cs +++ b/PluralKit.Core/Models/Patch/MemberPatch.cs @@ -58,7 +58,7 @@ namespace PluralKit.Core public new void AssertIsValid() { - if (Name.IsPresent) + if (Name.Value != null) AssertValid(Name.Value, "name", Limits.MaxMemberNameLength); if (DisplayName.Value != null) AssertValid(DisplayName.Value, "display_name", Limits.MaxMemberNameLength); @@ -77,7 +77,7 @@ namespace PluralKit.Core if (ProxyTags.IsPresent && (ProxyTags.Value.Length > 100 || ProxyTags.Value.Any(tag => tag.ProxyString.IsLongerThan(100)))) // todo: have a better error for this - throw new ValidationError("proxy_tags"); + Errors.Add(new ValidationError("proxy_tags")); } #nullable disable @@ -86,8 +86,12 @@ namespace PluralKit.Core { var patch = new MemberPatch(); - if (o.ContainsKey("name") && o["name"].Type == JTokenType.Null) - throw new ValidationError("Member name can not be set to null."); + if (o.ContainsKey("name")) + { + patch.Name = o.Value("name").NullIfEmpty(); + if (patch.Name.Value == null) + patch.Errors.Add(new ValidationError("name", "Member name can not be set to null.")); + } if (o.ContainsKey("name")) patch.Name = o.Value("name"); if (o.ContainsKey("color")) patch.Color = o.Value("color").NullIfEmpty()?.ToLower(); @@ -101,7 +105,7 @@ namespace PluralKit.Core var res = DateTimeFormats.DateExportFormat.Parse(str); if (res.Success) patch.Birthday = res.Value; else if (str == null) patch.Birthday = null; - else throw new ValidationError("birthday"); + else patch.Errors.Add(new ValidationError("birthday")); } if (o.ContainsKey("pronouns")) patch.Pronouns = o.Value("pronouns").NullIfEmpty(); @@ -123,7 +127,7 @@ namespace PluralKit.Core if (o.ContainsKey("privacy")) { - var plevel = o.ParsePrivacy("privacy"); + var plevel = patch.ParsePrivacy(o, "privacy"); patch.Visibility = plevel; patch.NamePrivacy = plevel; @@ -136,14 +140,14 @@ namespace PluralKit.Core } else { - if (o.ContainsKey("visibility")) patch.Visibility = o.ParsePrivacy("visibility"); - if (o.ContainsKey("name_privacy")) patch.NamePrivacy = o.ParsePrivacy("name_privacy"); - if (o.ContainsKey("description_privacy")) patch.DescriptionPrivacy = o.ParsePrivacy("description_privacy"); - if (o.ContainsKey("avatar_privacy")) patch.AvatarPrivacy = o.ParsePrivacy("avatar_privacy"); - if (o.ContainsKey("birthday_privacy")) patch.BirthdayPrivacy = o.ParsePrivacy("birthday_privacy"); - if (o.ContainsKey("pronoun_privacy")) patch.PronounPrivacy = o.ParsePrivacy("pronoun_privacy"); + if (o.ContainsKey("visibility")) patch.Visibility = patch.ParsePrivacy(o, "visibility"); + if (o.ContainsKey("name_privacy")) patch.NamePrivacy = patch.ParsePrivacy(o, "name_privacy"); + if (o.ContainsKey("description_privacy")) patch.DescriptionPrivacy = patch.ParsePrivacy(o, "description_privacy"); + if (o.ContainsKey("avatar_privacy")) patch.AvatarPrivacy = patch.ParsePrivacy(o, "avatar_privacy"); + if (o.ContainsKey("birthday_privacy")) patch.BirthdayPrivacy = patch.ParsePrivacy(o, "birthday_privacy"); + if (o.ContainsKey("pronoun_privacy")) patch.PronounPrivacy = patch.ParsePrivacy(o, "pronoun_privacy"); // if (o.ContainsKey("color_privacy")) member.ColorPrivacy = o.ParsePrivacy("member"); - if (o.ContainsKey("metadata_privacy")) patch.MetadataPrivacy = o.ParsePrivacy("metadata_privacy"); + if (o.ContainsKey("metadata_privacy")) patch.MetadataPrivacy = patch.ParsePrivacy(o, "metadata_privacy"); } break; } @@ -161,25 +165,25 @@ namespace PluralKit.Core var privacy = o.Value("privacy"); if (privacy.ContainsKey("visibility")) - patch.Visibility = privacy.ParsePrivacy("visibility"); + patch.Visibility = patch.ParsePrivacy(privacy, "visibility"); if (privacy.ContainsKey("name_privacy")) - patch.NamePrivacy = privacy.ParsePrivacy("name_privacy"); + patch.NamePrivacy = patch.ParsePrivacy(privacy, "name_privacy"); if (privacy.ContainsKey("description_privacy")) - patch.DescriptionPrivacy = privacy.ParsePrivacy("description_privacy"); + patch.DescriptionPrivacy = patch.ParsePrivacy(privacy, "description_privacy"); if (privacy.ContainsKey("avatar_privacy")) - patch.AvatarPrivacy = privacy.ParsePrivacy("avatar_privacy"); + patch.AvatarPrivacy = patch.ParsePrivacy(privacy, "avatar_privacy"); if (privacy.ContainsKey("birthday_privacy")) - patch.BirthdayPrivacy = privacy.ParsePrivacy("birthday_privacy"); + patch.BirthdayPrivacy = patch.ParsePrivacy(privacy, "birthday_privacy"); if (privacy.ContainsKey("pronoun_privacy")) - patch.PronounPrivacy = privacy.ParsePrivacy("pronoun_privacy"); + patch.PronounPrivacy = patch.ParsePrivacy(privacy, "pronoun_privacy"); if (privacy.ContainsKey("metadata_privacy")) - patch.MetadataPrivacy = privacy.ParsePrivacy("metadata_privacy"); + patch.MetadataPrivacy = patch.ParsePrivacy(privacy, "metadata_privacy"); } break; diff --git a/PluralKit.Core/Models/Patch/PatchObject.cs b/PluralKit.Core/Models/Patch/PatchObject.cs index f1ddc84a..0a35a513 100644 --- a/PluralKit.Core/Models/Patch/PatchObject.cs +++ b/PluralKit.Core/Models/Patch/PatchObject.cs @@ -1,50 +1,46 @@ using System; +using System.Collections.Generic; using System.Text.RegularExpressions; +using Newtonsoft.Json.Linq; + using SqlKata; namespace PluralKit.Core { public abstract class PatchObject { + public List Errors = new(); public abstract Query Apply(Query q); public void AssertIsValid() { } - protected bool AssertValid(string input, string name, int maxLength, Func? validate = null) + protected void AssertValid(string input, string name, int maxLength, Func? validate = null) { if (input.Length > maxLength) - throw new FieldTooLongError(name, maxLength, input.Length); + Errors.Add(new FieldTooLongError(name, maxLength, input.Length)); if (validate != null && !validate(input)) - throw new ValidationError(name); - return true; + Errors.Add(new ValidationError(name)); } - protected bool AssertValid(string input, string name, string pattern) + protected void AssertValid(string input, string name, string pattern) { if (!Regex.IsMatch(input, pattern)) - throw new ValidationError(name); - return true; + Errors.Add(new ValidationError(name)); } - } - public class ValidationError: Exception - { - public ValidationError(string message) : base(message) { } - } - - public class FieldTooLongError: ValidationError - { - public string Name; - public int MaxLength; - public int ActualLength; - - public FieldTooLongError(string name, int maxLength, int actualLength) : - base($"{name} too long ({actualLength} > {maxLength})") + public PrivacyLevel ParsePrivacy(JObject o, string propertyName) { - Name = name; - MaxLength = maxLength; - ActualLength = actualLength; + var input = o.Value(propertyName); + + if (input == null) return PrivacyLevel.Public; + if (input == "") return PrivacyLevel.Private; + if (input == "private") return PrivacyLevel.Private; + if (input == "public") return PrivacyLevel.Public; + + Errors.Add(new ValidationError(propertyName)); + // unused, but the compiler will complain if this isn't here + return PrivacyLevel.Private; } } } \ No newline at end of file diff --git a/PluralKit.Core/Models/Patch/SystemGuildPatch.cs b/PluralKit.Core/Models/Patch/SystemGuildPatch.cs index 241b12d5..3410fc6b 100644 --- a/PluralKit.Core/Models/Patch/SystemGuildPatch.cs +++ b/PluralKit.Core/Models/Patch/SystemGuildPatch.cs @@ -36,8 +36,14 @@ namespace PluralKit.Core if (o.ContainsKey("proxying_enabled") && o["proxying_enabled"].Type != JTokenType.Null) patch.ProxyEnabled = o.Value("proxying_enabled"); - if (o.ContainsKey("autoproxy_mode") && o["autoproxy_mode"].ParseAutoproxyMode() is { } autoproxyMode) - patch.AutoproxyMode = autoproxyMode; + if (o.ContainsKey("autoproxy_mode")) + { + var (val, err) = o["autoproxy_mode"].ParseAutoproxyMode(); + if (err != null) + patch.Errors.Add(err); + else + patch.AutoproxyMode = val.Value; + } patch.AutoproxyMember = memberId; diff --git a/PluralKit.Core/Models/Patch/SystemPatch.cs b/PluralKit.Core/Models/Patch/SystemPatch.cs index bff45971..486fa778 100644 --- a/PluralKit.Core/Models/Patch/SystemPatch.cs +++ b/PluralKit.Core/Models/Patch/SystemPatch.cs @@ -69,7 +69,7 @@ namespace PluralKit.Core if (Color.Value != null) AssertValid(Color.Value, "color", "^[0-9a-fA-F]{6}$"); if (UiTz.IsPresent && DateTimeZoneProviders.Tzdb.GetZoneOrNull(UiTz.Value) == null) - throw new ValidationError("avatar_url"); + Errors.Add(new ValidationError("timezone")); } #nullable disable @@ -91,10 +91,10 @@ namespace PluralKit.Core { if (o.ContainsKey("tz")) patch.UiTz = o.Value("tz") ?? "UTC"; - if (o.ContainsKey("description_privacy")) patch.DescriptionPrivacy = o.ParsePrivacy("description_privacy"); - if (o.ContainsKey("member_list_privacy")) patch.MemberListPrivacy = o.ParsePrivacy("member_list_privacy"); - if (o.ContainsKey("front_privacy")) patch.FrontPrivacy = o.ParsePrivacy("front_privacy"); - if (o.ContainsKey("front_history_privacy")) patch.FrontHistoryPrivacy = o.ParsePrivacy("front_history_privacy"); + if (o.ContainsKey("description_privacy")) patch.DescriptionPrivacy = patch.ParsePrivacy(o, "description_privacy"); + if (o.ContainsKey("member_list_privacy")) patch.MemberListPrivacy = patch.ParsePrivacy(o, "member_list_privacy"); + if (o.ContainsKey("front_privacy")) patch.FrontPrivacy = patch.ParsePrivacy(o, "front_privacy"); + if (o.ContainsKey("front_history_privacy")) patch.FrontHistoryPrivacy = patch.ParsePrivacy(o, "front_history_privacy"); break; } @@ -105,16 +105,16 @@ namespace PluralKit.Core var privacy = o.Value("privacy"); if (privacy.ContainsKey("description_privacy")) - patch.DescriptionPrivacy = privacy.ParsePrivacy("description_privacy"); + patch.DescriptionPrivacy = patch.ParsePrivacy(privacy, "description_privacy"); if (privacy.ContainsKey("member_list_privacy")) - patch.DescriptionPrivacy = privacy.ParsePrivacy("member_list_privacy"); + patch.DescriptionPrivacy = patch.ParsePrivacy(privacy, "member_list_privacy"); if (privacy.ContainsKey("front_privacy")) - patch.DescriptionPrivacy = privacy.ParsePrivacy("front_privacy"); + patch.DescriptionPrivacy = patch.ParsePrivacy(privacy, "front_privacy"); if (privacy.ContainsKey("front_history_privacy")) - patch.DescriptionPrivacy = privacy.ParsePrivacy("front_history_privacy"); + patch.DescriptionPrivacy = patch.ParsePrivacy(privacy, "front_history_privacy"); } break; diff --git a/PluralKit.Core/Models/Privacy/PrivacyLevel.cs b/PluralKit.Core/Models/Privacy/PrivacyLevel.cs index c04cf74c..5feffae7 100644 --- a/PluralKit.Core/Models/Privacy/PrivacyLevel.cs +++ b/PluralKit.Core/Models/Privacy/PrivacyLevel.cs @@ -1,7 +1,5 @@ using System; -using Newtonsoft.Json.Linq; - namespace PluralKit.Core { public enum PrivacyLevel @@ -42,18 +40,5 @@ namespace PluralKit.Core } public static string ToJsonString(this PrivacyLevel level) => level.LevelName(); - - public static PrivacyLevel ParsePrivacy(this JObject o, string propertyName) - { - var input = o.Value(propertyName); - - if (input == null) return PrivacyLevel.Public; - if (input == "") return PrivacyLevel.Private; - if (input == "private") return PrivacyLevel.Private; - if (input == "public") return PrivacyLevel.Public; - - throw new ValidationError(propertyName); - } - } } \ No newline at end of file diff --git a/PluralKit.Core/Models/SystemGuildSettings.cs b/PluralKit.Core/Models/SystemGuildSettings.cs index 1d47d15e..14750760 100644 --- a/PluralKit.Core/Models/SystemGuildSettings.cs +++ b/PluralKit.Core/Models/SystemGuildSettings.cs @@ -41,27 +41,27 @@ namespace PluralKit.Core return o; } - public static AutoproxyMode? ParseAutoproxyMode(this JToken o) + public static (AutoproxyMode?, ValidationError?) ParseAutoproxyMode(this JToken o) { if (o.Type == JTokenType.Null) - return AutoproxyMode.Off; + return (AutoproxyMode.Off, null); else if (o.Type != JTokenType.String) - return null; + return (null, new ValidationError("autoproxy_mode")); var value = o.Value(); switch (value) { case "off": - return AutoproxyMode.Off; + return (AutoproxyMode.Off, null); case "front": - return AutoproxyMode.Front; + return (AutoproxyMode.Front, null); case "latch": - return AutoproxyMode.Latch; + return (AutoproxyMode.Latch, null); case "member": - return AutoproxyMode.Member; + return (AutoproxyMode.Member, null); default: - throw new ValidationError($"Value '{value}' is not a valid autoproxy mode."); + return (null, new ValidationError("autoproxy_mode", $"Value '{value}' is not a valid autoproxy mode.")); } } } diff --git a/PluralKit.Core/Utils/BulkImporter/PluralKitImport.cs b/PluralKit.Core/Utils/BulkImporter/PluralKitImport.cs index 1412d612..6eaf0e33 100644 --- a/PluralKit.Core/Utils/BulkImporter/PluralKitImport.cs +++ b/PluralKit.Core/Utils/BulkImporter/PluralKitImport.cs @@ -20,13 +20,17 @@ namespace PluralKit.Core { var patch = SystemPatch.FromJSON(importFile); - try + patch.AssertIsValid(); + if (patch.Errors.Count > 0) { - patch.AssertIsValid(); - } - catch (ValidationError e) - { - throw new ImportException($"Field {e.Message} in export file is invalid."); + var err = patch.Errors[0]; + if (err is FieldTooLongError) + throw new ImportException($"Field {err.Key} in export file is too long " + + $"({(err as FieldTooLongError).ActualLength} > {(err as FieldTooLongError).MaxLength})."); + else if (err.Text != null) + throw new ImportException(err.Text); + else + throw new ImportException($"Field {err.Key} in export file is invalid."); } await _repo.UpdateSystem(_system.Id, patch, _conn); @@ -87,17 +91,18 @@ namespace PluralKit.Core ); var patch = MemberPatch.FromJSON(member); - try + + patch.AssertIsValid(); + if (patch.Errors.Count > 0) { - patch.AssertIsValid(); - } - catch (FieldTooLongError e) - { - throw new ImportException($"Field {e.Name} in member {referenceName} is too long ({e.ActualLength} > {e.MaxLength})."); - } - catch (ValidationError e) - { - throw new ImportException($"Field {e.Message} in member {referenceName} is invalid."); + var err = patch.Errors[0]; + if (err is FieldTooLongError) + throw new ImportException($"Field {err.Key} in member {name} is too long " + + $"({(err as FieldTooLongError).ActualLength} > {(err as FieldTooLongError).MaxLength})."); + else if (err.Text != null) + throw new ImportException($"member {name}: {err.Text}"); + else + throw new ImportException($"Field {err.Key} in member {name} is invalid."); } MemberId? memberId = found; @@ -128,17 +133,18 @@ namespace PluralKit.Core ); var patch = GroupPatch.FromJson(group); - try + + patch.AssertIsValid(); + if (patch.Errors.Count > 0) { - patch.AssertIsValid(); - } - catch (FieldTooLongError e) - { - throw new ImportException($"Field {e.Name} in group {referenceName} is too long ({e.ActualLength} > {e.MaxLength})."); - } - catch (ValidationError e) - { - throw new ImportException($"Field {e.Message} in group {referenceName} is invalid."); + var err = patch.Errors[0]; + if (err is FieldTooLongError) + throw new ImportException($"Field {err.Key} in group {name} is too long " + + $"({(err as FieldTooLongError).ActualLength} > {(err as FieldTooLongError).MaxLength})."); + else if (err.Text != null) + throw new ImportException($"group {name}: {err.Text}"); + else + throw new ImportException($"Field {err.Key} in group {name} is invalid."); } GroupId? groupId = found; diff --git a/PluralKit.Core/Utils/BulkImporter/TupperboxImport.cs b/PluralKit.Core/Utils/BulkImporter/TupperboxImport.cs index 43d8eb3f..7ebf305f 100644 --- a/PluralKit.Core/Utils/BulkImporter/TupperboxImport.cs +++ b/PluralKit.Core/Utils/BulkImporter/TupperboxImport.cs @@ -87,6 +87,19 @@ namespace PluralKit.Core patch.DisplayName = $"{name} {tag}"; } + patch.AssertIsValid(); + if (patch.Errors.Count > 0) + { + var err = patch.Errors[0]; + if (err is FieldTooLongError) + throw new ImportException($"Field {err.Key} in tupper {name} is too long " + + $"({(err as FieldTooLongError).ActualLength} > {(err as FieldTooLongError).MaxLength})."); + else if (err.Text != null) + throw new ImportException($"tupper {name}: {err.Text}"); + else + throw new ImportException($"Field {err.Key} in tupper {name} is invalid."); + } + var isNewMember = false; if (!_existingMemberNames.TryGetValue(name, out var memberId)) { @@ -101,19 +114,6 @@ namespace PluralKit.Core _logger.Debug("Importing member with identifier {FileId} to system {System} (is creating new member? {IsCreatingNewMember})", name, _system.Id, isNewMember); - try - { - patch.AssertIsValid(); - } - catch (FieldTooLongError e) - { - throw new ImportException($"Field {e.Name} in tupper {name} is too long ({e.ActualLength} > {e.MaxLength})."); - } - catch (ValidationError e) - { - throw new ImportException($"Field {e.Message} in tupper {name} is invalid."); - } - await _repo.UpdateMember(memberId, patch, _conn); return (lastSetTag, multipleTags, hasGroup);