| | | 1 | | using ArturRios.Data.Relational.Core.Repositories; |
| | | 2 | | using ArturRios.Util.Http; |
| | | 3 | | |
| | | 4 | | namespace ArturRios.Heimdall.Shared.Messages; |
| | | 5 | | |
| | | 6 | | /// <summary> |
| | | 7 | | /// HTTP status codes for the failures the persistence layer classifies, folded into every use |
| | | 8 | | /// case's own map so a repository failure answers with a status that describes it. |
| | | 9 | | /// </summary> |
| | | 10 | | /// <remarks> |
| | | 11 | | /// <para> |
| | | 12 | | /// These messages are not this application's. They come from |
| | | 13 | | /// <see cref="RelationalErrors" />, which since ArturRios.Data.Relational.Core 4.0.0 maps a |
| | | 14 | | /// provider exception onto one of a small set of fixed, caller-safe strings. Before that a |
| | | 15 | | /// failure arrived as the provider's own text — a unique violation named the index and the |
| | | 16 | | /// conflicting value, a truncation named the column and its type — so there was nothing |
| | | 17 | | /// stable to key a status off, and every one of them fell through to the resolver's 400 |
| | | 18 | | /// default. A duplicate that lost a race was reported as a bad request, and so was a |
| | | 19 | | /// database that had gone away. |
| | | 20 | | /// </para> |
| | | 21 | | /// <para> |
| | | 22 | | /// Only the classified failures are listed. <see cref="RelationalErrors.GenericMessage" /> is |
| | | 23 | | /// deliberately absent: it covers everything the library could not place, which spans both |
| | | 24 | | /// causes the caller can fix (a value longer than its column, when a validator failed to |
| | | 25 | | /// bound it) and causes only the operator can, so neither 400 nor 500 is right for all of it. |
| | | 26 | | /// It keeps the 400 default, which is the safer of the two — a 500 would tell a caller their |
| | | 27 | | /// request was blameless when it may not have been. |
| | | 28 | | /// </para> |
| | | 29 | | /// </remarks> |
| | | 30 | | public static class DataAccessMessageMap |
| | | 31 | | { |
| | 4 | 32 | | private static readonly IReadOnlyDictionary<string, int> StatusCodes = new Dictionary<string, int> |
| | 4 | 33 | | { |
| | 4 | 34 | | // A write lost a race against a unique index — the state the caller asked for conflicts with |
| | 4 | 35 | | // state that already exists, which is exactly 409. The application checks these rules before |
| | 4 | 36 | | // writing (FR-PE-09, FR-GO-07), so reaching here means two requests interleaved between the |
| | 4 | 37 | | // check and the insert; the index is what actually holds the invariant. |
| | 4 | 38 | | [RelationalErrors.UniqueViolationMessage] = HttpStatusCodes.Conflict, |
| | 4 | 39 | | |
| | 4 | 40 | | // A foreign key, NOT NULL, or CHECK the request would have broken. Also a conflict with |
| | 4 | 41 | | // existing state rather than a malformed request. |
| | 4 | 42 | | [RelationalErrors.IntegrityViolationMessage] = HttpStatusCodes.Conflict, |
| | 4 | 43 | | |
| | 4 | 44 | | // Someone else changed the row between the read and the write. The caller's request was |
| | 4 | 45 | | // valid when they made it and may well succeed on a retry. |
| | 4 | 46 | | [RelationalErrors.ConcurrencyMessage] = HttpStatusCodes.Conflict, |
| | 4 | 47 | | |
| | 4 | 48 | | // The database is unreachable or overloaded. 503 rather than 500: it says the condition is |
| | 4 | 49 | | // temporary, which is the one thing a client needs to know to decide whether to retry. |
| | 4 | 50 | | [RelationalErrors.TransientMessage] = HttpStatusCodes.ServiceUnavailable |
| | 4 | 51 | | }; |
| | | 52 | | |
| | | 53 | | /// <summary> |
| | | 54 | | /// Combines a use case's own message-to-status map with the persistence failures above. |
| | | 55 | | /// </summary> |
| | | 56 | | /// <remarks> |
| | | 57 | | /// The use case's entries win on a collision. None collide today — the library's messages are |
| | | 58 | | /// phrased unlike any of this application's — but a use case owns its own vocabulary, and a |
| | | 59 | | /// map assembled here should never be able to override it. |
| | | 60 | | /// </remarks> |
| | | 61 | | public static IReadOnlyDictionary<string, int> CombinedWith(IReadOnlyDictionary<string, int> useCaseStatusCodes) |
| | | 62 | | { |
| | 38 | 63 | | var combined = new Dictionary<string, int>(StatusCodes); |
| | | 64 | | |
| | 1634 | 65 | | foreach (var (message, statusCode) in useCaseStatusCodes) |
| | | 66 | | { |
| | 779 | 67 | | combined[message] = statusCode; |
| | | 68 | | } |
| | | 69 | | |
| | 38 | 70 | | return combined; |
| | | 71 | | } |
| | | 72 | | } |