1 # Connect error codes (https://connectrpc.com/docs/protocol/#error-codes). 2 # Only these sixteen codes are valid: Connect has no user-defined codes. 3 import Base 4 5 type Code is Data: 6 Canceled{} 7 Unknown{} 8 InvalidArgument{} 9 DeadlineExceeded{} 10 NotFound{} 11 AlreadyExists{} 12 PermissionDenied{} 13 ResourceExhausted{} 14 FailedPrecondition{} 15 Aborted{} 16 OutOfRange{} 17 Unimplemented{} 18 Internal{} 19 Unavailable{} 20 DataLoss{} 21 Unauthenticated{} 22 23 # The wire name, as it appears in an Error's "code" property. 24 def to_string(c: Code) -> String: 25 match c: 26 case Canceled{}: 27 "canceled" 28 case Unknown{}: 29 "unknown" 30 case InvalidArgument{}: 31 "invalid_argument" 32 case DeadlineExceeded{}: 33 "deadline_exceeded" 34 case NotFound{}: 35 "not_found" 36 case AlreadyExists{}: 37 "already_exists" 38 case PermissionDenied{}: 39 "permission_denied" 40 case ResourceExhausted{}: 41 "resource_exhausted" 42 case FailedPrecondition{}: 43 "failed_precondition" 44 case Aborted{}: 45 "aborted" 46 case OutOfRange{}: 47 "out_of_range" 48 case Unimplemented{}: 49 "unimplemented" 50 case Internal{}: 51 "internal" 52 case Unavailable{}: 53 "unavailable" 54 case DataLoss{}: 55 "data_loss" 56 case Unauthenticated{}: 57 "unauthenticated" 58 59 # The HTTP status a unary error response carries. 60 def to_http(c: Code) -> U32: 61 match c: 62 case Canceled{}: 63 499 64 case Unknown{}: 65 500 66 case InvalidArgument{}: 67 400 68 case DeadlineExceeded{}: 69 504 70 case NotFound{}: 71 404 72 case AlreadyExists{}: 73 409 74 case PermissionDenied{}: 75 403 76 case ResourceExhausted{}: 77 429 78 case FailedPrecondition{}: 79 400 80 case Aborted{}: 81 409 82 case OutOfRange{}: 83 400 84 case Unimplemented{}: 85 501 86 case Internal{}: 87 500 88 case Unavailable{}: 89 503 90 case DataLoss{}: 91 500 92 case Unauthenticated{}: 93 401 94 95 # All codes, in the order of the spec's table. 96 def all() -> List<&2, Code>: 97 [Canceled{}, Unknown{}, InvalidArgument{}, DeadlineExceeded{}, NotFound{}, 98 AlreadyExists{}, PermissionDenied{}, ResourceExhausted{}, FailedPrecondition{}, 99 Aborted{}, OutOfRange{}, Unimplemented{}, Internal{}, Unavailable{}, DataLoss{}, 100 Unauthenticated{}] 101 102 def from_string.pick(hit: Bool, c: Code, rest: Unit -> Maybe<&2, Code>) -> Maybe<&2, Code>: 103 match hit: 104 case True{}: 105 Some{c} 106 case False{}: 107 rest(Unit{}) 108 109 def from_string.go(cs: List<&2, Code>, +s: String) -> Maybe<&2, Code>: 110 match cs: 111 case Nil{}: 112 None{} 113 case Con{+c, rest}: 114 from_string.pick(String.eq(to_string(c), s), c, u => from_string.go(rest, s)) 115 116 # Parses a wire name; None for anything that is not one of the sixteen codes. 117 def from_string(+s: String) -> Maybe<&2, Code>: 118 from_string.go(all(), s) 119 120 # One row of the spec's HTTP-to-code table. 121 type Row is Data: 122 Row{status: U32, code: Code} 123 124 def from_http.pick(hit: Bool, c: Code, rest: Unit -> Code) -> Code: 125 match hit: 126 case True{}: 127 c 128 case False{}: 129 rest(Unit{}) 130 131 def from_http.go(table: List<&2, Row>, +status: U32) -> Code: 132 match table: 133 case Nil{}: 134 Unknown{} 135 case Con{Row{+k, c}, rest}: 136 from_http.pick(U32.is_eq(k, status), c, u => from_http.go(rest, status)) 137 138 # The code a client infers from a non-200 status with no Connect error body. 139 def from_http(+status: U32) -> Code: 140 from_http.go([Row{400, Internal{}}, Row{401, Unauthenticated{}}, Row{403, PermissionDenied{}}, 141 Row{404, Unimplemented{}}, Row{429, Unavailable{}}, Row{502, Unavailable{}}, 142 Row{503, Unavailable{}}, Row{504, Unavailable{}}], status)