crates/gunnar-ui-web/src/gate.rs
//! **A page whose namespace or repository does not exist draws nothing but
//! that fact.**
//!
//! The server answers every namespace-scoped read of a name the caller cannot
//! see — because it is not there, or because it is private and not theirs —
//! with one 404 and one body (`gunnar-ui-server`'s `api::absent`). Before that
//! existed, typing another namespace into the address bar drew the whole
//! repository page: header, clone line, every pane, each one a refusal box or
//! an empty table. The panes were asking a question the page had already been
//! answered: there is nothing here.
//!
//! So a repository page, and a namespace's usage page, first asks the one read
//! whose answer IS the question, and the decision over that answer is here —
//! pure, so it is tested without a browser. `Absent` renders the "does not
//! exist" view and NO panes: no pane component is even constructed, so none
//! of their requests are sent.
/// What the gate needs of a failed read: its HTTP status and the server's
/// code. Implemented by the browser's `api::Fail`; a trait so this decision
/// compiles and is tested on the host, where `api` (a wasm module) does not.
pub trait Refusal {
fn status(&self) -> u16;
fn code(&self) -> &str;
}
/// What a gated page does with the gating read.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Gate {
/// Not answered yet: draw the loading shape, and no panes.
Wait,
/// The name does not exist for this caller: draw the "does not exist"
/// view, and no panes.
Absent,
/// Draw the page. Also for every failure that is NOT absence — gunnar
/// down, a proxy page — because those are the panes' to report, as they
/// always have, with their retry buttons.
Show,
}
/// The server's "this page does not exist": a 404 carrying `notfound`.
///
/// Both halves, because a 404 without the code is not the console's answer
/// (a proxy's, or a static file miss) and must not be dressed as one.
pub fn is_absent(refusal: &impl Refusal) -> bool {
refusal.status() == 404 && refusal.code() == "notfound"
}
/// Decide over the gating read.
pub fn gate<T, E: Refusal>(read: &Option<Result<T, E>>) -> Gate {
match read {
None => Gate::Wait,
Some(Err(fail)) if is_absent(fail) => Gate::Absent,
Some(_) => Gate::Show,
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Fail(u16, &'static str);
impl Refusal for Fail {
fn status(&self) -> u16 {
self.0
}
fn code(&self) -> &str {
self.1
}
}
fn fail(status: u16, code: &'static str) -> Fail {
Fail(status, code)
}
/// Unknown namespace, and private-no-access (the SAME 404 from the
/// server): the "does not exist" view and no panes.
#[test]
fn the_servers_404_draws_no_panes() {
assert_eq!(gate::<(), Fail>(&Some(Err(fail(404, "notfound")))), Gate::Absent);
}
/// Existing: as today.
#[test]
fn an_answer_draws_the_page() {
assert_eq!(gate::<(), Fail>(&Some(Ok(()))), Gate::Show);
}
/// Nothing drawn before the answer: panes that rendered first and were
/// then taken away would already have sent their requests.
#[test]
fn no_panes_before_the_answer() {
assert_eq!(gate::<(), Fail>(&None), Gate::Wait);
}
/// Everything else stays the panes' business — an outage is not absence,
/// and a bare 404 that is not the console's own is not either.
#[test]
fn other_failures_are_not_absence() {
for (status, code) in [(503, "maintenance"), (0, ""), (404, ""), (403, "permissiondenied"), (502, "")] {
assert_eq!(gate::<(), Fail>(&Some(Err(fail(status, code)))), Gate::Show, "{status} {code}");
}
}
}