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;

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

#[api_model]
#[derive(Clone, Debug)]
struct MacroOutput {
    item_id: u64,
    message: String,
}

#[api_error]
enum MacroError {
    #[status(409)]
    #[code("value_already_exists")]
    #[message("The requested value already exists.")]
    ValueAlreadyExists,
}

#[derive(Clone)]
struct MacroPrefix(&'static str);

#[derive(Clone)]
struct MacroContext(String);

#[provider(singleton)]
fn macro_prefix() -> MacroPrefix {
    MacroPrefix("macro")
}

#[provider]
async fn macro_context(prefix: Depends<MacroPrefix>) -> Result<MacroContext, DependencyError> {
    Ok(MacroContext(format!("{}-context", prefix.0)))
}

#[operation(
    method = PUT,
    path = "/macro/{item_id}",
    id = "macro.upsert",
    summary = "Exercise the universal operation macro"
)]
#[mcp::tool(
    name = "macro_upsert",
    description = "Create or replace one macro fixture",
    risk = "write",
    idempotent = true
)]
async fn macro_upsert(
    Path(item_id): Path<u64>,
    Json(input): Json<MacroInput>,
    context: Depends<MacroContext>,
) -> Result<Json<MacroOutput>, MacroError> {
    if input.value == "taken" {
        return Err(MacroError::ValueAlreadyExists);
    }
    Ok(Json(MacroOutput {
        item_id,
        message: format!("{}:{}", context.0, input.value),
    }))
}

#[get(
    "/sync/{item_id}",
    id = "macro.sync",
    summary = "Exercise the synchronous operation fast path"
)]
fn sync_operation(Path(item_id): Path<u64>) -> Json<MacroOutput> {
    Json(MacroOutput {
        item_id,
        message: "sync".to_owned(),
    })
}

#[test]
fn synchronous_operations_use_the_same_typed_http_surface() {
    let executable =
        ExecutableApp::new(routes![sync_operation]).expect("sync operation should compile");
    let response = future::block_on(TestApp::new(&executable).call(Request::get("/sync/11")));

    assert_eq!(response.status(), 200);
    assert_eq!(
        response
            .json::<blazingly_json::Value>()
            .expect("sync response should be JSON"),
        json!({ "item_id": 11, "message": "sync" })
    );
}

#[test]
fn universal_operation_and_provider_macros_share_http_mcp_and_di() {
    assert_eq!(
        macro_prefix::provider().lifetime(),
        blazingly::DependencyLifetime::Singleton
    );
    assert_eq!(
        macro_context::provider().lifetime(),
        blazingly::DependencyLifetime::Request
    );

    let executable = ExecutableApp::from_plugin(
        Plugin::new("macro_surface")
            .provide(macro_prefix::provider())
            .provide(macro_context::provider())
            .routes(routes![macro_upsert]),
    )
    .expect("macro-generated provider graph should compile");

    let descriptor = &executable.definition().operations()[0];
    assert_eq!(descriptor.http.method, blazingly::HttpMethod::Put);
    assert_eq!(descriptor.contract.id.as_str(), "macro.upsert");
    assert_eq!(
        descriptor
            .mcp_tool()
            .expect("operation should have an MCP projection")
            .name,
        "macro_upsert"
    );

    let http = TestApp::new(&executable);
    let response = future::block_on(
        http.call(
            Request::put("/macro/7")
                .json(&json!({ "value": "ready" }))
                .expect("fixture should serialize"),
        ),
    );
    assert_eq!(response.status(), 200);
    assert_eq!(
        response
            .json::<blazingly_json::Value>()
            .expect("HTTP response should be JSON"),
        json!({ "item_id": 7, "message": "macro-context:ready" })
    );

    let runtime = blazingly::mcp::McpRuntime::new(&executable);
    let result = future::block_on(runtime.call_tool(
        "macro_upsert",
        json!({ "item_id": 9, "value": "agent" }),
        blazingly::mcp::McpCallContext::default(),
    ))
    .expect("MCP should execute the same operation");
    assert!(!result.is_error);
    assert_eq!(
        result.structured_content,
        Some(
            json!({ "item_id": 9, "message": "macro-context:agent" })
                .as_object()
                .expect("fixture is an object")
                .clone()
        )
    );

    let conflict = future::block_on(
        http.call(
            Request::put("/macro/7")
                .json(&json!({ "value": "taken" }))
                .expect("fixture should serialize"),
        ),
    );
    assert_eq!(conflict.status(), 409);
    assert_eq!(
        conflict
            .json::<blazingly_json::Value>()
            .expect("typed error should be JSON")["error"]["code"],
        "value_already_exists"
    );
}