| | | 1 | | using ArturRios.Heimdall.Shared.Messages; |
| | | 2 | | using ArturRios.Output; |
| | | 3 | | using ArturRios.Util.WebApi.AspNetCore; |
| | | 4 | | using Microsoft.AspNetCore.Mvc.Filters; |
| | | 5 | | |
| | | 6 | | namespace ArturRios.Heimdall.WebApi.Binding; |
| | | 7 | | |
| | | 8 | | /// <summary> |
| | | 9 | | /// Answers <c>400 Bad Request</c> with <see cref="RequestMessages.Malformed" /> when the request |
| | | 10 | | /// could not be bound to the action's parameters — a body that is not JSON, an empty one the |
| | | 11 | | /// action requires, or a value of the wrong type in the body or query string (NFR-10, |
| | | 12 | | /// issue #186). |
| | | 13 | | /// </summary> |
| | | 14 | | /// <remarks> |
| | | 15 | | /// <para> |
| | | 16 | | /// The controllers are not <c>[ApiController]</c>, so nothing refuses a failed binding: the |
| | | 17 | | /// action ran anyway with a null or half-filled argument, which surfaced as a 500 (a body that |
| | | 18 | | /// is not JSON), or as a request executed with a value the caller never sent (a query filter |
| | | 19 | | /// of the wrong type silently dropped, answering 200 unfiltered). Adding the attribute would |
| | | 20 | | /// change every action's other conventions at once; this filter states only the one rule. |
| | | 21 | | /// </para> |
| | | 22 | | /// <para> |
| | | 23 | | /// An action filter, because that is the stage that runs after model binding (which fills |
| | | 24 | | /// <c>ModelState</c>) and after every authorization filter. An authorization filter would run |
| | | 25 | | /// before binding and see nothing; and running after them keeps an unauthenticated or |
| | | 26 | | /// forbidden caller answering 401 or 403 rather than learning anything about the shape of an |
| | | 27 | | /// endpoint's input. Global, so no endpoint can be added that forgets it. |
| | | 28 | | /// </para> |
| | | 29 | | /// <para> |
| | | 30 | | /// The response carries no binding detail: the framework's own messages name properties and |
| | | 31 | | /// types of the internal model, which the caller has no use for. |
| | | 32 | | /// </para> |
| | | 33 | | /// </remarks> |
| | | 34 | | public class MalformedRequestFilter : IActionFilter |
| | | 35 | | { |
| | | 36 | | public void OnActionExecuting(ActionExecutingContext context) |
| | | 37 | | { |
| | 1220 | 38 | | if (context.ModelState.IsValid) |
| | | 39 | | { |
| | 1208 | 40 | | return; |
| | | 41 | | } |
| | | 42 | | |
| | 12 | 43 | | context.Result = DataOutput<object?>.New |
| | 12 | 44 | | .WithError(RequestMessages.Malformed) |
| | 12 | 45 | | .ToActionResult(statusMap: RequestMessageMap.StatusCodes) |
| | 12 | 46 | | .Result; |
| | 12 | 47 | | } |
| | | 48 | | |
| | | 49 | | public void OnActionExecuted(ActionExecutedContext context) |
| | | 50 | | { |
| | 1208 | 51 | | } |
| | | 52 | | } |