diff --git a/src/Tgstation.Server.Host/Authority/Core/AuthorityBase.cs b/src/Tgstation.Server.Host/Authority/Core/AuthorityBase.cs index 898c701eb8..f0e39151a3 100644 --- a/src/Tgstation.Server.Host/Authority/Core/AuthorityBase.cs +++ b/src/Tgstation.Server.Host/Authority/Core/AuthorityBase.cs @@ -53,6 +53,16 @@ namespace Tgstation.Server.Host.Authority.Core new ErrorMessageResponse(), HttpFailureResponse.Unauthorized); + /// + /// Generates a type . + /// + /// The of the . + /// A new, errored . + protected static AuthorityResponse Gone() + => new( + new ErrorMessageResponse(), + HttpFailureResponse.Gone); + /// /// Generates a type . /// @@ -73,6 +83,17 @@ namespace Tgstation.Server.Host.Authority.Core new ErrorMessageResponse(), HttpFailureResponse.NotFound); + /// + /// Generates a type . + /// + /// The of the . + /// The . + /// A new, errored . + protected static AuthorityResponse Conflict(ErrorCode errorCode) + => new( + new ErrorMessageResponse(errorCode), + HttpFailureResponse.Conflict); + /// /// Initializes a new instance of the class. /// diff --git a/src/Tgstation.Server.Host/Authority/Core/AuthorityResponse{TResult}.cs b/src/Tgstation.Server.Host/Authority/Core/AuthorityResponse{TResult}.cs index 44376f488c..9c2cfd295c 100644 --- a/src/Tgstation.Server.Host/Authority/Core/AuthorityResponse{TResult}.cs +++ b/src/Tgstation.Server.Host/Authority/Core/AuthorityResponse{TResult}.cs @@ -13,10 +13,16 @@ namespace Tgstation.Server.Host.Authority.Core public sealed class AuthorityResponse : AuthorityResponse { /// - [MemberNotNullWhen(true, nameof(Result))] - [MemberNotNullWhen(true, nameof(SuccessResponse))] + [MemberNotNullWhen(true, nameof(IsNoContent))] public override bool Success => base.Success; + /// + /// Checks if a the is a no content result. Only set on . + /// + [MemberNotNullWhen(false, nameof(Result))] + [MemberNotNullWhen(false, nameof(Result))] + public bool? IsNoContent => Success ? Result == null : null; + /// /// The success . /// @@ -47,5 +53,13 @@ namespace Tgstation.Server.Host.Authority.Core Result = result ?? throw new ArgumentNullException(nameof(result)); SuccessResponse = httpResponse; } + + /// + /// Initializes a new instance of the class. + /// + /// This generates an HTTP 204 response. + public AuthorityResponse() + { + } } } diff --git a/src/Tgstation.Server.Host/Authority/Core/GraphQLAuthorityInvoker{TAuthority}.cs b/src/Tgstation.Server.Host/Authority/Core/GraphQLAuthorityInvoker{TAuthority}.cs index 794bd467ef..a25a04e278 100644 --- a/src/Tgstation.Server.Host/Authority/Core/GraphQLAuthorityInvoker{TAuthority}.cs +++ b/src/Tgstation.Server.Host/Authority/Core/GraphQLAuthorityInvoker{TAuthority}.cs @@ -13,11 +13,12 @@ namespace Tgstation.Server.Host.Authority.Core /// Throws a for errored s. /// /// The potentially errored . - static void ThrowGraphQLErrorIfNecessary(AuthorityResponse authorityResponse) + /// If an error should be raised for and failures. + static void ThrowGraphQLErrorIfNecessary(AuthorityResponse authorityResponse, bool errorOnMissing) { if (authorityResponse.Success - || authorityResponse.FailureResponse.Value == HttpFailureResponse.NotFound - || authorityResponse.FailureResponse.Value == HttpFailureResponse.Gone) + || ((authorityResponse.FailureResponse.Value == HttpFailureResponse.NotFound + || authorityResponse.FailureResponse.Value == HttpFailureResponse.Gone) && !errorOnMissing)) return; var fallbackString = authorityResponse.FailureResponse.ToString()!; @@ -39,33 +40,41 @@ namespace Tgstation.Server.Host.Authority.Core ArgumentNullException.ThrowIfNull(authorityInvoker); var authorityResponse = await authorityInvoker(Authority); - ThrowGraphQLErrorIfNecessary(authorityResponse); + ThrowGraphQLErrorIfNecessary(authorityResponse, true); } /// - async ValueTask IGraphQLAuthorityInvoker.Invoke(Func>> authorityInvoker) + async ValueTask IGraphQLAuthorityInvoker.InvokeAllowMissing(Func>> authorityInvoker) where TApiModel : default { ArgumentNullException.ThrowIfNull(authorityInvoker); var authorityResponse = await authorityInvoker(Authority); - ThrowGraphQLErrorIfNecessary(authorityResponse); + ThrowGraphQLErrorIfNecessary(authorityResponse, false); return authorityResponse.Result; } /// - async ValueTask IGraphQLAuthorityInvoker.InvokeTransformable(Func>> authorityInvoker) + async ValueTask IGraphQLAuthorityInvoker.InvokeTransformableAllowMissing(Func>> authorityInvoker) where TApiModel : default { ArgumentNullException.ThrowIfNull(authorityInvoker); var authorityResponse = await authorityInvoker(Authority); - ThrowGraphQLErrorIfNecessary(authorityResponse); + ThrowGraphQLErrorIfNecessary(authorityResponse, false); var result = authorityResponse.Result; if (result == null) return default; return result.ToApi(); } + + /// + ValueTask IGraphQLAuthorityInvoker.Invoke(Func>> authorityInvoker) + => ((IGraphQLAuthorityInvoker)this).InvokeAllowMissing(authorityInvoker)!; + + /// + ValueTask IGraphQLAuthorityInvoker.InvokeTransformable(Func>> authorityInvoker) + => ((IGraphQLAuthorityInvoker)this).InvokeTransformableAllowMissing(authorityInvoker)!; } } diff --git a/src/Tgstation.Server.Host/Authority/Core/RestAuthorityInvoker{TAuthority}.cs b/src/Tgstation.Server.Host/Authority/Core/RestAuthorityInvoker{TAuthority}.cs index 83911ecb56..680dc98a4e 100644 --- a/src/Tgstation.Server.Host/Authority/Core/RestAuthorityInvoker{TAuthority}.cs +++ b/src/Tgstation.Server.Host/Authority/Core/RestAuthorityInvoker{TAuthority}.cs @@ -14,18 +14,22 @@ namespace Tgstation.Server.Host.Authority.Core where TAuthority : IAuthority { /// - /// Create an for a given successfuly API . + /// Create an for a given successfuly. /// /// The to use. - /// The resulting from the . + /// A transforming the from the into the . /// The . /// An for the . /// The result returned in the . /// The REST API result model built from . - static IActionResult CreateSuccessfulActionResult(ApiController controller, TApiModel result, AuthorityResponse authorityResponse) + static IActionResult CreateSuccessfulActionResult(ApiController controller, Func resultTransformer, AuthorityResponse authorityResponse) where TApiModel : notnull { + if (authorityResponse.IsNoContent!.Value) + return controller.NoContent(); + var successResponse = authorityResponse.SuccessResponse; + var result = resultTransformer(authorityResponse.Result!); return successResponse switch { HttpSuccessResponse.Ok => controller.Json(result), @@ -95,8 +99,7 @@ namespace Tgstation.Server.Host.Authority.Core if (erroredResult != null) return erroredResult; - var result = authorityResponse.Result!; - return CreateSuccessfulActionResult(controller, result, authorityResponse); + return CreateSuccessfulActionResult(controller, result => result, authorityResponse); } /// @@ -110,9 +113,7 @@ namespace Tgstation.Server.Host.Authority.Core if (erroredResult != null) return erroredResult; - var result = authorityResponse.Result!; - var apiModel = result.ToApi(); - return CreateSuccessfulActionResult(controller, apiModel, authorityResponse); + return CreateSuccessfulActionResult(controller, result => result.ToApi(), authorityResponse); } } } diff --git a/src/Tgstation.Server.Host/Authority/IGraphQLAuthorityInvoker{TAuthority}.cs b/src/Tgstation.Server.Host/Authority/IGraphQLAuthorityInvoker{TAuthority}.cs index eb179caf9b..edface219a 100644 --- a/src/Tgstation.Server.Host/Authority/IGraphQLAuthorityInvoker{TAuthority}.cs +++ b/src/Tgstation.Server.Host/Authority/IGraphQLAuthorityInvoker{TAuthority}.cs @@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.Authority /// The resulting of the return value. /// The returning a resulting in the . /// A resulting in the generated for the resulting . - ValueTask Invoke(Func>> authorityInvoker) + ValueTask InvokeAllowMissing(Func>> authorityInvoker) where TResult : TApiModel where TApiModel : notnull; @@ -39,7 +39,31 @@ namespace Tgstation.Server.Host.Authority /// The for converting s to s. /// The returning a resulting in the . /// A resulting in the generated for the resulting . - ValueTask InvokeTransformable(Func>> authorityInvoker) + ValueTask InvokeTransformableAllowMissing(Func>> authorityInvoker) + where TResult : notnull, IApiTransformable + where TApiModel : notnull + where TTransformer : ITransformer, new(); + + /// + /// Invoke a method and get the non-nullable result. + /// + /// The . + /// The resulting of the return value. + /// The returning a resulting in the . + /// A resulting in the generated for the resulting . + ValueTask Invoke(Func>> authorityInvoker) + where TResult : TApiModel + where TApiModel : notnull; + + /// + /// Invoke a method and get the non-nullable result. + /// + /// The . + /// The resulting of the return value. + /// The for converting s to s. + /// The returning a resulting in the . + /// A resulting in the generated for the resulting . + ValueTask InvokeTransformable(Func>> authorityInvoker) where TResult : notnull, IApiTransformable where TApiModel : notnull where TTransformer : ITransformer, new();