1 import Base 2 import ./Manifest.bend as Manifest 3 4 # Lists the structured durable error categories. 5 type ErrorKind is Data: 6 NotFound{} 7 AlreadyExists{} 8 Busy{} 9 Timeout{} 10 InvalidArgument{} 11 UnsupportedPlatform{} 12 UnsupportedFormat{} 13 Corruption{} 14 ResourceLimit{} 15 Io{} 16 CommitUnknown{} 17 18 # Carries operation, path, and host details with its category. 19 type Error is Data: 20 Error{kind: ErrorKind, operation: String, path: String, host_code: U32, host_message: String} 21 22 # Records the failure that ended a database open. 23 type OpenCause is Data: 24 MissingPath{} 25 ExistingPath{} 26 Locked{} 27 InvalidInput{} 28 UnknownFormat{} 29 CorruptData{} 30 LimitReached{} 31 OpenHostFailure{code: U32, message: String} 32 33 # Identifies whether a write failed before or after append. 34 type WritePhase is Data: 35 BeforeAppend{} 36 AppendStarted{} 37 38 # Records the codec or host failure behind a write result. 39 type WriteCause is Data: 40 ValidationFailure{} 41 WriteLimitReached{} 42 WriteHostFailure{code: U32, message: String} 43 44 # Builds an error with its structured host and operation details. 45 def error(kind: ErrorKind, operation: String, path: String, code: U32, message: String) -> Error: 46 Error{kind, operation, path, code, message} 47 48 # Maps open causes to their public error categories. 49 def classify_open(cause: OpenCause, operation: String, path: String) -> Error: 50 match cause: 51 case MissingPath{}: 52 error(NotFound{}, operation, path, 0, "") 53 case ExistingPath{}: 54 error(AlreadyExists{}, operation, path, 0, "") 55 case Locked{}: 56 error(Busy{}, operation, path, 0, "") 57 case InvalidInput{}: 58 error(InvalidArgument{}, operation, path, 0, "") 59 case UnknownFormat{}: 60 error(UnsupportedFormat{}, operation, path, 0, "") 61 case CorruptData{}: 62 error(Corruption{}, operation, path, 0, "") 63 case LimitReached{}: 64 error(ResourceLimit{}, operation, path, 0, "") 65 case OpenHostFailure{code, message}: 66 error(Io{}, operation, path, code, message) 67 68 def classify_recovery.limit(+code: U32, +message: String, +operation: String, +path: String) -> Error: 69 Bool.pick(Error, U32.is_eq(code, Manifest.parse_limit_error_code()), 70 error(ResourceLimit{}, operation, path, 0, "manifest size limit exceeded"), 71 classify_open(OpenHostFailure{code, message}, operation, path)) 72 73 def classify_recovery.corruption(+code: U32, +message: String, +operation: String, +path: String) -> Error: 74 Bool.pick(Error, U32.is_eq(code, Manifest.malformed_error_code()), 75 error(Corruption{}, operation, path, 0, "malformed manifest"), 76 classify_recovery.limit(code, message, operation, path)) 77 78 def classify_recovery.sst(+code: U32, +message: String, +operation: String, +path: String) -> Error: 79 Bool.pick(Error, U32.is_eq(code, 4294967292), 80 error(Corruption{}, operation, path, 0, "malformed SST"), 81 classify_recovery.corruption(code, message, operation, path)) 82 83 def classify_recovery.levels(+code: U32, +message: String, +operation: String, +path: String) -> Error: 84 Bool.pick(Error, U32.is_eq(code, 5), 85 error(Corruption{}, operation, path, 0, "invalid manifest names"), 86 classify_recovery.sst(code, message, operation, path)) 87 88 def classify_recovery.wal(+code: U32, +message: String, +operation: String, +path: String) -> Error: 89 Bool.pick(Error, U32.is_eq(code, 4294967290), 90 error(Corruption{}, operation, path, 0, "malformed WAL"), 91 classify_recovery.levels(code, message, operation, path)) 92 93 # Maps recovery failures to typed public errors. 94 def classify_recovery(+code: U32, +message: String, +operation: String, +path: String) -> Error: 95 Bool.pick(Error, U32.is_eq(code, Manifest.unsupported_version_error_code()), 96 error(UnsupportedFormat{}, operation, path, 0, "unsupported manifest version"), 97 classify_recovery.wal(code, message, operation, path)) 98 99 # Classifies failures that occur before WAL append begins. 100 def classify_write_before(cause: WriteCause, operation: String, path: String) -> Error: 101 match cause: 102 case ValidationFailure{}: 103 error(InvalidArgument{}, operation, path, 0, "") 104 case WriteLimitReached{}: 105 error(ResourceLimit{}, operation, path, 0, "") 106 case WriteHostFailure{code, message}: 107 error(Io{}, operation, path, code, message) 108 109 # Classifies failures after append begins as CommitUnknown. 110 def classify_write_after(cause: WriteCause, operation: String, path: String) -> Error: 111 match cause: 112 case ValidationFailure{}: 113 error(InvalidArgument{}, operation, path, 0, "") 114 case WriteLimitReached{}: 115 error(ResourceLimit{}, operation, path, 0, "") 116 case WriteHostFailure{code, message}: 117 error(CommitUnknown{}, operation, path, code, message) 118 119 # Selects the write error category from its append stage. 120 def classify_write(phase: WritePhase, cause: WriteCause, operation: String, path: String) -> Error: 121 match phase: 122 case BeforeAppend{}: 123 classify_write_before(cause, operation, path) 124 case AppendStarted{}: 125 classify_write_after(cause, operation, path)