From ae1d105e020e865492d6022ade66ba5373a44a3d Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Fri, 10 Jul 2026 00:14:32 +0000 Subject: [PATCH] path-uri: add URI-relative path helpers --- codex-rs/utils/path-uri/src/lib.rs | 47 +++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/codex-rs/utils/path-uri/src/lib.rs b/codex-rs/utils/path-uri/src/lib.rs index f9b35023c2..5686ddea24 100644 --- a/codex-rs/utils/path-uri/src/lib.rs +++ b/codex-rs/utils/path-uri/src/lib.rs @@ -263,23 +263,50 @@ impl PathUri { if self.0.host_str() != base.0.host_str() { return false; } - let Some(path_segments) = containment_path_segments( - &self.0, - self.infer_path_convention() - .unwrap_or(PathConvention::Posix), - ) else { + let Some(convention) = self.infer_path_convention() else { return false; }; - let Some(base_segments) = containment_path_segments( - &base.0, - base.infer_path_convention() - .unwrap_or(PathConvention::Posix), - ) else { + if base.infer_path_convention() != Some(convention) { + return false; + } + + let Some(path_segments) = containment_path_segments(&self.0, convention) else { + return false; + }; + let Some(base_segments) = containment_path_segments(&base.0, convention) else { return false; }; path_segments.starts_with(&base_segments) } + /// Returns this path relative to `base` using `/` as the separator. + /// + /// Both paths are compared lexically in URI space, without consulting the + /// host filesystem. `None` is returned when the paths use different native + /// conventions or authorities, when this path is outside `base`, or when an + /// encoded native separator makes containment ambiguous. + pub fn relative_path_from(&self, base: &Self) -> Option { + if self == base { + return Some(String::new()); + } + + let convention = self.infer_path_convention()?; + if base.infer_path_convention()? != convention || self.0.host_str() != base.0.host_str() { + return None; + } + + let path_segments = containment_path_segments(&self.0, convention)?; + let base_segments = containment_path_segments(&base.0, convention)?; + let relative_segments = path_segments.strip_prefix(base_segments.as_slice())?; + Some( + relative_segments + .iter() + .map(|segment| decode_uri_path(segment)) + .collect::>() + .join("/"), + ) + } + /// Lexically resolves native absolute or relative path text against this URI. /// /// Path text is interpreted using the POSIX or Windows convention inferred