feat(apiv2): better model validation error UX
This commit is contained in:
@@ -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<string>("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<string>("name");
|
||||
if (o.ContainsKey("display_name")) patch.DisplayName = o.Value<string>("display_name").NullIfEmpty();
|
||||
if (o.ContainsKey("description")) patch.Description = o.Value<string>("description").NullIfEmpty();
|
||||
if (o.ContainsKey("icon")) patch.Icon = o.Value<string>("icon").NullIfEmpty();
|
||||
@@ -74,16 +77,16 @@ namespace PluralKit.Core
|
||||
var privacy = o.Value<JObject>("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;
|
||||
|
@@ -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<string>("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<string>("name");
|
||||
if (o.ContainsKey("color")) patch.Color = o.Value<string>("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<string>("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<JObject>("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;
|
||||
|
@@ -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<ValidationError> Errors = new();
|
||||
public abstract Query Apply(Query q);
|
||||
|
||||
public void AssertIsValid() { }
|
||||
|
||||
protected bool AssertValid(string input, string name, int maxLength, Func<string, bool>? validate = null)
|
||||
protected void AssertValid(string input, string name, int maxLength, Func<string, bool>? 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<string>(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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -36,8 +36,14 @@ namespace PluralKit.Core
|
||||
if (o.ContainsKey("proxying_enabled") && o["proxying_enabled"].Type != JTokenType.Null)
|
||||
patch.ProxyEnabled = o.Value<bool>("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;
|
||||
|
||||
|
@@ -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<string>("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<JObject>("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;
|
||||
|
Reference in New Issue
Block a user