src/code.bend

bendonnect-rpc@0.1.0.0 · 142 lines · 11 definitions

1# Connect error codes (https://connectrpc.com/docs/protocol/#error-codes).
2# Only these sixteen codes are valid: Connect has no user-defined codes.
3import Base
4
5type 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.
24def 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.
60def 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.
96def 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
102def 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
109def 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.
117def 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.
121type Row is Data:
122  Row{status: U32, code: Code}
123
124def 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
131def 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.
139def 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)