use serde::de::{MapAccess, SeqAccess, Visitor};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use crate::models::{
cron::CronTaskConfig, BalanceStrategy, ProxyGroupConfig, ProxyGroupType, RegexMatchConfig,
RulesetConfig,
};
use crate::settings::settings::toml_settings::TemplateSettings;
pub trait ImportableInToml: serde::de::DeserializeOwned + Clone {
fn is_import_node(&self) -> bool;
fn get_import_path(&self) -> Option<String>;
fn try_from_toml_value(value: &toml::Value) -> Result<Self, Box<dyn std::error::Error>> {
Ok(value.clone().try_into()?)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct RegexMatchRuleInToml {
#[serde(rename = "match")]
pub match_str: Option<String>,
#[serde(alias = "emoji")]
pub replace: Option<String>,
pub script: Option<String>,
pub import: Option<String>,
}
impl Into<RegexMatchConfig> for RegexMatchRuleInToml {
fn into(self) -> RegexMatchConfig {
let mut config = RegexMatchConfig::new(
self.match_str.unwrap_or_default(),
self.replace.unwrap_or_default(),
self.script.unwrap_or_default(),
);
config.compile();
config
}
}
impl ImportableInToml for RegexMatchRuleInToml {
fn is_import_node(&self) -> bool {
self.import.is_some()
}
fn get_import_path(&self) -> Option<String> {
self.import.clone()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct RulesetConfigInToml {
pub group: String,
pub ruleset: Option<String>,
#[serde(rename = "type")]
pub ruleset_type: Option<String>,
pub interval: Option<u32>,
pub import: Option<String>,
}
impl ImportableInToml for RulesetConfigInToml {
fn is_import_node(&self) -> bool {
self.import.is_some()
}
fn get_import_path(&self) -> Option<String> {
self.import.clone()
}
}
impl Into<RulesetConfig> for RulesetConfigInToml {
fn into(self) -> RulesetConfig {
RulesetConfig {
url: self.ruleset.unwrap_or_default(),
group: self.group,
interval: self.interval.unwrap_or(300),
}
}
}
fn default_test_url() -> Option<String> {
Some("http://www.gstatic.com/generate_204".to_string())
}
fn default_interval() -> Option<u32> {
Some(300)
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct ProxyGroupConfigInToml {
pub name: String,
#[serde(rename = "type")]
pub group_type: String,
pub strategy: Option<String>,
pub rule: Vec<String>,
#[serde(default = "default_test_url")]
pub url: Option<String>,
#[serde(default = "default_interval")]
pub interval: Option<u32>,
pub lazy: Option<bool>,
pub tolerance: Option<u32>,
pub timeout: Option<u32>,
pub disable_udp: Option<bool>,
pub import: Option<String>,
}
impl ImportableInToml for ProxyGroupConfigInToml {
fn is_import_node(&self) -> bool {
self.import.is_some()
}
fn get_import_path(&self) -> Option<String> {
self.import.clone()
}
}
impl Into<ProxyGroupConfig> for ProxyGroupConfigInToml {
fn into(self) -> ProxyGroupConfig {
let group_type = match self.group_type.as_str() {
"select" => ProxyGroupType::Select,
"url-test" => ProxyGroupType::URLTest,
"load-balance" => ProxyGroupType::LoadBalance,
"fallback" => ProxyGroupType::Fallback,
"relay" => ProxyGroupType::Relay,
"ssid" => ProxyGroupType::SSID,
"smart" => ProxyGroupType::Smart,
_ => ProxyGroupType::Select, };
let strategy = match self.strategy.as_deref() {
Some("consistent-hashing") => BalanceStrategy::ConsistentHashing,
Some("round-robin") => BalanceStrategy::RoundRobin,
_ => BalanceStrategy::ConsistentHashing,
};
let mut config = ProxyGroupConfig {
name: self.name,
group_type,
proxies: self.rule,
url: self.url.unwrap_or_default(),
interval: self.interval.unwrap_or(300),
tolerance: self.tolerance.unwrap_or(0),
timeout: self.timeout.unwrap_or(5),
lazy: self.lazy.unwrap_or(false),
disable_udp: self.disable_udp.unwrap_or(false),
strategy,
persistent: false,
evaluate_before_use: false,
using_provider: Vec::new(),
};
match config.group_type {
ProxyGroupType::URLTest | ProxyGroupType::Smart => {
if config.url.is_empty() {
config.url = "http://www.gstatic.com/generate_204".to_string();
}
}
ProxyGroupType::LoadBalance => {
if config.url.is_empty() {
config.url = "http://www.gstatic.com/generate_204".to_string();
}
}
ProxyGroupType::Fallback => {
if config.url.is_empty() {
config.url = "http://www.gstatic.com/generate_204".to_string();
}
}
_ => {}
}
config
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct TaskConfigInToml {
pub name: String,
pub cronexp: String,
pub path: String,
pub timeout: u32,
pub import: Option<String>,
}
impl ImportableInToml for TaskConfigInToml {
fn is_import_node(&self) -> bool {
self.import.is_some()
}
fn get_import_path(&self) -> Option<String> {
self.import.clone()
}
}
impl Into<CronTaskConfig> for TaskConfigInToml {
fn into(self) -> CronTaskConfig {
CronTaskConfig {
name: self.name,
cron_exp: self.cronexp,
path: self.path,
timeout: self.timeout,
}
}
}
pub fn deserialize_template_as_template_settings<'de, D>(
deserializer: D,
) -> Result<TemplateSettings, D::Error>
where
D: serde::Deserializer<'de>,
{
struct TemplateSettingsVisitor;
impl<'de> Visitor<'de> for TemplateSettingsVisitor {
type Value = TemplateSettings;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a TemplateSettings struct")
}
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
where
V: MapAccess<'de>,
{
let mut template_settings = TemplateSettings::default();
while let Some(key) = map.next_key::<String>()? {
let value = map.next_value::<String>()?;
if key == "template_path" {
template_settings.template_path = value.clone();
} else {
template_settings.globals.insert(key, value);
}
}
Ok(template_settings)
}
}
deserializer.deserialize_any(TemplateSettingsVisitor)
}
#[derive(Debug, Clone, Deserialize, Default)]
struct TemplateArgument {
pub key: String,
pub value: String,
}
pub fn deserialize_template_args_as_hash_map<'de, D>(
deserializer: D,
) -> Result<Option<HashMap<String, String>>, D::Error>
where
D: serde::Deserializer<'de>,
{
struct TemplateArgsVisitor;
impl<'de> Visitor<'de> for TemplateArgsVisitor {
type Value = Option<HashMap<String, String>>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a sequence of template arguments or a map of key-value pairs")
}
fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
where
S: SeqAccess<'de>,
{
let mut template_args = HashMap::new();
while let Some(item) = seq.next_element::<TemplateArgument>()? {
template_args.insert(item.key, item.value);
}
if template_args.is_empty() {
Ok(None)
} else {
Ok(Some(template_args))
}
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(None)
}
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(None)
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut template_args = HashMap::new();
while let Some((key, value)) = map.next_entry::<String, String>()? {
template_args.insert(key, value);
}
if template_args.is_empty() {
Ok(None)
} else {
Ok(Some(template_args))
}
}
}
deserializer.deserialize_any(TemplateArgsVisitor)
}