Move config to its own crate + fix config parsing

This commit is contained in:
s1idewhist1e 2023-05-08 22:42:34 -07:00
parent 393c8c9427
commit 6033a63446
No known key found for this signature in database
GPG key ID: 9CC756BB9B325062
11 changed files with 373 additions and 165 deletions

View file

@ -106,7 +106,7 @@ id: 'aid'
# Max note length, should be < 8000.
#maxNoteLength: 3000
# Maximum lenght of an image caption or file comment (default 1500, max 8192)
# Maximum length of an image caption or file comment (default 1500, max 8192)
#maxCaptionLength: 1500
# Whether disable HSTS

View file

@ -11,11 +11,20 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backend"
version = "0.0.0"
dependencies = [
"config",
"logging",
"queue",
"server",
]
[[package]]
name = "config"
version = "0.1.0"
dependencies = [
"once_cell",
"serde",
"serde_yaml",
"server",
]
[[package]]
@ -40,6 +49,13 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "logging"
version = "0.1.0"
dependencies = [
"config",
]
[[package]]
name = "once_cell"
version = "1.17.1"

View file

@ -8,12 +8,18 @@ default-run = "backend"
members = ["crates/*"]
[profile.development]
inherits = "dev"
[profile.production]
inherits = "release"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
once_cell = "1.17.1"
serde = { version = "1.0.160", features = [ "derive" ] }
serde_yaml = "0.9.21"
server = { path = "crates/server" }
logging = { path = "crates/logging" }
queue = { path = "crates/queue" }
config = { path = "crates/config" }
[dev-dependencies]

View file

@ -0,0 +1,12 @@
[package]
name = "config"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
once_cell = "1.17.1"
serde = { version = "1.0.160", features = [ "derive" ] }
serde_yaml = "0.9.21"

View file

@ -0,0 +1,263 @@
use serde::{de::Visitor, Deserialize};
type Port = u16;
#[derive(Debug, PartialEq, Deserialize)]
pub struct MaxNoteLength(pub u16);
#[derive(Debug, PartialEq, Deserialize)]
pub struct MaxCommentLength(pub u16);
#[derive(Debug, PartialEq)]
pub enum IpFamily {
Both,
IPv4,
IPv6,
}
impl<'de> Deserialize<'de> for IpFamily {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct IpFamilyVisitor;
impl<'de> Visitor<'de> for IpFamilyVisitor {
type Value = IpFamily;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("One of `4` `6` `0`")
}
fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
{
match v {
0 => Ok(IpFamily::Both),
4 => Ok(IpFamily::IPv4),
6 => Ok(IpFamily::IPv6),
_ => Err(E::unknown_variant(&v.to_string(), &["0", "4", "6"])),
}
}
}
}
deserializer.deserialize_u8(IpFamilyVisitor)
}
}
impl Default for IpFamily {
fn default() -> Self {
Self::Both
}
}
impl<'de> Deserialize<'de> for Host {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct HostVisitor;
impl<'de> Visitor<'de> for HostVisitor {
type Value = Host;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("(proto://)host(.tld)(/)")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let components: Vec<&str> = v.split("://").collect();
match components.len() {
1 => Ok(Host(None, components[0].into())),
2 => Ok(Host(
Some(components[0].into()),
components[1].trim_end_matches('/').into(),
)),
_ => Err(E::custom(format!("Invalid url: {}", v))), // FIXME: more descriptive
// error message
}
}
}
deserializer.deserialize_str(HostVisitor)
}
}
#[derive(Debug, PartialEq, Default)]
// TODO: Convert to uri later and maybe some more enums
pub struct Host(pub Option<String>, pub String);
#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename = "camelCase")]
pub struct Config {
pub repository_url: Option<String>,
pub feedback_url: Option<String>,
pub url: Host,
pub port: Port,
pub db: db::DbConfig,
pub redis: redis::RedisConfig,
// pub sonic: sonic::SonicConfig,
// pub elasticsearch: elasticsearch::ElasticsearchConfig,
// pub id: IdGenerator,
#[serde(default)]
pub max_note_length: MaxNoteLength,
#[serde(default)]
pub max_caption_length: MaxCommentLength,
// pub disable_hsts: bool,
pub cluster_limit: Option<u16>,
// pub deliver_job_concurrency: u16,
// pub inbox_job_concurrency: u16,
// pub deliver_job_per_sec: u16,
// pub inbox_job_per_sec: u16,
// pub deliver_job_max_attempts: u16,
// pub inbox_job_max_attempts: u16,
// pub outgoing_address_family: IpFamily,
// pub syslog: syslog::SyslogConfig,
// pub proxy: Option<Host>,
// pub proxy_smtp: Option<Host>,
// pub proxy_bypass_hosts: Vec<Host>,
// pub allowed_private_networks: Vec<Host>,
// pub max_file_size: Option<u32>,
// pub media_proxy: Option<String>,
// pub proxy_remote_files: bool,
// pub twa: Option<twa::TWAConfig>,
// pub reserved_usernames: Vec<String>,
// pub max_user_signups: Option<u32>,
// pub is_managed_hosting: bool,
// pub deepl: Option<deepl::DeepLConfig>,
// pub libre_translate: Option<libre_translate::LibreTranslateConfig>,
// pub email: Option<email::Email>,
// pub object_storage: Option<object_storage::ObjectStorageConfig>,
// pub summaly_proxy_url: Option<Host>,
}
#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename = "lowercase")]
pub enum IdGenerator {
AId,
MeId,
ULId,
ObjectID,
}
/// database config
pub mod db {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename = "camelCase")]
pub struct DbConfig {
pub host: Host,
pub port: Port,
pub db: String,
pub user: String,
pub pass: String,
#[serde(default = "true_fn")]
pub disable_cache: bool,
#[serde(default)]
pub extra: Extra,
}
#[derive(Debug, PartialEq, Deserialize)]
pub struct Extra {
#[serde(default = "true_fn")]
pub ssl: bool,
}
impl Default for Extra {
fn default() -> Self {
Self { ssl: true }
}
}
}
/// redis config
pub mod redis {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename = "camelCase")]
pub struct RedisConfig {
pub host: Host,
pub port: Port,
#[serde(default)]
pub family: IpFamily,
pub pass: Option<String>,
pub prefix: Option<String>,
#[serde(default)]
pub db: u8,
}
}
/// sonic search config
pub mod sonic {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
pub struct SonicConfig {
pub host: Host,
pub port: Port,
#[serde(default)]
pub auth: Option<String>,
#[serde(default)]
pub collection: Option<String>,
#[serde(default)]
pub bucket: Option<String>,
}
}
/// elasticsearch config
pub mod elasticsearch {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
pub struct ElasticsearchConfig {
pub host: Host,
pub port: Port,
#[serde(default)]
pub ssl: bool,
pub user: Option<String>,
pub pass: Option<String>,
pub index: Option<String>,
}
}
/// syslog configuration
pub mod syslog {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
pub struct SyslogConfig {
host: Host,
port: Port,
}
}
/// TWA configuration
pub mod twa {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
pub struct TWAConfig {}
}
impl Default for MaxNoteLength {
fn default() -> Self {
Self(3000)
}
}
impl Default for MaxCommentLength {
fn default() -> Self {
Self(1500)
}
}
fn true_fn() -> bool {
true
}

View file

@ -5,9 +5,9 @@ use std::path::Path;
use once_cell::sync::OnceCell;
mod cfg;
mod data;
pub use cfg::*;
pub use data::*;
// Config Errors
#[derive(Debug)]
@ -76,18 +76,6 @@ mod tests {
path::PathBuf,
};
struct Guard(PathBuf);
impl Drop for Guard {
fn drop(&mut self) {
println!("removing temp file...");
match remove_file(&self.0) {
Ok(_) => println!("Successfully removed file"),
Err(e) => println!("Could not remove file: {}", e),
}
}
}
use super::*;
#[test]
@ -97,11 +85,37 @@ mod tests {
#[test]
fn parses_test_config() {
struct Guard(PathBuf);
impl Drop for Guard {
fn drop(&mut self) {
println!("removing temp file...");
match remove_file(&self.0) {
Ok(_) => println!("Successfully removed file"),
Err(e) => println!("Could not remove file: {}", e),
}
}
}
// setup test temp config
let mut temp_file = std::env::temp_dir();
temp_file.push(Path::new("calckey.test.config"));
let err = File::create(&temp_file).unwrap().write_all(br"");
let err = File::create(&temp_file).unwrap().write_all(
br"
url: https://example.tld/
port: 3000
db:
host: localhost
port: 5432
db: calckey
user: example-calckey-user
pass: example-calckey-pass
redis:
host: localhost
port: 6379
",
);
let _g = Guard(temp_file.clone());
@ -112,20 +126,31 @@ mod tests {
assert_eq!(
config,
Config {
url: Host("https://example.tld/".into()),
url: Host(Some("https".into()), "example.tld".into()),
port: 3000,
db: db::DbConfig {
host: String::from("localhost"),
host: Host(None, "localhost".into()),
port: 5432,
db: String::from("calckey"),
user: String::from("example-calckey-user"),
pass: String::from("example-calckey-pass"),
disable_cache: true,
extra: db::Extra { extra: true }
}
extra: db::Extra { ssl: true }
},
repository_url: None,
feedback_url: None,
redis: redis::RedisConfig {
host: Host(None, "localhost".into()),
port: 6379,
family: IpFamily::Both,
pass: None,
prefix: None,
db: 0,
},
max_note_length: MaxNoteLength(3000),
max_caption_length: MaxCommentLength(1500),
cluster_limit: None
}
);
remove_file(&temp_file).unwrap();
}
}

View file

@ -0,0 +1,9 @@
[package]
name = "logging"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
config = { path = "../config" }

View file

@ -0,0 +1,7 @@
pub fn idk() {
config::get_config();
}

View file

@ -3,11 +3,11 @@
"private": true,
"type": "module",
"scripts": {
"start": "cargo run --profile $( [ ${NODE_ENV:=dev} = 'production' ] && echo 'release' || echo $NODE_ENV )",
"start": "cargo run --profile ${NODE_ENV:=development}",
"start:test": "NODE_ENV=test pnpm node ./built/index.js",
"check": "cargo check",
"migrate": "cargo run --bin migrate",
"build": "cargo build --profile $( [ ${NODE_ENV:=dev} = 'production' ] && echo 'release' || echo $NODE_ENV )",
"build": "cargo build --profile ${NODE_ENV:=development}",
"revertmigration": "typeorm migration:revert -d ormconfig.js",
"lint": "cargo check",
"test": "cargo test --workspace"

View file

@ -1,133 +0,0 @@
use serde::Deserialize;
type Port = u16;
#[derive(Debug, PartialEq, Deserialize)]
pub struct MaxNoteLength(pub u16);
#[derive(Debug, PartialEq, Deserialize)]
pub enum IpFamily {
Both = 0,
IPv4 = 4,
IPv6 = 6,
}
#[derive(Debug, PartialEq, Deserialize, Default)]
pub struct Host(pub String);
#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename = "camelCase")]
pub struct Config {
pub url: Host,
pub port: Port,
pub db: db::DbConfig,
pub redis: redis::RedisConfig,
pub sonic: sonic::SonicConfig,
pub elasticsearch: elasticsearch::ElasticsearchConfig,
pub id: IdGenerator,
pub max_note_length: MaxNoteLength,
pub max_caption_length: MaxNoteLength,
pub disable_hsts: bool,
pub cluster_limit: u16,
pub deliver_job_concurrency: u16,
pub inbox_job_concurrency: u16,
pub deliver_job_per_sec: u16,
pub inbox_job_per_sec: u16,
pub deliver_job_max_attempts: u16,
pub inbox_job_max_attempts: u16,
pub outgoing_address_family: IpFamily,
pub syslog: syslog::SyslogConfig,
pub proxy: Host,
}
#[derive(Debug, PartialEq, Deserialize)]
pub enum IdGenerator {
AId,
MeId,
ULId,
ObjectID,
}
pub mod db {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename = "camelCase")]
pub struct DbConfig {
pub host: String,
pub port: Port,
pub db: String,
pub user: String,
pub pass: String,
#[serde(default = "true_fn")]
pub disable_cache: bool,
pub extra: Extra,
}
#[derive(Debug, PartialEq, Deserialize)]
pub struct Extra {
#[serde(default = "true_fn")]
pub extra: bool,
}
}
pub mod redis {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename = "camelCase")]
pub struct RedisConfig {
pub host: String,
pub port: Port,
#[serde(default = "ip_family_fn")]
pub family: IpFamily,
#[serde(default)]
pub pass: Option<String>,
#[serde(default)]
pub prefix: Option<String>,
#[serde(default)]
pub db: Option<i32>,
}
}
pub mod sonic {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
pub struct SonicConfig {
pub host: Host,
pub port: Port,
pub auth: String,
pub collection: String,
pub bucket: Option<String>,
}
}
pub mod elasticsearch {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
pub struct ElasticsearchConfig {
pub host: Host,
pub port: Port,
#[serde(default)]
pub ssl: bool,
pub user: Option<String>,
pub pass: Option<String>,
}
}
pub mod syslog {
use super::*;
#[derive(Debug, PartialEq, Deserialize)]
pub struct SyslogConfig {}
}
fn true_fn() -> bool {
true
}
fn ip_family_fn() -> IpFamily {
IpFamily::Both
}

View file

@ -1,14 +1,17 @@
use std::path::Path;
use std::{path::Path, error::Error};
mod config;
#[cfg(debug_assertions)]
extern crate config;
fn main() {
fn main() -> Result<(), Box<dyn Error>> {
// bootstrap
// ENV
// get config
config::init_config(Path::new(""));
config::init_config(Path::new(""))?;
Ok(())
}