ubl_runtime/
lib.rs

1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3#![allow(clippy::multiple_crate_versions)]
4//! Runtime/router for DIM dispatch with middleware and UBL logging.
5/// Application context and logging.
6pub mod ctx;
7/// Events emitted to UBL.
8pub mod events;
9/// Middleware and budget helpers.
10pub mod mw;
11/// DIM router and middleware wiring.
12pub mod router;
13/// Typed handler helper.
14pub mod typed;
15/// HTTP octet parsing helpers.
16pub mod web;
17pub use ctx::AppCtx;
18pub use mw::{Budgets, Middleware};
19pub use router::{FnHandler, HandlerFn, Router};
20pub use typed::handle_typed;
21pub use web::parse_http_octets;
22
23use anyhow::{anyhow, Result};
24use ubl_crypto::blake3_hex;
25use ubl_types::{ActorId, Dim};
26use events::{IntentCompleted, IntentReceived};
27
28/// Processa cápsula com middlewares e budgets.
29///
30/// # Errors
31///
32/// - `anyhow::Error` se budgets estourarem, não houver handler ou handlers/middlewares falharem
33pub fn process(
34    dim: Dim,
35    actor: &ActorId,
36    capsule_bytes: &[u8],
37    router: &Router,
38    ctx: &AppCtx,
39    budgets: &mut Budgets,
40) -> Result<Vec<u8>> {
41    let cid = blake3_hex(capsule_bytes);
42    let received_ev = IntentReceived {
43        dim: dim.0,
44        capsule_cid_hex: cid.clone(),
45        size: capsule_bytes.len() as u64,
46    };
47    ctx.received(&received_ev)?;
48
49    if let Some(rem) = budgets.consume(actor, 1) {
50        if rem == 0 {
51            ctx.log(
52                "budget.limit_hit",
53                &serde_json::json!({"actor":actor, "dim":dim.0}),
54            )?;
55            return Err(anyhow!("budget exceeded"));
56        }
57    }
58
59    for m in &router.mw_before {
60        m.before(dim, actor, capsule_bytes, ctx)?;
61    }
62
63    let h = router
64        .get(dim)
65        .ok_or_else(|| anyhow!(format!("no handler for dim=0x{:04x}", dim.0)))?;
66    let out = h(capsule_bytes);
67
68    for m in &router.mw_after {
69        m.after(
70            dim,
71            actor,
72            capsule_bytes,
73            out.as_deref().unwrap_or(&[]),
74            ctx,
75        )?;
76    }
77
78    match out {
79        Ok(ref b) => {
80            let completed_ev = IntentCompleted {
81                dim: dim.0,
82                capsule_cid_hex: cid,
83                ok: true,
84                result_size: b.len() as u64,
85            };
86            ctx.completed(&completed_ev)?;
87        }
88        Err(ref e) => {
89            let completed_ev = IntentCompleted {
90                dim: dim.0,
91                capsule_cid_hex: cid,
92                ok: false,
93                result_size: 0,
94            };
95            ctx.completed(&completed_ev)?;
96            return Err(anyhow!(format!("handler failed: {e}")));
97        }
98    }
99    out
100}