220 lines
7 KiB
C#
220 lines
7 KiB
C#
// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions)
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published
|
|
// by the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
using System.Net;
|
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
namespace Foxnouns.Backend;
|
|
|
|
public class FoxnounsError(string message, Exception? inner = null) : Exception(message)
|
|
{
|
|
public Exception? Inner => inner;
|
|
|
|
public class DatabaseError(string message, Exception? inner = null)
|
|
: FoxnounsError(message, inner);
|
|
|
|
public class UnknownEntityError(Type entityType, Exception? inner = null)
|
|
: DatabaseError($"Entity of type {entityType.Name} not found", inner);
|
|
|
|
public class RemoteAuthError(string message, string? errorBody = null, Exception? inner = null)
|
|
: FoxnounsError(message, inner)
|
|
{
|
|
public string? ErrorBody => errorBody;
|
|
|
|
public override string ToString() =>
|
|
$"{Message}: {ErrorBody} {(Inner != null ? $"({Inner})" : "")}";
|
|
}
|
|
}
|
|
|
|
public class ApiError(
|
|
string message,
|
|
HttpStatusCode? statusCode = null,
|
|
ErrorCode? errorCode = null
|
|
) : FoxnounsError(message)
|
|
{
|
|
public readonly HttpStatusCode StatusCode = statusCode ?? HttpStatusCode.InternalServerError;
|
|
public readonly ErrorCode ErrorCode = errorCode ?? ErrorCode.InternalServerError;
|
|
|
|
public class Unauthorized(string message, ErrorCode errorCode = ErrorCode.AuthenticationError)
|
|
: ApiError(message, HttpStatusCode.Unauthorized, errorCode);
|
|
|
|
public class Forbidden(
|
|
string message,
|
|
IEnumerable<string>? scopes = null,
|
|
ErrorCode errorCode = ErrorCode.Forbidden
|
|
) : ApiError(message, HttpStatusCode.Forbidden, errorCode)
|
|
{
|
|
public readonly string[] Scopes = scopes?.ToArray() ?? [];
|
|
}
|
|
|
|
public class BadRequest(
|
|
string message,
|
|
IReadOnlyDictionary<string, IEnumerable<ValidationError>>? errors = null
|
|
) : ApiError(message, HttpStatusCode.BadRequest)
|
|
{
|
|
public BadRequest(string message, string field, object? actualValue)
|
|
: this(
|
|
"Error validating input",
|
|
new Dictionary<string, IEnumerable<ValidationError>>
|
|
{
|
|
{ field, [ValidationError.GenericValidationError(message, actualValue)] },
|
|
}
|
|
) { }
|
|
|
|
public JObject ToJson()
|
|
{
|
|
var o = new JObject
|
|
{
|
|
{ "status", (int)HttpStatusCode.BadRequest },
|
|
{ "message", Message },
|
|
{ "code", "BAD_REQUEST" },
|
|
};
|
|
if (errors == null)
|
|
return o;
|
|
|
|
var a = new JArray();
|
|
foreach (KeyValuePair<string, IEnumerable<ValidationError>> error in errors)
|
|
{
|
|
var errorObj = new JObject
|
|
{
|
|
{ "key", error.Key },
|
|
{ "errors", JArray.FromObject(error.Value) },
|
|
};
|
|
a.Add(errorObj);
|
|
}
|
|
|
|
o.Add("errors", a);
|
|
return o;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A special version of BadRequest that ASP.NET generates when it encounters an invalid request.
|
|
/// Any other methods should use <see cref="ApiError.BadRequest" /> instead.
|
|
/// </summary>
|
|
public class AspBadRequest(string message, ModelStateDictionary? modelState = null)
|
|
: ApiError(message, HttpStatusCode.BadRequest)
|
|
{
|
|
public JObject ToJson()
|
|
{
|
|
var o = new JObject
|
|
{
|
|
{ "status", (int)HttpStatusCode.BadRequest },
|
|
{ "message", Message },
|
|
{ "code", "BAD_REQUEST" },
|
|
};
|
|
if (modelState == null)
|
|
return o;
|
|
|
|
var a = new JArray();
|
|
foreach (
|
|
KeyValuePair<string, ModelStateEntry?> error in modelState.Where(e =>
|
|
e.Value is { Errors.Count: > 0 }
|
|
)
|
|
)
|
|
{
|
|
var errorObj = new JObject
|
|
{
|
|
{ "key", error.Key },
|
|
{
|
|
"errors",
|
|
new JArray(
|
|
error.Value!.Errors.Select(e => new JObject
|
|
{
|
|
{ "message", e.ErrorMessage },
|
|
})
|
|
)
|
|
},
|
|
};
|
|
a.Add(errorObj);
|
|
}
|
|
|
|
o.Add("errors", a);
|
|
return o;
|
|
}
|
|
}
|
|
|
|
public class NotFound(string message, ErrorCode? code = null)
|
|
: ApiError(message, HttpStatusCode.NotFound, code);
|
|
|
|
public class AuthenticationError(string message) : ApiError(message, HttpStatusCode.BadRequest);
|
|
}
|
|
|
|
public enum ErrorCode
|
|
{
|
|
InternalServerError,
|
|
Forbidden,
|
|
BadRequest,
|
|
AuthenticationError,
|
|
AuthenticationRequired,
|
|
MissingScopes,
|
|
GenericApiError,
|
|
UserNotFound,
|
|
MemberNotFound,
|
|
AccountAlreadyLinked,
|
|
LastAuthMethod,
|
|
InvalidReportTarget,
|
|
InvalidWarningTarget,
|
|
}
|
|
|
|
public class ValidationError
|
|
{
|
|
public required string Message { get; init; }
|
|
|
|
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
|
public int? MinLength { get; init; }
|
|
|
|
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
|
public int? MaxLength { get; init; }
|
|
|
|
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
|
public int? ActualLength { get; init; }
|
|
|
|
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
|
public IEnumerable<object>? AllowedValues { get; init; }
|
|
|
|
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
|
public object? ActualValue { get; init; }
|
|
|
|
public static ValidationError LengthError(
|
|
string message,
|
|
int minLength,
|
|
int maxLength,
|
|
int actualLength
|
|
) =>
|
|
new()
|
|
{
|
|
Message = message,
|
|
MinLength = minLength,
|
|
MaxLength = maxLength,
|
|
ActualLength = actualLength,
|
|
};
|
|
|
|
public static ValidationError DisallowedValueError(
|
|
string message,
|
|
IEnumerable<object> allowedValues,
|
|
object actualValue
|
|
) =>
|
|
new()
|
|
{
|
|
Message = message,
|
|
AllowedValues = allowedValues,
|
|
ActualValue = actualValue,
|
|
};
|
|
|
|
public static ValidationError GenericValidationError(string message, object? actualValue) =>
|
|
new() { Message = message, ActualValue = actualValue };
|
|
}
|