1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3#![allow(clippy::multiple_crate_versions)]
4pub mod ctx;
7pub mod events;
9pub mod mw;
11pub mod router;
13pub mod typed;
15pub 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
28pub 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}