plugx_config/loader/
mod.rs1use crate::entity::ConfigurationEntity;
19use serde::{de::IntoDeserializer, Deserialize, Deserializer, Serialize};
20use std::fmt;
21use std::fmt::{Debug, Display};
22use std::marker::PhantomData;
23use url::Url;
24
25pub mod closure;
26#[cfg(feature = "env")]
27pub mod env;
28#[cfg(feature = "fs")]
29pub mod fs;
30
31#[derive(Debug, thiserror::Error)]
33pub enum Error {
34 #[error("{loader} configuration loader could not found {item} from URL `{url}`")]
36 NotFound {
37 loader: String,
38 url: Url,
39 item: Box<String>,
40 },
41 #[error("{loader} configuration loader has no access to load configuration from `{url}`")]
43 NoAccess { loader: String, url: Url },
44 #[error(
46 "{loader} configuration loader reached timeout `{timeout_in_seconds}s` to load `{url}`"
47 )]
48 Timeout {
49 loader: String,
50 url: Url,
51 timeout_in_seconds: usize,
52 },
53 #[error("{loader} configuration loader got invalid URL `{url}`")]
55 InvalidUrl {
56 loader: String,
57 url: String,
58 source: anyhow::Error,
59 },
60 #[error("Could not found configuration loader for scheme {scheme}")]
62 UrlSchemeNotFound { scheme: String },
63 #[error("{loader} configuration loader found duplicate configurations `{url}/{plugin}.({format_1}|{format_2})`")]
65 Duplicate {
66 loader: Box<String>,
67 url: Url,
68 plugin: Box<String>,
69 format_1: Box<String>,
70 format_2: Box<String>,
71 },
72 #[error("{loader} configuration loader could not {description} `{url}`")]
74 Load {
75 loader: String,
76 url: Url,
77 description: Box<String>,
78 source: anyhow::Error,
79 },
80 #[error("Could not found a loader that supports URL scheme `{scheme}` in given URL `{url}`")]
81 LoaderNotFound { scheme: String, url: Url },
82 #[error(transparent)]
83 Other(#[from] anyhow::Error),
84}
85
86#[derive(Debug, Clone, PartialEq, Serialize)]
134#[serde(rename_all = "kebab-case")]
135pub enum SoftErrors<T> {
136 All,
137 List(Vec<T>),
138}
139
140struct SoftErrorsVisitor<T> {
141 _marker: PhantomData<T>,
142}
143
144pub trait Loader: Send + Sync + Debug + Display {
146 fn scheme_list(&self) -> Vec<String>;
150
151 fn load(
158 &self,
159 url: &Url,
160 maybe_whitelist: Option<&[String]>,
161 skip_soft_errors: bool,
162 ) -> Result<Vec<(String, ConfigurationEntity)>, Error>;
163}
164
165#[cfg(feature = "qs")]
166pub fn deserialize_query_string<T: serde::de::DeserializeOwned>(
170 loader_name: impl AsRef<str>,
171 url: &Url,
172) -> Result<T, Error> {
173 serde_qs::from_str(url.query().unwrap_or_default()).map_err(|error| Error::InvalidUrl {
174 loader: loader_name.as_ref().to_string(),
175 source: error.into(),
176 url: url.to_string(),
177 })
178}
179
180impl<'de, T: Deserialize<'de>> SoftErrors<T> {
181 pub fn new_all() -> Self {
182 Self::All
183 }
184
185 pub fn new_list() -> Self {
186 Self::List(Vec::with_capacity(0))
187 }
188
189 pub fn skip_all(&self) -> bool {
190 matches!(self, Self::All)
191 }
192
193 pub fn add_soft_error(&mut self, soft_error: T) {
194 if let Self::List(soft_errors) = self {
195 soft_errors.push(soft_error);
196 }
197 }
198
199 pub fn with_soft_error(mut self, soft_error: T) -> Self {
200 self.add_soft_error(soft_error);
201 self
202 }
203
204 pub fn maybe_soft_error_list(&self) -> Option<&Vec<T>> {
205 if let Self::List(soft_errors) = self {
206 Some(soft_errors)
207 } else {
208 None
209 }
210 }
211
212 pub fn maybe_soft_error_list_mut(&mut self) -> Option<&mut Vec<T>> {
213 if let Self::List(soft_errors) = self {
214 Some(soft_errors)
215 } else {
216 None
217 }
218 }
219}
220
221impl<'de, T: Deserialize<'de> + PartialEq> SoftErrors<T> {
222 pub fn contains(&self, soft_error: &T) -> bool {
223 if let Self::List(soft_errors) = self {
224 soft_errors.contains(soft_error)
225 } else {
226 true
227 }
228 }
229}
230
231impl<'de, T: Deserialize<'de>> Default for SoftErrors<T> {
232 fn default() -> Self {
233 Self::new_list()
234 }
235}
236
237impl<'de, T> serde::de::Visitor<'de> for SoftErrorsVisitor<T>
238where
239 T: Deserialize<'de>,
240{
241 type Value = SoftErrors<T>;
242
243 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
244 formatter.write_str("`all` or dot separated soft errors for configuration loader")
245 }
246
247 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
248 where
249 E: serde::de::Error,
250 {
251 let parts: Vec<_> = v
252 .split('.')
253 .filter(|item| !item.is_empty())
254 .map(String::from)
255 .collect();
256 if parts.contains(&"all".to_string()) {
257 Ok(SoftErrors::All)
258 } else {
259 Ok(SoftErrors::List(Vec::deserialize(
260 parts.into_deserializer(),
261 )?))
262 }
263 }
264
265 fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
266 where
267 E: serde::de::Error,
268 {
269 self.visit_str(v)
270 }
271
272 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
273 where
274 E: serde::de::Error,
275 {
276 self.visit_str(v.as_str())
277 }
278}
279
280impl<'de, T: Deserialize<'de>> Deserialize<'de> for SoftErrors<T> {
281 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
282 where
283 D: Deserializer<'de>,
284 {
285 deserializer.deserialize_str(SoftErrorsVisitor {
286 _marker: PhantomData,
287 })
288 }
289}