mirror of
https://git.asonix.dog/asonix/pict-rs
synced 2024-12-22 19:31:35 +00:00
Start redoing config again
This commit is contained in:
parent
d3d0817cb0
commit
ca28f68ef5
5 changed files with 646 additions and 587 deletions
594
src/config.rs
594
src/config.rs
|
@ -1,592 +1,12 @@
|
||||||
use crate::serde_str::Serde;
|
mod commandline;
|
||||||
use clap::{ArgEnum, Parser, Subcommand};
|
mod defaults;
|
||||||
use std::{collections::HashSet, net::SocketAddr, path::PathBuf};
|
mod file;
|
||||||
use url::Url;
|
mod primitives;
|
||||||
|
|
||||||
use crate::magick::ValidInputType;
|
use crate::magick::ValidInputType;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Parser)]
|
pub(crate) use file::ConfigFile as Configuration;
|
||||||
pub(crate) struct Args {
|
|
||||||
#[clap(short, long, help = "Path to the pict-rs configuration file")]
|
|
||||||
config_file: Option<PathBuf>,
|
|
||||||
|
|
||||||
#[clap(subcommand)]
|
pub(crate) fn configure() -> anyhow::Result<Configuration> {
|
||||||
command: Command,
|
unimplemented!()
|
||||||
|
|
||||||
#[clap(flatten)]
|
|
||||||
overrides: Overrides,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_false(b: &bool) -> bool {
|
|
||||||
!b
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Serialize, Parser)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub(crate) struct Overrides {
|
|
||||||
#[clap(
|
|
||||||
short,
|
|
||||||
long,
|
|
||||||
help = "Whether to skip validating images uploaded via the internal import API"
|
|
||||||
)]
|
|
||||||
#[serde(skip_serializing_if = "is_false")]
|
|
||||||
skip_validate_imports: bool,
|
|
||||||
|
|
||||||
#[clap(short, long, help = "The address and port the server binds to.")]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
addr: Option<SocketAddr>,
|
|
||||||
|
|
||||||
#[clap(short, long, help = "The path to the data directory, e.g. data/")]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
path: Option<PathBuf>,
|
|
||||||
|
|
||||||
#[clap(
|
|
||||||
short,
|
|
||||||
long,
|
|
||||||
help = "An optional image format to convert all uploaded files into, supports 'jpg', 'png', and 'webp'"
|
|
||||||
)]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
image_format: Option<Format>,
|
|
||||||
|
|
||||||
#[clap(
|
|
||||||
short,
|
|
||||||
long,
|
|
||||||
help = "An optional list of filters to permit, supports 'identity', 'thumbnail', 'resize', 'crop', and 'blur'"
|
|
||||||
)]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
filters: Option<Vec<String>>,
|
|
||||||
|
|
||||||
#[clap(
|
|
||||||
short,
|
|
||||||
long,
|
|
||||||
help = "Specify the maximum allowed uploaded file size (in Megabytes)"
|
|
||||||
)]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
max_file_size: Option<usize>,
|
|
||||||
|
|
||||||
#[clap(long, help = "Specify the maximum width in pixels allowed on an image")]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
max_image_width: Option<usize>,
|
|
||||||
|
|
||||||
#[clap(long, help = "Specify the maximum width in pixels allowed on an image")]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
max_image_height: Option<usize>,
|
|
||||||
|
|
||||||
#[clap(long, help = "Specify the maximum area in pixels allowed in an image")]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
max_image_area: Option<usize>,
|
|
||||||
|
|
||||||
#[clap(
|
|
||||||
long,
|
|
||||||
help = "Specify the number of events the console subscriber is allowed to buffer"
|
|
||||||
)]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
console_buffer_capacity: Option<usize>,
|
|
||||||
|
|
||||||
#[clap(
|
|
||||||
long,
|
|
||||||
help = "An optional string to be checked on requests to privileged endpoints"
|
|
||||||
)]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
api_key: Option<String>,
|
|
||||||
|
|
||||||
#[clap(
|
|
||||||
short,
|
|
||||||
long,
|
|
||||||
help = "Enable OpenTelemetry Tracing exports to the given OpenTelemetry collector"
|
|
||||||
)]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
opentelemetry_url: Option<Url>,
|
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
#[clap(
|
|
||||||
short = 'R',
|
|
||||||
long,
|
|
||||||
help = "Set the database implementation. Available options are 'sled'. Default is 'sled'"
|
|
||||||
)]
|
|
||||||
repo: Option<Repo>,
|
|
||||||
|
|
||||||
#[clap(flatten)]
|
|
||||||
sled: Sled,
|
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
#[clap(
|
|
||||||
short = 'S',
|
|
||||||
long,
|
|
||||||
help = "Set the image store. Available options are 'object-storage' or 'filesystem'. Default is 'filesystem'"
|
|
||||||
)]
|
|
||||||
store: Option<Store>,
|
|
||||||
|
|
||||||
#[clap(flatten)]
|
|
||||||
filesystem_storage: FilesystemStorage,
|
|
||||||
|
|
||||||
#[clap(flatten)]
|
|
||||||
object_storage: ObjectStorage,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjectStorage {
|
|
||||||
pub(crate) fn required(&self) -> Result<RequiredObjectStorage, RequiredError> {
|
|
||||||
Ok(RequiredObjectStorage {
|
|
||||||
bucket_name: self
|
|
||||||
.object_store_bucket_name
|
|
||||||
.as_ref()
|
|
||||||
.cloned()
|
|
||||||
.ok_or(RequiredError("object-store-bucket-name"))?,
|
|
||||||
region: self
|
|
||||||
.object_store_region
|
|
||||||
.as_ref()
|
|
||||||
.cloned()
|
|
||||||
.map(Serde::into_inner)
|
|
||||||
.ok_or(RequiredError("object-store-region"))?,
|
|
||||||
access_key: self.object_store_access_key.as_ref().cloned(),
|
|
||||||
secret_key: self.object_store_secret_key.as_ref().cloned(),
|
|
||||||
security_token: self.object_store_security_token.as_ref().cloned(),
|
|
||||||
session_token: self.object_store_session_token.as_ref().cloned(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Overrides {
|
|
||||||
fn is_default(&self) -> bool {
|
|
||||||
!self.skip_validate_imports
|
|
||||||
&& self.addr.is_none()
|
|
||||||
&& self.path.is_none()
|
|
||||||
&& self.image_format.is_none()
|
|
||||||
&& self.filters.is_none()
|
|
||||||
&& self.max_file_size.is_none()
|
|
||||||
&& self.max_image_width.is_none()
|
|
||||||
&& self.max_image_height.is_none()
|
|
||||||
&& self.max_image_area.is_none()
|
|
||||||
&& self.console_buffer_capacity.is_none()
|
|
||||||
&& self.api_key.is_none()
|
|
||||||
&& self.opentelemetry_url.is_none()
|
|
||||||
&& self.repo.is_none()
|
|
||||||
&& self.store.is_none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, Subcommand)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
#[serde(tag = "type")]
|
|
||||||
pub(crate) enum Command {
|
|
||||||
Run,
|
|
||||||
Dump { path: PathBuf },
|
|
||||||
MigrateStore { to: Store },
|
|
||||||
MigrateRepo { to: Repo },
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) enum CommandConfig {
|
|
||||||
Run,
|
|
||||||
Dump {
|
|
||||||
path: PathBuf,
|
|
||||||
},
|
|
||||||
MigrateStore {
|
|
||||||
to: Storage,
|
|
||||||
},
|
|
||||||
MigrateRepo {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
to: Repository,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize, ArgEnum)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub(crate) enum Repo {
|
|
||||||
Sled,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, Parser)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub(crate) struct Sled {
|
|
||||||
// defaults to {config.path}
|
|
||||||
#[clap(long, help = "Path in which pict-rs will create it's 'repo' directory")]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub(crate) sled_path: Option<PathBuf>,
|
|
||||||
|
|
||||||
#[clap(
|
|
||||||
long,
|
|
||||||
help = "The number of bytes sled is allowed to use for it's in-memory cache"
|
|
||||||
)]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub(crate) sled_cache_capacity: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize, ArgEnum)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub(crate) enum Store {
|
|
||||||
Filesystem,
|
|
||||||
ObjectStorage,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, Parser)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub(crate) struct FilesystemStorage {
|
|
||||||
// defaults to {config.path}
|
|
||||||
#[clap(
|
|
||||||
long,
|
|
||||||
help = "Path in which pict-rs will create it's 'files' directory"
|
|
||||||
)]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub(crate) filesystem_storage_path: Option<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, Parser)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub(crate) struct ObjectStorage {
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
#[clap(long, help = "Name of the bucket in which pict-rs will store images")]
|
|
||||||
object_store_bucket_name: Option<String>,
|
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
#[clap(
|
|
||||||
long,
|
|
||||||
help = "Region in which the bucket exists, can be an http endpoint"
|
|
||||||
)]
|
|
||||||
object_store_region: Option<Serde<s3::Region>>,
|
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
#[clap(long)]
|
|
||||||
object_store_access_key: Option<String>,
|
|
||||||
|
|
||||||
#[clap(long)]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
object_store_secret_key: Option<String>,
|
|
||||||
|
|
||||||
#[clap(long)]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
object_store_security_token: Option<String>,
|
|
||||||
|
|
||||||
#[clap(long)]
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
object_store_session_token: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct RequiredSledRepo {
|
|
||||||
pub(crate) path: PathBuf,
|
|
||||||
pub(crate) cache_capacity: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct RequiredObjectStorage {
|
|
||||||
pub(crate) bucket_name: String,
|
|
||||||
pub(crate) region: s3::Region,
|
|
||||||
pub(crate) access_key: Option<String>,
|
|
||||||
pub(crate) secret_key: Option<String>,
|
|
||||||
pub(crate) security_token: Option<String>,
|
|
||||||
pub(crate) session_token: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct RequiredFilesystemStorage {
|
|
||||||
pub(crate) path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) enum Storage {
|
|
||||||
ObjectStorage(RequiredObjectStorage),
|
|
||||||
Filesystem(RequiredFilesystemStorage),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) enum Repository {
|
|
||||||
Sled(RequiredSledRepo),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub(crate) struct Config {
|
|
||||||
skip_validate_imports: bool,
|
|
||||||
addr: SocketAddr,
|
|
||||||
path: PathBuf,
|
|
||||||
image_format: Option<Format>,
|
|
||||||
filters: Option<Vec<String>>,
|
|
||||||
max_file_size: usize,
|
|
||||||
max_image_width: usize,
|
|
||||||
max_image_height: usize,
|
|
||||||
max_image_area: usize,
|
|
||||||
console_buffer_capacity: Option<usize>,
|
|
||||||
api_key: Option<String>,
|
|
||||||
opentelemetry_url: Option<Url>,
|
|
||||||
repo: Repo,
|
|
||||||
store: Store,
|
|
||||||
command: Command,
|
|
||||||
sled: Option<Sled>,
|
|
||||||
filesystem_storage: Option<FilesystemStorage>,
|
|
||||||
object_storage: Option<ObjectStorage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub(crate) struct Defaults {
|
|
||||||
command: Command,
|
|
||||||
skip_validate_imports: bool,
|
|
||||||
addr: SocketAddr,
|
|
||||||
max_file_size: usize,
|
|
||||||
max_image_width: usize,
|
|
||||||
max_image_height: usize,
|
|
||||||
max_image_area: usize,
|
|
||||||
repo: Repo,
|
|
||||||
sled: SledDefaults,
|
|
||||||
store: Store,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
struct SledDefaults {
|
|
||||||
sled_cache_capacity: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Defaults {
|
|
||||||
fn new(command: Command) -> Self {
|
|
||||||
Defaults {
|
|
||||||
command,
|
|
||||||
skip_validate_imports: false,
|
|
||||||
addr: ([0, 0, 0, 0], 8080).into(),
|
|
||||||
max_file_size: 40,
|
|
||||||
max_image_width: 10_000,
|
|
||||||
max_image_height: 10_000,
|
|
||||||
max_image_area: 40_000_000,
|
|
||||||
repo: Repo::Sled,
|
|
||||||
sled: SledDefaults {
|
|
||||||
sled_cache_capacity: 1024 * 1024 * 64,
|
|
||||||
},
|
|
||||||
store: Store::Filesystem,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub(crate) fn build() -> anyhow::Result<Self> {
|
|
||||||
let args = Args::parse();
|
|
||||||
|
|
||||||
let mut base_config = config::Config::builder()
|
|
||||||
.add_source(config::Config::try_from(&Defaults::new(args.command))?);
|
|
||||||
|
|
||||||
if let Some(path) = args.config_file {
|
|
||||||
base_config = base_config.add_source(config::File::from(path));
|
|
||||||
};
|
|
||||||
|
|
||||||
if !args.overrides.is_default() {
|
|
||||||
let merging = config::Config::try_from(&args.overrides)?;
|
|
||||||
|
|
||||||
base_config = base_config.add_source(merging);
|
|
||||||
}
|
|
||||||
|
|
||||||
let config: Self = base_config
|
|
||||||
.add_source(config::Environment::with_prefix("PICTRS").separator("__"))
|
|
||||||
.build()?
|
|
||||||
.try_deserialize()?;
|
|
||||||
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn command(&self) -> anyhow::Result<CommandConfig> {
|
|
||||||
Ok(match &self.command {
|
|
||||||
Command::Run => CommandConfig::Run,
|
|
||||||
Command::Dump { path } => CommandConfig::Dump { path: path.clone() },
|
|
||||||
Command::MigrateStore { to } => CommandConfig::MigrateStore {
|
|
||||||
to: match to {
|
|
||||||
Store::ObjectStorage => Storage::ObjectStorage(
|
|
||||||
self.object_storage
|
|
||||||
.as_ref()
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.required()?,
|
|
||||||
),
|
|
||||||
Store::Filesystem => Storage::Filesystem(RequiredFilesystemStorage {
|
|
||||||
path: self
|
|
||||||
.filesystem_storage
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|f| f.filesystem_storage_path.clone())
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
let mut path = self.path.clone();
|
|
||||||
path.push("files");
|
|
||||||
path
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Command::MigrateRepo { to } => CommandConfig::MigrateRepo {
|
|
||||||
to: match to {
|
|
||||||
Repo::Sled => {
|
|
||||||
let sled = self.sled.as_ref().cloned().unwrap_or_default();
|
|
||||||
|
|
||||||
Repository::Sled(RequiredSledRepo {
|
|
||||||
path: sled.sled_path.unwrap_or_else(|| {
|
|
||||||
let mut path = self.path.clone();
|
|
||||||
path.push("sled-repo");
|
|
||||||
path
|
|
||||||
}),
|
|
||||||
cache_capacity: sled.sled_cache_capacity.unwrap_or(1024 * 1024 * 64),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn store(&self) -> anyhow::Result<Storage> {
|
|
||||||
Ok(match self.store {
|
|
||||||
Store::Filesystem => Storage::Filesystem(RequiredFilesystemStorage {
|
|
||||||
path: self
|
|
||||||
.filesystem_storage
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|f| f.filesystem_storage_path.clone())
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
let mut path = self.path.clone();
|
|
||||||
path.push("files");
|
|
||||||
path
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
Store::ObjectStorage => Storage::ObjectStorage(
|
|
||||||
self.object_storage
|
|
||||||
.as_ref()
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.required()?,
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn repo(&self) -> Repository {
|
|
||||||
match self.repo {
|
|
||||||
Repo::Sled => {
|
|
||||||
let sled = self.sled.as_ref().cloned().unwrap_or_default();
|
|
||||||
|
|
||||||
Repository::Sled(RequiredSledRepo {
|
|
||||||
path: sled.sled_path.unwrap_or_else(|| {
|
|
||||||
let mut path = self.path.clone();
|
|
||||||
path.push("sled-repo");
|
|
||||||
path
|
|
||||||
}),
|
|
||||||
cache_capacity: sled.sled_cache_capacity.unwrap_or(1024 * 1024 * 64),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn bind_address(&self) -> SocketAddr {
|
|
||||||
self.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn data_dir(&self) -> PathBuf {
|
|
||||||
self.path.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn console_buffer_capacity(&self) -> Option<usize> {
|
|
||||||
self.console_buffer_capacity
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn format(&self) -> Option<Format> {
|
|
||||||
self.image_format
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn allowed_filters(&self) -> Option<HashSet<String>> {
|
|
||||||
self.filters.as_ref().map(|wl| wl.iter().cloned().collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn validate_imports(&self) -> bool {
|
|
||||||
!self.skip_validate_imports
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn max_file_size(&self) -> usize {
|
|
||||||
self.max_file_size
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn max_width(&self) -> usize {
|
|
||||||
self.max_image_width
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn max_height(&self) -> usize {
|
|
||||||
self.max_image_height
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn max_area(&self) -> usize {
|
|
||||||
self.max_image_area
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn api_key(&self) -> Option<&str> {
|
|
||||||
self.api_key.as_deref()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn opentelemetry_url(&self) -> Option<&Url> {
|
|
||||||
self.opentelemetry_url.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[error("Invalid format supplied, {0}")]
|
|
||||||
pub(crate) struct FormatError(String);
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[error("Invalid store supplied, {0}")]
|
|
||||||
pub(crate) struct StoreError(String);
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[error("Invalid repo supplied, {0}")]
|
|
||||||
pub(crate) struct RepoError(String);
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[error("Missing required {0} field")]
|
|
||||||
pub(crate) struct RequiredError(&'static str);
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize, ArgEnum)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub(crate) enum Format {
|
|
||||||
Jpeg,
|
|
||||||
Png,
|
|
||||||
Webp,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Format {
|
|
||||||
pub(crate) fn as_magick_format(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Format::Jpeg => "JPEG",
|
|
||||||
Format::Png => "PNG",
|
|
||||||
Format::Webp => "WEBP",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn as_hint(&self) -> Option<ValidInputType> {
|
|
||||||
match self {
|
|
||||||
Format::Jpeg => Some(ValidInputType::Jpeg),
|
|
||||||
Format::Png => Some(ValidInputType::Png),
|
|
||||||
Format::Webp => Some(ValidInputType::Webp),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::str::FromStr for Format {
|
|
||||||
type Err = FormatError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
for variant in Self::value_variants() {
|
|
||||||
if variant.to_possible_value().unwrap().matches(s, false) {
|
|
||||||
return Ok(*variant);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(FormatError(s.into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::str::FromStr for Store {
|
|
||||||
type Err = StoreError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
for variant in Self::value_variants() {
|
|
||||||
if variant.to_possible_value().unwrap().matches(s, false) {
|
|
||||||
return Ok(*variant);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(StoreError(s.into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::str::FromStr for Repo {
|
|
||||||
type Err = RepoError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
for variant in Self::value_variants() {
|
|
||||||
if variant.to_possible_value().unwrap().matches(s, false) {
|
|
||||||
return Ok(*variant);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(RepoError(s.into()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
216
src/config/commandline.rs
Normal file
216
src/config/commandline.rs
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
use crate::config::primitives::{ImageFormat, LogFormat, Targets};
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
use std::{net::SocketAddr, path::PathBuf};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
/// Run the pict-rs application
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[clap(author, version, about, long_about = None)]
|
||||||
|
pub(crate) struct Args {
|
||||||
|
/// Path to the pict-rs configuration file
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub(crate) config_file: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Format of logs printed to stdout
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) log_format: Option<LogFormat>,
|
||||||
|
/// Log levels to print to stdout, respects RUST_LOG formatting
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) log_targets: Option<Targets>,
|
||||||
|
|
||||||
|
/// Address and port to expose tokio-console metrics
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) console_address: Option<SocketAddr>,
|
||||||
|
/// Capacity of the console-subscriber Event Buffer
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) console_buffer_capacity: Option<usize>,
|
||||||
|
|
||||||
|
/// URL to send OpenTelemetry metrics
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) opentelemetry_url: Option<Url>,
|
||||||
|
/// Service Name to use for OpenTelemetry
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) opentelemetry_service_name: Option<String>,
|
||||||
|
/// Log levels to use for OpenTelemetry, respects RUST_LOG formatting
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) opentelemetry_targets: Option<Targets>,
|
||||||
|
|
||||||
|
/// File to save the current configuration for reproducible runs
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) save_to: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub(crate) command: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
/// Runs the pict-rs web server
|
||||||
|
Run(Run),
|
||||||
|
|
||||||
|
/// Migrates from one provided media store to another
|
||||||
|
#[clap(flatten)]
|
||||||
|
MigrateStore(MigrateStore),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub(crate) struct Run {
|
||||||
|
/// The address and port to bind the pict-rs web server
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub(crate) address: SocketAddr,
|
||||||
|
|
||||||
|
/// The API KEY required to access restricted routes
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) api_key: Option<String>,
|
||||||
|
|
||||||
|
/// Whether to validate media on the "import" endpoint
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) media_skip_validate_imports: Option<bool>,
|
||||||
|
/// The maximum width, in pixels, for uploaded media
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) media_max_width: Option<usize>,
|
||||||
|
/// The maximum height, in pixels, for uploaded media
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) media_max_height: Option<usize>,
|
||||||
|
/// The maximum area, in pixels, for uploaded media
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) media_max_area: Option<usize>,
|
||||||
|
/// The maximum size, in megabytes, for uploaded media
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) media_max_file_size: Option<usize>,
|
||||||
|
/// Whether to enable GIF and silent MP4 uploads. Full videos are unsupported
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) media_enable_silent_video: Option<bool>,
|
||||||
|
/// Which media filters should be enabled on the `process` endpoint
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) media_filters: Option<Vec<String>>,
|
||||||
|
/// Enforce uploaded media is transcoded to the provided format
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) media_format: Option<ImageFormat>,
|
||||||
|
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub(crate) store: Option<RunStore>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the provided storage
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub(crate) enum Store {
|
||||||
|
/// configure filesystem storage
|
||||||
|
Filesystem(Filesystem),
|
||||||
|
|
||||||
|
/// configure object storage
|
||||||
|
ObjectStorage(ObjectStorage),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run pict-rs with the provided storage
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub(crate) enum RunStore {
|
||||||
|
/// Run pict-rs with filesystem storage
|
||||||
|
Filesystem(RunFilesystem),
|
||||||
|
|
||||||
|
/// Run pict-rs with object storage
|
||||||
|
ObjectStorage(RunObjectStorage),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the pict-rs storage migration
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub(crate) enum MigrateStore {
|
||||||
|
/// Migrate from the provided filesystem storage
|
||||||
|
Filesystem(MigrateFilesystem),
|
||||||
|
|
||||||
|
/// Migrate from the provided object storage
|
||||||
|
ObjectStorage(MigrateObjectStorage),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Migrate pict-rs' storage from the provided filesystem storage
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub(crate) struct MigrateFilesystem {
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub(crate) from: Filesystem,
|
||||||
|
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub(crate) to: RunStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Migrate pict-rs' storage from the provided object storage
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub(crate) struct MigrateObjectStorage {
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub(crate) from: ObjectStorage,
|
||||||
|
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub(crate) to: RunStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run pict-rs with the provided filesystem storage
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub(crate) struct RunFilesystem {
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub(crate) system: Filesystem,
|
||||||
|
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub(crate) repo: Repo,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run pict-rs with the provided object storage
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub(crate) struct RunObjectStorage {
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub(crate) storage: ObjectStorage,
|
||||||
|
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub(crate) repo: Repo,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration for data repositories
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub(crate) enum Repo {
|
||||||
|
/// Run pict-rs with the provided sled-backed data repository
|
||||||
|
Sled(Sled),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration for filesystem media storage
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub(crate) struct Filesystem {
|
||||||
|
/// The path to store uploaded media
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub(crate) path: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration for Object Storage
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub(crate) struct ObjectStorage {
|
||||||
|
/// The bucket in which to store media
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub(crate) bucket_name: Option<String>,
|
||||||
|
|
||||||
|
/// The region the bucket is located in
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub(crate) region: Option<s3::Region>,
|
||||||
|
|
||||||
|
/// The Access Key for the user accessing the bucket
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub(crate) access_key: Option<String>,
|
||||||
|
|
||||||
|
/// The secret key for the user accessing the bucket
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub(crate) secret_key: Option<String>,
|
||||||
|
|
||||||
|
/// The security token for accessing the bucket
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) security_token: Option<String>,
|
||||||
|
|
||||||
|
/// The session token for accessing the bucket
|
||||||
|
#[clap(long)]
|
||||||
|
pub(crate) session_token: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration for the sled-backed data repository
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub(crate) struct Sled {
|
||||||
|
/// The path to store the sled database
|
||||||
|
pub(crate) path: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// The cache capacity, in bytes, allowed to sled for in-memory operations
|
||||||
|
pub(crate) cache_capacity: Option<u64>,
|
||||||
|
}
|
181
src/config/defaults.rs
Normal file
181
src/config/defaults.rs
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
use crate::{
|
||||||
|
config::primitives::{LogFormat, Targets},
|
||||||
|
serde_str::Serde,
|
||||||
|
};
|
||||||
|
use std::{net::SocketAddr, path::PathBuf};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct Defaults {
|
||||||
|
server: ServerDefaults,
|
||||||
|
tracing: TracingDefaults,
|
||||||
|
old_db: OldDbDefaults,
|
||||||
|
media: MediaDefaults,
|
||||||
|
repo: RepoDefaults,
|
||||||
|
store: StoreDefaults,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct ServerDefaults {
|
||||||
|
address: SocketAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct TracingDefaults {
|
||||||
|
logging: LoggingDefaults,
|
||||||
|
|
||||||
|
console: ConsoleDefaults,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct LoggingDefaults {
|
||||||
|
format: LogFormat,
|
||||||
|
targets: Serde<Targets>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct ConsoleDefaults {
|
||||||
|
buffer_capacity: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct OldDbDefaults {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct MediaDefaults {
|
||||||
|
max_width: usize,
|
||||||
|
max_height: usize,
|
||||||
|
max_area: usize,
|
||||||
|
max_file_size: usize,
|
||||||
|
enable_silent_video: bool,
|
||||||
|
filters: Vec<String>,
|
||||||
|
skip_validate_imports: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
enum RepoDefaults {
|
||||||
|
Sled(SledDefaults),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct SledDefaults {
|
||||||
|
path: PathBuf,
|
||||||
|
cache_capacity: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
enum StoreDefaults {
|
||||||
|
Filesystem(FilesystemDefaults),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct FilesystemDefaults {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Defaults {
|
||||||
|
fn default() -> Self {
|
||||||
|
Defaults {
|
||||||
|
server: ServerDefaults::default(),
|
||||||
|
tracing: TracingDefaults::default(),
|
||||||
|
old_db: OldDbDefaults::default(),
|
||||||
|
media: MediaDefaults::default(),
|
||||||
|
repo: RepoDefaults::default(),
|
||||||
|
store: StoreDefaults::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ServerDefaults {
|
||||||
|
fn default() -> Self {
|
||||||
|
ServerDefaults {
|
||||||
|
address: "0.0.0.0:8080".parse().expect("Valid address string"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TracingDefaults {
|
||||||
|
fn default() -> TracingDefaults {
|
||||||
|
TracingDefaults {
|
||||||
|
logging: LoggingDefaults::default(),
|
||||||
|
console: ConsoleDefaults::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LoggingDefaults {
|
||||||
|
fn default() -> Self {
|
||||||
|
LoggingDefaults {
|
||||||
|
format: LogFormat::Normal,
|
||||||
|
targets: "info".parse().expect("Valid targets string"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ConsoleDefaults {
|
||||||
|
fn default() -> Self {
|
||||||
|
ConsoleDefaults {
|
||||||
|
buffer_capacity: 1024 * 100,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for OldDbDefaults {
|
||||||
|
fn default() -> Self {
|
||||||
|
OldDbDefaults {
|
||||||
|
path: PathBuf::from(String::from("/mnt")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MediaDefaults {
|
||||||
|
fn default() -> Self {
|
||||||
|
MediaDefaults {
|
||||||
|
max_width: 10_000,
|
||||||
|
max_height: 10_000,
|
||||||
|
max_area: 40_000_000,
|
||||||
|
max_file_size: 40,
|
||||||
|
enable_silent_video: true,
|
||||||
|
filters: vec![
|
||||||
|
"identity".into(),
|
||||||
|
"thumbnail".into(),
|
||||||
|
"resize".into(),
|
||||||
|
"crop".into(),
|
||||||
|
"blur".into(),
|
||||||
|
],
|
||||||
|
skip_validate_imports: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RepoDefaults {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Sled(SledDefaults::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SledDefaults {
|
||||||
|
fn default() -> Self {
|
||||||
|
SledDefaults {
|
||||||
|
path: PathBuf::from(String::from("/mnt/sled-repo")),
|
||||||
|
cache_capacity: 1024 * 1024 * 64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for StoreDefaults {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Filesystem(FilesystemDefaults::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FilesystemDefaults {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
path: PathBuf::from(String::from("/mnt/files")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
131
src/config/file.rs
Normal file
131
src/config/file.rs
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
use crate::{
|
||||||
|
config::primitives::{ImageFormat, LogFormat, Targets},
|
||||||
|
serde_str::Serde,
|
||||||
|
};
|
||||||
|
use std::{net::SocketAddr, path::PathBuf};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct ConfigFile {
|
||||||
|
pub(crate) server: Server,
|
||||||
|
|
||||||
|
pub(crate) tracing: Tracing,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) old_db: Option<OldDb>,
|
||||||
|
|
||||||
|
pub(crate) media: Media,
|
||||||
|
|
||||||
|
pub(crate) repo: Repo,
|
||||||
|
|
||||||
|
pub(crate) store: Store,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub(crate) enum Repo {
|
||||||
|
Sled(Sled),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub(crate) enum Store {
|
||||||
|
Filesystem(Filesystem),
|
||||||
|
|
||||||
|
ObjectStorage(ObjectStorage),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct Server {
|
||||||
|
pub(crate) address: SocketAddr,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) api_key: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct Tracing {
|
||||||
|
logging: Logging,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
console: Option<Console>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
opentelemetry: Option<OpenTelemetry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct Logging {
|
||||||
|
pub(crate) format: LogFormat,
|
||||||
|
|
||||||
|
pub(crate) targets: Serde<Targets>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct OpenTelemetry {
|
||||||
|
pub(crate) url: Url,
|
||||||
|
|
||||||
|
pub(crate) service_name: String,
|
||||||
|
|
||||||
|
pub(crate) targets: Serde<Targets>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct Console {
|
||||||
|
pub(crate) address: SocketAddr,
|
||||||
|
pub(crate) buffer_capacity: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct OldDb {
|
||||||
|
pub(crate) path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct Media {
|
||||||
|
pub(crate) max_width: usize,
|
||||||
|
|
||||||
|
pub(crate) max_height: usize,
|
||||||
|
|
||||||
|
pub(crate) max_area: usize,
|
||||||
|
|
||||||
|
pub(crate) max_file_size: usize,
|
||||||
|
|
||||||
|
pub(crate) enable_silent_video: bool,
|
||||||
|
|
||||||
|
pub(crate) filters: Vec<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) format: Option<ImageFormat>,
|
||||||
|
|
||||||
|
pub(crate) skip_validate_imports: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct Filesystem {
|
||||||
|
pub(crate) path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct ObjectStorage {
|
||||||
|
pub(crate) bucket_name: String,
|
||||||
|
|
||||||
|
pub(crate) region: Serde<s3::Region>,
|
||||||
|
|
||||||
|
pub(crate) access_key: String,
|
||||||
|
|
||||||
|
pub(crate) secret_key: String,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) security_token: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) session_token: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct Sled {
|
||||||
|
pub(crate) path: PathBuf,
|
||||||
|
|
||||||
|
pub(crate) cache_capacity: u64,
|
||||||
|
}
|
111
src/config/primitives.rs
Normal file
111
src/config/primitives.rs
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
use clap::ArgEnum;
|
||||||
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Clone,
|
||||||
|
Debug,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
PartialOrd,
|
||||||
|
Ord,
|
||||||
|
Hash,
|
||||||
|
serde::Deserialize,
|
||||||
|
serde::Serialize,
|
||||||
|
ArgEnum,
|
||||||
|
)]
|
||||||
|
pub(crate) enum LogFormat {
|
||||||
|
Compact,
|
||||||
|
Json,
|
||||||
|
Normal,
|
||||||
|
Pretty,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Clone,
|
||||||
|
Debug,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
PartialOrd,
|
||||||
|
Ord,
|
||||||
|
Hash,
|
||||||
|
serde::Deserialize,
|
||||||
|
serde::Serialize,
|
||||||
|
ArgEnum,
|
||||||
|
)]
|
||||||
|
pub(crate) enum ImageFormat {
|
||||||
|
Jpeg,
|
||||||
|
Webp,
|
||||||
|
Png,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct Targets {
|
||||||
|
pub(crate) targets: tracing_subscriber::filter::Targets,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Targets {
|
||||||
|
type Err = <tracing_subscriber::filter::Targets as FromStr>::Err;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(Targets {
|
||||||
|
targets: s.parse()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Targets {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let targets = self
|
||||||
|
.targets
|
||||||
|
.iter()
|
||||||
|
.map(|(path, level)| format!("{}={}", path, level))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(",");
|
||||||
|
|
||||||
|
write!(f, "{}", targets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ImageFormat {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
for variant in Self::value_variants() {
|
||||||
|
if variant.to_possible_value().unwrap().matches(s, false) {
|
||||||
|
return Ok(*variant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(format!("Invalid variant: {}", s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for LogFormat {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
for variant in Self::value_variants() {
|
||||||
|
if variant.to_possible_value().unwrap().matches(s, false) {
|
||||||
|
return Ok(*variant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(format!("Invalid variant: {}", s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ImageFormat {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.to_possible_value()
|
||||||
|
.expect("no values are skipped")
|
||||||
|
.get_name()
|
||||||
|
.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LogFormat {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.to_possible_value()
|
||||||
|
.expect("no values are skipped")
|
||||||
|
.get_name()
|
||||||
|
.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue