plugx_config/loader/
closure.rs

1//! Custom configuration loader with [Fn].
2//!
3//! ### Example
4//! ```rust
5//! use plugx_config::{
6//!     entity::ConfigurationEntity,
7//!     loader::{Loader, closure::Closure},
8//!     ext::url::Url,
9//! };
10//!
11//! let url_scheme = "xyz";
12//! let loader_name = "my-custom-loader";
13//! let loader_fn = move |url: &Url, maybe_whitelist: Option<&[String]>, skip_soft_errors: bool| {
14//!     // TODO: check `url` and get my own options
15//!     let mut result = Vec::new();
16//!     // TODO: check whitelist
17//!     // load configurations
18//!     // for example I load configuration for plugin named `foo`:
19//!     let entity = ConfigurationEntity::new("foo_sub_item", url.clone(), "foo", loader_name)
20//!         // If you do not set format here, `Configuration` struct will try to guess it later:
21//!         .with_format("yml")
22//!         // If you do not set contents here, the default value will be `plugx_input::Input::empty_map()`
23//!         .with_contents("hello: world");
24//!     result.push(("foo".to_string(), entity));
25//!     Ok(result)
26//! };
27//! let url = "xyz:///my/own/path?my_option=value".parse().unwrap();
28//! let loader = Closure::new(loader_name, Box::new(loader_fn), url_scheme);
29//! let loaded = loader.load(&url, None, false).unwrap();
30//! assert_eq!(loaded.len(), 1);
31//! ```
32//!
33//! * See [crate::loader] documentation to known how loaders work.
34//! * To detect your own options easily see [crate::loader::deserialize_query_string].
35//! * To detect your soft errors and apply them see [crate::loader::SoftErrors].
36
37use crate::{
38    entity::ConfigurationEntity,
39    loader::{Error, Loader},
40};
41use std::fmt::{Debug, Display, Formatter};
42use url::Url;
43
44/// A `|&Url, Option<&[String]>, bool| -> Result<Vec<String, ConfigurationEntity>, ConfigurationLoadError>` [Fn]
45pub type BoxedLoaderFn = Box<
46    dyn Fn(&Url, Option<&[String]>, bool) -> Result<Vec<(String, ConfigurationEntity)>, Error>
47        + Send
48        + Sync,
49>;
50
51/// Builder struct.
52pub struct Closure {
53    name: String,
54    loader: BoxedLoaderFn,
55    scheme_list: Vec<String>,
56}
57
58impl Debug for Closure {
59    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
60        f.debug_struct("ConfigurationLoaderFn")
61            .field("name", &self.name)
62            .field("scheme_list", &self.scheme_list)
63            .finish()
64    }
65}
66
67impl Closure {
68    pub fn new<S: AsRef<str>, N: AsRef<str>>(name: N, loader: BoxedLoaderFn, scheme: S) -> Self {
69        Self {
70            name: name.as_ref().to_string(),
71            loader,
72            scheme_list: [scheme.as_ref().into()].into(),
73        }
74    }
75
76    pub fn set_name<N: AsRef<str>>(&mut self, name: N) {
77        self.name = name.as_ref().to_string()
78    }
79
80    pub fn with_name<N: AsRef<str>>(mut self, name: N) -> Self {
81        self.set_name(name);
82        self
83    }
84
85    pub fn set_loader(&mut self, loader: BoxedLoaderFn) {
86        self.loader = loader
87    }
88
89    pub fn with_loader(mut self, loader: BoxedLoaderFn) -> Self {
90        self.set_loader(loader);
91        self
92    }
93
94    pub fn set_scheme_list<S: AsRef<str>>(&mut self, scheme_list: Vec<S>) {
95        self.scheme_list = scheme_list
96            .into_iter()
97            .map(|scheme| scheme.as_ref().to_string())
98            .collect();
99    }
100
101    pub fn with_scheme_list<S: AsRef<str>>(mut self, scheme_list: Vec<S>) -> Self {
102        self.set_scheme_list(scheme_list);
103        self
104    }
105}
106
107impl Display for Closure {
108    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
109        f.write_str(self.name.as_str())
110    }
111}
112
113impl Loader for Closure {
114    fn scheme_list(&self) -> Vec<String> {
115        self.scheme_list.clone()
116    }
117
118    fn load(
119        &self,
120        url: &Url,
121        maybe_whitelist: Option<&[String]>,
122        skip_soft_errors: bool,
123    ) -> Result<Vec<(String, ConfigurationEntity)>, Error> {
124        (self.loader)(url, maybe_whitelist, skip_soft_errors)
125    }
126}