From 2b73f83a2a556d7ef4c48d15fc47c5aafe76170f Mon Sep 17 00:00:00 2001 From: Klesh Wong Date: Mon, 20 Jul 2026 14:52:39 +0800 Subject: [PATCH] feat: dedupe MATCH --- internal/corecfg/manager.go | 23 ++++++++++++++++++++++ internal/corecfg/manager_test.go | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 internal/corecfg/manager_test.go diff --git a/internal/corecfg/manager.go b/internal/corecfg/manager.go index a53da48..8b42ebd 100644 --- a/internal/corecfg/manager.go +++ b/internal/corecfg/manager.go @@ -377,9 +377,32 @@ func (m *Manager) mergeSubscriptions(subs []*model.Subscription) (map[string]int combined = deepMerge(combined, data) } + combined["rules"] = dedupeMatchRule(combined["rules"]) + return combined, proxies, true } +// dedupeMatchRule drops every "MATCH,..." catch-all rule contributed by the +// merged subscriptions — each one covers all their own proxies, so once +// concatenated they'd shadow all subsequent subscriptions' rules — and +// appends a single "MATCH,all proxies" catch-all in their place. +func dedupeMatchRule(rulesRaw interface{}) interface{} { + rules, ok := rulesRaw.([]interface{}) + if !ok { + return rulesRaw + } + + filtered := make([]interface{}, 0, len(rules)+1) + for _, item := range rules { + if line, ok := item.(string); ok && strings.HasPrefix(line, "MATCH,") { + continue + } + filtered = append(filtered, item) + } + + return append(filtered, "MATCH,all proxies") +} + func openInEditor(path string) bool { return editor.OpenFileInEditor(path) } diff --git a/internal/corecfg/manager_test.go b/internal/corecfg/manager_test.go new file mode 100644 index 0000000..63379fb --- /dev/null +++ b/internal/corecfg/manager_test.go @@ -0,0 +1,33 @@ +package corecfg + +import ( + "reflect" + "testing" +) + +func TestDedupeMatchRule(t *testing.T) { + rules := []interface{}{ + "DOMAIN,example.com,sub-a | Proxy", + "MATCH,sub-a | Auto", + "DOMAIN,other.com,sub-b | Proxy", + "MATCH,sub-b | Auto", + } + + got := dedupeMatchRule(rules) + + want := []interface{}{ + "DOMAIN,example.com,sub-a | Proxy", + "DOMAIN,other.com,sub-b | Proxy", + "MATCH,all proxies", + } + + if !reflect.DeepEqual(got, want) { + t.Fatalf("got %#v, want %#v", got, want) + } +} + +func TestDedupeMatchRuleNonList(t *testing.T) { + if got := dedupeMatchRule(nil); got != nil { + t.Fatalf("expected nil passthrough, got %#v", got) + } +}