{"record":{"id":"7bb9c73b98921ff5","repo":"JuliusBrussee/caveman","slug":"cacheengine-driver-needs-non-empty-provider-and-i","errorCode":null,"errorMessage":"cacheengine: driver needs non-empty provider and implementation","messagePattern":"cacheengine: driver needs non-empty provider and implementation","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cacheengine/engine.go","lineNumber":88,"sourceCode":"\t\tmaxShards = 64\n\t}\n\tmaxRequestBytes := config.MaxRequestBytes\n\tif maxRequestBytes == 0 {\n\t\tmaxRequestBytes = defaultInputByteLimit\n\t}\n\tmaxStablePrefixBytes := config.MaxStablePrefixBytes\n\tif maxStablePrefixBytes == 0 {\n\t\tmaxStablePrefixBytes = defaultInputByteLimit\n\t}\n\tresolver := config.ResolveProfile\n\tif resolver == nil {\n\t\tresolver = defaultProfile\n\t}\n\tdrivers := make(map[string]Driver, len(config.Drivers))\n\tfor rawProvider, driver := range config.Drivers {\n\t\tprovider := strings.ToLower(strings.TrimSpace(rawProvider))\n\t\tif provider == \"\" || driver == nil {\n\t\t\treturn nil, errors.New(\"cacheengine: driver needs non-empty provider and implementation\")\n\t\t}\n\t\tif _, exists := drivers[provider]; exists {\n\t\t\treturn nil, fmt.Errorf(\"cacheengine: duplicate normalized driver provider %q\", provider)\n\t\t}\n\t\tdrivers[provider] = driver\n\t}\n\treturn &Engine{\n\t\tguard: cacheguard.New(), prefixSafety: newPrefixSafetyCache(8192),\n\t\tmaxKeyShards: maxShards, maxRequestBytes: maxRequestBytes, maxStablePrefixBytes: maxStablePrefixBytes,\n\t\tresolveProfile: resolver, drivers: drivers,\n\t}, nil\n}\n\n// Plan selects profitable stable-prefix cache boundaries without editing wire bytes.\nfunc (e *Engine) Plan(request PlanRequest) (Plan, error) {\n\tif e == nil || e.guard == nil || e.prefixSafety == nil {\n\t\treturn Plan{}, errors.New(\"cacheengine: nil engine\")\n\t}","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/cacheengine/engine.go#L70-L106","documentation":"Returned by cacheengine.NewEngine when the config.Drivers map contains an entry whose key trims/lower-cases to an empty provider string or whose Driver value is nil. Provider names are normalized (TrimSpace + ToLower) before this check, so a key of \"  \" fails just like \"\". Each provider must map to a real Driver implementation before the Engine is constructed.","triggerScenarios":"Building Config{Drivers: map[string]Driver{\"\": drv}} or including a nil Driver value (e.g. a typed-nil from a constructor that returned nil on its own error). Constructing the map from external config where a blank provider key sneaks in.","commonSituations":"Registering drivers in a loop where a factory returns nil for unsupported providers and the nil is stored anyway; config-driven provider lists with an empty entry; trimmed whitespace keys like \" OpenAI \" are fine (they normalize) but fully-blank keys are not.","solutions":["Ensure every Drivers map key is a non-blank provider name and every value is a non-nil Driver","Check factory errors before inserting: if constructor returns nil driver with err, skip/abort instead of storing nil","Filter empty keys when building the map from external configuration","Watch for typed-nil interfaces: a (*myDriver)(nil) stored in a Driver interface is non-nil to ==nil only if the concrete type is nil pointer stored directly — store only successfully constructed values"],"exampleFix":"// before\nif drv, err := newDriver(p); err != nil {\n    log.Println(err) // but still stores nil below in some path\n}\ndrivers[p] = drv\n\n// after\nif drv, err := newDriver(p); err != nil {\n    return fmt.Errorf(\"provider %s: %w\", p, err)\n}\nif strings.TrimSpace(p) == \"\" || drv == nil {\n    return errors.New(\"empty provider or nil driver\")\n}\ndrivers[p] = drv","handlingStrategy":"validation","validationCode":"func validDrivers(drivers map[string]cacheengine.Driver) error {\n\tfor name, d := range drivers {\n\t\tif strings.TrimSpace(strings.ToLower(name)) == \"\" {\n\t\t\treturn fmt.Errorf(\"blank provider name %q\", name)\n\t\t}\n\t\tif d == nil {\n\t\t\treturn fmt.Errorf(\"nil driver for provider %q\", name)\n\t\t}\n\t}\n\treturn nil\n}","typeGuard":"func hasDrivers(cfg cacheengine.Config) bool {\n\tfor name, d := range cfg.Drivers {\n\t\tif strings.TrimSpace(name) != \"\" && d != nil {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}","tryCatchPattern":"engine, err := cacheengine.NewEngine(cfg)\nif err != nil {\n\tif strings.Contains(err.Error(), \"driver needs non-empty provider\") {\n\t\treturn fmt.Errorf(\"driver registry misconfigured: %+v\", cfg.Drivers)\n\t}\n\treturn err\n}","preventionTips":["Never store a driver when its constructor returned an error; fail the registration loop","Beware typed-nil interfaces: only insert values from successful constructor calls"],"tags":["go","configuration","nil-safety","constructor"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}