{"record":{"id":"36e8ef1f7d27c4d1","repo":"gofiber/fiber","slug":"multiple-binding-source-tags-found-on-struct-s","errorCode":null,"errorMessage":"multiple binding_source tags found on struct %s","messagePattern":"multiple binding_source tags found on struct (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"bind.go","lineNumber":459,"sourceCode":"type cachedPrecedence struct {\n\terr     error\n\tsources []bindSource\n}\n\nvar bindingPrecedenceCache sync.Map // map[reflect.Type]cachedPrecedence\n\nfunc getBindingPrecedence(t reflect.Type) ([]bindSource, error) {\n\tif cached, ok := bindingPrecedenceCache.Load(t); ok {\n\t\tif cp, ok := cached.(cachedPrecedence); ok {\n\t\t\treturn cp.sources, cp.err\n\t\t}\n\t}\n\tvar precedence []bindSource\n\tvar tagFound bool\n\tfor i := range t.NumField() {\n\t\tif tag := t.Field(i).Tag.Get(\"binding_source\"); tag != \"\" {\n\t\t\tif tagFound {\n\t\t\t\terr := fmt.Errorf(\"multiple binding_source tags found on struct %s\", t.Name())\n\t\t\t\tbindingPrecedenceCache.Store(t, cachedPrecedence{err: err, sources: nil})\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\ttagFound = true\n\n\t\t\tparts := strings.SplitSeq(tag, \",\")\n\t\t\tfor p := range parts {\n\t\t\t\tsourceName := strings.TrimSpace(p)\n\t\t\t\tif sourceName == \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tvar source bindSource\n\t\t\t\tswitch sourceName {\n\t\t\t\tcase \"uri\":\n\t\t\t\t\tsource = sourceURI\n\t\t\t\tcase \"body\":\n\t\t\t\t\tsource = sourceBody\n\t\t\t\tcase \"query\":","sourceCodeStart":441,"sourceCodeEnd":477,"githubUrl":"https://github.com/gofiber/fiber/blob/a105acad6c1e4576a77f01e02973f67e962bb58d/bind.go#L441-L477","documentation":"The binder allows at most ONE field per struct to carry a `binding_source` tag (used to define custom binding precedence). More than one is ambiguous, so getBindingPrecedence caches and returns fmt.Errorf(\"multiple binding_source tags found on struct %s\", t.Name()) at reflection time. The result is cached in bindingPrecedenceCache so subsequent calls fail fast.","triggerScenarios":"Defining a struct with two (or more) fields annotated `binding_source:\"...\"` and binding a request to it.","commonSituations":"Copy-pasting binding annotations during refactors; merging structs without removing old tags; misunderstanding the one-tag-per-struct rule.","solutions":["Keep exactly one binding_source tag per struct.","Combine multiple sources into a single comma-separated tag: `binding_source:\"uri,body,query\"`.","Remove stale tags after refactors; add a unit test that binds a sample request to catch the error early."],"exampleFix":"// before\ntype Req struct {\n    ID   string `binding_source:\"uri\"`\n    Body struct{} `binding_source:\"body\"`\n}\n\n// after\ntype Req struct {\n    ID   string `binding_source:\"uri\"`\n    Body struct{}\n}\n// or, if precedence must be explicit, put it on one field:\ntype Req struct {\n    Combined CombinedInput `binding_source:\"uri,body,query\"`\n}","handlingStrategy":"validation","validationCode":"// At app startup, smoke-bind a sample request to each annotated struct\nvar req Req\nif err := app.Test(...); err != nil { log.Fatalf(\"bind config: %v\", err) }","typeGuard":"func hasOneBindingSource(t reflect.Type) int {\n    n := 0\n    for i := range t.NumField() {\n        if t.Field(i).Tag.Get(\"binding_source\") != \"\" { n++ }\n    }\n    return n\n}","tryCatchPattern":null,"preventionTips":["Keep exactly one binding_source tag per struct; combine sources with commas.","Add a unit test that binds a sample request to each annotated struct.","Remove stale tags when merging structs."],"tags":["bind","struct-tags","reflection","config"],"backgroundTag":null,"analyzedSha":"a105acad6c1e4576a77f01e02973f67e962bb58d","analyzedAt":"2026-08-11T17:33:26.942Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}