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