facet-wrapped/src/groups.rs
//! **Bundle groups** (2026-09-29, owner: facets are grouped through the build thing).
//!
//! Every registry entry belongs to exactly one group, and every group is its own wasm
//! bundle with its own manifest:
//!
//! | group | built by | holds |
//! |---|---|---|
//! | `core` | `facet-wrapped` (no features) | every entry not listed below — byte-identical to the pre-group bundle |
//! | `docs` | `facet-wrapped-group --features docs` | docview, manual |
//! | `model3d` | `facet-wrapped-group --features model3d` | model3d |
//! | `sim` | `facet-wrapped-group --features sim` | simview |
//!
//! Entry → group, the group → row/dist table and the routing table the loader reads
//! (`assets/bundles.json`) are ALL the recipe's: `facett-build-thing`'s `BUNDLE_GROUPS`.
//! [`GROUP_ENTRIES`] is the compile-time copy this crate needs for its own refusal (the
//! recipe cannot be a dependency of the wasm without moving the core bundle's bytes);
//! `facet-wrapped-group/tests/groups.rs` holds it to the recipe both ways and to the
//! modules that register the entries.
/// The core group: the default bundle, every entry not in [`GROUP_ENTRIES`].
pub const CORE: &str = "core";
/// The entries outside `core`, with their group. The modules that register them
/// (`docs.rs`, `model.rs`) are compiled only under their group's feature.
pub const GROUP_ENTRIES: &[(&str, &str)] = &[("docview", "docs"), ("manual", "docs"), ("model3d", "model3d"), ("simview", "sim")];
/// The group of an entry.
#[must_use]
pub fn group_of(name: &str) -> &'static str {
GROUP_ENTRIES.iter().find(|(n, _)| *n == name).map_or(CORE, |(_, g)| g)
}
/// The group THIS build is. `facet-wrapped` declares no features, so it is `core`;
/// `facet-wrapped-group` is built with exactly one group feature.
#[allow(unexpected_cfgs)]
pub const THIS_BUNDLE: &str = if cfg!(feature = "docs") {
"docs"
} else if cfg!(feature = "model3d") {
"model3d"
} else if cfg!(feature = "sim") {
"sim"
} else {
CORE
};
/// A mount of `name` in a bundle that does not hold it: the refusal, naming the
/// bundle that does. `None` when this bundle holds `name` (or `name` is unknown to
/// every group — the registry's own "unknown component" answers that).
#[must_use]
pub fn wrong_bundle(name: &str) -> Option<String> {
let right = group_of(name);
(right != THIS_BUNDLE).then(|| {
format!(
"component `{name}` is not in the `{THIS_BUNDLE}` bundle: it is in the `{right}` bundle — \
load that bundle for it (the loader picks it from the routing table)"
)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_core_name_is_core_and_a_grouped_name_is_its_group() {
assert_eq!(group_of("table"), CORE);
assert_eq!(group_of("docview"), "docs");
assert_eq!(group_of("model3d"), "model3d");
}
#[test]
fn the_refusal_names_the_right_bundle() {
if THIS_BUNDLE == CORE {
let m = wrong_bundle("docview").expect("docview is not core");
assert!(m.contains("`docs` bundle"), "{m}");
assert!(wrong_bundle("table").is_none());
} else {
let m = wrong_bundle("table").expect("table is core");
assert!(m.contains("`core` bundle"), "{m}");
}
}
}