1use std::collections::{BTreeSet, HashMap};
4use std::path::PathBuf;
5use std::sync::Arc;
6
7use rust_embed::RustEmbed;
8use serde::de::DeserializeOwned;
9
10use crate::error::AssetError;
11use crate::source::{AssetSource, DirectorySource, EmbeddedSource, MemorySource};
12
13#[derive(Clone, Default)]
16pub struct Assets {
17 layers: Arc<[Arc<dyn AssetSource>]>,
18}
19
20impl Assets {
21 pub fn builder() -> AssetsBuilder {
23 AssetsBuilder::default()
24 }
25
26 #[must_use]
29 pub fn open(&self, path: &str) -> Option<Vec<u8>> {
30 for layer in self.layers.iter().rev() {
31 if let Some(bytes) = layer.read(path) {
32 return Some(bytes);
33 }
34 }
35 None
36 }
37
38 pub fn open_text(&self, path: &str) -> Result<String, AssetError> {
41 let bytes = self.open(path).ok_or_else(|| AssetError::NotFound(path.to_string()))?;
42 String::from_utf8(bytes).map_err(|_| AssetError::NotUtf8 { path: path.to_string() })
43 }
44
45 #[must_use]
47 pub fn exists(&self, path: &str) -> bool {
48 self.layers.iter().any(|l| l.read(path).is_some())
49 }
50
51 #[must_use]
54 pub fn list_dir(&self, dir: &str) -> Vec<String> {
55 let mut all = BTreeSet::new();
56 for layer in self.layers.iter() {
57 for entry in layer.list(dir) {
58 all.insert(entry);
59 }
60 }
61 all.into_iter().collect()
62 }
63
64 pub fn load_merged_yaml<T: DeserializeOwned>(&self, path: &str) -> Result<T, AssetError> {
74 self.load_merged(path, "YAML", |bytes, source_name| {
75 let s = std::str::from_utf8(bytes).map_err(|e| AssetError::Parse {
76 path: format!("{path} (layer `{source_name}`)"),
77 format: "YAML",
78 message: e.to_string(),
79 })?;
80 let yaml_value: serde_yaml::Value =
81 serde_yaml::from_str(s).map_err(|e| AssetError::Parse {
82 path: format!("{path} (layer `{source_name}`)"),
83 format: "YAML",
84 message: e.to_string(),
85 })?;
86 serde_json::to_value(yaml_value).map_err(|e| AssetError::Parse {
91 path: format!("{path} (layer `{source_name}`)"),
92 format: "YAML",
93 message: e.to_string(),
94 })
95 })
96 }
97
98 pub fn load_merged_json<T: DeserializeOwned>(&self, path: &str) -> Result<T, AssetError> {
100 self.load_merged(path, "JSON", |bytes, source_name| {
101 serde_json::from_slice::<serde_json::Value>(bytes).map_err(|e| AssetError::Parse {
102 path: format!("{path} (layer `{source_name}`)"),
103 format: "JSON",
104 message: e.to_string(),
105 })
106 })
107 }
108
109 fn load_merged<T, F>(&self, path: &str, format: &'static str, parse: F) -> Result<T, AssetError>
110 where
111 T: DeserializeOwned,
112 F: Fn(&[u8], &str) -> Result<serde_json::Value, AssetError>,
113 {
114 let mut merged: Option<serde_json::Value> = None;
115 for layer in self.layers.iter() {
116 let Some(bytes) = layer.read(path) else { continue };
117 let parsed = parse(&bytes, layer.name())?;
118 merged = Some(match merged {
119 None => parsed,
120 Some(mut acc) => {
121 json_patch::merge(&mut acc, &parsed);
122 acc
123 }
124 });
125 }
126 let merged = merged.ok_or_else(|| AssetError::NotFound(path.to_string()))?;
127 serde_json::from_value::<T>(merged).map_err(|e| AssetError::Parse {
128 path: path.to_string(),
129 format,
130 message: e.to_string(),
131 })
132 }
133}
134
135#[derive(Default)]
138#[must_use]
139pub struct AssetsBuilder {
140 layers: Vec<Arc<dyn AssetSource>>,
141}
142
143impl std::fmt::Debug for AssetsBuilder {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 let names: Vec<&str> = self.layers.iter().map(|l| l.name()).collect();
146 f.debug_struct("AssetsBuilder").field("layers", &names).finish()
147 }
148}
149
150impl std::fmt::Debug for Assets {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 let names: Vec<&str> = self.layers.iter().map(|l| l.name()).collect();
153 f.debug_struct("Assets").field("layers", &names).finish()
154 }
155}
156
157impl AssetsBuilder {
158 pub fn new() -> Self {
160 Self::default()
161 }
162
163 pub fn embedded<E>(mut self, label: &'static str) -> Self
177 where
178 E: RustEmbed + Send + Sync + 'static,
179 {
180 self.layers.push(Arc::new(EmbeddedSource::<E>::new(label)));
181 self
182 }
183
184 pub fn directory(mut self, root: impl Into<PathBuf>, label: impl Into<String>) -> Self {
186 self.layers.push(Arc::new(DirectorySource::new(root, label)));
187 self
188 }
189
190 pub fn memory(mut self, label: impl Into<String>, files: HashMap<String, Vec<u8>>) -> Self {
192 self.layers.push(Arc::new(MemorySource::new(label, files)));
193 self
194 }
195
196 pub fn source(mut self, source: Arc<dyn AssetSource>) -> Self {
199 self.layers.push(source);
200 self
201 }
202
203 #[must_use]
205 pub fn build(self) -> Assets {
206 Assets { layers: self.layers.into() }
207 }
208}