{"record":{"id":"e50858ed21506302","repo":"hashicorp/consul","slug":"unable-to-convert-t-to-proto","errorCode":null,"errorMessage":"unable to convert %T to proto","messagePattern":"unable to convert %T to proto","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proto/private/pbconfigentry/config_entry.go","lineNumber":264,"sourceCode":"\t\t}\n\tcase *structs.JWTProviderConfigEntry:\n\t\tvar jwtProvider JWTProvider\n\t\tJWTProviderFromStructs(v, &jwtProvider)\n\n\t\tconfigEntry.Kind = Kind_KindJWTProvider\n\t\tconfigEntry.Entry = &ConfigEntry_JWTProvider{\n\t\t\tJWTProvider: &jwtProvider,\n\t\t}\n\tcase *structs.ExportedServicesConfigEntry:\n\t\tvar es ExportedServices\n\t\tExportedServicesFromStructs(v, &es)\n\n\t\tconfigEntry.Kind = Kind_KindExportedServices\n\t\tconfigEntry.Entry = &ConfigEntry_ExportedServices{\n\t\t\tExportedServices: &es,\n\t\t}\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"unable to convert %T to proto\", s))\n\t}\n\n\treturn configEntry\n}\n\nfunc tlsVersionToStructs(s string) types.TLSVersion {\n\treturn types.TLSVersion(s)\n}\n\nfunc tlsVersionFromStructs(t types.TLSVersion) string {\n\treturn t.String()\n}\n\nfunc cipherSuitesToStructs(cs []string) []types.TLSCipherSuite {\n\tcipherSuites := make([]types.TLSCipherSuite, len(cs))\n\tfor idx, suite := range cs {\n\t\tcipherSuites[idx] = types.TLSCipherSuite(suite)\n\t}","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/hashicorp/consul/blob/2397ff0d763d34f2fe37fe59fde6a7f7fc430a3e/proto/private/pbconfigentry/config_entry.go#L246-L282","documentation":"ConfigEntryFromStructs (proto/private/pbconfigentry/config_entry.go:264 region) panics when the concrete Go type of the structs.ConfigEntry interface value is not one of the handled cases (e.g. *structs.MeshConfigEntry, *structs.ServiceResolverConfigEntry, ... *structs.ExportedServicesConfigEntry). Unlike a missing enum case, this is a Go type-switch exhaustiveness problem: any wrapper/decorator implementing the interface, any newly added entry type, and a nil interface all fall through. The %T in the message tells you exactly which concrete type was rejected.","triggerScenarios":"Passing a new structs.*ConfigEntry kind that has no case in the switch; passing a wrapper or test double that implements structs.ConfigEntry around a real entry; passing a nil structs.ConfigEntry; enterprise/CE divergence where one side has types the other lacks.","commonSituations":"Consul development adding a new config entry kind without updating both converters; wrapping entries for validation/mutation and forgetting to unwrap before serialization; test helpers that build mock ConfigEntry implementations.","solutions":["Add the missing case to the type switch in ConfigEntryFromStructs (and the enum case in ConfigEntryToStructs)","Unwrap decorated/wrapped entries to their underlying concrete type before calling the converter","Nil-check the entry before conversion","Keep mixed-version clusters aligned so all binaries know the same entry types"],"exampleFix":"// before\npbEntry := pbconfigentry.ConfigEntryFromStructs(wrappedEntry) // wrapper type -> panic\n\n// after\nswitch e := wrappedEntry.(type) {\ncase *structs.ServiceDefaultsConfigEntry, *structs.MeshConfigEntry:\n\tpbEntry = pbconfigentry.ConfigEntryFromStructs(e)\ndefault:\n\treturn fmt.Errorf(\"unsupported config entry type %T\", e)\n}","handlingStrategy":"type-guard","validationCode":"func registeredConfigEntry(s structs.ConfigEntry) bool {\n\tswitch s.(type) {\n\tcase *structs.MeshConfigEntry, *structs.ServiceResolverConfigEntry, *structs.IngressGatewayConfigEntry,\n\t\t*structs.ServiceIntentionsConfigEntry, *structs.ServiceConfigEntry, *structs.APIGatewayConfigEntry,\n\t\t*structs.BoundAPIGatewayConfigEntry, *structs.TCPRouteConfigEntry, *structs.HTTPRouteConfigEntry,\n\t\t*structs.FileSystemCertificateConfigEntry, *structs.InlineCertificateConfigEntry,\n\t\t*structs.SamenessGroupConfigEntry, *structs.JWTProviderConfigEntry, *structs.ExportedServicesConfigEntry:\n\t\treturn true\n\t}\n\treturn false\n}\n\nif s == nil || !registeredConfigEntry(s) {\n\treturn fmt.Errorf(\"unsupported config entry type %T\", s)\n}\npbEntry := pbconfigentry.ConfigEntryFromStructs(s)","typeGuard":"func asRegisteredEntry(s structs.ConfigEntry) (structs.ConfigEntry, bool) {\n\tif s == nil {\n\t\treturn nil, false\n\t}\n\tif w, ok := s.(interface{ Unwrap() structs.ConfigEntry }); ok {\n\t\ts = w.Unwrap() // peel wrappers/decorators\n\t}\n\treturn s, registeredConfigEntry(s)\n}","tryCatchPattern":"defer func() {\n\tif r := recover(); r != nil {\n\t\terr = fmt.Errorf(\"config entry serialization failed for %T: %v\", entry, r)\n\t}\n}()","preventionTips":["Never pass wrapper/mock implementations of structs.ConfigEntry into proto converters; unwrap first","When adding a new entry kind, update both switch statements in proto/private/pbconfigentry/config_entry.go together","Nil-check the interface before conversion; nil also falls through to the panic","Let the %T in the panic message drive the fix: it names the exact offending type"],"tags":["go","consul","panic","type-switch","config-entry","protobuf","serialization"],"backgroundTag":null,"analyzedSha":"2397ff0d763d34f2fe37fe59fde6a7f7fc430a3e","analyzedAt":"2026-08-15T19:19:47.700Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}