diff --git a/config/config.go b/config/config.go index b1669aad..ac4ba4f1 100644 --- a/config/config.go +++ b/config/config.go @@ -365,7 +365,7 @@ type RawOverride struct { Hostname string `yaml:"hostname" json:"hostname"` Username string `yaml:"username" json:"username"` ListStrategy ListMergeStrategy `yaml:"list-strategy" json:"list-strategy"` - Content *RawConfig `yaml:"content" json:"content"` + Content map[string]any `yaml:"content" json:"content"` } type RawConfig struct { diff --git a/config/override.go b/config/override.go index 5893e8a2..971bf5ac 100644 --- a/config/override.go +++ b/config/override.go @@ -1,17 +1,19 @@ package config import ( - "dario.cat/mergo" - "fmt" "github.com/metacubex/mihomo/log" + "gopkg.in/yaml.v3" "os" "os/user" - "reflect" "runtime" ) type ListMergeStrategy string +// insert-front: [old slice] -> [new slice, old slice] +// append: [old slice] -> [old slice, new slice] +// override: [old slice] -> [new slice] (Default) + const ( InsertFront ListMergeStrategy = "insert-front" Append ListMergeStrategy = "append" @@ -19,44 +21,9 @@ const ( Default ListMergeStrategy = "" ) -// overrideTransformer is to merge slices with give strategy instead of the default behavior -// - insert-front: [old slice] -> [new slice, old slice] -// - append: [old slice] -> [old slice, new slice] -// - override: [old slice] -> [new slice] (Default) -type overrideTransformer struct { - listStrategy ListMergeStrategy -} - -func (t overrideTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { - if typ.Kind() == reflect.Slice { - return func(dst, src reflect.Value) error { - if src.IsNil() || !dst.CanSet() { - return nil - } - if src.Kind() != reflect.Slice || dst.Kind() != reflect.Slice { - return nil - } - // merge slice according to strategy - switch t.listStrategy { - case InsertFront: - newSlice := reflect.AppendSlice(src, dst) - dst.Set(newSlice) - case Append: - newSlice := reflect.AppendSlice(dst, src) - dst.Set(newSlice) - case Override, Default: - dst.Set(src) - default: - return fmt.Errorf("unknown list override strategy: %s", t.listStrategy) - } - return nil - } - } - return nil -} - func ApplyOverride(rawCfg *RawConfig, overrides []RawOverride) error { for id, override := range overrides { + // check override conditions if override.OS != "" && override.OS != runtime.GOOS { continue } @@ -84,12 +51,29 @@ func ApplyOverride(rawCfg *RawConfig, overrides []RawOverride) error { } } - // merge rawConfig override - err := mergo.Merge(rawCfg, *override.Content, mergo.WithTransformers(overrideTransformer{ - listStrategy: override.ListStrategy, - }), mergo.WithOverride) + // marshal override content back to text + overrideContent, err := yaml.Marshal(override.Content) if err != nil { log.Errorln("Error when applying override #%v: %v", id, err) + continue + } + + // unmarshal override content into rawConfig, with custom list merge strategy + switch override.ListStrategy { + case Append: + options := yaml.NewDecodeOptions().ListDecodeOption(yaml.ListDecodeAppend) + err = yaml.UnmarshalWith(options, overrideContent, rawCfg) + case InsertFront: + options := yaml.NewDecodeOptions().ListDecodeOption(yaml.ListDecodeInsertFront) + err = yaml.UnmarshalWith(options, overrideContent, rawCfg) + case Override, Default: + err = yaml.Unmarshal(overrideContent, rawCfg) + default: + log.Errorln("Bad list strategy in override #%v: %v", id, override.ListStrategy) + } + if err != nil { + log.Errorln("Error when applying override #%v: %v", id, err) + continue } } return nil diff --git a/config/override_test.go b/config/override_test.go index 1e07b612..f564ef39 100644 --- a/config/override_test.go +++ b/config/override_test.go @@ -36,6 +36,30 @@ override: assert.Equal(t, "0.0.0.0:9090", cfg.Controller.ExternalController) }) + t.Run("override_zero_value_test", func(t *testing.T) { + config_file := ` +mixed-port: 7890 +ipv6: true +log-level: debug +allow-lan: true +unified-delay: false +tcp-concurrent: true +external-controller: 127.0.0.1:9090 +default-nameserver: + - "223.5.5.5" +override: + - content: + external-controller: "" + allow-lan: false` + rawCfg, err := UnmarshalRawConfig([]byte(config_file)) + assert.NoError(t, err) + cfg, err := ParseRawConfig(rawCfg) + assert.NoError(t, err) + assert.Equal(t, log.DEBUG, cfg.General.LogLevel) + assert.Equal(t, false, cfg.General.AllowLan) + assert.Equal(t, "", cfg.Controller.ExternalController) + }) + t.Run("add_new", func(t *testing.T) { config_file := ` mixed-port: 7890 diff --git a/go.mod b/go.mod index 429e4beb..9405a2f1 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/metacubex/mihomo go 1.20 require ( - dario.cat/mergo v1.0.1 github.com/3andne/restls-client-go v0.1.6 github.com/bahlo/generic-list-go v0.2.0 github.com/coreos/go-iptables v0.8.0