From 0e8eb41b87ab60803d48cb2a183face3f4e4248e Mon Sep 17 00:00:00 2001 From: Bruno Teixeira Lopes <143887730+Brunotlps@users.noreply.github.com> Date: Fri, 29 May 2026 18:05:41 -0300 Subject: [PATCH] httpcaddyfile: fix incorrect error message on duplicate matchers (#7780) Parse each matcher segment individually using NewDispenser(segment) instead of DispenseDirective(dir), which coalesced all same-name segments into one token stream. This caused the second definition name to be misinterpreted as a matcher module name, producing 'module not registered: http.matchers.@name' instead of the correct 'matcher is defined more than once' error. By parsing segments individually, the existing duplicate check in parseMatcherDefinitions naturally catches the duplicate on the second pass. Signed-off-by: Brunotlps --- caddyconfig/httpcaddyfile/httptype.go | 2 +- caddyconfig/httpcaddyfile/httptype_test.go | 41 ++++++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index c6979e56d..1c907572f 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -108,7 +108,7 @@ func (st ServerType) Setup( matcherDefs := make(map[string]caddy.ModuleMap) for _, segment := range sb.block.Segments { if dir := segment.Directive(); strings.HasPrefix(dir, matcherPrefix) { - d := sb.block.DispenseDirective(dir) + d := caddyfile.NewDispenser(segment) err := parseMatcherDefinitions(d, matcherDefs) if err != nil { return nil, warnings, err diff --git a/caddyconfig/httpcaddyfile/httptype_test.go b/caddyconfig/httpcaddyfile/httptype_test.go index 2436efcd9..b9a94fca9 100644 --- a/caddyconfig/httpcaddyfile/httptype_test.go +++ b/caddyconfig/httpcaddyfile/httptype_test.go @@ -2,6 +2,7 @@ package httpcaddyfile import ( "encoding/json" + "strings" "testing" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" @@ -10,8 +11,9 @@ import ( func TestMatcherSyntax(t *testing.T) { for i, tc := range []struct { - input string - expectError bool + input string + expectError bool + expectContains string }{ { input: `http://localhost @@ -53,6 +55,34 @@ func TestMatcherSyntax(t *testing.T) { `, expectError: false, }, + { + input: `http://localhost { + @test { + path /test + } + @test { + path /other + } + respond @test "hello" + } + `, + expectError: true, + expectContains: "is defined more than once", + }, + { + input: `(snippet) { + @{args[0]} { + path /{args[0]} + } + respond @{args[0]} "hello" + } + http://localhost { + import snippet foo + import snippet bar + } + `, + expectError: false, + }, { input: `@matcher { path /matcher-not-allowed/outside-of-site-block/* @@ -73,6 +103,13 @@ func TestMatcherSyntax(t *testing.T) { t.Errorf("Test %d error expectation failed Expected: %v, got %s", i, tc.expectError, err) continue } + + if err != nil && tc.expectContains != "" { + if !strings.Contains(err.Error(), tc.expectContains) { + t.Errorf("Test %d error message mismatch: expected to contain %q, got %q", + i, tc.expectContains, err.Error()) + } + } } }