max / quasi
| 1 | //! Failure, classified once so every host renders it the same way. |
| 2 | //! |
| 3 | //! Decision 9 on the wiki note. A handler that cannot do what was asked returns |
| 4 | //! a [`RouteError`] rather than a [`Response`](crate::Response) describing the |
| 5 | //! problem. Two things fall out of that, and both are the reason the type |
| 6 | //! exists. |
| 7 | //! |
| 8 | //! A class is not a message. The class is what the *host* acts on: axum turns |
| 9 | //! it into a status code, the terminal and egui turn it into a banner. Handing |
| 10 | //! back a plain response instead would have hosted axum answer 200 to |
| 11 | //! everything, so caches, logs and monitoring could not tell a denied action |
| 12 | //! from a completed one. |
| 13 | //! |
| 14 | //! The message is already UI. It carries a `makeover-layout` [`Notice`], so |
| 15 | //! errors become something on screen in one place rather than once per handler. |
| 16 | |
| 17 | use ; |
| 18 | |
| 19 | /// What kind of failure it was. |
| 20 | /// |
| 21 | /// Four, and each one is a different thing for a host to do. `NotFound` and |
| 22 | /// `Denied` are separate because a host that conflates them cannot answer the |
| 23 | /// question its own access log asks. `Conflict` is separate because it is the |
| 24 | /// one failure the user can fix by looking at the screen again, which is a |
| 25 | /// banner rather than a toast. |
| 26 | /// |
| 27 | /// `#[non_exhaustive]` for the reason `makeover-layout` puts it on the |
| 28 | /// vocabularies renderers match against: growth must not be a lockstep event |
| 29 | /// across every host adapter. |
| 30 | |
| 31 | |
| 32 | |
| 33 | /// The thing addressed is not there. |
| 34 | NotFound, |
| 35 | /// The thing is there and this caller may not have it. |
| 36 | Denied, |
| 37 | /// The request was answerable and the current state refuses it. |
| 38 | Conflict, |
| 39 | /// We are broken. |
| 40 | Internal, |
| 41 | |
| 42 | |
| 43 | |
| 44 | /// What the failure is saying, in `makeover-layout`'s vocabulary. |
| 45 | /// |
| 46 | /// Only `Internal` is [`Tone::Danger`]. The other three are conditions the |
| 47 | /// user can understand and often act on, and spending the loudest tone on |
| 48 | /// all of them is how a `Danger` stops meaning anything. |
| 49 | |
| 50 | pub const |
| 51 | match self |
| 52 | SelfNotFound | SelfDenied | SelfConflict => Warning, |
| 53 | SelfInternal => Danger, |
| 54 | |
| 55 | |
| 56 | |
| 57 | /// The status code an HTTP host answers with. |
| 58 | /// |
| 59 | /// A convenience rather than a leak: this is a `u16`, no host crate is |
| 60 | /// imported to produce it, and every HTTP adapter we will write would |
| 61 | /// otherwise hand-roll the same four-arm match. Hosts with no notion of a |
| 62 | /// status code ignore it and read [`Class`] directly. |
| 63 | |
| 64 | pub const |
| 65 | match self |
| 66 | SelfNotFound => 404, |
| 67 | SelfDenied => 403, |
| 68 | SelfConflict => 409, |
| 69 | SelfInternal => 500, |
| 70 | |
| 71 | |
| 72 | |
| 73 | /// Whether the failure is ours rather than the caller's. |
| 74 | /// |
| 75 | /// The line a host logs on. A `NotFound` at volume is a broken link |
| 76 | /// somewhere; an `Internal` at any volume is a page. |
| 77 | |
| 78 | pub const |
| 79 | matches! |
| 80 | |
| 81 | |
| 82 | |
| 83 | /// A route that could not answer. |
| 84 | /// |
| 85 | /// Carries a class for the host and a notice for the screen. The notice |
| 86 | /// defaults to [`Notice::Banner`], because a failure is persistent by nature: |
| 87 | /// it is dismissed by fixing the condition that caused it, which is exactly |
| 88 | /// what `makeover-layout` says a banner is for. Call [`RouteError::as_toast`] |
| 89 | /// where the failure really is transient. |
| 90 | |
| 91 | |
| 92 | /// What kind of failure it was. |
| 93 | pub class: Class, |
| 94 | /// How the message sits on screen. |
| 95 | pub notice: Notice, |
| 96 | /// What to tell the user. Already user-facing text, not a debug string. |
| 97 | pub message: String, |
| 98 | |
| 99 | |
| 100 | |
| 101 | /// A failure of the given class, shown as a banner. |
| 102 | |
| 103 | Self |
| 104 | class, |
| 105 | notice: Banner, |
| 106 | message: message.into, |
| 107 | |
| 108 | |
| 109 | |
| 110 | /// The thing addressed is not there. |
| 111 | |
| 112 | Selfnew |
| 113 | |
| 114 | |
| 115 | /// The caller may not have it. |
| 116 | |
| 117 | Selfnew |
| 118 | |
| 119 | |
| 120 | /// The current state refuses the request. |
| 121 | |
| 122 | Selfnew |
| 123 | |
| 124 | |
| 125 | /// We are broken. |
| 126 | |
| 127 | Selfnew |
| 128 | |
| 129 | |
| 130 | /// The same failure, shown as a toast instead of a banner. |
| 131 | |
| 132 | |
| 133 | self.notice = Toast; |
| 134 | self |
| 135 | |
| 136 | |
| 137 | /// What the failure is saying. Delegates to the class. |
| 138 | |
| 139 | pub const |
| 140 | self.class.tone |
| 141 | |
| 142 | |
| 143 | |
| 144 | |
| 145 | |
| 146 | write! |
| 147 | |
| 148 | |
| 149 | |
| 150 | |
| 151 |