feat(apiv2): better model validation error UX
This commit is contained in:
@@ -58,21 +58,21 @@ namespace PluralKit.API
|
||||
await using var tx = await conn.BeginTransactionAsync();
|
||||
var member = await _repo.CreateMember(systemId, properties.Value<string>("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);
|
||||
|
@@ -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);
|
||||
|
@@ -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());
|
||||
|
@@ -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<ValidationError> _errors { get; init; }
|
||||
public ModelParseError(IEnumerable<ValidationError> 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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
||||
|
Reference in New Issue
Block a user