storm_config/file/source/
file.rs

1use std::error::Error;
2use std::path::PathBuf;
3use std::{env, fs, io};
4
5use read_url::*;
6
7use crate::FileFormat;
8use crate::file::format::ALL_EXTENSIONS;
9use crate::file::source::FileSourceResult;
10use crate::file::{FileSource, FileStoredFormat, Format};
11
12/// Describes a file sourced from a file
13#[derive(Clone, Debug)]
14pub struct FileSourceFile {
15  /// Path of configuration file
16  name: PathBuf,
17}
18
19impl FileSourceFile {
20  pub fn new(name: PathBuf) -> Self {
21    Self { name }
22  }
23
24  fn find_file<F>(
25    &self,
26    format_hint: Option<F>,
27  ) -> Result<(PathBuf, Box<dyn Format>), Box<dyn Error + Send + Sync>>
28  where
29    F: FileStoredFormat + Format + 'static,
30  {
31    let filename = if self.name.is_absolute() {
32      self.name.clone()
33    } else {
34      env::current_dir()?.as_path().join(&self.name)
35    };
36
37    // First check for an _exact_ match
38    if filename.is_file() {
39      return if let Some(format) = format_hint {
40        Ok((filename, Box::new(format)))
41      } else {
42        for (format, extensions) in ALL_EXTENSIONS.iter() {
43          if extensions
44            .contains(&filename.extension().unwrap_or_default().to_string_lossy().as_ref())
45          {
46            return Ok((filename, Box::new(*format)));
47          }
48        }
49
50        Err(Box::new(io::Error::new(
51          io::ErrorKind::NotFound,
52          format!(
53            "configuration file \"{}\" is not of a registered file format",
54            filename.to_string_lossy()
55          ),
56        )))
57      };
58    }
59
60    // Adding a dummy extension will make sure we will not override secondary extensions, i.e. "file.local"
61    // This will make the following set_extension function calls to append the extension.
62    let mut filename = add_dummy_extension(filename);
63
64    match format_hint {
65      Some(format) => {
66        for ext in format.file_extensions() {
67          filename.set_extension(ext);
68
69          if filename.is_file() {
70            return Ok((filename, Box::new(format)));
71          }
72        }
73      }
74
75      None => {
76        for format in ALL_EXTENSIONS.keys() {
77          for ext in format.extensions() {
78            filename.set_extension(ext);
79
80            if filename.is_file() {
81              return Ok((filename, Box::new(*format)));
82            }
83          }
84        }
85      }
86    }
87
88    Err(Box::new(io::Error::new(
89      io::ErrorKind::NotFound,
90      format!("configuration file \"{}\" not found", self.name.to_string_lossy()),
91    )))
92  }
93}
94
95impl<F> FileSource<F> for FileSourceFile
96where
97  F: Format + FileStoredFormat + 'static,
98{
99  fn resolve(
100    &self,
101    format_hint: Option<F>,
102  ) -> Result<FileSourceResult, Box<dyn Error + Send + Sync>> {
103    if format_hint.is_none() {
104      return Ok(FileSourceResult {
105        uri: None,
106        content: "{}".into(),
107        format: Box::new(FileFormat::default()),
108      });
109    }
110
111    // Find file
112    match self.find_file(format_hint) {
113      Ok((filename, format)) => {
114        return Ok(FileSourceResult {
115          uri: Some(
116            env::current_dir()
117              .ok()
118              .and_then(|base| pathdiff::diff_paths(&filename, base))
119              .unwrap_or_else(|| filename.clone())
120              .to_string_lossy()
121              .into_owned(),
122          ),
123          content: fs::read_to_string(filename)?,
124          format,
125        });
126      }
127      Err(e) => {
128        let context = UrlContext::new();
129
130        let url = context.url(self.name.to_str().unwrap_or_default())?;
131        let mut reader = url.open()?;
132
133        let mut content = String::default();
134        reader.read_to_string(&mut content)?;
135
136        return Ok(FileSourceResult {
137          uri: Some(url.to_string()),
138          content,
139          format: Box::new(find_file_extension(self.name.clone())),
140        });
141      }
142    };
143  }
144}
145
146fn add_dummy_extension(mut filename: PathBuf) -> PathBuf {
147  match filename.extension() {
148    Some(extension) => {
149      let mut ext = extension.to_os_string();
150      ext.push(".");
151      ext.push("dummy");
152      filename.set_extension(ext);
153    }
154    None => {
155      filename.set_extension("dummy");
156    }
157  }
158
159  filename
160}
161
162fn find_file_extension(filename: PathBuf) -> FileFormat {
163  if let Some(extension) = filename.extension() {
164    let ext = extension.to_string_lossy();
165    for format in ALL_EXTENSIONS.keys() {
166      if format.extensions().contains(&ext.as_ref()) {
167        return format.clone();
168      }
169    }
170  }
171
172  FileFormat::default()
173}