{"record":{"id":"e62543a1b6106c36","repo":"cilium/cilium","slug":"both-t-transform-and-transformmany-cannot-be-set","errorCode":null,"errorMessage":"Both %T.Transform and .TransformMany cannot be set","messagePattern":"Both %T\\.Transform and \\.TransformMany cannot be set","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/k8s/statedb.go","lineNumber":373,"sourceCode":"\nfunc (cfg ReflectorConfig[Obj]) validate() error {\n\tif cfg.Name == \"\" {\n\t\treturn fmt.Errorf(\"%T.Name cannot be empty\", cfg)\n\t}\n\tif cfg.Table == nil {\n\t\treturn fmt.Errorf(\"%T.Table cannot be nil\", cfg)\n\t}\n\tif (cfg.ListerWatcher == nil) == (cfg.SharedListerWatcher == nil) {\n\t\treturn fmt.Errorf(\"Exactly one of %[1]T.ListerWatcher and %[1]T.SharedListerWatcher must be set\", cfg)\n\t}\n\tif cfg.BufferSize <= 0 {\n\t\treturn fmt.Errorf(\"%T.BufferSize (%d) must be larger than zero\", cfg, cfg.BufferSize)\n\t}\n\tif cfg.BufferWaitTime <= 0 {\n\t\treturn fmt.Errorf(\"%T.BufferWaitTime (%d) must be larger than zero\", cfg, cfg.BufferWaitTime)\n\t}\n\tif cfg.Transform != nil && cfg.TransformMany != nil {\n\t\treturn fmt.Errorf(\"Both %T.Transform and .TransformMany cannot be set\", cfg)\n\t}\n\treturn nil\n}\n\ntype k8sReflector[Obj any] struct {\n\tReflectorConfig[Obj]\n\n\tlog      *slog.Logger\n\tinitDone func(statedb.WriteTxn)\n\tdb       *statedb.DB\n\ttable    statedb.RWTable[Obj]\n\tsource   stream.Observable[CacheStoreEvent]\n}\n\nfunc (r *k8sReflector[Obj]) run(ctx context.Context, health cell.Health) error {\n\tif r.CRDSync != nil {\n\t\t// Wait for the CRD to be registered.\n\t\thealth.OK(\"Waiting for CRD registration\")","sourceCodeStart":355,"sourceCodeEnd":391,"githubUrl":"https://github.com/cilium/cilium/blob/ac7b90affa4baf0642e6685319d56907b3a73a6d/pkg/k8s/statedb.go#L355-L391","documentation":"validate() rejects a ReflectorConfig that sets both Transform and TransformMany, since the reflector cannot know which mapping to apply to each watch event. Exactly one (or neither) must be provided for RegisterReflector to succeed.","triggerScenarios":"RegisterReflector with a config where both Transform and TransformMany function fields are non-nil — typically after switching from per-object to per-batch transforms (or vice versa) without clearing the old field.","commonSituations":"Migrating to TransformMany for efficiency while leaving the legacy Transform assigned; a config builder that unconditionally sets Transform and additionally sets TransformMany for list handling.","solutions":["Keep only TransformMany and set Transform to nil (or the reverse) before calling RegisterReflector","Refactor config construction so a single transform strategy is selected via a flag/branch","Centralize transform assignment in one helper to avoid double assignment"],"exampleFix":"// before\ncfg := k8s.ReflectorConfig[Obj]{..., Transform: t, TransformMany: tm}\n// after\ncfg := k8s.ReflectorConfig[Obj]{..., TransformMany: tm} // Transform cleared","handlingStrategy":"validation","validationCode":"if cfg.Transform != nil && cfg.TransformMany != nil {\n    return fmt.Errorf(\"reflector %s: set either Transform or TransformMany, not both\", cfg.Name)\n}","typeGuard":"func hasSingleTransform[Obj statedb.Object](cfg k8s.ReflectorConfig[Obj]) bool {\n    return !(cfg.Transform != nil && cfg.TransformMany != nil)\n}","tryCatchPattern":"if err := RegisterReflector(db, cfg); err != nil {\n    if strings.Contains(err.Error(), \"Transform\") {\n        return fmt.Errorf(\"reflector %s: conflicting transform config: %w\", cfg.Name, err)\n    }\n    return err\n}","preventionTips":["Choose one transform strategy per reflector and encode it in the config builder","When switching to TransformMany, delete the Transform assignment in the same commit","Add a table-driven test covering the valid combinations of transform fields"],"tags":["kubernetes","statedb","config-validation","reflector"],"backgroundTag":"missing-required-config-field","analyzedSha":"ac7b90affa4baf0642e6685319d56907b3a73a6d","analyzedAt":"2026-08-31T18:27:15.868Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}