fmtstruct/loader/
dyn_loader.rs1#[cfg(feature = "alloc")]
4use crate::{FmtError, Format, LoadResult, PreProcess, Source, ValidateConfig, format::AnyFormat};
5#[cfg(feature = "alloc")]
6use alloc::boxed::Box;
7#[cfg(feature = "alloc")]
8use alloc::vec::Vec;
9#[cfg(feature = "alloc")]
10use serde::de::DeserializeOwned;
11
12#[cfg(feature = "alloc")]
13pub struct DynLoader {
14 source: Box<dyn Source>,
15 formats: Vec<AnyFormat>,
16}
17
18#[cfg(feature = "alloc")]
19impl core::fmt::Debug for DynLoader {
20 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
21 f.debug_struct("DynLoader")
22 .field("source", &"<dyn Source>")
23 .field("formats", &self.formats)
24 .finish()
25 }
26}
27
28#[cfg(feature = "alloc")]
29pub struct DynLoaderBuilder {
30 source: Option<Box<dyn Source>>,
31 formats: Vec<AnyFormat>,
32}
33
34#[cfg(feature = "alloc")]
35impl DynLoaderBuilder {
36 pub fn new() -> Self {
37 Self {
38 source: None,
39 formats: Vec::new(),
40 }
41 }
42
43 pub fn source(mut self, source: impl Source + 'static) -> Self {
44 self.source = Some(Box::new(source));
45 self
46 }
47
48 pub fn format(mut self, format: AnyFormat) -> Self {
49 self.formats.push(format);
50 self
51 }
52
53 pub fn build(self) -> Result<DynLoader, &'static str> {
54 let source = self.source.ok_or("source is required")?;
55 if self.formats.is_empty() {
56 return Err("at least one format is required");
57 }
58 Ok(DynLoader {
59 source,
60 formats: self.formats,
61 })
62 }
63}
64
65#[cfg(feature = "alloc")]
66impl DynLoader {
67 pub fn new(source: Box<dyn Source>, formats: Vec<AnyFormat>) -> Self {
68 Self { source, formats }
69 }
70
71 pub fn builder() -> DynLoaderBuilder {
72 DynLoaderBuilder::new()
73 }
74
75 pub async fn load<T>(&self, base_name: &str) -> LoadResult<T>
77 where
78 T: DeserializeOwned + PreProcess + ValidateConfig,
79 {
80 let mut found: Option<(alloc::string::String, &AnyFormat)> = None;
81 let mut conflicts = Vec::new();
82
83 for format in &self.formats {
84 for ext in format.extensions() {
85 let key = alloc::format!("{}.{}", base_name, ext);
86 if self.source.exists(&key).await {
87 if found.is_some() {
88 conflicts.push(key);
89 } else {
90 found = Some((key, format));
91 }
92 }
93 }
94 }
95
96 if let Some((key, format)) = found {
97 self.load_explicit(&key, format, conflicts).await
98 } else {
99 LoadResult::NotFound
100 }
101 }
102
103 pub async fn load_file<T>(&self, path: &str) -> LoadResult<T>
105 where
106 T: DeserializeOwned + PreProcess + ValidateConfig,
107 {
108 let ext = if let Some(idx) = path.rfind('.') {
109 &path[idx + 1..]
110 } else {
111 #[cfg(feature = "alloc")]
112 return LoadResult::Invalid(FmtError::ParseError(alloc::string::String::from(
113 "missing extension",
114 )));
115 #[cfg(not(feature = "alloc"))]
116 return LoadResult::Invalid(FmtError::ParseError);
117 };
118
119 for format in &self.formats {
120 if format.extensions().contains(&ext) {
121 return self.load_explicit(path, format, Vec::new()).await;
122 }
123 }
124 LoadResult::NotFound
125 }
126
127 #[cfg(feature = "validate")]
129 pub async fn validate<T>(&self, base_name: &str) -> Result<(), FmtError>
130 where
131 T: DeserializeOwned + PreProcess + validator::Validate,
132 {
133 match self.load::<T>(base_name).await {
134 LoadResult::Ok { mut value, .. } => {
135 value.set_context(base_name);
136 value.validate_config()
137 }
138 LoadResult::Invalid(e) => Err(e),
139 LoadResult::NotFound => Err(FmtError::NotFound),
140 }
141 }
142
143 async fn load_explicit<T>(
145 &self,
146 key: &str,
147 format: &AnyFormat,
148 conflicts: Vec<alloc::string::String>,
149 ) -> LoadResult<T>
150 where
151 T: DeserializeOwned + PreProcess + ValidateConfig,
152 {
153 let bytes = match self.source.read(key).await {
154 Ok(b) => b,
155 Err(FmtError::NotFound) => return LoadResult::NotFound,
156 Err(e) => return LoadResult::Invalid(e),
157 };
158
159 match format.parse::<T>(&bytes) {
160 Ok(mut obj) => {
161 obj.pre_process();
162
163 LoadResult::Ok {
164 value: obj,
165 info: crate::LoadInfo {
166 #[cfg(feature = "std")]
167 path: std::path::PathBuf::from(key),
168 #[cfg(not(feature = "std"))]
169 key: alloc::string::String::from(key),
170 format: format.extensions().first().copied().unwrap_or("unknown"),
171 #[cfg(feature = "std")]
172 conflicts: conflicts
173 .into_iter()
174 .map(std::path::PathBuf::from)
175 .collect(),
176 #[cfg(not(feature = "std"))]
177 conflicts,
178 },
179 }
180 }
181 Err(e) => LoadResult::Invalid(e),
182 }
183 }
184}