use std::path::{Component, Path, PathBuf};
pub fn to_posix(path: &Path) -> String {
let mut buf = PathBuf::new();
for comp in path.components() {
match comp {
Component::CurDir => continue,
other => buf.push(other.as_os_str()),
}
}
let s = buf.to_string_lossy();
let stripped = s.strip_prefix(r"\\?\").unwrap_or(&s);
stripped.replace('\\', "/")
}
#[cfg(feature = "serde")]
pub fn serialize_path<S>(path: &PathBuf, s: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
s.serialize_str(&to_posix(path))
}
#[cfg(feature = "serde")]
pub fn serialize_path_opt<S>(path: &Option<PathBuf>, s: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match path {
Some(p) => s.serialize_str(&to_posix(p)),
None => s.serialize_none(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn drops_curdir_segments() {
let p = Path::new("a").join(".").join("b");
assert_eq!(to_posix(&p), "a/b");
}
#[test]
fn replaces_backslashes() {
let p = PathBuf::from(r"a\b\c");
assert_eq!(to_posix(&p), "a/b/c");
}
#[test]
fn strips_windows_verbatim_prefix() {
let p = PathBuf::from(r"\\?\C:\foo\bar");
let out = to_posix(&p);
assert_eq!(out, "C:/foo/bar");
}
#[test]
fn mixed_separators_with_curdir() {
let p = PathBuf::from(r"C:\Users\me\.\proj/sub");
assert_eq!(to_posix(&p), "C:/Users/me/proj/sub");
}
#[test]
fn keeps_parent_dir() {
let p = PathBuf::from("a/../b");
let out = to_posix(&p);
assert!(out.contains(".."), "ParentDir should be preserved (got {})", out);
}
#[test]
fn empty_path() {
let p = PathBuf::new();
assert_eq!(to_posix(&p), "");
}
#[cfg(feature = "serde")]
#[test]
fn serialize_path_emits_posix_string() {
#[derive(serde::Serialize)]
struct W {
#[serde(serialize_with = "super::serialize_path")]
p: PathBuf,
}
let w = W { p: PathBuf::from(r"a\b") };
let json = serde_json::to_string(&w).unwrap();
assert_eq!(json, r#"{"p":"a/b"}"#);
}
#[cfg(feature = "serde")]
#[test]
fn serialize_path_opt_some() {
#[derive(serde::Serialize)]
struct W {
#[serde(serialize_with = "super::serialize_path_opt")]
p: Option<PathBuf>,
}
let w = W { p: Some(PathBuf::from(r"a\.\b")) };
let json = serde_json::to_string(&w).unwrap();
assert_eq!(json, r#"{"p":"a/b"}"#);
}
#[cfg(feature = "serde")]
#[test]
fn serialize_path_opt_none() {
#[derive(serde::Serialize)]
struct W {
#[serde(serialize_with = "super::serialize_path_opt")]
p: Option<PathBuf>,
}
let w = W { p: None };
let json = serde_json::to_string(&w).unwrap();
assert_eq!(json, r#"{"p":null}"#);
}
}