| | | 1 | | using ArturRios.Data.MongoDb.Exceptions; |
| | | 2 | | using MongoDB.Driver; |
| | | 3 | | |
| | | 4 | | namespace ArturRios.Data.MongoDb.Repositories; |
| | | 5 | | |
| | | 6 | | /// <summary> |
| | | 7 | | /// Classification of MongoDB failures, and the caller-safe messages that replace driver text. |
| | | 8 | | /// Driver messages name indexes, collections, key values and cluster endpoints, so they are used |
| | | 9 | | /// for classification only and are never returned to the caller. |
| | | 10 | | /// </summary> |
| | | 11 | | public static class MongoErrors |
| | | 12 | | { |
| | | 13 | | /// <summary>Message returned on an optimistic-concurrency conflict.</summary> |
| | | 14 | | public const string ConcurrencyMessage = |
| | | 15 | | "Concurrency conflict: the document was modified or removed by another process."; |
| | | 16 | | |
| | | 17 | | /// <summary>Message returned when a write violates a unique index.</summary> |
| | | 18 | | public const string UniqueViolationMessage = "Conflict: a document with the same unique value already exists."; |
| | | 19 | | |
| | | 20 | | /// <summary>Message returned when the failure is transient and the operation may be retried.</summary> |
| | | 21 | | public const string TransientMessage = "The data store is temporarily unavailable. Please retry."; |
| | | 22 | | |
| | | 23 | | /// <summary>Message returned when an operation fails with no finer classification.</summary> |
| | | 24 | | public const string GenericMessage = "A data-access error occurred."; |
| | | 25 | | |
| | | 26 | | // Server error codes for a duplicate key on a unique index. |
| | | 27 | | private const int DuplicateKeyCode = 11000; |
| | | 28 | | private const int DuplicateKeyOnUpdateCode = 11001; |
| | | 29 | | |
| | | 30 | | /// <summary>Detects a duplicate-key (unique index) violation anywhere in the exception chain.</summary> |
| | | 31 | | /// <param name="ex">The exception caught by a guard.</param> |
| | | 32 | | /// <returns><c>true</c> when the failure is a duplicate-key violation.</returns> |
| | | 33 | | public static bool IsUniqueViolation(Exception ex) |
| | | 34 | | { |
| | 24 | 35 | | for (var current = ex; current is not null; current = current.InnerException) |
| | | 36 | | { |
| | 8 | 37 | | var duplicate = current switch |
| | 8 | 38 | | { |
| | 4 | 39 | | MongoWriteException write => write.WriteError?.Category == ServerErrorCategory.DuplicateKey, |
| | 0 | 40 | | MongoBulkWriteException bulk => bulk.WriteErrors.Any(e => |
| | 0 | 41 | | e.Category == ServerErrorCategory.DuplicateKey), |
| | 0 | 42 | | MongoCommandException command => command.Code is DuplicateKeyCode or DuplicateKeyOnUpdateCode, |
| | 4 | 43 | | _ => false |
| | 8 | 44 | | }; |
| | | 45 | | |
| | 8 | 46 | | if (duplicate) |
| | | 47 | | { |
| | 4 | 48 | | return true; |
| | | 49 | | } |
| | | 50 | | } |
| | | 51 | | |
| | 4 | 52 | | return false; |
| | | 53 | | } |
| | | 54 | | |
| | | 55 | | /// <summary>Detects a failure the caller may retry: connection, election or timeout.</summary> |
| | | 56 | | /// <param name="ex">The exception caught by a guard.</param> |
| | | 57 | | /// <returns><c>true</c> when the failure is transient.</returns> |
| | | 58 | | public static bool IsTransient(Exception ex) |
| | | 59 | | { |
| | 16 | 60 | | for (var current = ex; current is not null; current = current.InnerException) |
| | | 61 | | { |
| | 4 | 62 | | if (current is MongoConnectionException or MongoNodeIsRecoveringException |
| | 4 | 63 | | or MongoNotPrimaryException or MongoExecutionTimeoutException or TimeoutException) |
| | | 64 | | { |
| | 0 | 65 | | return true; |
| | | 66 | | } |
| | | 67 | | } |
| | | 68 | | |
| | 4 | 69 | | return false; |
| | | 70 | | } |
| | | 71 | | |
| | | 72 | | /// <summary> |
| | | 73 | | /// Classifies a failure and returns the caller-safe message for it. Never includes driver text. |
| | | 74 | | /// </summary> |
| | | 75 | | /// <param name="ex">The exception caught by a guard.</param> |
| | | 76 | | /// <returns>One of the caller-safe messages exposed by this class.</returns> |
| | 12 | 77 | | public static string Describe(Exception ex) => ex switch |
| | 12 | 78 | | { |
| | 4 | 79 | | MongoConcurrencyException => ConcurrencyMessage, |
| | 12 | 80 | | _ when IsUniqueViolation(ex) => UniqueViolationMessage, |
| | 4 | 81 | | _ when IsTransient(ex) => TransientMessage, |
| | 4 | 82 | | _ => GenericMessage |
| | 12 | 83 | | }; |
| | | 84 | | } |