1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Custom configuration loader with [Fn].
//!
//! ### Example
//! ```rust
//! use std::collections::HashMap;
//! use url::Url;
//! use plugx_config::entity::ConfigurationEntity;
//! use plugx_config::loader::{ConfigurationLoader, closure::ConfigurationLoaderFn};
//!
//! let url_scheme = "xyz";
//! let loader_name = "my-custom-loader";
//! let loader_fn = move |url: &Url, maybe_whitelist: Option<&[String]>| {
//!     // TODO: check `url` and get my own options
//!     let mut result = HashMap::new();
//!     // TODO: check whitelist
//!     // load configurations
//!     // for example I load configuration for plugin named `foo`:
//!     let entity = ConfigurationEntity::new(url.clone(), "foo", loader_name)
//!         // If you do not set format here, `Configuration` struct will try to guess it later:
//!         .with_format("yml")
//!         // If you do not set contents here, the default value will be `plugx_input::Input::empty_map()`
//!         .with_contents("hello: world");
//!     result.insert("foo".into(), entity);
//!     Ok(result)
//! };
//! let url = "xyz:///my/own/path?my_option=value".parse().unwrap();
//! let loader = ConfigurationLoaderFn::new(loader_name, Box::new(loader_fn), url_scheme);
//! let loaded = loader.try_load(&url, None).unwrap();
//! assert!(loaded.contains_key("foo"));
//! ```
//!
//! See [crate::loader] documentation to known how loaders work.

use crate::{
    entity::ConfigurationEntity,
    loader::{BoxedLoaderModifierFn, ConfigurationLoadError, ConfigurationLoader},
};
use std::{
    collections::HashMap,
    fmt::{Debug, Formatter},
};
use url::Url;

/// A `|&Url, Option<&[String]>| -> Result<HashMap<_, _>, ConfigurationLoadError>` [Fn]
pub type BoxedLoaderFn = Box<
    dyn Fn(
            &Url,
            Option<&[String]>,
        ) -> Result<HashMap<String, ConfigurationEntity>, ConfigurationLoadError>
        + Send
        + Sync,
>;

/// Builder struct.
pub struct ConfigurationLoaderFn {
    name: &'static str,
    loader: BoxedLoaderFn,
    maybe_modifier: Option<BoxedLoaderModifierFn>,
    scheme_list: Vec<String>,
}

impl Debug for ConfigurationLoaderFn {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ConfigurationLoaderFn")
            .field("name", &self.name)
            .field("scheme_list", &self.scheme_list)
            .finish()
    }
}

impl ConfigurationLoaderFn {
    pub fn new<S: AsRef<str>>(name: &'static str, loader: BoxedLoaderFn, scheme: S) -> Self {
        Self {
            name,
            loader,
            maybe_modifier: None,
            scheme_list: [scheme.as_ref().into()].into(),
        }
    }

    pub fn set_name(&mut self, name: &'static str) {
        self.name = name
    }

    pub fn with_name(mut self, name: &'static str) -> Self {
        self.set_name(name);
        self
    }

    pub fn set_loader(&mut self, loader: BoxedLoaderFn) {
        self.loader = loader
    }

    pub fn with_loader(mut self, loader: BoxedLoaderFn) -> Self {
        self.set_loader(loader);
        self
    }

    pub fn set_modifier(&mut self, modifier: BoxedLoaderModifierFn) {
        self.maybe_modifier = Some(modifier)
    }

    pub fn with_modifier(mut self, modifier: BoxedLoaderModifierFn) -> Self {
        self.set_modifier(modifier);
        self
    }

    pub fn set_scheme_list<S: AsRef<str>>(&mut self, scheme_list: Vec<S>) {
        self.scheme_list = scheme_list
            .into_iter()
            .map(|scheme| scheme.as_ref().to_string())
            .collect();
    }

    pub fn with_scheme_list<S: AsRef<str>>(mut self, scheme_list: Vec<S>) -> Self {
        self.set_scheme_list(scheme_list);
        self
    }
}

impl ConfigurationLoader for ConfigurationLoaderFn {
    fn name(&self) -> &'static str {
        self.name
    }

    fn scheme_list(&self) -> Vec<String> {
        self.scheme_list.clone()
    }

    fn try_load(
        &self,
        url: &Url,
        maybe_whitelist: Option<&[String]>,
    ) -> Result<HashMap<String, ConfigurationEntity>, ConfigurationLoadError> {
        let mut result = (self.loader)(url, maybe_whitelist)?;
        if let Some(ref modifier) = self.maybe_modifier {
            // TODO: logging
            modifier(url, &mut result)?
        }
        Ok(result)
    }
}