From 1c28b6b75c58f0117ccc6adb56dc4ae7db6dfb05 Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Fri, 29 May 2026 14:43:08 +0200 Subject: [PATCH 1/4] chore: simplify, modernize (Go 1.26), update deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - config.go: extract parseDSN helper (strings.Cut) to deduplicate DSN validation between Valid() and Dialer(); replace two strings.Split+len sites with the helper - plugin.go: rename local WholeCfg → wholeCfg (unexported style); replace TrimPrefix+Index+slice with strings.Cut --- config.go | 22 ++++++++++++++++------ plugin.go | 11 ++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/config.go b/config.go index f5b45cc..89f236e 100644 --- a/config.go +++ b/config.go @@ -40,10 +40,20 @@ func (c *Config) InitDefaults() { } } +// parseDSN splits a "scheme://address" DSN and returns the two parts. +// Returns an error when the DSN does not contain "://". +func parseDSN(dsn string) (scheme, addr string, err error) { + scheme, addr, ok := strings.Cut(dsn, "://") + if !ok { + return "", "", errors.New("invalid socket DSN (tcp://:6001, unix://file.sock)") + } + return scheme, addr, nil +} + // Valid returns nil if config is valid. func (c *Config) Valid() error { - if dsn := strings.Split(c.Listen, "://"); len(dsn) != 2 { - return errors.New("invalid socket DSN (tcp://:6001, unix://file.sock)") + if _, _, err := parseDSN(c.Listen); err != nil { + return err } if c.RequestTimeout < 0 { return errors.New("rpc request_timeout must be non-negative") @@ -63,10 +73,10 @@ func (c *Config) Listener() (net.Listener, error) { // Dialer creates rpc socket Dialer. func (c *Config) Dialer() (net.Conn, error) { - dsn := strings.Split(c.Listen, "://") - if len(dsn) != 2 { - return nil, errors.New("invalid socket DSN (tcp://:6001, unix://file.sock)") + scheme, addr, err := parseDSN(c.Listen) + if err != nil { + return nil, err } var d net.Dialer - return d.DialContext(context.Background(), dsn[0], dsn[1]) + return d.DialContext(context.Background(), scheme, addr) } diff --git a/plugin.go b/plugin.go index e590aff..526d1a1 100644 --- a/plugin.go +++ b/plugin.go @@ -85,13 +85,13 @@ func (s *Plugin) Init(cfg Configurer, log Logger) error { return errors.E(op, err) } - var WholeCfg any - err = cfg.Unmarshal(&WholeCfg) + var wholeCfg any + err = cfg.Unmarshal(&wholeCfg) if err != nil { return errors.E(op, err) } - s.wcfg, err = json.Marshal(WholeCfg) + s.wcfg, err = json.Marshal(wholeCfg) if err != nil { return err } @@ -125,10 +125,7 @@ func (s *Plugin) Serve() chan error { mux.Handle(path, handler) // derive the gRPC service name from the mount path // (`//` or `//`) - svc := strings.TrimPrefix(path, "/") - if i := strings.Index(svc, "/"); i >= 0 { - svc = svc[:i] - } + svc, _, _ := strings.Cut(strings.TrimPrefix(path, "/"), "/") services = append(services, svc) } From 3093e78af6d1e6ccf043be2f909465ba5603decb Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sat, 30 May 2026 00:03:11 +0200 Subject: [PATCH 2/4] fix(config): reject DSNs with multiple "://" separators in parseDSN strings.Cut only splits on the first delimiter, so the parseDSN refactor accepted inputs like tcp://host://port that the prior strings.Split(...); len != 2 check rejected. Restore exactly-one-separator semantics and add a regression test covering Valid() and Dialer(). --- config.go | 4 ++-- tests/config_test.go | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/config.go b/config.go index 89f236e..e750572 100644 --- a/config.go +++ b/config.go @@ -41,10 +41,10 @@ func (c *Config) InitDefaults() { } // parseDSN splits a "scheme://address" DSN and returns the two parts. -// Returns an error when the DSN does not contain "://". +// Returns an error unless the DSN contains exactly one "://" separator. func parseDSN(dsn string) (scheme, addr string, err error) { scheme, addr, ok := strings.Cut(dsn, "://") - if !ok { + if !ok || strings.Contains(addr, "://") { return "", "", errors.New("invalid socket DSN (tcp://:6001, unix://file.sock)") } return scheme, addr, nil diff --git a/tests/config_test.go b/tests/config_test.go index 6d23f6d..9482e4e 100644 --- a/tests/config_test.go +++ b/tests/config_test.go @@ -156,6 +156,18 @@ func Test_Config_DialerErrorMethod(t *testing.T) { assert.Error(t, err) } +func Test_Config_MultipleSeparators(t *testing.T) { + // A DSN with more than one "://" must be rejected by both Valid and Dialer. + cfg := &rpc.Config{Listen: "tcp://host://6001"} + + assert.Error(t, cfg.Valid()) + + conn, err := cfg.Dialer() + assert.Nil(t, conn) + assert.Error(t, err) + assert.Equal(t, "invalid socket DSN (tcp://:6001, unix://file.sock)", err.Error()) +} + func Test_Config_Defaults(t *testing.T) { c := &rpc.Config{} c.InitDefaults() From d9ce02009d5b286d3aee95c2de7529597d0e827d Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sun, 31 May 2026 19:11:32 +0200 Subject: [PATCH 3/4] refactor(rpc): return a named dsn struct from parseDSN Two bare same-typed string returns (scheme, addr) are positionally ambiguous at call sites; a small struct makes each field self-documenting and swap-proof. Validation semantics (reject multi-"://") unchanged. --- config.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/config.go b/config.go index e750572..f2f129f 100644 --- a/config.go +++ b/config.go @@ -40,19 +40,25 @@ func (c *Config) InitDefaults() { } } -// parseDSN splits a "scheme://address" DSN and returns the two parts. -// Returns an error unless the DSN contains exactly one "://" separator. -func parseDSN(dsn string) (scheme, addr string, err error) { - scheme, addr, ok := strings.Cut(dsn, "://") +// dsn is a parsed "scheme://address" RPC listen string. +type dsn struct { + scheme string + addr string +} + +// parseDSN splits a "scheme://address" listen string into its scheme and +// address. It errors unless the string contains exactly one "://" separator. +func parseDSN(listen string) (dsn, error) { + scheme, addr, ok := strings.Cut(listen, "://") if !ok || strings.Contains(addr, "://") { - return "", "", errors.New("invalid socket DSN (tcp://:6001, unix://file.sock)") + return dsn{}, errors.New("invalid socket DSN (tcp://:6001, unix://file.sock)") } - return scheme, addr, nil + return dsn{scheme: scheme, addr: addr}, nil } // Valid returns nil if config is valid. func (c *Config) Valid() error { - if _, _, err := parseDSN(c.Listen); err != nil { + if _, err := parseDSN(c.Listen); err != nil { return err } if c.RequestTimeout < 0 { @@ -73,10 +79,10 @@ func (c *Config) Listener() (net.Listener, error) { // Dialer creates rpc socket Dialer. func (c *Config) Dialer() (net.Conn, error) { - scheme, addr, err := parseDSN(c.Listen) + parsed, err := parseDSN(c.Listen) if err != nil { return nil, err } var d net.Dialer - return d.DialContext(context.Background(), scheme, addr) + return d.DialContext(context.Background(), parsed.scheme, parsed.addr) } From 634c6bb9c7489a11e64ec6b163cc0ce070cabd37 Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sun, 31 May 2026 19:12:35 +0200 Subject: [PATCH 4/4] chore: udpate Signed-off-by: Valery Piashchynski --- go.work.sum | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/go.work.sum b/go.work.sum index f0a183d..fce1f3f 100644 --- a/go.work.sum +++ b/go.work.sum @@ -364,6 +364,8 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= @@ -416,6 +418,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=