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 serde_saphyr::from_str_with_options(s, yaml_options()).map_err(|e| AssetError::Parse {
81 path: format!("{path} (layer `{source_name}`)"),
82 format: "YAML",
83 message: e.to_string(),
84 })
85 })
86 }
87
88 pub fn load_merged_json<T: DeserializeOwned>(&self, path: &str) -> Result<T, AssetError> {
90 self.load_merged(path, "JSON", |bytes, source_name| {
91 serde_json::from_slice::<serde_json::Value>(bytes).map_err(|e| AssetError::Parse {
92 path: format!("{path} (layer `{source_name}`)"),
93 format: "JSON",
94 message: e.to_string(),
95 })
96 })
97 }
98
99 fn load_merged<T, F>(&self, path: &str, format: &'static str, parse: F) -> Result<T, AssetError>
100 where
101 T: DeserializeOwned,
102 F: Fn(&[u8], &str) -> Result<serde_json::Value, AssetError>,
103 {
104 let mut merged: Option<serde_json::Value> = None;
105 for layer in self.layers.iter() {
106 let Some(bytes) = layer.read(path) else { continue };
107 let parsed = parse(&bytes, layer.name())?;
108 merged = Some(match merged {
109 None => parsed,
110 Some(mut acc) => {
111 json_patch::merge(&mut acc, &parsed);
112 acc
113 }
114 });
115 }
116 let merged = merged.ok_or_else(|| AssetError::NotFound(path.to_string()))?;
117 serde_json::from_value::<T>(merged).map_err(|e| AssetError::Parse {
118 path: path.to_string(),
119 format,
120 message: e.to_string(),
121 })
122 }
123}
124
125#[derive(Default)]
128#[must_use]
129pub struct AssetsBuilder {
130 layers: Vec<Arc<dyn AssetSource>>,
131}
132
133impl std::fmt::Debug for AssetsBuilder {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 let names: Vec<&str> = self.layers.iter().map(|l| l.name()).collect();
136 f.debug_struct("AssetsBuilder").field("layers", &names).finish()
137 }
138}
139
140impl std::fmt::Debug for Assets {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 let names: Vec<&str> = self.layers.iter().map(|l| l.name()).collect();
143 f.debug_struct("Assets").field("layers", &names).finish()
144 }
145}
146
147impl AssetsBuilder {
148 pub fn new() -> Self {
150 Self::default()
151 }
152
153 pub fn embedded<E>(mut self, label: &'static str) -> Self
167 where
168 E: RustEmbed + Send + Sync + 'static,
169 {
170 self.layers.push(Arc::new(EmbeddedSource::<E>::new(label)));
171 self
172 }
173
174 pub fn directory(mut self, root: impl Into<PathBuf>, label: impl Into<String>) -> Self {
176 self.layers.push(Arc::new(DirectorySource::new(root, label)));
177 self
178 }
179
180 pub fn memory(mut self, label: impl Into<String>, files: HashMap<String, Vec<u8>>) -> Self {
182 self.layers.push(Arc::new(MemorySource::new(label, files)));
183 self
184 }
185
186 pub fn source(mut self, source: Arc<dyn AssetSource>) -> Self {
189 self.layers.push(source);
190 self
191 }
192
193 #[must_use]
195 pub fn build(self) -> Assets {
196 Assets { layers: self.layers.into() }
197 }
198}
199
200fn yaml_options() -> serde_saphyr::Options {
205 serde_saphyr::options! {
206 strict_booleans: true,
207 merge_keys: serde_saphyr::MergeKeyPolicy::AsOrdinary,
208 duplicate_keys: serde_saphyr::DuplicateKeyPolicy::Error,
209 with_snippet: false,
210 }
211}