Skip to content

Commit 8689e07

Browse files
kate-shineKateřina ChuranováCopilot
authored
feat(templated_uri): add TryFrom<&str> for PathAndQuery (#464)
## Summary Re-adds a `TryFrom<&str>` implementation for `templated_uri::PathAndQuery` that was lost during prior refactorings, restoring parity with `http::uri::PathAndQuery` and giving callers a natural way to build a `PathAndQuery` from runtime string data. ` ust let pq = PathAndQuery::try_from("/api/v1/users?active=true")?; ` ## Behavior - Delegates to `http::uri::PathAndQuery::try_from` and wraps the result via the existing `From<HttpPathAndQuery> for PathAndQuery` impl. - Errors propagate through `?` as `UriError` via the existing `From<InvalidUri> for UriError` impl (label: `uri_invalid`). No extra allocation, source is preserved on `Error::source()`. - Inputs without a leading `/` are rejected. This is enforced by the upstream `http` parser, which behaves identically across its entry points (`try_from` returns `InvalidUri`; `from_static` panics with *"static str is not valid path"*), so no duplicate check is added here. ## Tests Three new unit tests in `crates/templated_uri/src/path_and_query.rs`: - `try_from_str_succeeds` — happy path with query string. - `try_from_str_invalid_errors` — invalid character (`/invalid path\0`) returns `UriError` with label `uri_invalid`. - `try_from_str_without_leading_slash_errors` — slash-less input returns `UriError` with label `uri_invalid`. ## Not included - No `TryFrom<&[u8]>` variant. The upstream `http` crate offers it to let byte-oriented network code skip a UTF-8 check, but `PathAndQuery` at this layer is composed from string templates, `&'static str`, and `Sensitive<String>` — no concrete byte-slice caller exists. Trivial to add later if one appears. - No manual `CHANGELOG.md` entry — handled at release time by `scripts/release-crate.ps1`. --------- Co-authored-by: Kateřina Churanová <katerina.churanova@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3e7cc35 commit 8689e07

1 file changed

Lines changed: 67 additions & 0 deletions

File tree

crates/templated_uri/src/path_and_query.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,40 @@ impl From<HttpPathAndQuery> for PathAndQuery {
137137
}
138138
}
139139

140+
impl TryFrom<&str> for PathAndQuery {
141+
type Error = UriError;
142+
143+
/// Parses a string into a [`PathAndQuery`].
144+
///
145+
/// The input must start with `/` (per RFC 3986 `path-abempty`); inputs without a
146+
/// leading slash are rejected to avoid inconsistent rendering downstream.
147+
///
148+
/// # Errors
149+
///
150+
/// Returns a [`UriError`] if the string does not start with `/` or is not a valid
151+
/// path-and-query.
152+
fn try_from(value: &str) -> Result<Self, Self::Error> {
153+
Ok(Self::from(HttpPathAndQuery::try_from(value)?))
154+
}
155+
}
156+
157+
impl TryFrom<String> for PathAndQuery {
158+
type Error = UriError;
159+
160+
/// Parses an owned string into a [`PathAndQuery`], reusing its buffer.
161+
///
162+
/// Prefer this over the `&str` overload when the string is already owned: the
163+
/// underlying [`http::uri::PathAndQuery`] takes the buffer without copying.
164+
///
165+
/// # Errors
166+
///
167+
/// Returns a [`UriError`] if the string does not start with `/` or is not a valid
168+
/// path-and-query.
169+
fn try_from(value: String) -> Result<Self, Self::Error> {
170+
Ok(Self::from(HttpPathAndQuery::try_from(value)?))
171+
}
172+
}
173+
140174
impl TryFrom<&PathAndQuery> for HttpPathAndQuery {
141175
type Error = UriError;
142176

@@ -220,4 +254,37 @@ mod tests {
220254
let converted_ref: HttpPathAndQuery = HttpPathAndQuery::try_from(&target_path).unwrap();
221255
assert_eq!(converted, converted_ref);
222256
}
257+
258+
#[test]
259+
fn try_from_str_succeeds() {
260+
let target_path = PathAndQuery::try_from("/api/v1/users?active=true").unwrap();
261+
assert_eq!(target_path.to_string().declassify_ref(), "/api/v1/users?active=true");
262+
}
263+
264+
#[test]
265+
fn try_from_str_invalid_errors() {
266+
use ohno::Labeled;
267+
let err = PathAndQuery::try_from("/invalid path\0").unwrap_err();
268+
assert_eq!(err.label(), "uri_invalid");
269+
}
270+
271+
#[test]
272+
fn try_from_str_without_leading_slash_errors() {
273+
use ohno::Labeled;
274+
let err = PathAndQuery::try_from("api/v1/users").unwrap_err();
275+
assert_eq!(err.label(), "uri_invalid");
276+
}
277+
278+
#[test]
279+
fn try_from_string_succeeds() {
280+
let target_path = PathAndQuery::try_from(String::from("/api/v1/users?active=true")).unwrap();
281+
assert_eq!(target_path.to_string().declassify_ref(), "/api/v1/users?active=true");
282+
}
283+
284+
#[test]
285+
fn try_from_string_invalid_errors() {
286+
use ohno::Labeled;
287+
let err = PathAndQuery::try_from(String::from("api/v1/users")).unwrap_err();
288+
assert_eq!(err.label(), "uri_invalid");
289+
}
223290
}

0 commit comments

Comments
 (0)