{"record":{"id":"ec3951fa6be69e16","repo":"go-kratos/kratos","slug":"unsupported-message-type-q","errorCode":null,"errorMessage":"unsupported message type: %q","messagePattern":"unsupported message type: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"encoding/form/proto_decode.go","lineNumber":328,"sourceCode":"\t\tfm := &fieldmaskpb.FieldMask{}\n\t\tfor _, fv := range strings.Split(value, \",\") {\n\t\t\tfm.Paths = append(fm.Paths, jsonSnakeCase(fv))\n\t\t}\n\t\tmsg = fm\n\tcase \"google.protobuf.Value\":\n\t\tfm, err := structpb.NewValue(value)\n\t\tif err != nil {\n\t\t\treturn protoreflect.Value{}, err\n\t\t}\n\t\tmsg = fm\n\tcase \"google.protobuf.Struct\":\n\t\tvar v structpb.Struct\n\t\tif err := protojson.Unmarshal([]byte(value), &v); err != nil {\n\t\t\treturn protoreflect.Value{}, err\n\t\t}\n\t\tmsg = &v\n\tdefault:\n\t\treturn protoreflect.Value{}, fmt.Errorf(\"unsupported message type: %q\", string(md.FullName()))\n\t}\n\treturn protoreflect.ValueOfMessage(msg.ProtoReflect()), nil\n}\n\n// jsonSnakeCase converts a camelCase identifier to a snake_case identifier,\n// according to the protobuf JSON specification.\n// references: https://github.com/protocolbuffers/protobuf-go/blob/master/encoding/protojson/well_known_types.go#L864\nfunc jsonSnakeCase(s string) string {\n\tvar builder strings.Builder\n\tbuilder.Grow(len(s))\n\n\tfor i := 0; i < len(s); i++ { // proto identifiers are always ASCII\n\t\tc := s[i]\n\t\tif isASCIIUpper(c) {\n\t\t\tbuilder.WriteByte('_')\n\t\t\tc += 'a' - 'A' // convert to lowercase\n\t\t}\n\t\tbuilder.WriteByte(c)","sourceCodeStart":310,"sourceCodeEnd":346,"githubUrl":"https://github.com/go-kratos/kratos/blob/668db92c2c001e9552594ba5a8aede8456af6d7e/encoding/form/proto_decode.go#L310-L346","documentation":"Form DEcoder error for message-typed form fields: the field's message type full name is not in the supported well-known-type switch (Timestamp, Duration, the eight wrapper types, BytesValue, FieldMask, Value, Struct). Any other message type used as a singular form/query field hits the default and is rejected, because the codec can only render a message as a flat string for these special types.","triggerScenarios":"Binding a query param into a field typed as a custom message (e.g. `?address=...` where address is your own proto message), or a WKT not in the switch such as google.protobuf.Any, ListValue, google.protobuf.Empty; deeply nested messages beyond the WKT set are equally unsupported in the value position.","commonSituations":"Proto evolution turning a scalar into a structured message while the HTTP API still uses form/query binding; trying to reuse a JSON-oriented API shape over GET query params; frontend expecting nested JSON in a query value.","solutions":["Restructure the proto for form-bound endpoints: use scalars or the supported WKTs (wrappers, Timestamp, Duration, FieldMask, Value, Struct) for query-encoded fields","Flatten nested messages into prefixed scalar fields (address_city=...) or switch the endpoint to POST with the JSON codec for structured data","For arbitrary JSON-in-query, type the field as google.protobuf.Value or Struct, which ARE supported and accept JSON payloads","If a specific custom message must be supported, contribute a case to parseMessage or pre-decode the string yourself before binding"],"exampleFix":"// before: message Address { string city = 1; } + field Address address = 1;\n//         GET /x?address=Berlin -> unsupported message type: \"pkg.Address\"\n// after: flatten scalars for query binding\n//         string address_city = 1;   GET /x?address_city=Berlin","handlingStrategy":"validation","validationCode":"// Ensure every message-typed field reachable from the form schema is a supported WKT\nvar formSupported = map[string]bool{\n\t\"google.protobuf.Timestamp\": true, \"google.protobuf.Duration\": true,\n\t\"google.protobuf.BytesValue\": true, \"google.protobuf.FieldMask\": true,\n\t\"google.protobuf.Value\": true, \"google.protobuf.Struct\": true,\n}\nfunc formBindable(msg protoreflect.Message) error {\n\tvar err error\n\tmsg.Descriptor().Fields().Range(func(fd protoreflect.FieldDescriptor) bool {\n\t\tif fd.Kind() == protoreflect.MessageKind {\n\t\t\tname := string(fd.Message().FullName())\n\t\t\tif !formSupported[name] && !strings.HasPrefix(name, \"google.protobuf.\") {\n\t\t\t\terr = fmt.Errorf(\"custom message field %q cannot be form-bound\", fd.Name())\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\treturn err\n}","typeGuard":"func isSupportedFormMessage(fullName protoreflect.FullName) bool {\n\tswitch fullName {\n\tcase \"google.protobuf.Timestamp\", \"google.protobuf.Duration\",\n\t\t\"google.protobuf.BytesValue\", \"google.protobuf.FieldMask\",\n\t\t\"google.protobuf.Value\", \"google.protobuf.Struct\",\n\t\t\"google.protobuf.DoubleValue\", \"google.protobuf.FloatValue\",\n\t\t\"google.protobuf.Int64Value\", \"google.protobuf.Int32Value\",\n\t\t\"google.protobuf.UInt64Value\", \"google.protobuf.UInt32Value\",\n\t\t\"google.protobuf.BoolValue\", \"google.protobuf.StringValue\":\n\t\treturn true\n\t}\n\treturn false\n}","tryCatchPattern":"if err := binding.BindQuery(msg, q); err != nil {\n\tif strings.Contains(err.Error(), \"unsupported message type\") {\n\t\t// API design issue: restructure proto or switch endpoint to JSON body\n\t\treturn errors.BadRequest(\"UNSUPPORTED_FIELD_TYPE\", err.Error())\n\t}\n}","preventionTips":["Design query-bound protos with scalars and supported WKTs only","Use POST + protojson codec for structured/nested request bodies","Run a schema lint rule rejecting custom message fields on GET endpoints","Document that form binding flattens messages; reserve custom messages for JSON APIs"],"tags":["go","kratos","form-binding","protobuf","well-known-types","encode-decode"],"backgroundTag":null,"analyzedSha":"668db92c2c001e9552594ba5a8aede8456af6d7e","analyzedAt":"2026-08-16T02:07:20.704Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}