< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.Shared.Messages.DataAccessMessageMap
Assembly: ArturRios.Heimdall.Shared
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Shared/Messages/DataAccessMessageMap.cs
Line coverage
100%
Covered lines: 24
Uncovered lines: 0
Coverable lines: 24
Total lines: 72
Line coverage: 100%
Branch coverage
100%
Covered branches: 2
Total branches: 2
Branch coverage: 100%
Method coverage
100%
Covered methods: 2
Fully covered methods: 2
Total methods: 2
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
CombinedWith(...)100%22100%

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Shared/Messages/DataAccessMessageMap.cs

#LineLine coverage
 1using ArturRios.Data.Relational.Core.Repositories;
 2using ArturRios.Util.Http;
 3
 4namespace 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>
 30public static class DataAccessMessageMap
 31{
 432    private static readonly IReadOnlyDictionary<string, int> StatusCodes = new Dictionary<string, int>
 433    {
 434        // A write lost a race against a unique index — the state the caller asked for conflicts with
 435        // state that already exists, which is exactly 409. The application checks these rules before
 436        // writing (FR-PE-09, FR-GO-07), so reaching here means two requests interleaved between the
 437        // check and the insert; the index is what actually holds the invariant.
 438        [RelationalErrors.UniqueViolationMessage] = HttpStatusCodes.Conflict,
 439
 440        // A foreign key, NOT NULL, or CHECK the request would have broken. Also a conflict with
 441        // existing state rather than a malformed request.
 442        [RelationalErrors.IntegrityViolationMessage] = HttpStatusCodes.Conflict,
 443
 444        // Someone else changed the row between the read and the write. The caller's request was
 445        // valid when they made it and may well succeed on a retry.
 446        [RelationalErrors.ConcurrencyMessage] = HttpStatusCodes.Conflict,
 447
 448        // The database is unreachable or overloaded. 503 rather than 500: it says the condition is
 449        // temporary, which is the one thing a client needs to know to decide whether to retry.
 450        [RelationalErrors.TransientMessage] = HttpStatusCodes.ServiceUnavailable
 451    };
 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    {
 3863        var combined = new Dictionary<string, int>(StatusCodes);
 64
 163465        foreach (var (message, statusCode) in useCaseStatusCodes)
 66        {
 77967            combined[message] = statusCode;
 68        }
 69
 3870        return combined;
 71    }
 72}