{"record":{"id":"b62fb7558e7fd85a","repo":"caddyserver/caddy","slug":"marshaling-t-matcher-v","errorCode":null,"errorMessage":"marshaling %T matcher: %v","messagePattern":"marshaling %T matcher: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddyhttp/matchers.go","lineNumber":1730,"sourceCode":"\t\tif rm, ok := unm.(RequestMatcherWithError); ok {\n\t\t\tmatcherMap[matcherName] = rm\n\t\t\tcontinue\n\t\t}\n\t\tif rm, ok := unm.(RequestMatcher); ok {\n\t\t\tmatcherMap[matcherName] = rm\n\t\t\tcontinue\n\t\t}\n\t\treturn nil, fmt.Errorf(\"matcher module '%s' is not a request matcher\", matcherName)\n\t}\n\n\t// we should now have a functional matcher, but we also\n\t// need to be able to marshal as JSON, otherwise config\n\t// adaptation will be missing the matchers!\n\tmatcherSet := make(caddy.ModuleMap)\n\tfor name, matcher := range matcherMap {\n\t\tjsonBytes, err := json.Marshal(matcher)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"marshaling %T matcher: %v\", matcher, err)\n\t\t}\n\t\tmatcherSet[name] = jsonBytes\n\t}\n\n\treturn matcherSet, nil\n}\n\nvar wordRE = regexp.MustCompile(`\\w+`)\n\nconst regexpPlaceholderPrefix = \"http.regexp\"\n\n// MatcherErrorVarKey is the key used for the variable that\n// holds an optional error emitted from a request matcher,\n// to short-circuit the handler chain, since matchers cannot\n// return errors via the RequestMatcher interface.\n//\n// Deprecated: Matchers should implement RequestMatcherWithError\n// which can return an error directly, instead of smuggling it","sourceCodeStart":1712,"sourceCodeEnd":1748,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/matchers.go#L1712-L1748","documentation":"After successfully parsing Caddyfile matcher definitions, parseMatcherDefinitions marshals each matcher back to JSON so the adapted config contains it. If json.Marshal fails on the matcher value — typically custom types with unmarshalable fields (func, chan, complex, or cycles) or a broken MarshalJSON — this error reports the Go type and the marshal error.","triggerScenarios":"A third-party matcher holding a func/chan field or an unexported state that its MarshalJSON mishandles; a matcher type whose MarshalJSON returns an error for certain configs.","commonSituations":"Plugin development where the struct is provisioned into a runtime form (compiled regex, locks) with no custom marshaling back; cyclic references in matcher structs; plugin version drift after Caddy changed expected JSON shapes.","solutions":["In the plugin, implement json.Marshaler (MarshalJSON) that emits the original config form, not runtime state.","Keep runtime-only fields unexported and ensure the rest are plain JSON-serializable types.","Update the plugin to a version compatible with your Caddy release."],"exampleFix":"// before (plugin)\ntype Fancy struct {\n  Re *regexp.Regexp `json:\"-\"`\n}\n\n// after\ntype Fancy struct {\n  Pattern string `json:\"pattern\"`\n  Re *regexp.Regexp `json:\"-\"`\n}\nfunc (f Fancy) MarshalJSON() ([]byte, error) { return json.Marshal(f.Pattern) }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Plugin authors: make marshaling deterministic and error-free\nfunc (m MyMatcher) MarshalJSON() ([]byte, error) {\n\ttype plain MyMatcher // drop methods, keep fields\n\treturn json.Marshal(plain(m))\n}","preventionTips":["Never store funcs/channels/locks in exported matcher fields.","Implement MarshalJSON emitting the config form, not runtime state.","Unit-test round-trip: Unmarshal(marshal(m)) in the plugin."],"tags":["caddy","plugin","json","serialization","matcher"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}