Skip to content

Conversation

@Eyad3skr
Copy link
Contributor

Summary

This PR fixes #22420

Asset path resolution no longer relies on PathBuf / Path for joining and normalization.
It now uses URL-style resolver that operates only on /-split path segments, ensuring identical behavior across platforms and avoiding filesystem-specific semantics.

AssetPath is not a filesystem path. Using PathBuf introduced platform-dependent behavior like Windows drive letters and backslashes.
This change makes resolution predictable, testable, and explicit.

How it works

1. Splitting (only /)

  • split_asset_path_segments(path: &str) -> (is_rooted, segments)
  • Splits only on /
  • :, \, and // are not special

Examples:

  • a/b -> (false, ["a", "b"])
  • /a/b -> (true, ["a", "b"])
  • C:file -> (false, ["C:file"])
  • a\b -> (false, ["a\\b"])
  • a//b -> (false, ["a", "", "b"])

2. Normalization (. and ..)

  • normalize_asset_path_segments(segments, is_rooted)
  • . removed
  • .. pops previous segment when possible, otherwise preserved
  • Rooted behavior preserved:
    • /a/b + ../c → /a/c
    • /a + ../b → /b

3. Joining & resolving

  • join_and_normalize_asset_path(...)
  • Handles:
    • base + relative
    • resolve_embed replace semantics
    • trailing slash behavior
    • rooted relative ignores base
    • explicit source ignores base
  • Single normalization pass after joining
  • No extra leading / added (existing behavior preserved)

4. resolve_from_parts

  • Uses the new helpers for all logic
  • Only remaining PathBuf usage is PathBuf::from(resolved) at the end
  • Removed from resolve path:
    • PathBuf::from(self.path())
    • PathBuf::push
    • Path::strip_prefix
    • normalize_path

What stays the same

  • normalize_path unchanged
  • All existing semantics preserved:
    • resolve, resolve_embed
    • label-only
    • rooted relative
    • explicit source
    • trailing slash
    • .. underflow
  • AssetPath still stores a Path; only computation changed

New internal helpers

  • split_asset_path_segments(path_str: &str) -> (bool, Vec<&str>)
  • normalize_asset_path_segments(segments: &[&str], is_rooted: bool) -> Vec<String>
  • join_and_normalize_asset_path(...) -> String

Tests

Unit

  • split_asset_path_segments: a/b, /a/b, C:file, a\b, a//b
  • normalize_asset_path_segments: ., .., underflow, rooted
  • join_and_normalize_asset_path: replace, trailing slash, rooted

Regression

  • Colon preserved: C:file, a:ba/b/C:file, a/b/a:b
  • Backslash preserved: x\y, x\y/za/b/x\y, a/b/x\y/z
  • Rooted + ..:
    • /a/b + ../c → /a/c
    • /a + ../b → /b
  • Multiple / preserved: a//b → x/a//b

Check

  • cargo test -p bevy_asset (incl. doctests) passes

Use a URL-style resolver (split on /, normalize . and ..) so resolve
and resolve_embed are platform-independent. Stops PathBuf from applying
filesystem rules (e.g. drive letters, backslash). Add tests for :, \\,
rooted .., and a//b.
Tidy docs so identifiers and function names use backticks for
doc_markdown. Gate normalize_path on the file_watcher feature so it
is only built when that feature is enabled and is no longer reported
as dead when it is off, avoiding allow(dead_code).
@IQuick143 IQuick143 added C-Bug An unexpected or incorrect behavior A-Assets Load files from disk to use for things like images, models, and sounds S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jan 19, 2026
Copy link
Contributor

@andriyDev andriyDev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine, and gets us one step closer to AssetPath storing String instead of PathBuf.

Comment on lines 1339 to 1345
/*
"a/b" -> (false, ["a", "b"])
"/a/b" -> (true, ["a", "b"])
"C:file" -> (false, ["C:file"]) (no split on :)
"a\\b" -> (false, ["a\\b"]) (no split on \)
"a//b" -> ["a","","b"]
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems redundant with the test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kinda yes, it was a blank comment I made to know what I wanna do myself and forgot to remove it. Sorry about that.

Comment on lines 683 to 687
/*
function that splits only on /, and returns (bool, Vec<&str>):
bool = path starts with /
Vec<&str> = segments between / (define once whether you keep or drop "" from // or trailing / and stick to it)
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this a proper doc-comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

totally agree, on it

@Eyad3skr
Copy link
Contributor Author

Since there is no other modifications I guess it is ready to be merged right? or there is a remaining review? @andriyDev

@andriyDev
Copy link
Contributor

This still needs a second review. Anything not trivial needs two community reviews and then a final review from a maintainer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Assets Load files from disk to use for things like images, models, and sounds C-Bug An unexpected or incorrect behavior S-Needs-Review Needs reviewer attention (from anyone!) to move forward

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace PathBuf-based resolution in AssetPath::resolve with URL-like segment resolver

3 participants