refactor: backend (#7102)

* refactor(tray): move tray log opening to utils helper

* refactor(enhance): move owned config through pipeline

* refactor(enhance): move sequence values in use_seq

* refactor(enhance): move values while sorting fields

* refactor(enhance): move values when lowercasing owned config

* refactor(backend): move owned config values in utilities

* refactor(tray): borrow hotkey accelerators

* refactor(core): borrow proxy config strings

* fix(backend): preserve config patch semantics

* refactor(enhance): clarify config transform names
This commit is contained in:
Tunglies
2026-05-26 10:47:03 +00:00
committed by GitHub
parent a287d13b4d
commit 761209bbbc
11 changed files with 198 additions and 157 deletions
+37 -22
View File
@@ -1,6 +1,5 @@
use serde_yaml_ng::{Mapping, Value};
use smartstring::alias::String;
use std::collections::HashSet;
pub const HANDLE_FIELDS: [&str; 12] = [
"mode",
@@ -19,46 +18,62 @@ pub const HANDLE_FIELDS: [&str; 12] = [
pub const DEFAULT_FIELDS: [&str; 5] = ["proxies", "proxy-providers", "proxy-groups", "rule-providers", "rules"];
fn lowercase_key(key: &str) -> Value {
let mut key = String::from(key);
key.make_ascii_lowercase();
Value::from(key.as_str())
}
pub fn use_lowercase(config: &Mapping) -> Mapping {
let mut ret = Mapping::new();
let mut lowercased = Mapping::new();
for (key, value) in config.into_iter() {
if let Some(key_str) = key.as_str() {
let mut key_str = String::from(key_str);
key_str.make_ascii_lowercase();
ret.insert(Value::from(key_str.as_str()), value.clone());
lowercased.insert(lowercase_key(key_str), value.clone());
}
}
ret
lowercased
}
pub fn use_sort(config: Mapping) -> Mapping {
let mut ret = Mapping::new();
pub fn use_lowercase_owned(config: Mapping) -> Mapping {
let mut lowercased = Mapping::new();
for (key, value) in config {
if let Some(key_str) = key.as_str() {
lowercased.insert(lowercase_key(key_str), value);
}
}
lowercased
}
pub fn use_sort(mut config: Mapping) -> Mapping {
let mut sorted = Mapping::new();
HANDLE_FIELDS.into_iter().for_each(|key| {
let key = Value::from(key);
if let Some(value) = config.get(&key) {
ret.insert(key, value.clone());
if let Some(value) = config.remove(&key) {
sorted.insert(key, value);
}
});
let supported_keys: HashSet<&str> = HANDLE_FIELDS.into_iter().chain(DEFAULT_FIELDS).collect();
let config_keys: HashSet<&str> = config.keys().filter_map(|e| e.as_str()).collect();
config_keys.difference(&supported_keys).for_each(|&key| {
let key = Value::from(key);
if let Some(value) = config.get(&key) {
ret.insert(key, value.clone());
let mut default_field_values = Mapping::new();
for (key, value) in config {
if let Some(key_str) = key.as_str() {
if DEFAULT_FIELDS.contains(&key_str) {
default_field_values.insert(key, value);
} else if !HANDLE_FIELDS.contains(&key_str) {
sorted.insert(key, value);
}
}
});
}
DEFAULT_FIELDS.into_iter().for_each(|key| {
let key = Value::from(key);
if let Some(value) = config.get(&key) {
ret.insert(key, value.clone());
if let Some(value) = default_field_values.remove(&key) {
sorted.insert(key, value);
}
});
ret
sorted
}
#[inline]
+5 -5
View File
@@ -301,7 +301,7 @@ async fn process_global_items(
if let ChainType::Merge(merge) = global_merge.data {
exists_keys.extend(use_keys(&merge));
config = use_merge(&merge, config.to_owned());
config = use_merge(&merge, config);
}
if let ChainType::Script(script) = global_script.data {
@@ -333,20 +333,20 @@ async fn process_profile_items(
profile_name: &String,
) -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
if let ChainType::Rules(rules) = rules_item.data {
config = use_seq(rules, config.to_owned(), "rules");
config = use_seq(rules, config, "rules");
}
if let ChainType::Proxies(proxies) = proxies_item.data {
config = use_seq(proxies, config.to_owned(), "proxies");
config = use_seq(proxies, config, "proxies");
}
if let ChainType::Groups(groups) = groups_item.data {
config = use_seq(groups, config.to_owned(), "proxy-groups");
config = use_seq(groups, config, "proxy-groups");
}
if let ChainType::Merge(merge) = merge_item.data {
exists_keys.extend(use_keys(&merge));
config = use_merge(&merge, config.to_owned());
config = use_merge(&merge, config);
}
if let ChainType::Script(script) = script_item.data {
+2 -2
View File
@@ -1,6 +1,6 @@
use crate::process::AsyncHandler;
use super::use_lowercase;
use super::field::{use_lowercase, use_lowercase_owned};
use anyhow::{Error, Result};
use boa_engine::{Context, JsString, JsValue, Source, native_function::NativeFunction};
use clash_verge_logging::{Type, logging_error};
@@ -126,7 +126,7 @@ fn use_script_sync(script: String, config: &Mapping, name: &String) -> Result<(M
let res: Result<Mapping, Error> = parse_json_safely(&result);
match res {
Ok(config) => Ok((use_lowercase(&config), outputs.lock().to_vec())),
Ok(config) => Ok((use_lowercase_owned(config), outputs.lock().to_vec())),
Err(err) => {
outputs
.lock()
+85 -49
View File
@@ -49,13 +49,13 @@ pub fn use_seq(seq: SeqMap, mut config: Mapping, field: &str) -> Mapping {
Vec::new()
};
let mut new_seq = Sequence::new();
new_seq.extend(prepend);
let mut updated_items = Sequence::new();
updated_items.extend(prepend);
if let Some(Value::Sequence(origin)) = config.get(field) {
if let Some(Value::Sequence(existing_items)) = config.remove(field) {
// Filter out deleted items
let filtered: Sequence = origin
.iter()
let kept_items: Sequence = existing_items
.into_iter()
.filter(|item| {
if let Value::String(s) = item {
!delete.contains(s)
@@ -69,25 +69,34 @@ pub fn use_seq(seq: SeqMap, mut config: Mapping, field: &str) -> Mapping {
true
}
})
.cloned()
.collect();
new_seq.extend(filtered);
updated_items.extend(kept_items);
}
new_seq.extend(append);
config.insert(Value::String(field.into()), Value::Sequence(new_seq));
updated_items.extend(append);
config.insert(Value::String(field.into()), Value::Sequence(updated_items));
// If this is proxies field, we also need to filter proxy-groups
if field == "proxies"
&& let Some(Value::Sequence(groups)) = config.get_mut("proxy-groups")
{
let mut new_groups = Sequence::new();
let mut appended_to_selector = false;
for group in groups {
if let Value::Mapping(group_map) = group {
let mut proxies_seq = group_map.get("proxies").and_then(Value::as_sequence).map(|proxies| {
if field != "proxies" {
return config;
}
let Some(proxy_groups_value) = config.remove("proxy-groups") else {
return config;
};
let Value::Sequence(proxy_groups) = proxy_groups_value else {
config.insert(Value::String("proxy-groups".into()), proxy_groups_value);
return config;
};
let mut updated_groups = Sequence::new();
let mut appended_to_selector = false;
for group in proxy_groups {
if let Value::Mapping(mut group_map) = group {
let mut group_proxies = match group_map.remove("proxies") {
Some(Value::Sequence(proxies)) => Some(
proxies
.iter()
.into_iter()
.filter(|p| {
if let Value::String(name) = p {
!delete.contains(name)
@@ -95,42 +104,46 @@ pub fn use_seq(seq: SeqMap, mut config: Mapping, field: &str) -> Mapping {
true
}
})
.cloned()
.collect::<Sequence>()
});
if !appended_to_selector && !added_proxy_names.is_empty() && is_selector_group(group_map) {
let base_seq = proxies_seq.unwrap_or_else(Sequence::new);
let mut seq = Sequence::new();
let mut existing = HashSet::new();
for name in &added_proxy_names {
if existing.insert(name.clone()) {
seq.push(Value::String(name.clone()));
}
}
for value in base_seq {
if let Value::String(name) = &value
&& !existing.insert(name.to_owned())
{
continue;
}
seq.push(value);
}
proxies_seq = Some(seq);
appended_to_selector = true;
.collect::<Sequence>(),
),
Some(value) => {
group_map.insert(Value::String("proxies".into()), value);
None
}
None => None,
};
if let Some(seq) = proxies_seq {
group_map.insert(Value::String("proxies".into()), Value::Sequence(seq));
if !appended_to_selector && !added_proxy_names.is_empty() && is_selector_group(&group_map) {
let base_group_proxies = group_proxies.unwrap_or_else(Sequence::new);
let mut merged_proxies = Sequence::new();
let mut seen_proxy_names = HashSet::new();
for name in &added_proxy_names {
if seen_proxy_names.insert(name.clone()) {
merged_proxies.push(Value::String(name.clone()));
}
}
new_groups.push(Value::Mapping(group_map.to_owned()));
} else {
new_groups.push(group.to_owned());
for value in base_group_proxies {
if let Value::String(name) = &value
&& !seen_proxy_names.insert(name.to_owned())
{
continue;
}
merged_proxies.push(value);
}
group_proxies = Some(merged_proxies);
appended_to_selector = true;
}
if let Some(group_proxies) = group_proxies {
group_map.insert(Value::String("proxies".into()), Value::Sequence(group_proxies));
}
updated_groups.push(Value::Mapping(group_map));
} else {
updated_groups.push(group);
}
config.insert(Value::String("proxy-groups".into()), Value::Sequence(new_groups));
}
config.insert(Value::String("proxy-groups".into()), Value::Sequence(updated_groups));
config
}
@@ -288,4 +301,27 @@ proxy-groups:
let names: Vec<&str> = group2_proxies.iter().filter_map(Value::as_str).collect();
assert_eq!(names, vec!["proxy1"]);
}
#[test]
#[allow(clippy::unwrap_used)]
#[allow(clippy::expect_used)]
fn test_preserve_non_sequence_proxy_groups() {
let config_str = r#"
proxies:
- name: "proxy1"
type: "ss"
proxy-groups: "invalid"
"#;
let mut config: Mapping = serde_yaml_ng::from_str(config_str).expect("Failed to parse test config YAML");
let seq = SeqMap {
prepend: Sequence::new(),
append: Sequence::new(),
delete: vec!["proxy1".to_string()],
};
config = use_seq(seq, config, "proxies");
assert_eq!(config.get("proxy-groups").and_then(Value::as_str), Some("invalid"));
}
}