mirror of
https://github.com/girlbossceo/conduwuit.git
synced 2024-12-01 07:16:42 +00:00
add config generator controls via attribute metadatas
Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
parent
367d153380
commit
5cb0a5f676
|
@ -28,10 +28,19 @@ use self::proxy::ProxyConfig;
|
||||||
use crate::{err, error::Error, utils::sys, Result};
|
use crate::{err, error::Error, utils::sys, Result};
|
||||||
|
|
||||||
/// all the config options for conduwuit
|
/// all the config options for conduwuit
|
||||||
#[config_example_generator]
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
#[allow(rustdoc::broken_intra_doc_links, rustdoc::bare_urls)]
|
#[allow(rustdoc::broken_intra_doc_links, rustdoc::bare_urls)]
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[config_example_generator(
|
||||||
|
filename = "conduwuit-example.toml",
|
||||||
|
section = "global",
|
||||||
|
undocumented = "# This item is undocumented. Please contribute documentation for it.",
|
||||||
|
header = "### Conduwuit Configuration\n###\n### THIS FILE IS GENERATED. YOUR CHANGES WILL BE OVERWRITTEN!\n### \
|
||||||
|
You should rename this file before configuring your server. Changes\n### to documentation and defaults \
|
||||||
|
can be contributed in sourcecode at\n### src/core/config/mod.rs. This file is generated when \
|
||||||
|
building.\n###\n",
|
||||||
|
ignore = "catchall well_known tls"
|
||||||
|
)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// The server_name is the pretty name of this server. It is used as a
|
/// The server_name is the pretty name of this server. It is used as a
|
||||||
/// suffix for user and room ids. Examples: matrix.org, conduit.rs
|
/// suffix for user and room ids. Examples: matrix.org, conduit.rs
|
||||||
|
@ -71,6 +80,7 @@ pub struct Config {
|
||||||
#[serde(default = "default_port")]
|
#[serde(default = "default_port")]
|
||||||
port: ListeningPort,
|
port: ListeningPort,
|
||||||
|
|
||||||
|
// external structure; separate section
|
||||||
pub tls: Option<TlsConfig>,
|
pub tls: Option<TlsConfig>,
|
||||||
|
|
||||||
/// Uncomment unix_socket_path to listen on a UNIX socket at the specified
|
/// Uncomment unix_socket_path to listen on a UNIX socket at the specified
|
||||||
|
@ -458,15 +468,18 @@ pub struct Config {
|
||||||
#[serde(default = "true_fn")]
|
#[serde(default = "true_fn")]
|
||||||
pub allow_unstable_room_versions: bool,
|
pub allow_unstable_room_versions: bool,
|
||||||
|
|
||||||
|
/// default: 10
|
||||||
#[serde(default = "default_default_room_version")]
|
#[serde(default = "default_default_room_version")]
|
||||||
pub default_room_version: RoomVersionId,
|
pub default_room_version: RoomVersionId,
|
||||||
|
|
||||||
|
// external structure; separate section
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub well_known: WellKnownConfig,
|
pub well_known: WellKnownConfig,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub allow_jaeger: bool,
|
pub allow_jaeger: bool,
|
||||||
|
|
||||||
|
/// default: "info"
|
||||||
#[serde(default = "default_jaeger_filter")]
|
#[serde(default = "default_jaeger_filter")]
|
||||||
pub jaeger_filter: String,
|
pub jaeger_filter: String,
|
||||||
|
|
||||||
|
@ -478,12 +491,38 @@ pub struct Config {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub tracing_flame: bool,
|
pub tracing_flame: bool,
|
||||||
|
|
||||||
|
/// default: "info"
|
||||||
#[serde(default = "default_tracing_flame_filter")]
|
#[serde(default = "default_tracing_flame_filter")]
|
||||||
pub tracing_flame_filter: String,
|
pub tracing_flame_filter: String,
|
||||||
|
|
||||||
|
/// default: "./tracing.folded"
|
||||||
#[serde(default = "default_tracing_flame_output_path")]
|
#[serde(default = "default_tracing_flame_output_path")]
|
||||||
pub tracing_flame_output_path: String,
|
pub tracing_flame_output_path: String,
|
||||||
|
|
||||||
|
/// Examples:
|
||||||
|
/// - No proxy (default):
|
||||||
|
/// proxy ="none"
|
||||||
|
///
|
||||||
|
/// - For global proxy, create the section at the bottom of this file:
|
||||||
|
/// [global.proxy]
|
||||||
|
/// global = { url = "socks5h://localhost:9050" }
|
||||||
|
///
|
||||||
|
/// - To proxy some domains:
|
||||||
|
/// [global.proxy]
|
||||||
|
/// [[global.proxy.by_domain]]
|
||||||
|
/// url = "socks5h://localhost:9050"
|
||||||
|
/// include = ["*.onion", "matrix.myspecial.onion"]
|
||||||
|
/// exclude = ["*.myspecial.onion"]
|
||||||
|
///
|
||||||
|
/// Include vs. Exclude:
|
||||||
|
/// - If include is an empty list, it is assumed to be `["*"]`.
|
||||||
|
/// - If a domain matches both the exclude and include list, the proxy will
|
||||||
|
/// only be used if it was included because of a more specific rule than
|
||||||
|
/// it was excluded. In the above example, the proxy would be used for
|
||||||
|
/// `ordinary.onion`, `matrix.myspecial.onion`, but not
|
||||||
|
/// `hello.myspecial.onion`.
|
||||||
|
///
|
||||||
|
/// default: "none"
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub proxy: ProxyConfig,
|
pub proxy: ProxyConfig,
|
||||||
|
|
||||||
|
@ -1278,6 +1317,7 @@ pub struct Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[config_example_generator(filename = "conduwuit-example.toml", section = "global.tls")]
|
||||||
pub struct TlsConfig {
|
pub struct TlsConfig {
|
||||||
pub certs: String,
|
pub certs: String,
|
||||||
pub key: String,
|
pub key: String,
|
||||||
|
@ -1287,6 +1327,7 @@ pub struct TlsConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Default)]
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
|
#[config_example_generator(filename = "conduwuit-example.toml", section = "global.well_known")]
|
||||||
pub struct WellKnownConfig {
|
pub struct WellKnownConfig {
|
||||||
pub client: Option<Url>,
|
pub client: Option<Url>,
|
||||||
pub server: Option<OwnedServerName>,
|
pub server: Option<OwnedServerName>,
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
use std::{fmt::Write as _, fs::File, io::Write as _};
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
fmt::Write as _,
|
||||||
|
fs::OpenOptions,
|
||||||
|
io::Write as _,
|
||||||
|
};
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use syn::{
|
use syn::{
|
||||||
parse::Parser, punctuated::Punctuated, Error, Expr, ExprLit, Field, Fields, FieldsNamed, ItemStruct, Lit, Meta,
|
parse::Parser, punctuated::Punctuated, spanned::Spanned, Error, Expr, ExprLit, Field, Fields, FieldsNamed,
|
||||||
MetaList, MetaNameValue, Type, TypePath,
|
ItemStruct, Lit, Meta, MetaList, MetaNameValue, Type, TypePath,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{utils::is_cargo_build, Result};
|
use crate::{utils::is_cargo_build, Result};
|
||||||
|
|
||||||
const UNDOCUMENTED: &str = "# This item is undocumented. Please contribute documentation for it.";
|
const UNDOCUMENTED: &str = "# This item is undocumented. Please contribute documentation for it.";
|
||||||
const HEADER: &str = "## Conduwuit Configuration\n##\n## THIS FILE IS GENERATED. Changes to documentation and \
|
|
||||||
defaults must\n## be made within the code found at src/core/config/\n";
|
|
||||||
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub(super) fn example_generator(input: ItemStruct, args: &[Meta]) -> Result<TokenStream> {
|
pub(super) fn example_generator(input: ItemStruct, args: &[Meta]) -> Result<TokenStream> {
|
||||||
|
@ -25,11 +28,41 @@ pub(super) fn example_generator(input: ItemStruct, args: &[Meta]) -> Result<Toke
|
||||||
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn generate_example(input: &ItemStruct, _args: &[Meta]) -> Result<()> {
|
fn generate_example(input: &ItemStruct, args: &[Meta]) -> Result<()> {
|
||||||
let mut file = File::create("conduwuit-example.toml")
|
let settings = get_settings(args);
|
||||||
|
|
||||||
|
let filename = settings
|
||||||
|
.get("filename")
|
||||||
|
.ok_or_else(|| Error::new(args[0].span(), "missing required 'filename' attribute argument"))?;
|
||||||
|
|
||||||
|
let undocumented = settings
|
||||||
|
.get("undocumented")
|
||||||
|
.map_or(UNDOCUMENTED, String::as_str);
|
||||||
|
|
||||||
|
let ignore: HashSet<&str> = settings
|
||||||
|
.get("ignore")
|
||||||
|
.map_or("", String::as_str)
|
||||||
|
.split(' ')
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let section = settings
|
||||||
|
.get("section")
|
||||||
|
.ok_or_else(|| Error::new(args[0].span(), "missing required 'section' attribute argument"))?;
|
||||||
|
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(section == "global")
|
||||||
|
.truncate(section == "global")
|
||||||
|
.append(section != "global")
|
||||||
|
.open(filename)
|
||||||
.map_err(|e| Error::new(Span::call_site(), format!("Failed to open config file for generation: {e}")))?;
|
.map_err(|e| Error::new(Span::call_site(), format!("Failed to open config file for generation: {e}")))?;
|
||||||
|
|
||||||
file.write_all(HEADER.as_bytes())
|
if let Some(header) = settings.get("header") {
|
||||||
|
file.write_all(header.as_bytes())
|
||||||
|
.expect("written to config file");
|
||||||
|
}
|
||||||
|
|
||||||
|
file.write_fmt(format_args!("\n[{section}]\n"))
|
||||||
.expect("written to config file");
|
.expect("written to config file");
|
||||||
|
|
||||||
if let Fields::Named(FieldsNamed {
|
if let Fields::Named(FieldsNamed {
|
||||||
|
@ -42,12 +75,16 @@ fn generate_example(input: &ItemStruct, _args: &[Meta]) -> Result<()> {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if ignore.contains(ident.to_string().as_str()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let Some(type_name) = get_type_name(field) else {
|
let Some(type_name) = get_type_name(field) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let doc = get_doc_comment(field)
|
let doc = get_doc_comment(field)
|
||||||
.unwrap_or_else(|| UNDOCUMENTED.into())
|
.unwrap_or_else(|| undocumented.into())
|
||||||
.trim_end()
|
.trim_end()
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
|
@ -75,9 +112,47 @@ fn generate_example(input: &ItemStruct, _args: &[Meta]) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(footer) = settings.get("footer") {
|
||||||
|
file.write_all(footer.as_bytes())
|
||||||
|
.expect("written to config file");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_settings(args: &[Meta]) -> HashMap<String, String> {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
for arg in args {
|
||||||
|
let Meta::NameValue(MetaNameValue {
|
||||||
|
path,
|
||||||
|
value,
|
||||||
|
..
|
||||||
|
}) = arg
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Expr::Lit(
|
||||||
|
ExprLit {
|
||||||
|
lit: Lit::Str(str),
|
||||||
|
..
|
||||||
|
},
|
||||||
|
..,
|
||||||
|
) = value
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(key) = path.segments.iter().next().map(|s| s.ident.clone()) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
map.insert(key.to_string(), str.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
fn get_default(field: &Field) -> Option<String> {
|
fn get_default(field: &Field) -> Option<String> {
|
||||||
for attr in &field.attrs {
|
for attr in &field.attrs {
|
||||||
let Meta::List(MetaList {
|
let Meta::List(MetaList {
|
||||||
|
|
Loading…
Reference in a new issue