{"record":{"id":"6118eb5cd52de802","repo":"caddyserver/caddy","slug":"marshaling-matcher-set-v-v","errorCode":null,"errorMessage":"marshaling matcher set %#v: %v","messagePattern":"marshaling matcher set %#v: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"caddyconfig/httpcaddyfile/httptype.go","lineNumber":1764,"sourceCode":"\tfor nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {\n\t\tmatcherName := d.Val()\n\t\ttokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)\n\t}\n\tfor matcherName, tokens := range tokensByMatcherName {\n\t\terr := makeMatcher(matcherName, tokens)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcherWithError) (caddy.ModuleMap, error) {\n\tmsEncoded := make(caddy.ModuleMap)\n\tfor matcherName, val := range matchers {\n\t\tjsonBytes, err := json.Marshal(val)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"marshaling matcher set %#v: %v\", matchers, err)\n\t\t}\n\t\tmsEncoded[matcherName] = jsonBytes\n\t}\n\treturn msEncoded, nil\n}\n\n// WasReplacedPlaceholderShorthand checks if a token string was\n// likely a replaced shorthand of the known Caddyfile placeholder\n// replacement outputs. Useful to prevent some user-defined map\n// output destinations from overlapping with one of the\n// predefined shorthands.\nfunc WasReplacedPlaceholderShorthand(token string) string {\n\tprev := \"\"\n\tfor i, item := range placeholderShorthands() {\n\t\t// only look at every 2nd item, which is the replacement\n\t\tif i%2 == 0 {\n\t\t\tprev = item\n\t\t\tcontinue","sourceCodeStart":1746,"sourceCodeEnd":1782,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/caddyconfig/httpcaddyfile/httptype.go#L1746-L1782","documentation":"encodeMatcherSet json.Marshal's each request matcher value (map[string]caddyhttp.RequestMatcherWithError) when encoding matcher sets. This only fails if a matcher type carries fields json.Marshal cannot represent (MarshalJSON returning an error, unsupported map key types, channels/funcs). Core matchers never trigger it; a misbehaving plugin matcher can.","triggerScenarios":"A plugin RequestMatcher with an exported field of type func(), chan, complex, or non-string map keys that json.Marshal rejects; a custom MarshalJSON on the matcher that returns an error under some inputs.","commonSituations":"Developing a plugin matcher that stores callbacks or unexported runtime state in exported fields; matchers embedding non-serializable third-party structs.","solutions":["Inspect the matcher type named in the failing set and find non-serializable exported fields; replace funcs/chans with serializable config (e.g. IDs resolved at Provision time).","Implement json.Marshaler on the matcher to control serialization.","Reproduce with 'caddy adapt --config ... --adapter caddyfile' to iterate quickly without running the server.","Report/patch the plugin if you are only a consumer of it."],"exampleFix":"// before\ntype MatchThing struct {\n\tCheck func(*http.Request) bool `json:\"-\"` // exported func field: Marshal refuses\n}\n\n// after\ntype MatchThing struct {\n\tCheckExpr string `json:\"check_expr\"` // serializable config; compiled in Provision\n}","handlingStrategy":"validation","validationCode":"// in plugin tests: assert the matcher marshals cleanly\nb, err := json.Marshal(matcher)\nif err != nil {\n    t.Fatalf(\"matcher not serializable: %v\", err)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Unit-test JSON marshaling of every exported matcher config in plugin CI.","Keep runtime state (funcs, chans) unexported or tagged json:\"-\"."],"tags":["caddyfile","json","matcher","plugin"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}