{"record":{"id":"69431eaa746f732d","repo":"go-kratos/kratos","slug":"invalid-path-q-is-not-a-message","errorCode":null,"errorMessage":"invalid path: %q is not a message","messagePattern":"invalid path: %q is not a message","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"encoding/form/proto_decode.go","lineNumber":62,"sourceCode":"\n\tvar fd protoreflect.FieldDescriptor\n\tfor i, fieldName := range fieldPath {\n\t\tif fd = getFieldDescriptor(v, fieldName); fd == nil {\n\t\t\t// ignore unexpected field.\n\t\t\treturn nil\n\t\t}\n\t\tif fd.IsMap() && len(fieldPath) == 2 {\n\t\t\treturn populateMapField(fd, v.Mutable(fd).Map(), fieldPath, values)\n\t\t}\n\t\tif i == len(fieldPath)-1 {\n\t\t\tbreak\n\t\t}\n\t\tif fd.Message() == nil || fd.Cardinality() == protoreflect.Repeated {\n\t\t\tif fd.IsMap() && len(fieldPath) > 1 {\n\t\t\t\t// post subfield\n\t\t\t\treturn populateMapField(fd, v.Mutable(fd).Map(), []string{fieldPath[1]}, values)\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"invalid path: %q is not a message\", fieldName)\n\t\t}\n\n\t\tv = v.Mutable(fd).Message()\n\t}\n\tif of := fd.ContainingOneof(); of != nil {\n\t\tif f := v.WhichOneof(of); f != nil {\n\t\t\treturn fmt.Errorf(\"field already set for oneof %q\", of.FullName().Name())\n\t\t}\n\t}\n\tswitch {\n\tcase fd.IsList():\n\t\treturn populateRepeatedField(fd, v.Mutable(fd).List(), values)\n\tcase fd.IsMap():\n\t\treturn populateMapField(fd, v.Mutable(fd).Map(), fieldPath, values)\n\t}\n\tif len(values) > 1 {\n\t\treturn fmt.Errorf(\"too many values for field %q: %s\", fd.FullName().Name(), strings.Join(values, \", \"))\n\t}","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/go-kratos/kratos/blob/668db92c2c001e9552594ba5a8aede8456af6d7e/encoding/form/proto_decode.go#L44-L80","documentation":"Form-binding decoder error (encoding/form) raised while walking a dotted field path from a query string/form body into a proto message: the path tries to descend into fieldName as if it were a nested message, but the resolved field is a scalar, or it is a repeated field that is not a map, so no further traversal is possible. Only singular message fields (and map fields via the special two-segment map syntax) can be traversed.","triggerScenarios":"Binding http.Request query/form data into a proto request struct with a key whose dotted path crosses a non-message field: `?user.name=kratos` when `user` is a string field; `?items[0].id=1` when `items` is a repeated message field (repeated + not map => error); `?map.a.b=1`-style paths deeper than the supported map syntax. Also occurs when a proto field was refactored from message to scalar and old clients still send nested keys.","commonSituations":"Client and server proto definitions out of sync after a field type change; frontend sending structured query params that the proto does not model as nested messages; using repeated message fields in GET query params, which this form codec cannot express (only maps get special handling).","solutions":["Compare the failing query key (in the error path context, fieldName) against the proto schema and fix the client to send a flat key for scalar/repeated fields","For repeated message fields, do not use query/form binding - switch the endpoint to a JSON body (POST) with the json codec","For maps, use the supported bracket syntax field[key]=value (one level) instead of dotted descent through the map","Regenerate/verify the proto types in use so client and server agree on which fields are messages","Return a 400 with this message to the caller so the offending key is discoverable during integration"],"exampleFix":"// proto: string user = 1;\n// before: query ?user.name=kratos  -> invalid path: \"user\" is not a message\n// after:  query ?user=kratos","handlingStrategy":"validation","validationCode":"// Reject query keys that try to descend through non-message fields before binding\nfunc validateQueryKeys(v url.Values, msg protoreflect.Message) error {\n\tfor key := range v {\n\t\tparts := strings.Split(key, \"[\")[0]\n\t\tm := msg\n\t\tok := true\n\t\tfor _, seg := range strings.Split(parts, \".\") {\n\t\t\tfd := m.Descriptor().Fields().ByName(protoreflect.Name(seg))\n\t\t\tif fd == nil {\n\t\t\t\tok = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif fd.Kind() == protoreflect.MessageKind && !fd.IsList() {\n\t\t\t\tm = m.Mutable(fd).Message()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak // scalar/repeated leaf is fine as final segment\n\t\t}\n\t\t_ = ok\n\t}\n\treturn nil\n}","typeGuard":"func isBindablePath(msg protoreflect.Message, key string) bool {\n\tm := msg\n\tsegs := strings.Split(strings.SplitN(key, \"[\", 2)[0], \".\")\n\tfor i, seg := range segs {\n\t\tfd := m.Descriptor().Fields().ByName(protoreflect.Name(seg))\n\t\tif fd == nil {\n\t\t\treturn false\n\t\t}\n\t\tif i == len(segs)-1 {\n\t\t\treturn true\n\t\t}\n\t\tif fd.Kind() != protoreflect.MessageKind || fd.Cardinality() == protoreflect.Repeated {\n\t\t\treturn false // cannot descend\n\t\t}\n\t\tm = m.Mutable(fd).Message()\n\t}\n\treturn true\n}","tryCatchPattern":"if err := binding.BindQuery(msg, r.URL.Query()); err != nil {\n\tif strings.Contains(err.Error(), \"is not a message\") {\n\t\treturn errors.BadRequest(\"INVALID_QUERY\", err.Error()) // surface offending key to caller\n\t}\n\treturn err\n}","preventionTips":["Publish the query-parameter contract (flat keys + map bracket syntax) generated from the proto","Never use dotted paths through repeated message fields in query strings; use POST+JSON","Add contract tests that bind representative query strings against the proto in CI","Version protos: when a message field becomes scalar, coordinate the client change in the same release"],"tags":["go","kratos","form-binding","protobuf","http","validation"],"backgroundTag":null,"analyzedSha":"668db92c2c001e9552594ba5a8aede8456af6d7e","analyzedAt":"2026-08-16T02:07:20.704Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}