{"record":{"id":"49091bd838284d3a","repo":"go-kratos/kratos","slug":"unsupported-message-type-q-49091b","errorCode":null,"errorMessage":"unsupported message type: %q","messagePattern":"unsupported message type: %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"encoding/form/proto_encode.go","lineNumber":168,"sourceCode":"\t\treturn marshalDuration(value.Message())\n\tcase bytesMessageFullname:\n\t\treturn marshalBytes(value.Message())\n\tcase \"google.protobuf.DoubleValue\", \"google.protobuf.FloatValue\", \"google.protobuf.Int64Value\", \"google.protobuf.Int32Value\",\n\t\t\"google.protobuf.UInt64Value\", \"google.protobuf.UInt32Value\", \"google.protobuf.BoolValue\", \"google.protobuf.StringValue\":\n\t\tfd := msgDescriptor.Fields()\n\t\tv := value.Message().Get(fd.ByName(\"value\"))\n\t\treturn fmt.Sprint(v.Interface()), nil\n\tcase fieldMaskFullName:\n\t\tm, ok := value.Message().Interface().(*fieldmaskpb.FieldMask)\n\t\tif !ok || m == nil {\n\t\t\treturn \"\", nil\n\t\t}\n\t\tfor i, v := range m.Paths {\n\t\t\tm.Paths[i] = jsonCamelCase(v)\n\t\t}\n\t\treturn strings.Join(m.Paths, \",\"), nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"unsupported message type: %q\", string(msgDescriptor.FullName()))\n\t}\n}\n\n// EncodeFieldMask return field mask name=paths\nfunc EncodeFieldMask(m protoreflect.Message) (query string) {\n\tm.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {\n\t\tif fd.Kind() == protoreflect.MessageKind {\n\t\t\tif msg := fd.Message(); msg.FullName() == fieldMaskFullName {\n\t\t\t\tvalue, err := encodeMessage(msg, v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tif fd.HasJSONName() {\n\t\t\t\t\tquery = fd.JSONName() + \"=\" + value\n\t\t\t\t} else {\n\t\t\t\t\tquery = fd.TextName() + \"=\" + value\n\t\t\t\t}\n\t\t\t\treturn false","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/go-kratos/kratos/blob/668db92c2c001e9552594ba5a8aede8456af6d7e/encoding/form/proto_encode.go#L150-L186","documentation":"Form ENcoder error (EncodeField -> encodeMessage) mirroring the decoder limit: when encoding a proto message to form/query output, a message-typed field whose full name is not one of the supported well-known types (Timestamp, Duration, BytesValue, the scalar wrappers, FieldMask) hits the default and fails. Custom message fields and unsupported WKTs (Any, Struct, Value, Empty) cannot be encoded into the flat string form.","triggerScenarios":"Calling form encoding (form.EncodeField / building query strings from a proto message, e.g. generating request URLs or form bodies from messages) where a field's type is a user-defined message like `Address address = 1` or `google.protobuf.Any data = 2`. EncodeField dispatches MessageKind to encodeMessage, whose switch has no case for that full name.","commonSituations":"Client-side request builders encoding proto request messages into query strings; Kratos HTTP clients generating query params for GET endpoints whose proto grew structured fields; test helpers serializing full messages into forms; mirroring a JSON API into GET params after adding nested types.","solutions":["Keep form-encoded messages limited to scalar fields and supported WKTs (Timestamp, Duration, wrappers, FieldMask, BytesValue)","Flatten structured data into prefixed scalar fields for query-param APIs, or move the data to a JSON body via POST","For JSON-shaped values use google.protobuf.Value/Struct on the wire - but note the ENcoder does not support them either; encode the JSON string yourself into a string field","Write a custom encoder case (or pre-encode the submessage to JSON into a string field) if the field must stay a message"],"exampleFix":"// before: message with custom message field\n// message Req { Address address = 1; }\n// form.EncodeField -> unsupported message type: \"pkg.Address\"\n\n// after: flatten for form encoding\n// message Req { string address_city = 1; string address_country = 2; }","handlingStrategy":"validation","validationCode":"// Before encoding to form, assert no unsupported message fields exist\nfunc encodableToForm(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 && !isSupportedFormMessage(fd.Message().FullName()) {\n\t\t\terr = fmt.Errorf(\"field %q of type %s cannot be form-encoded\", fd.Name(), fd.Message().FullName())\n\t\t\treturn false\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.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 q, err := form.EncodeQuery(msg); err != nil {\n\tif strings.Contains(err.Error(), \"unsupported message type\") {\n\t\t// fall back to JSON body for this call\n\t\treq.SetBodyProtoJSON(msg)\n\t\treturn nil\n\t}\n\treturn err\n}","preventionTips":["Generate request builders from proto and encode only form-safe fields into queries","Keep structured payloads in bodies (JSON), scalars in query strings","Add a lint rule that GET-endpoint protos contain no custom message fields","Unit-test form encoding of every request message type in the client SDK"],"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"}