pub fn resolve_relative(
base: &Path,
relative: &str,
) -> Result<PathBuf, PathError>Expand description
Join a portable, /-separated relative path onto base, using the
platform path separator throughout.
Source documents and manifests spell relative paths with / (manifest convention).
Joining such a string with Path::join directly leaves the / embedded on Windows,
producing mixed paths like dir\sub/file.
This rebuilds the relative portion component-by-component so the result is
uniformly native-separated. A relative value that is absolute or rooted
passes through unchanged; on Windows a leading / is rooted but
prefix-less, which Path::is_absolute alone misses, so has_root is
checked too.
Normalization is lexical and stack-based: . and empty (a//b) segments are
dropped; a .. pops the last resolved name; a .. that escapes a
relative base is preserved as a leading .. (proj/sub +
../../../shared/x -> ../shared/x), not silently swallowed; and a .. at
the root of an absolute base is clamped (/a + ../../x -> /x),
matching the OS. Nothing here touches the filesystem, so .. is resolved
textually and ignores symlinks; identity-grade canonicalization (NFC, escape
rejection) lives in mos_cache::ProjectPath. Each /-delimited segment must
be a single portable name – manifest paths use / only, so a segment that
smuggles in a platform separator (\), a drive prefix, or an absolute root
is rejected (see Errors) rather than handed to PathBuf::push, which would
let the OS re-split it past this lexical model.
§Errors
Returns PathError::UnsafeSegment when a /-delimited segment is not a
single portable component. An absolute or rooted relative is not a
segment and still passes through unchanged.
§Examples
use std::path::Path;
use mos_core::resolve_relative;
assert_eq!(
resolve_relative(Path::new("proj"), "sub/file.txt"),
Ok(Path::new("proj").join("sub").join("file.txt")),
);
// `.` and `..` segments are normalized away within the base:
assert_eq!(
resolve_relative(Path::new("proj"), "sub/../assets/./logo.png"),
Ok(Path::new("proj").join("assets").join("logo.png")),
);
// `..` past a relative base is preserved, not eaten:
assert_eq!(
resolve_relative(Path::new("proj/sub"), "../../../shared/x"),
Ok(Path::new("..").join("shared").join("x")),
);