Skip to main content

camel_component_api/
embedded_assets.rs

1//! Process-global registry of embedded xslt/xsd/sql assets (mission 351).
2//!
3//! Sealed artifacts carry their xslt/xsd/sql resources as verbatim store
4//! assets. At boot, `camel-cli` registers every xslt-, xsd-, and
5//! sql-class entry here under its store path (`assets/xslt/t.xslt`)
6//! from digest-verified store entries, and rewrites the corresponding
7//! endpoint/site references to `camel-embedded:<asset path>`. The
8//! in-process resolvers read such references from this registry — no
9//! file is written for those classes, so a sealed artifact boots on a
10//! host with no writable temp directory. This module mirrors the
11//! `camel-proto-compiler::embedded` registry (mission 350) that serves
12//! the proto class; the two live in separate leaf crates because the
13//! proto compiler and this API crate cannot depend on each other.
14//!
15//! # Reserved reference prefix
16//!
17//! [`crate::embedded_assets::EMBEDDED_REF_PREFIX`] (`camel-embedded:`) and its percent-encoded
18//! spelling (`camel-embedded%3A`) are reserved: a value that starts with
19//! either spelling is resolved against this registry and never against
20//! the filesystem. A real file whose name starts with either spelling is
21//! therefore shadowed — an accepted reservation, documented here and in
22//! the consuming components' acceptance rules.
23//!
24//! Resolution fails closed: a reference whose key was never registered
25//! resolves to `None`, and the caller reports a boot error instead of
26//! falling back to the host filesystem.
27
28use std::collections::HashMap;
29use std::sync::{Arc, Mutex, OnceLock};
30
31/// Prefix marking an in-memory asset reference: `camel-embedded:<asset
32/// path>`. Reserved across the workspace; see the module documentation.
33pub const EMBEDDED_REF_PREFIX: &str = "camel-embedded:";
34
35/// Percent-encoded spelling of [`EMBEDDED_REF_PREFIX`], used when the
36/// reference travels inside a URI component.
37const ENCODED_REF_PREFIX: &str = "camel-embedded%3A";
38
39type Registry = HashMap<String, Arc<[u8]>>;
40
41fn registry() -> &'static Mutex<Registry> {
42    static REGISTRY: OnceLock<Mutex<Registry>> = OnceLock::new();
43    REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
44}
45
46/// Registers `bytes` under `name` (the store asset path, e.g.
47/// `assets/xslt/t.xslt`). A later registration for the same name
48/// replaces the earlier bytes. Idempotent and infallible: the registry
49/// is populated once per boot from verified store entries.
50pub fn register(name: &str, bytes: Vec<u8>) {
51    let mut guard = registry().lock().unwrap_or_else(|p| p.into_inner());
52    guard.insert(name.to_owned(), Arc::from(bytes.into_boxed_slice()));
53}
54
55/// Returns the bytes registered under `name`, or `None` when the name is
56/// not registered (the reference then fails closed at resolution).
57pub fn get(name: &str) -> Option<Arc<[u8]>> {
58    let guard = registry().lock().unwrap_or_else(|p| p.into_inner());
59    guard.get(name).cloned()
60}
61
62/// Resolves an embedded asset reference to its registered bytes.
63///
64/// Two postures, distinguished by spelling:
65///
66/// - a value starting with the canonical [`EMBEDDED_REF_PREFIX`] is a
67///   literal registry key after prefix removal; the suffix is NEVER
68///   decoded (store paths may contain spaces, Unicode, colons, and
69///   literal `%` sequences, and decoding would corrupt the key);
70/// - a value starting with the encoded prefix (`camel-embedded%3A`) is
71///   percent-decoded in full, exactly once, and the canonical prefix is
72///   then stripped from the result. Invalid UTF-8 after decoding yields
73///   `None`.
74///
75/// Returns `None` when the value carries neither spelling or when the
76/// key is not registered (fail closed; no filesystem fallback).
77pub fn resolve_ref(value: &str) -> Option<Arc<[u8]>> {
78    if let Some(name) = value.strip_prefix(EMBEDDED_REF_PREFIX) {
79        return get(name);
80    }
81    if value.starts_with(ENCODED_REF_PREFIX) {
82        let decoded = percent_encoding::percent_decode_str(value)
83            .decode_utf8()
84            .ok()?;
85        let name = decoded.strip_prefix(EMBEDDED_REF_PREFIX)?;
86        return get(name);
87    }
88    None
89}
90
91#[cfg(test)]
92mod tests {
93    use super::{register, resolve_ref};
94    use std::sync::Arc;
95
96    fn bytes(value: Option<Arc<[u8]>>) -> Vec<u8> {
97        value.expect("registered reference resolves").to_vec()
98    }
99
100    #[test]
101    fn encoded_prefix_is_the_canonical_prefix_with_encoded_colon() {
102        assert_eq!(
103            super::ENCODED_REF_PREFIX,
104            super::EMBEDDED_REF_PREFIX.replace(':', "%3A")
105        );
106    }
107
108    #[test]
109    fn resolve_ref_resolves_canonical_and_percent_encoded_spellings() {
110        register("assets/xslt/t.xslt", b"stylesheet-bytes".to_vec());
111        assert_eq!(
112            &bytes(resolve_ref("camel-embedded:assets/xslt/t.xslt"))[..],
113            b"stylesheet-bytes"
114        );
115        assert_eq!(
116            &bytes(resolve_ref("camel-embedded%3Aassets%2Fxslt%2Ft.xslt"))[..],
117            b"stylesheet-bytes"
118        );
119    }
120
121    #[test]
122    fn resolve_ref_canonical_suffix_is_literal() {
123        register("assets/x/a%20b.xslt", b"pct".to_vec());
124        register("assets/x/a b.xslt", b"space".to_vec());
125        assert_eq!(
126            &bytes(resolve_ref("camel-embedded:assets/x/a%20b.xslt"))[..],
127            b"pct",
128            "the canonical suffix must never be percent-decoded"
129        );
130        assert_eq!(
131            &bytes(resolve_ref("camel-embedded:assets/x/a b.xslt"))[..],
132            b"space"
133        );
134    }
135
136    #[test]
137    fn resolve_ref_encoded_handles_spaces_unicode_colon_and_double_encode() {
138        register("assets/styles/a b.xslt", b"s".to_vec());
139        register("assets/styles/ünïcode.xslt", b"u".to_vec());
140        register("assets/styles/a:b.xslt", b"c".to_vec());
141        register("assets/x/lit%20.xslt", b"litpct".to_vec());
142        register("assets/x/lit.xslt", b"lit".to_vec());
143        // The two `lit` fixtures back the literal canonical spellings: the
144        // percent sequence in the first key stays undecoded.
145        assert_eq!(
146            &bytes(resolve_ref("camel-embedded:assets/x/lit%20.xslt"))[..],
147            b"litpct"
148        );
149        assert_eq!(
150            &bytes(resolve_ref("camel-embedded:assets/x/lit.xslt"))[..],
151            b"lit"
152        );
153        // Re-registered with the same bytes so this test does not depend on
154        // the sibling test's registration order (the registry is global).
155        register("assets/x/a%20b.xslt", b"pct".to_vec());
156        register("assets/x/a b.xslt", b"space".to_vec());
157
158        assert_eq!(
159            &bytes(resolve_ref("camel-embedded%3Aassets%2Fstyles%2Fa%20b.xslt"))[..],
160            b"s"
161        );
162        assert_eq!(
163            &bytes(resolve_ref(
164                "camel-embedded%3Aassets%2Fstyles%2F%C3%BCn%C3%AFcode.xslt"
165            ))[..],
166            b"u"
167        );
168        assert_eq!(
169            &bytes(resolve_ref("camel-embedded%3Aassets%2Fstyles%2Fa%3Ab.xslt"))[..],
170            b"c"
171        );
172
173        // Decode-once: `%2520` → `%20`, so the literal key (with the percent
174        // sequence) is looked up and resolves to the `pct` registration.
175        assert_eq!(
176            &bytes(resolve_ref("camel-embedded%3Aassets%2Fx%2Fa%2520b.xslt"))[..],
177            b"pct"
178        );
179        assert_eq!(
180            &bytes(resolve_ref("camel-embedded%3Aassets%2Fx%2Fa%20b.xslt"))[..],
181            b"space"
182        );
183
184        // `%253A` decodes exactly once to the literal key
185        // `assets/x/lit%3A.xslt` (the `lit` fixtures above are unrelated —
186        // they only back the canonical assertions). That key is never
187        // registered, so resolution fails closed.
188        assert!(
189            resolve_ref("camel-embedded%3Aassets%2Fx%2Flit%253A.xslt").is_none(),
190            "a double-encoded reference must decode exactly once"
191        );
192    }
193
194    #[test]
195    fn resolve_ref_fails_closed_on_unknown_or_plain_values() {
196        assert!(resolve_ref("plain/path").is_none());
197        assert!(resolve_ref("camel-embedded:assets/never-registered").is_none());
198        assert!(
199            resolve_ref("camel-embedded%3Aassets%2Fx%2F%FFbad").is_none(),
200            "invalid UTF-8 after percent-decoding must fail closed"
201        );
202    }
203}