zenith_core/asset/
provider.rs1use std::collections::BTreeMap;
8use std::sync::Arc;
9
10use crate::ast::AssetKind;
11
12#[derive(Clone)]
14pub struct AssetData {
15 pub id: String,
17 pub bytes: Arc<[u8]>,
19 pub kind: AssetKind,
21}
22
23impl std::fmt::Debug for AssetData {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 f.debug_struct("AssetData")
26 .field("id", &self.id)
27 .field("bytes_len", &self.bytes.len())
28 .field("kind", &self.kind)
29 .finish()
30 }
31}
32
33pub trait AssetProvider {
38 #[must_use]
42 fn by_id(&self, id: &str) -> Option<AssetData>;
43}
44
45#[derive(Default)]
48pub struct BytesAssetProvider {
49 by_id: BTreeMap<String, AssetData>,
50}
51
52impl std::fmt::Debug for BytesAssetProvider {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 f.debug_struct("BytesAssetProvider")
55 .field("registered_assets", &self.by_id.keys().collect::<Vec<_>>())
56 .finish()
57 }
58}
59
60impl BytesAssetProvider {
61 #[must_use]
63 pub fn new() -> Self {
64 Self {
65 by_id: BTreeMap::new(),
66 }
67 }
68
69 pub fn register(&mut self, id: &str, kind: AssetKind, bytes: Arc<[u8]>) {
72 let key = id.to_owned();
73 let data = AssetData {
74 id: key.clone(),
75 bytes,
76 kind,
77 };
78 self.by_id.insert(key, data);
79 }
80}
81
82impl AssetProvider for BytesAssetProvider {
83 fn by_id(&self, id: &str) -> Option<AssetData> {
84 self.by_id.get(id).cloned()
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 fn dummy_bytes(fill: u8, len: usize) -> Arc<[u8]> {
93 Arc::from(vec![fill; len].as_slice())
94 }
95
96 #[test]
97 fn provider_new_is_empty() {
98 let p = BytesAssetProvider::new();
99 assert!(p.by_id("asset.missing").is_none());
100 }
101
102 #[test]
103 fn provider_register_and_by_id() {
104 let mut p = BytesAssetProvider::new();
105 let bytes_a = dummy_bytes(0xAA, 4);
106 let bytes_b = dummy_bytes(0xBB, 8);
107
108 p.register("asset.logo", AssetKind::Svg, bytes_a.clone());
109 p.register("asset.hero", AssetKind::Image, bytes_b.clone());
110
111 let logo = p.by_id("asset.logo").expect("asset.logo must be found");
112 assert_eq!(logo.id, "asset.logo");
113 assert_eq!(logo.kind, AssetKind::Svg);
114 assert_eq!(logo.bytes[0], 0xAA);
115 assert_eq!(logo.bytes.len(), 4);
116
117 let hero = p.by_id("asset.hero").expect("asset.hero must be found");
118 assert_eq!(hero.id, "asset.hero");
119 assert_eq!(hero.kind, AssetKind::Image);
120 assert_eq!(hero.bytes[0], 0xBB);
121 assert_eq!(hero.bytes.len(), 8);
122 }
123
124 #[test]
125 fn provider_unknown_id_returns_none() {
126 let mut p = BytesAssetProvider::new();
127 p.register("asset.a", AssetKind::Font, dummy_bytes(0, 1));
128 assert!(p.by_id("asset.does-not-exist").is_none());
129 }
130
131 #[test]
132 fn provider_re_register_overwrites() {
133 let mut p = BytesAssetProvider::new();
134 p.register("asset.x", AssetKind::Image, dummy_bytes(0x01, 2));
135 p.register("asset.x", AssetKind::Svg, dummy_bytes(0x02, 3));
136
137 let data = p.by_id("asset.x").expect("must be found");
138 assert_eq!(data.kind, AssetKind::Svg);
139 assert_eq!(data.bytes[0], 0x02);
140 assert_eq!(data.bytes.len(), 3);
141 }
142
143 #[test]
144 fn provider_by_id_clones_independently() {
145 let mut p = BytesAssetProvider::new();
146 p.register("asset.a", AssetKind::Image, dummy_bytes(0xFF, 4));
147
148 let d1 = p.by_id("asset.a").expect("first lookup");
149 let d2 = p.by_id("asset.a").expect("second lookup");
150 assert_eq!(d1.bytes.len(), d2.bytes.len());
152 assert_eq!(d1.id, d2.id);
153 }
154}