Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar-ui

crates/gunnar-ui-web/src/guest.rs

//! **A visitor with no session reads public repositories, read-only** (GUN-GUEST,
//! owner 2026-10-30): files, commits, the README and the issues. Where a signed-in
//! person sees a control that writes (open an issue, comment, close, open a pull,
//! merge, the settings), a visitor sees "Sign in to …", which leads to the login
//! page and back here.
//!
//! Nothing here is a permission. The server decides every read and refuses every
//! write to whoever may not make it; this only chooses what is drawn, from the
//! session the server described (`/auth/status`) and gunnar's per-repository
//! answer (`crate::rights`).

/// What a page draws where a control that writes would be.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Affordance {
    /// The control itself: this person may try it.
    Control,
    /// A visitor: "Sign in to {what}", to the login page and back to `here`, and
    /// a sign-up link beside it where this console signs people up.
    SignIn { words: String, login: String, signup: Option<String> },
    /// Signed in, and gunnar gives this person no right here: nothing.
    Nothing,
}

/// Pure: **the control, a "Sign in to …", or nothing**. `signed_in` is a live
/// session (or the open console); `may` is gunnar's answer for this person on
/// this repository; `here` is the page's own path and query; `signup` is where
/// this console signs people up (`/auth/status`'s `signup_url`), if it does.
pub fn affordance(signed_in: bool, may: bool, what: &str, here: &str, signup: Option<&str>) -> Affordance {
    match (signed_in, may) {
        (true, true) => Affordance::Control,
        (true, false) => Affordance::Nothing,
        (false, _) => Affordance::SignIn {
            words: format!("Sign in to {what}"),
            login: crate::auth::login_url(here),
            signup: signup.filter(|s| s.starts_with('/') && !s.starts_with("//")).map(str::to_owned),
        },
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// ★ GUN-GUEST: a visitor is offered the sign-in where a control would be, a
    /// person who may is given the control, a person who may not is given nothing.
    #[test]
    fn a_visitor_is_asked_to_sign_in_where_a_control_would_be() {
        assert_eq!(affordance(true, true, "open an issue", "/n/r/issues", None), Affordance::Control);
        assert_eq!(affordance(true, false, "open an issue", "/n/r/issues", Some("/signup")), Affordance::Nothing);
        assert_eq!(
            affordance(false, false, "open an issue", "/n/r/issues", None),
            Affordance::SignIn { words: "Sign in to open an issue".into(), login: "/login?next=/n/r/issues".into(), signup: None }
        );
        // A visitor's rights are never asked (there is no session to ask for):
        // whatever `may` says, a visitor is asked to sign in.
        assert_eq!(
            affordance(false, true, "comment", "/n/r/issues/7", Some("/signup")),
            Affordance::SignIn { words: "Sign in to comment".into(), login: "/login?next=/n/r/issues/7".into(), signup: Some("/signup".into()) }
        );
        // The sign-up link is only ever a local page.
        match affordance(false, false, "comment", "/n/r/issues/7", Some("https://evil.test/")) {
            Affordance::SignIn { signup, .. } => assert_eq!(signup, None),
            other => panic!("{other:?}"),
        }
    }
}