forma_ir/lib.rs
1//! Forma Module IR: binary format definitions and parsers.
2//!
3//! This crate provides the core IR types and parsing logic that can be compiled
4//! to both native Rust and WebAssembly targets.
5//!
6//! The `wasm` feature, which exports `render` and `render_island` for the
7//! browser's WASM walker (`@getforma/core/wasm`), is deprecated since 0.2.2 and
8//! is removed in 3.0.0: islands re-render in the browser with
9//! `@getforma/core`'s `h()`. Both exports still build and work through 0.2.x.
10
11// README.md and SECURITY.md both promise "Zero `unsafe` blocks". This is what
12// makes that a compiler-enforced property of the shipped library rather than a
13// claim. It is crate-root rather than a `[lints]` entry because `[lints]`
14// covers every target in the package, and the allocation-counting harness in
15// `tests/list_scaling.rs` must implement `GlobalAlloc` — a separate crate, not
16// code that ships.
17#![forbid(unsafe_code)]
18#![allow(clippy::type_complexity)]
19// No `unwrap`, `expect` or `panic!` in the code that ships (DR-12, SR44): a
20// walk returns its failure, it does not take the request down. Tests may.
21// The wasm exports below are the documented exception.
22#![cfg_attr(
23 not(test),
24 deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)
25)]
26
27/// On-disk FMIR layout: header, section table, opcodes, and the table entry
28/// types. See `docs/FMIR-FORMAT.md` for the stability contract.
29pub mod format;
30/// Bounds-checked parsing and one-shot structural validation of an FMIR file.
31pub mod parser;
32/// Runtime slot values and the dense `SlotData` a page handler populates.
33pub mod slot;
34/// The SSR walker: opcode stream plus slot values in, HTML out.
35pub mod walker;
36
37/// INTERNAL, behind the `__fault-injection` feature: the stand-in component
38/// that panics, for SR44's tests. Never in a build that ships.
39#[cfg(any(test, feature = "__fault-injection"))]
40#[doc(hidden)]
41pub mod fault_injection {
42 /// A DYN_TEXT value equal to this makes the walk panic, but only inside
43 /// [`crate::walker::walk_to_html_counted`], which catches it: the
44 /// uncaught `walk_to_html` path never arms the trigger. It lets a test
45 /// show that a panic inside the walk becomes an error naming its island,
46 /// and that the next render works. Compiled only for this crate's tests
47 /// and under the internal `__fault-injection` feature, which only the two
48 /// crates' own dev-dependencies enable; `test-helpers` does not.
49 pub const FAULT_TRIGGER: &str = "\u{0}forma-test-fault\u{0}";
50}
51
52/// Human-readable text dump of a module, for debugging and diffing.
53#[cfg(feature = "dump")]
54pub mod dump;
55
56/// Doctests pinning the `wasm` exports' deprecation; built only by
57/// `cargo test --doc --features wasm`.
58#[cfg(all(doctest, feature = "wasm"))]
59mod deprecations;
60
61// Re-exports for convenience
62pub use format::{
63 IrError, IrHeader, IslandEntry, IslandTrigger, Opcode, PropsMode, SectionDescriptor,
64 SectionTable, SlotEntry, SlotSource, SlotType, HEADER_SIZE, IR_VERSION, MAGIC,
65 SECTION_TABLE_SIZE,
66};
67pub use parser::{IrModule, IslandTableParsed, SlotTable, StringTable};
68pub use slot::{json_to_slot_value, SlotData, SlotValue};
69pub use walker::{
70 walk_island, walk_island_with_limits, walk_to_html, walk_to_html_counted,
71 walk_to_html_with_limits, WalkCounts, WalkFailure, WalkLimits, WalkOutput,
72 DEFAULT_MAX_LIST_ITERATIONS, DEFAULT_MAX_OUTPUT_BYTES,
73};
74
75#[cfg(feature = "wasm")]
76use wasm_bindgen::prelude::*;
77
78/// Full page render: parse IR bytes + JSON slots → HTML string.
79///
80/// Panics on error (throws a JS exception in WASM — the client loader catches
81/// it and falls back to Phase 1). That set of errors now includes the walker's
82/// resource budgets ([`WalkLimits`]): an over-limit render throws here rather
83/// than aborting the wasm instance, and malformed control-flow lengths that
84/// used to render garbage now throw too. The client loader must have a
85/// rejection handler on this call — that is pre-existing behavior for parse
86/// errors, but the set of inputs that reach it is wider.
87///
88/// Deprecated with the `wasm` feature, which 3.0.0 removes.
89#[cfg(feature = "wasm")]
90#[deprecated(
91 since = "0.2.2",
92 note = "the WASM walker goes with the `wasm` feature: re-render an island in the browser \
93 with @getforma/core's `h()`, as islands already do. Removed in 3.0.0"
94)]
95// wasm-bindgen's generated export calls this function; the allow keeps that
96// call quiet. A caller's own use still warns: the lint fires at the caller.
97// Verified by: crates/forma-ir/src/deprecations.rs > "WasmRender"
98#[allow(deprecated)]
99#[wasm_bindgen]
100// The panic is this export's contract: wasm-bindgen turns it into the JS
101// exception the client loader catches. The `wasm` feature goes in 3.0.0
102// (DR-23); until then it is the one place this crate panics by design.
103#[allow(clippy::panic)]
104pub fn render(ir_bytes: &[u8], slots_json: &str) -> String {
105 let module = parser::IrModule::parse(ir_bytes).unwrap_or_else(|e| panic!("IR parse: {e}"));
106 let slots =
107 slot::SlotData::from_json(slots_json, &module).unwrap_or_else(|e| panic!("slots: {e}"));
108 walker::walk_to_html(&module, &slots).unwrap_or_else(|e| panic!("walk: {e}"))
109}
110
111/// Fragment render: parse IR bytes + JSON slots → the island's ROOT element,
112/// carrying its full `data-forma-*` attribute set (including `data-forma-props`
113/// when the island has props). Swap it in with `replaceWith`/`outerHTML`, never
114/// `innerHTML` — that would discard the attributes the runtime selects on.
115///
116/// Panics on error (throws a JS exception in WASM — client loader catches and
117/// falls back). See [`render`] for the widened error set.
118///
119/// Deprecated with the `wasm` feature, which 3.0.0 removes.
120#[cfg(feature = "wasm")]
121#[deprecated(
122 since = "0.2.2",
123 note = "the WASM walker goes with the `wasm` feature: re-render an island in the browser \
124 with @getforma/core's `h()`, as islands already do. Removed in 3.0.0"
125)]
126#[allow(deprecated)] // as `render`: wasm-bindgen's generated export calls it
127#[wasm_bindgen]
128#[allow(clippy::panic)] // as `render`: the panic is the JS exception
129pub fn render_island(ir_bytes: &[u8], slots_json: &str, island_id: u16) -> String {
130 let module = parser::IrModule::parse(ir_bytes).unwrap_or_else(|e| panic!("IR parse: {e}"));
131 let slots =
132 slot::SlotData::from_json(slots_json, &module).unwrap_or_else(|e| panic!("slots: {e}"));
133 walker::walk_island(&module, &slots, island_id).unwrap_or_else(|e| panic!("walk island: {e}"))
134}