tablo-core 0.9.0

The core toolkit types for Tablo, a server-rendered admin toolkit on Topcoat and Toasty.
Documentation
use topcoat::{
    Result,
    context::Cx,
    router::Body,
    view::{View, view},
};

use crate::Page;

struct ReportsPage;
impl Page for ReportsPage {
    async fn render(cx: &Cx) -> Result<impl View> {
        Ok(view! { <h1>"Quarterly reports"</h1> })
    }
}

/// A page re-checks the resolved user before rendering: with auth enforced
/// and no user, it answers the login redirect and never renders.
#[tokio::test]
async fn a_page_redirects_to_login_without_a_resolved_user() {
    use topcoat::{context::Cx, router::response::IntoResponse, view::ViewExt};

    let (parts, ()) = http::Request::builder()
        .uri("/admin/reports")
        .body(())
        .unwrap()
        .into_parts();
    let cx = crate::panel::test_support::serving(
        Cx::default().with(parts),
        &std::sync::Arc::new(crate::panel::test_support::panel_state(
            "/admin",
            crate::Auth::password(),
        )),
    );
    let Err(error) = super::page_handler::<ReportsPage>(&cx, Body::empty())
        .single()
        .await
    else {
        panic!("a page must not render without a resolved user");
    };
    let response = error.into_response(&cx).expect("the gate redirect renders");
    let location = response
        .headers()
        .get(http::header::LOCATION)
        .and_then(|value| value.to_str().ok())
        .unwrap_or_default();
    assert!(
        location.starts_with("/admin/login"),
        "an anonymous page request redirects to login, got {location}"
    );
}