{"record":{"id":"d05e8f48184ec8a4","repo":"go-playground/validator","slug":"s-tag-must-be-immediately-preceded-by-the-s","errorCode":null,"errorMessage":"'%s' tag must be immediately preceded by the '%s' tag","messagePattern":"'(.+?)' tag must be immediately preceded by the '(.+?)' tag","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"cache.go","lineNumber":216,"sourceCode":"\n\t\tif i == 0 {\n\t\t\tcurrent = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true, typeof: typeDefault}\n\t\t\tfirstCtag = current\n\t\t} else {\n\t\t\tprevTag = current.typeof\n\t\t\tcurrent.next = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}\n\t\t\tcurrent = current.next\n\t\t}\n\n\t\tswitch t {\n\t\tcase diveTag:\n\t\t\tcurrent.typeof = typeDive\n\n\t\tcase keysTag:\n\t\t\tcurrent.typeof = typeKeys\n\n\t\t\tif i == 0 || prevTag != typeDive {\n\t\t\t\tpanic(fmt.Sprintf(\"'%s' tag must be immediately preceded by the '%s' tag\", keysTag, diveTag))\n\t\t\t}\n\n\t\t\t// need to pass along only keys tag\n\t\t\t// need to increment i to skip over the keys tags\n\t\t\tb := make([]byte, 0, 64)\n\n\t\t\ti++\n\n\t\t\tfor ; i < len(tags); i++ {\n\t\t\t\tb = append(b, tags[i]...)\n\t\t\t\tb = append(b, ',')\n\n\t\t\t\tif tags[i] == endKeysTag {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcurrent.keys, _ = v.parseFieldTagsRecursive(string(b[:len(b)-1]), fieldName, \"\", false)","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/go-playground/validator/blob/facf128d2eecf42bd4ff447adc961aa21bb64aed/cache.go#L198-L234","documentation":"During struct-tag parsing (cached per type), the `keys` tag is only legal immediately after a `dive` tag. If `keys` appears at the start of the tag list or not directly after `dive`, cache.go panics: \"'keys' tag must be immediately preceded by the 'dive' tag\". This panic happens once when the struct type is first validated (tag cache build), not per value.","triggerScenarios":"Any Validate.Struct call on a struct whose field tag contains `keys` without a preceding `dive`, e.g. `validate:\"keys,endkeys,uuid\"` or `validate:\"required,keys\"`.","commonSituations":"Hand-writing map-key validation tags out of order; refactoring tags and dropping the dive; misunderstanding that keys must start a dive-into-map block.","solutions":["Reorder so `dive` comes right before `keys`: `dive,keys,<key-validators>,endkeys,<value-validators>`","Remove the stray `keys` tag if map-key validation is not intended","Fix the constant/aliased tag expression that emits keys without dive","Add a smoke test that validates a sample struct at startup to catch tag errors early"],"exampleFix":"// before\nM map[string]string `validate:\"keys,endkeys,required\"`\n// after\nM map[string]string `validate:\"dive,keys,required,endkeys,required\"`","handlingStrategy":"validation","validationCode":"// tag order check before Validate.Struct\nfunc checkKeysOrder(t reflect.StructTag) error {\n    tags := strings.Split(t.Get(\"validate\"), \",\")\n    for i, tg := range tags {\n        if tg == \"keys\" && (i == 0 || tags[i-1] != \"dive\") {\n            return fmt.Errorf(\"keys must immediately follow dive\")\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"func buildAndValidate(s any) (err error) {\n    defer func() {\n        if r := recover(); r != nil {\n            err = fmt.Errorf(\"tag syntax panic: %v\", r)\n        }\n    }()\n    return validate.Struct(s)\n}","preventionTips":["Always write map-key validation as dive,keys,...,endkeys,...","Run a validation smoke test on all structs at startup","Do not edit dive/keys/endkeys sequences independently","Document tag order in team conventions"],"tags":["go","validator","panic","tag-syntax","cache"],"backgroundTag":"validator-invalid-tag-syntax","analyzedSha":"facf128d2eecf42bd4ff447adc961aa21bb64aed","analyzedAt":"2026-09-02T10:19:04.986Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T16:17:10.729Z"}