{"record":{"id":"c896710ce857e1e1","repo":"libnyanpasu/clash-nyanpasu","slug":"destination-path-has-no-file-name","errorCode":null,"errorMessage":"destination path has no file name: {}","messagePattern":"destination path has no file name: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/core/migration/fs.rs","lineNumber":21,"sourceCode":"//! Wraps [`atomicwrites`] (already a workspace dependency via `nyanpasu-core`)\n//! behind a single helper so the migration store and every config rewrite share\n//! one durable write path. Keeping the third-party type in one place means a\n//! future swap only touches this file.\n\nuse anyhow::{Context, ensure};\nuse atomicwrites::{AllowOverwrite, AtomicFile};\nuse std::{io::Write, path::Path};\n\n/// Atomically write `contents` to `path`.\n///\n/// The destination is never left half-written: `atomicwrites` writes the bytes\n/// into a temp file under a randomized `.atomicwrite` subdirectory of the\n/// target's parent, fsyncs it, then atomically replaces `path`. On Unix it also\n/// fsyncs the parent directories so the rename survives a crash; on Windows it\n/// replaces via `MoveFileExW` with write-through semantics. Missing parent\n/// directories are created first.\npub(crate) fn atomic_write(path: &Path, contents: &[u8]) -> anyhow::Result<()> {\n    ensure!(\n        path.file_name().is_some(),\n        \"destination path has no file name: {}\",\n        path.display()\n    );\n    if let Some(parent) = path\n        .parent()\n        .filter(|parent| !parent.as_os_str().is_empty())\n    {\n        std::fs::create_dir_all(parent)\n            .with_context(|| format!(\"failed to create dir {}\", parent.display()))?;\n    }\n    AtomicFile::new(path, AllowOverwrite)\n        .write(|file| file.write_all(contents))\n        .with_context(|| format!(\"failed to atomically write {}\", path.display()))?;\n    Ok(())\n}\n","sourceCodeStart":3,"sourceCodeEnd":38,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/core/migration/fs.rs#L3-L38","documentation":"`atomic_write` performs a crash-safe write by creating a temp file in a `.atomicwrite` directory under the target's parent and renaming it onto `path`. A rename/replace target must be an actual file name, so the guard rejects any path whose `file_name()` is None. This happens for paths that end in a separator, are a filesystem root, or are otherwise just a directory path.","triggerScenarios":"Calling `fs::atomic_write(path, contents)` with a path such as `/`, `C:\\`, `some/dir/` (trailing slash), or any `Path` built without a final file component — `path.file_name()` returns `None` and the `ensure!` fires with the formatted path.","commonSituations":"Passing a directory instead of a file path; concatenating paths so a trailing separator survives (e.g. from user input or an env var); resolving a config path to its root/parent by mistake.","solutions":["Append the intended file name to the target path before calling `atomic_write`","Strip trailing separators from user- or env-supplied paths (use `PathBuf::set_extension` or push the file name explicitly)","Check `path.file_name().is_some()` at the call site and log the full path to find where the path lost its last component"],"exampleFix":"// before\nlet dir = config_dir(); // e.g. \"/home/u/.config/nyanpasu/\"\natomic_write(Path::new(&dir), &bytes)?;\n// after\nlet path = config_dir().join(\"verge.yaml\");\natomic_write(&path, &bytes)?;","handlingStrategy":"validation","validationCode":"fn ensure_writable_file_path(path: &std::path::Path) -> anyhow::Result<()> {\n    anyhow::ensure!(\n        path.file_name().is_some(),\n        \"atomic_write needs a file path, got: {}\",\n        path.display()\n    );\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always build target paths with `dir.join(\"file.yaml\")`, never by string concatenation that can leave a trailing separator","Strip trailing '/' or '\\\\' from user/env-supplied paths before constructing PathBuf","Keep a helper that normalizes destination paths once instead of ad-hoc path math at call sites","Assert `file_name().is_some()` in tests for every path that reaches `atomic_write`"],"tags":["rust","filesystem","atomic-write","path"],"backgroundTag":"invalid-argument-value","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}