blazingly 0.2.2

Facade crate re-exporting the Blazingly framework: routing, extraction, validation, dependency injection, OpenAPI, and MCP
Documentation
use blazingly::prelude::*;
use blazingly_json::json;
use futures_lite::future;
use std::cell::RefCell;
use std::future::{Ready, ready};
use std::rc::Rc;

type Events = Rc<RefCell<Vec<&'static str>>>;

#[api_model]
#[derive(Clone, Debug)]
struct HookInput {
    #[min_length(2)]
    value: String,
}

#[api_model]
#[derive(Clone, Debug)]
struct HookOutput {
    value: String,
}

#[derive(Clone)]
struct HookEvents(Events);

#[post("/hooks", id = "hooks.run")]
async fn hook_operation(
    Json(input): Json<HookInput>,
    events: Depends<HookEvents>,
) -> Json<HookOutput> {
    events.0.borrow_mut().push("handler");
    Json(HookOutput { value: input.value })
}

#[test]
fn inherited_hooks_follow_the_compiled_operation_lifecycle() {
    let events = Rc::new(RefCell::new(Vec::new()));
    let executable = ExecutableApp::from_plugin(
        Plugin::new("app")
            .provide(Provider::value(HookEvents(Rc::clone(&events))))
            .on_request(operation_hook(Rc::clone(&events), "root:on_request"))
            .pre_parse(operation_hook(Rc::clone(&events), "root:pre_parse"))
            .pre_validate(operation_hook(Rc::clone(&events), "root:pre_validate"))
            .pre_handler(operation_hook(Rc::clone(&events), "root:pre_handler"))
            .pre_serialize(operation_hook(Rc::clone(&events), "root:pre_serialize"))
            .on_error(response_hook(Rc::clone(&events), "root:on_error"))
            .on_response(response_hook(Rc::clone(&events), "root:on_response"))
            .plugin(
                Plugin::new("feature")
                    .on_request(operation_hook(Rc::clone(&events), "child:on_request"))
                    .pre_parse(operation_hook(Rc::clone(&events), "child:pre_parse"))
                    .pre_validate(operation_hook(Rc::clone(&events), "child:pre_validate"))
                    .pre_handler(operation_hook(Rc::clone(&events), "child:pre_handler"))
                    .pre_serialize(operation_hook(Rc::clone(&events), "child:pre_serialize"))
                    .on_error(response_hook(Rc::clone(&events), "child:on_error"))
                    .on_response(response_hook(Rc::clone(&events), "child:on_response"))
                    .routes(routes![hook_operation]),
            ),
    )
    .expect("hook graph should compile");
    let http = TestApp::new(&executable);

    let response = future::block_on(
        http.call(
            Request::post("/hooks")
                .json(&json!({ "value": "ready" }))
                .expect("fixture should serialize"),
        ),
    );
    assert_eq!(response.status(), 200);
    assert_eq!(
        events.borrow().as_slice(),
        [
            "root:on_request",
            "child:on_request",
            "root:pre_parse",
            "child:pre_parse",
            "root:pre_validate",
            "child:pre_validate",
            "root:pre_handler",
            "child:pre_handler",
            "handler",
            "root:pre_serialize",
            "child:pre_serialize",
            "child:on_response",
            "root:on_response",
        ]
    );

    events.borrow_mut().clear();
    let invalid = future::block_on(
        http.call(
            Request::post("/hooks")
                .json(&json!({ "value": "x" }))
                .expect("fixture should serialize"),
        ),
    );
    assert_eq!(invalid.status(), 422);
    assert_eq!(
        events.borrow().as_slice(),
        [
            "root:on_request",
            "child:on_request",
            "root:pre_parse",
            "child:pre_parse",
            "root:pre_validate",
            "child:pre_validate",
            "child:on_error",
            "root:on_error",
            "child:on_response",
            "root:on_response",
        ]
    );
}

#[test]
fn shutdown_runs_child_first_and_continues_after_failure() {
    let events = Rc::new(RefCell::new(Vec::new()));
    let executable = ExecutableApp::from_plugin(
        Plugin::new("app")
            .on_shutdown(shutdown_hook(Rc::clone(&events), "root:shutdown", false))
            .plugin(Plugin::new("feature").on_shutdown(shutdown_hook(
                Rc::clone(&events),
                "child:shutdown",
                true,
            ))),
    )
    .expect("shutdown hook graph should compile");

    let error = future::block_on(executable.shutdown()).expect_err("child hook should fail");
    assert!(error.to_string().contains("child shutdown failed"));
    assert_eq!(
        events.borrow().as_slice(),
        ["child:shutdown", "root:shutdown"]
    );
}

fn operation_hook(
    events: Events,
    event: &'static str,
) -> impl Fn(HookContext) -> Ready<Result<(), DependencyError>> {
    move |_| {
        events.borrow_mut().push(event);
        ready(Ok(()))
    }
}

fn response_hook(
    events: Events,
    event: &'static str,
) -> impl Fn(HookContext, HookOutcome) -> Ready<()> {
    move |_, _| {
        events.borrow_mut().push(event);
        ready(())
    }
}

fn shutdown_hook(
    events: Events,
    event: &'static str,
    fails: bool,
) -> impl Fn() -> Ready<Result<(), DependencyError>> {
    move || {
        events.borrow_mut().push(event);
        ready(if fails {
            Err(DependencyError::internal(
                "shutdown_failed",
                "child shutdown failed",
            ))
        } else {
            Ok(())
        })
    }
}