{"record":{"id":"3b79947dceee9172","repo":"tonhowtf/omniget","slug":"external-data-cache-namespace-must-not-be-empty","errorCode":null,"errorMessage":"external_data_cache: namespace must not be empty","messagePattern":"external_data_cache: namespace must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/plugin_host.rs","lineNumber":118,"sourceCode":"        if managed_path.exists() {\n            return Some(managed_path);\n        }\n\n        which::which(&bin_name).ok()\n    }\n\n    fn default_output_dir(&self) -> PathBuf {\n        dirs::download_dir()\n            .or_else(dirs::home_dir)\n            .unwrap_or_else(|| PathBuf::from(\".\"))\n    }\n\n    fn external_data_cache(&self, plugin_id: &str, namespace: &str) -> anyhow::Result<PathBuf> {\n        if plugin_id.is_empty() {\n            anyhow::bail!(\"external_data_cache: plugin_id must not be empty\");\n        }\n        if namespace.is_empty() {\n            anyhow::bail!(\"external_data_cache: namespace must not be empty\");\n        }\n        if plugin_id.contains(['/', '\\\\', ':', '\\0']) || namespace.contains(['/', '\\\\', ':', '\\0'])\n        {\n            anyhow::bail!(\n                \"external_data_cache: plugin_id/namespace must not contain path separators or null bytes\"\n            );\n        }\n\n        // portable installs keep every file next to the app, so the cache\n        // lives under the app data dir instead of the OS cache dir\n        let base = if std::env::var(\"OMNIGET_PORTABLE\").is_ok() {\n            omniget_core::core::paths::app_data_dir()\n                .ok_or_else(|| anyhow::anyhow!(\"external_data_cache: app data dir unavailable\"))?\n                .join(\"cache\")\n        } else {\n            dirs::cache_dir()\n                .ok_or_else(|| {\n                    anyhow::anyhow!(","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/src/plugin_host.rs#L100-L136","documentation":"`external_data_cache` requires a non-empty `namespace` to build a per-namespace cache subdirectory; an empty namespace would map all plugins' cache files into one directory. It bails via anyhow before touching the filesystem.","triggerScenarios":"Calling external_data_cache with `namespace: \"\"`, e.g. a hardcoded empty string, a config key that resolved to empty, or an optional namespace field left unset.","commonSituations":"Plugin author forgets to set the namespace constant; data read from a JSON/IPC message has an empty namespace field; refactoring removed the default namespace value.","solutions":["Provide a meaningful namespace constant when calling the cache API","Read namespace from plugin config and validate non-empty before use","Reject plugins whose cache calls use empty namespaces at registration"],"exampleFix":"// before\nhost.external_data_cache(id, \"\")?;\n// after\nlet ns = config.namespace.as_deref().unwrap_or(\"default\");\nensure!(!ns.is_empty(), \"namespace required\");\nhost.external_data_cache(id, ns)?;","handlingStrategy":"validation","validationCode":"if namespace.is_empty() { return Err(anyhow!(\"namespace required before cache access\")); }","typeGuard":"fn valid_namespace(ns: &str) -> bool { !ns.is_empty() && !ns.contains(['/', '\\\\', ':', '\\0']) }","tryCatchPattern":"let cache = host.external_data_cache(id, ns)\n    .with_context(|| format!(\"cache namespace {ns:?}\"))?;","preventionTips":["Define a non-empty default namespace constant per plugin","Validate config-provided namespaces","Reject plugins emitting empty namespaces"],"tags":["rust","anyhow","plugin","validation"],"backgroundTag":"empty-required-field","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}