pub struct ResolvedPath<'a>(/* private fields */);Expand description
A request path that has already been fully resolved and checked.
§What the value guarantees
Every value of this type was produced by ResolvedPath::from_checked, which
is the only constructor. So, for any ResolvedPath an implementor receives:
- Every
${var}template-literal interpolation (LAYER 1) and every{key}placeholder (LAYER 2) has already been substituted. There is nothing left to resolve and an implementor must not attempt its own placeholder resolution. - Each substituted contribution passed
validate_path_placeholderwhere it was produced. - The COMPOSED string passed
validate_resolved_path— so it carries no parent-directory sequence, no residual{/}, no#, no backslash, no ASCII control byte, no over-cap segment and no empty interior segment, on EITHER side of an author-written?. None of those is visible to a per-value check, because a composition belongs to no single value. - At most one
?, and only one an author wrote into aPathPart::Literal. SeeResolvedPath::from_checkedfor why that single exemption is safe. - The
bodypassed alongside has already had the path-consumed keys removed.
§What the value does NOT guarantee
It does not prove that a spec-declared narrowing (an OpenAPI pattern or
maxLength) was applied — that depends on the implementor’s
HttpExecutor::placeholder_rules override, and an implementor that keeps the
default still gets the unconditional floor and the always-on cap but no
narrowing. It is also a migration marker as much as a proof carrier: its
job is to make a stale implementor written against the old &str parameter
fail to COMPILE rather than silently receive already-resolved data and
double-resolve it (Phase 128, D-09).
§Constructor shape
There is deliberately no new and no new_unchecked. A public unchecked
constructor whose rustdoc claims an invariant is exactly the
documented-but-absent class Phase 128 exists to correct, so the check lives
inside the one constructor and the type cannot be forged from outside.
Implementations§
Source§impl<'a> ResolvedPath<'a>
impl<'a> ResolvedPath<'a>
Sourcepub fn from_checked(path: &'a str) -> Result<Self, PlaceholderRefusal>
pub fn from_checked(path: &'a str) -> Result<Self, PlaceholderRefusal>
Check path as a composed request path and wrap it on success.
This is the only constructor: it runs
validate_resolved_target, so the
invariant documented on the type is established here rather than
asserted.
§The one narrowing: an author-written ? is permitted
validate_resolved_path refuses a query separator ANYWHERE, and core keeps
that strict rule — it is a general-purpose composed-path checker and other
callers want it. Its core SIBLING validate_resolved_target — which this
constructor calls, and which the curated surface
(pmcp_server_toolkit::http::HttpClient::check_composed_path) calls too, so
the two cannot drift — splits at the FIRST ? and applies the full rule set
to each side, which exempts exactly that one separator and nothing else.
Why that is safe rather than a hole. Both per-value floors already refuse
? in a substituted value, in literal AND percent-encoded form, with a
decode-once pass so %253F-style regress cannot slip through. So a ?
surviving into the composed string can only have come from a
PathPart::Literal — script text the operator authored and shipped, not
caller data. The asymmetry with traversal is the whole point: refusing ..
from a literal catches a traversal bug, while refusing ? from a literal
rejects legitimate authoring. Same rule, different work.
What the split does NOT relax, because a narrowing must not become a hole:
- Traversal, control bytes, backslash,
#, residual{/}, over-cap segments and empty interior segments are checked on both sides. So/a/../b?x=1is still refused for the traversal, and/a?x=%00is still refused for the control byte. - A SECOND
?is still refused: only the first is split off, so the query portion is checked by the unmodified rule, which denies?. - An empty query portion is still refused — a dangling
/x?is a doubled or trailing separator, which is the same class as a trailing/. - A
?reaching the composed string from a VALUE never gets here: the per-value floor has already refused it.
One inherited conservatism, stated so it is not a surprise: %25 is
refused outright (it is what bounds the decode to a single pass), so a query
carrying a percent-encoded percent sign is refused. That is unchanged from
the path portion’s long-standing behaviour, not new here.
§Errors
Returns the PlaceholderRefusal from
validate_resolved_target. The refusal is value-free: it names the rule and
the declared expectation, never any byte of the path it refused.
Trait Implementations§
Source§impl<'a> Clone for ResolvedPath<'a>
impl<'a> Clone for ResolvedPath<'a>
impl<'a> Copy for ResolvedPath<'a>
Source§impl<'a> Debug for ResolvedPath<'a>
impl<'a> Debug for ResolvedPath<'a>
Source§impl Display for ResolvedPath<'_>
impl Display for ResolvedPath<'_>
impl<'a> Eq for ResolvedPath<'a>
Source§impl<'a> PartialEq for ResolvedPath<'a>
impl<'a> PartialEq for ResolvedPath<'a>
impl<'a> StructuralPartialEq for ResolvedPath<'a>
Auto Trait Implementations§
impl<'a> Freeze for ResolvedPath<'a>
impl<'a> RefUnwindSafe for ResolvedPath<'a>
impl<'a> Send for ResolvedPath<'a>
impl<'a> Sync for ResolvedPath<'a>
impl<'a> Unpin for ResolvedPath<'a>
impl<'a> UnsafeUnpin for ResolvedPath<'a>
impl<'a> UnwindSafe for ResolvedPath<'a>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more