tablo-core 0.9.0

The core toolkit types for Tablo, a server-rendered admin toolkit on Topcoat and Toasty.
Documentation
use super::*;
use crate::{
    Ability, Action, ResourceDef,
    panel::test_support::{Dummy, dummy_table, mount, panel_for},
    test_support::memory_db,
};

struct Rename;

impl Action<Hidden> for Rename {
    type Input = ();
    const NAME: &'static str = "rename";

    fn label() -> crate::Text {
        "Rename".into()
    }

    async fn run(
        _: &Cx,
        _: &[Dummy],
        _: (),
        _: &mut dyn toasty::Executor,
    ) -> topcoat::Result<crate::Outcome, crate::WriteError<std::convert::Infallible>> {
        Ok(crate::Outcome::done())
    }
}

/// A resource whose policy allows `View` alone: it refuses `ViewAny` and `RunAny`.
struct Hidden;

impl Resource for Hidden {
    type Model = Dummy;
    type Form = crate::NoForm<Self::Model>;

    fn declare() -> ResourceDef<Self> {
        ResourceDef::new()
            .slug("dummies")
            .policy_fn(|_cx, ability| matches!(ability, Ability::View(_)))
            .table(dummy_table())
            .action::<Rename>()
    }
}

#[tokio::test]
async fn an_unknown_action_answers_a_refused_caller_like_a_known_one() {
    let db = memory_db(toasty::models!(Dummy)).await;
    let router = mount(db, panel_for::<Hidden>()).expect("panel builds");
    for name in ["rename", "missing"] {
        let response = router
            .handle(
                http::Request::builder()
                    .uri(format!("/admin/dummies/-/actions/{name}"))
                    .method(http::Method::POST)
                    .body(Body::empty())
                    .unwrap(),
            )
            .await;
        assert_eq!(
            response.status(),
            http::StatusCode::FORBIDDEN,
            "`{name}` answers 403 without `ViewAny` or `RunAny`, naming no action"
        );
    }
}