credence_lib/configuration/
credence.rs

1use super::{
2    super::{middleware::*, util::*},
3    caching::*,
4    encoding::*,
5    error::*,
6    files::*,
7    port::*,
8    render::*,
9    requests::*,
10    urls::*,
11};
12
13use {
14    compris::{normal::*, parse::*, resolve::*, *},
15    kutil_cli::debug::*,
16    kutil_http::{
17        cache::{Cache, CommonCacheKey},
18        tower::caching::*,
19        *,
20    },
21    std::{collections::*, io, path::*},
22};
23
24//
25// CredenceConfiguration
26//
27
28/// Credence configuration.
29#[derive(Clone, Debug, Debuggable, Resolve)]
30pub struct CredenceConfiguration {
31    /// Definitions (ignored).
32    #[resolve]
33    #[debuggable(skip)]
34    pub definitions: Option<Value>,
35
36    /// Files.
37    #[resolve]
38    #[debuggable(as(debuggable))]
39    pub files: FilesConfiguration,
40
41    /// Ports.
42    #[resolve]
43    #[debuggable(iter(kv), key_style(number), as(debuggable))]
44    pub ports: BTreeMap<u16, Port>,
45
46    /// Requests.
47    #[resolve]
48    #[debuggable(as(debuggable))]
49    pub requests: RequestsConfiguration,
50
51    /// URLs.
52    #[resolve]
53    #[debuggable(as(debuggable))]
54    pub urls: UrlsConfiguration,
55
56    /// Render.
57    #[resolve]
58    #[debuggable(as(debuggable))]
59    pub render: RenderConfiguration,
60
61    /// Caching.
62    #[resolve]
63    #[debuggable(as(debuggable))]
64    pub caching: CachingConfiguration,
65
66    /// Encoding.
67    #[resolve]
68    #[debuggable(as(debuggable))]
69    pub encoding: EncodingConfiguration,
70}
71
72impl CredenceConfiguration {
73    /// Resolve.
74    pub fn read<ReadT>(reader: &mut ReadT) -> io::Result<Self>
75    where
76        ReadT: io::Read,
77    {
78        let value =
79            Parser::new(Format::YAML).with_try_unsigned_integers(true).parse(reader).map_err(io::Error::other)?;
80
81        <Value as Resolve<_, CommonResolveContext, CommonResolveError>>::resolve(&value)
82            .map_err(io::Error::other)?
83            .ok_or(io::Error::other("no configuration"))
84    }
85
86    /// Validate.
87    pub fn validate<PathT>(&mut self, base_path: PathT) -> Result<(), ConfigurationError>
88    where
89        PathT: AsRef<Path>,
90    {
91        for port in &mut self.ports.values_mut() {
92            port.validate(&base_path)?;
93        }
94
95        self.files.validate(base_path)
96    }
97
98    /// Caching layer
99    pub fn caching_layer<RequestBodyT, CacheT>(
100        &self,
101        cache: CacheT,
102    ) -> CachingLayer<RequestBodyT, CacheT, CommonCacheKey>
103    where
104        CacheT: Cache<CommonCacheKey>,
105    {
106        // For closure move
107        let skip_media_types = self.encoding.skip_media_types();
108
109        CachingLayer::new()
110            .cache(cache.clone())
111            .cacheable_by_default(self.caching.default)
112            .cache_key(|context| {
113                if let Some(socket) = context.request.extensions().get::<Socket>() {
114                    context.cache_key.host = Some(socket.host.clone());
115                }
116            })
117            .min_cacheable_body_size(self.caching.min_body_size.value.into())
118            .max_cacheable_body_size(self.caching.max_body_size.value.into())
119            .min_encodable_body_size(self.encoding.min_body_size.value.into())
120            .encodable_by_default(self.encoding.default)
121            .encodable_by_response(move |context| match context.headers.content_type() {
122                Some(content_type) => !skip_media_types.contains(&content_type),
123                None => true,
124            })
125    }
126
127    /// Whether the URI path is hidden.
128    pub fn hide(&self, uri_path: &str) -> bool {
129        if uri_path_has_hidden_segment(uri_path) {
130            return true;
131        }
132
133        for hide in &self.urls.hide {
134            if hide.value.is_match(uri_path) {
135                return true;
136            }
137        }
138
139        self.render.is_rendered_page(uri_path).is_some()
140    }
141
142    /// Rendered page URI path.
143    ///
144    /// "{path}" -> "{path}.r.yaml" or "{path}.r.*"
145    pub fn rendered_page_uri_path(&self, uri_path: &str) -> io::Result<Option<String>> {
146        let asset_path = self.files.asset(uri_path);
147        if let Some(base_file_name) = asset_path.file_name() {
148            if let Some(parent) = asset_path.parent() {
149                if parent.is_dir() {
150                    let base_file_name = base_file_name.to_string_lossy().into_owned() + &self.render.midfix;
151                    for file_path in parent.read_dir()? {
152                        let file_path = file_path?.path();
153                        if let Some(file_name) = file_path.file_name() {
154                            let file_name = file_name.to_string_lossy();
155                            if file_name.starts_with(&base_file_name) {
156                                let extension = &file_name[base_file_name.len()..];
157                                return Ok(Some(String::from(uri_path) + &self.render.midfix + extension));
158                            }
159                        }
160                    }
161                }
162            }
163        }
164
165        Ok(None)
166    }
167}
168
169impl Default for CredenceConfiguration {
170    fn default() -> Self {
171        let mut port = Port::default();
172        port.name = "http".into();
173
174        Self {
175            files: FilesConfiguration::default(),
176            ports: BTreeMap::from([(8000, port)]),
177            requests: RequestsConfiguration::default(),
178            caching: CachingConfiguration::default(),
179            encoding: EncodingConfiguration::default(),
180            render: RenderConfiguration::default(),
181            urls: UrlsConfiguration::default(),
182            definitions: None,
183        }
184    }
185}