Compare commits
4 Commits
f49e700dd4
...
3e0cd31961
Author | SHA1 | Date |
---|---|---|
meeg_leeto | 3e0cd31961 | |
meeg_leeto | a5fd6bf61a | |
meeg_leeto | 8c12dfc132 | |
meeg_leeto | ce4706c4b1 |
171
src/main.rs
171
src/main.rs
|
@ -145,6 +145,12 @@ mod conf {
|
|||
/// example). See the definition of each of the member structs for more
|
||||
/// information.
|
||||
pub struct Config {
|
||||
/// The "version" of the configuration, corresponding to the MAJOR in
|
||||
/// semantic versioning. Should be increased every time the
|
||||
/// configuration structure suffers breaking changes.
|
||||
/// This value is optional because sufficiently old configuration files
|
||||
/// may not have a version field.
|
||||
pub version: Option<usize>,
|
||||
/// Configuration regarding the Redis database.
|
||||
pub db: DbConfig,
|
||||
/// Configuration regarding the types of (URL shorten) slugs produced.
|
||||
|
@ -153,6 +159,122 @@ mod conf {
|
|||
pub serve_rules: ServeRules,
|
||||
}
|
||||
|
||||
/// Get the configuration version field that this version of lonk expects.
|
||||
pub fn config_version() -> usize {
|
||||
usize::from_str(env!("CARGO_PKG_VERSION_MAJOR")).unwrap()
|
||||
}
|
||||
|
||||
pub enum ConfigParseError {
|
||||
SerdeError(serde_json::error::Error),
|
||||
OldVersion(usize),
|
||||
ServeFileNotFile(PathBuf),
|
||||
ServeFileNotExists(PathBuf),
|
||||
ServeDirNotDir(PathBuf),
|
||||
ServeDirNotExists(PathBuf),
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn from_sync_buffer<R: std::io::Read>(
|
||||
buffer: std::io::BufReader<R>,
|
||||
) -> Result<Self, ConfigParseError> {
|
||||
let parsed: Config =
|
||||
serde_json::from_reader(buffer).map_err(|err| ConfigParseError::SerdeError(err))?;
|
||||
parsed.validate()
|
||||
}
|
||||
|
||||
fn validate(self) -> Result<Self, ConfigParseError> {
|
||||
// Check configuration version
|
||||
let parsed_version = self.version.unwrap_or(0);
|
||||
if parsed_version != config_version() {
|
||||
return Err(ConfigParseError::OldVersion(parsed_version));
|
||||
}
|
||||
|
||||
// Check existence of serve file or directory
|
||||
match &self.serve_rules.dir {
|
||||
ServeDirRules::File(file) => {
|
||||
if !file.exists() {
|
||||
return Err(ConfigParseError::ServeFileNotExists(file.clone()));
|
||||
}
|
||||
if !file.is_file() {
|
||||
return Err(ConfigParseError::ServeFileNotFile(file.clone()));
|
||||
}
|
||||
}
|
||||
ServeDirRules::Dir(dir) => {
|
||||
if !dir.exists() {
|
||||
return Err(ConfigParseError::ServeDirNotExists(dir.clone()));
|
||||
}
|
||||
|
||||
if !dir.is_dir() {
|
||||
return Err(ConfigParseError::ServeDirNotDir(dir.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigParseError {
|
||||
pub fn panic_with_message(self, config_file_name: &str) -> ! {
|
||||
match self {
|
||||
ConfigParseError::SerdeError(err) => match err.classify() {
|
||||
serde_json::error::Category::Io => {
|
||||
panic!("IO error when reading configuration file.")
|
||||
}
|
||||
serde_json::error::Category::Syntax => panic!(
|
||||
"Configuration file is syntactically incorrect.
|
||||
See {}:line {}, column {}.",
|
||||
config_file_name,
|
||||
err.line(),
|
||||
err.column()
|
||||
),
|
||||
serde_json::error::Category::Data => panic!(
|
||||
"Error deserializing configuration file; expected different data type.
|
||||
See {}:line {}, column {}.",
|
||||
config_file_name,
|
||||
err.line(),
|
||||
err.column()
|
||||
),
|
||||
serde_json::error::Category::Eof => {
|
||||
panic!("Unexpected end of file when reading configuration file.")
|
||||
}
|
||||
},
|
||||
ConfigParseError::OldVersion(old_version) => {
|
||||
panic!(
|
||||
"Configuration file has outdated version.
|
||||
Expected version field to be {} but got {}.",
|
||||
old_version,
|
||||
config_version()
|
||||
);
|
||||
}
|
||||
ConfigParseError::ServeDirNotExists(dir) => {
|
||||
panic!(
|
||||
"Configuration file indicates directory {} should be served, but it does not exist.",
|
||||
dir.to_string_lossy()
|
||||
)
|
||||
}
|
||||
ConfigParseError::ServeDirNotDir(dir) => {
|
||||
panic!(
|
||||
"Configuration file indicates directory {} should be served, but it is not a directory.",
|
||||
dir.to_string_lossy()
|
||||
)
|
||||
}
|
||||
ConfigParseError::ServeFileNotExists(file) => {
|
||||
panic!(
|
||||
"Configuration file indicates file {} should be served, but it does not exist.",
|
||||
file.to_string_lossy()
|
||||
)
|
||||
}
|
||||
ConfigParseError::ServeFileNotFile(file) => {
|
||||
panic!(
|
||||
"Configuration file indicates file {} should be served, but it is not a file.",
|
||||
file.to_string_lossy()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default implementations
|
||||
|
||||
impl Default for DbConfig {
|
||||
|
@ -185,7 +307,7 @@ mod conf {
|
|||
|
||||
impl Default for ServeDirRules {
|
||||
fn default() -> Self {
|
||||
ServeDirRules::Dir(PathBuf::from_str("/etc/lonk/served").unwrap())
|
||||
ServeDirRules::Dir("/etc/lonk/served".into())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,6 +332,7 @@ mod conf {
|
|||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
version: Some(config_version()),
|
||||
db: Default::default(),
|
||||
slug_rules: Default::default(),
|
||||
serve_rules: Default::default(),
|
||||
|
@ -225,6 +348,7 @@ mod service {
|
|||
#[derive(Validator)]
|
||||
#[validator(http_url(local(NotAllow)))]
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
/// A struct representing a URL.
|
||||
pub struct HttpUrl {
|
||||
url: validators::url::Url,
|
||||
|
@ -233,6 +357,7 @@ mod service {
|
|||
|
||||
#[derive(Validator)]
|
||||
#[validator(domain(ipv4(Allow), local(NotAllow), at_least_two_labels(Must), port(Allow)))]
|
||||
#[allow(dead_code)]
|
||||
pub struct Domain {
|
||||
domain: String,
|
||||
port: Option<u16>,
|
||||
|
@ -822,27 +947,16 @@ async fn serve() {
|
|||
),
|
||||
};
|
||||
});
|
||||
let config_buf = std::io::BufReader::new(config_file);
|
||||
serde_json::from_reader(config_buf).unwrap_or_else(|err| match err.classify() {
|
||||
serde_json::error::Category::Io => panic!("IO error when reading configuration file."),
|
||||
serde_json::error::Category::Syntax => panic!(
|
||||
"Configuration file is syntactically incorrect.
|
||||
See {}:line {}, column {}.",
|
||||
&config_file_name,
|
||||
err.line(),
|
||||
err.column()
|
||||
),
|
||||
serde_json::error::Category::Data => panic!(
|
||||
"Error deserializing configuration file; expected different data type.
|
||||
See {}:line {}, column {}.",
|
||||
&config_file_name,
|
||||
err.line(),
|
||||
err.column()
|
||||
),
|
||||
serde_json::error::Category::Eof => {
|
||||
panic!("Unexpected end of file when reading configuration file.")
|
||||
}
|
||||
let parse_result = tokio::task::spawn_blocking(move || {
|
||||
conf::Config::from_sync_buffer(std::io::BufReader::new(config_file))
|
||||
})
|
||||
.await
|
||||
.expect("Tokio error from blocking task.");
|
||||
|
||||
match parse_result {
|
||||
Err(err) => err.panic_with_message(&config_file_name),
|
||||
Ok(config) => config,
|
||||
}
|
||||
};
|
||||
|
||||
// Create slug factory
|
||||
|
@ -918,21 +1032,32 @@ async fn serve() {
|
|||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// Start lonk.
|
||||
struct Run {
|
||||
/// print the version and quit
|
||||
#[argh(switch)]
|
||||
version: bool,
|
||||
/// write a default configuration to stdout and quit
|
||||
#[argh(switch)]
|
||||
print_default_config: bool,
|
||||
}
|
||||
|
||||
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
fn main() {
|
||||
let run = argh::from_env::<Run>();
|
||||
|
||||
if run.version {
|
||||
println!("lonk v{}", VERSION);
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
if run.print_default_config {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&conf::Config::default())
|
||||
.expect("Default configuration should always be JSON serializable")
|
||||
);
|
||||
} else {
|
||||
serve();
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
serve();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue