| | | 1 | | namespace ZhEaIsNsAaBn.Utilities.APIResponses; |
| | | 2 | | |
| | | 3 | | /// <summary> |
| | | 4 | | /// The JSON envelope every API endpoint returns: HTTP <see cref="Status"/>, <see cref="Errors"/>, a <see cref="Message" |
| | | 5 | | /// <see cref="TraceId"/> / <see cref="TransId"/> that tie the response to the logs. |
| | | 6 | | /// </summary> |
| | | 7 | | public class BaseResponse |
| | | 8 | | { |
| | | 9 | | /// <summary>The HTTP status code, also used as the response status by <see cref="ToAPI"/>.</summary> |
| | | 10 | | public int Status { get; set; } |
| | | 11 | | |
| | | 12 | | /// <summary>The errors; <see langword="null"/> on success.</summary> |
| | | 13 | | public List<Error>? Errors { get; set; } |
| | | 14 | | |
| | | 15 | | /// <summary>An optional message for the caller.</summary> |
| | | 16 | | public string? Message { get; set; } |
| | | 17 | | |
| | | 18 | | /// <summary>The transaction id from <c>HttpContext.Items["transId"]</c>, set by <see cref="ToAPI"/>.</summary> |
| | | 19 | | public string? TransId { get; set; } |
| | | 20 | | |
| | | 21 | | /// <summary>The trace id of the current <see cref="Activity"/>, else the request's <see cref="HttpContext.TraceIden |
| | 362 | 22 | | public string? TraceId { get; set; } = Activity.Current?.TraceId.ToString(); |
| | | 23 | | |
| | | 24 | | |
| | | 25 | | /// <summary>A 400 response with <paramref name="errors"/>.</summary> |
| | | 26 | | /// <param name="errors">The errors.</param> |
| | | 27 | | /// <returns>The response.</returns> |
| | 9 | 28 | | public static BaseResponse BadRequest( List<Error> errors ) => new() { Status = (int) HttpStatusCode.BadRequest, Err |
| | | 29 | | |
| | | 30 | | /// <summary>A 400 response carrying the validation <paramref name="errors"/>, or 200 when there are none.</summary> |
| | | 31 | | /// <param name="errors">The column errors.</param> |
| | | 32 | | /// <returns>The response.</returns> |
| | | 33 | | public static BaseResponse<ValidationResponse> FromValidation( List<ColumnValidationError>? errors ) |
| | 9 | 34 | | => errors is { Count: > 0 } |
| | 9 | 35 | | ? new BaseResponse<ValidationResponse> { Status = (int) HttpStatusCode.BadRequest, Data = errors } |
| | 9 | 36 | | : new BaseResponse<ValidationResponse> { Status = (int) HttpStatusCode.OK }; |
| | | 37 | | |
| | | 38 | | /// <summary>A 400 response carrying the validation <paramref name="error"/>, or 200 when it is <see langword="null" |
| | | 39 | | /// <param name="error">The column error.</param> |
| | | 40 | | /// <returns>The response.</returns> |
| | | 41 | | public static BaseResponse<ValidationResponse> FromValidation( ColumnValidationError? error ) |
| | 0 | 42 | | => error is not null |
| | 0 | 43 | | ? new BaseResponse<ValidationResponse> { Status = (int) HttpStatusCode.BadRequest, Data = error } |
| | 0 | 44 | | : new BaseResponse<ValidationResponse> { Status = (int) HttpStatusCode.OK }; |
| | | 45 | | |
| | | 46 | | /// <summary>A 200 response.</summary> |
| | | 47 | | /// <returns>The response.</returns> |
| | 3 | 48 | | public static BaseResponse Ok() => new() { Status = (int) HttpStatusCode.OK }; |
| | | 49 | | |
| | | 50 | | /// <summary> |
| | | 51 | | /// 200 on success; otherwise the errors with the status mapped from the first error (see <see cref="APIResponsesSet |
| | | 52 | | /// defaults here, the app's settings when <see cref="ToAPI"/> executes). |
| | | 53 | | /// </summary> |
| | | 54 | | /// <param name="response">The layer result.</param> |
| | | 55 | | /// <exception cref="ArgumentNullException"><paramref name="response"/> is <see langword="null"/>.</exception> |
| | | 56 | | public static explicit operator BaseResponse( LayerResponse response ) |
| | | 57 | | { |
| | 117 | 58 | | ArgumentNullException.ThrowIfNull( response ); |
| | | 59 | | |
| | 117 | 60 | | return response.IsSuccess ? new BaseResponse { Status = (int) HttpStatusCode.OK } : FromErrors<BaseResponse>( re |
| | | 61 | | } |
| | | 62 | | |
| | | 63 | | /// <summary> |
| | | 64 | | /// Success for a 2xx <see cref="Status"/>; otherwise a failure with the <see cref="Errors"/>, or, when there are no |
| | | 65 | | /// <c>HttpStatusError</c> error whose additional data holds the <c>Status</c>. |
| | | 66 | | /// </summary> |
| | | 67 | | /// <param name="response">The API response.</param> |
| | | 68 | | /// <exception cref="ArgumentNullException"><paramref name="response"/> is <see langword="null"/>.</exception> |
| | | 69 | | public static explicit operator LayerResponse( BaseResponse response ) |
| | | 70 | | { |
| | 15 | 71 | | ArgumentNullException.ThrowIfNull( response ); |
| | | 72 | | |
| | 18 | 73 | | if ( response.Status is >= 200 and < 300 ) { return LayerResponse.Success(); } |
| | | 74 | | |
| | | 75 | | |
| | 12 | 76 | | var errors = response.Errors?.Where( e => e != Error.None ).ToList() ?? []; |
| | | 77 | | |
| | | 78 | | |
| | 12 | 79 | | return LayerResponse.Failure( errors.Count > 0 ? errors : [ HttpStatusError( response.Status ) ] ); |
| | | 80 | | } |
| | | 81 | | |
| | | 82 | | /// <summary>A failed response with one error; status mapped from it (see <see cref="APIResponsesSettings"/>).</summ |
| | | 83 | | /// <param name="error">The error.</param> |
| | 48 | 84 | | public static implicit operator BaseResponse( Error error ) => FromErrors<BaseResponse>( [ error ] ); |
| | | 85 | | |
| | | 86 | | /// <summary>A failed response with the errors; status mapped from the first one (see <see cref="APIResponsesSetting |
| | | 87 | | /// <param name="errors">The errors.</param> |
| | 85 | 88 | | public static implicit operator BaseResponse( List<Error> errors ) => FromErrors<BaseResponse>( errors ); |
| | | 89 | | |
| | | 90 | | /// <summary> |
| | | 91 | | /// Wraps the response in a <see cref="BaseResponseResult"/> with <see cref="Status"/> as the HTTP status code. A st |
| | | 92 | | /// from errors is mapped again with the app's <see cref="APIResponsesSettings"/> when the result executes. |
| | | 93 | | /// </summary> |
| | | 94 | | /// <param name="httpContext">The request; when given, fills <see cref="TraceId"/> (if empty) and <see cref="TransId |
| | | 95 | | /// <returns>The action result.</returns> |
| | | 96 | | public ObjectResult ToAPI( HttpContext? httpContext ) |
| | | 97 | | { |
| | 69 | 98 | | if ( httpContext is not null ) |
| | | 99 | | { |
| | 69 | 100 | | TraceId ??= httpContext.TraceIdentifier; |
| | 69 | 101 | | TransId = httpContext.Items[ "transId" ]?.ToString() ?? string.Empty; |
| | | 102 | | } |
| | | 103 | | |
| | | 104 | | |
| | 69 | 105 | | return new BaseResponseResult( this ); |
| | | 106 | | } |
| | | 107 | | |
| | | 108 | | |
| | | 109 | | /// <summary><see langword="true"/> when <see cref="Status"/> was mapped from <see cref="Errors"/> (not set explicit |
| | | 110 | | internal bool StatusFromErrors { get; private protected set; } |
| | | 111 | | |
| | | 112 | | |
| | | 113 | | /// <summary>A failed response whose status comes from the default mapping of <paramref name="errors"/>.</summary> |
| | | 114 | | private protected static TResponse FromErrors< TResponse >( List<Error> errors ) |
| | | 115 | | where TResponse : BaseResponse, new() |
| | 295 | 116 | | => new() { Status = (int) new APIResponsesSettings().StatusFor( errors ), Errors = errors, StatusFromErrors = tr |
| | | 117 | | |
| | | 118 | | |
| | | 119 | | private static Error HttpStatusError( int status ) |
| | 9 | 120 | | => new( "HttpStatusError", "ApiError", $"فشل الطلب (رمز الحالة {status}).", $"The request failed with HTTP statu |
| | 9 | 121 | | new Dictionary<string, object> { [ "Status" ] = status } ); |
| | | 122 | | } |