<div align="center">
<img src="https://raw.githubusercontent.com/microsoft/oxidizer/refs/heads/main/logo.svg" alt="Http Path Template Logo" width="96">
# Http Path Template
[](https://crates.io/crates/http_path_template)
[](https://docs.rs/http_path_template)
[](https://crates.io/crates/http_path_template)
[](https://github.com/microsoft/oxidizer/actions/workflows/main.yml)
[](https://codecov.io/gh/microsoft/oxidizer)
[](https://github.com/microsoft/oxidizer/blob/main/LICENSE)
<a href="https://github.com/microsoft/oxidizer"><img src="https://raw.githubusercontent.com/microsoft/oxidizer/refs/heads/main/logo.svg" alt="This crate was developed as part of the Oxidizer project" width="20"></a>
</div>
A parser for the [`google.api.http`][__link0] path-template grammar.
A path template is the pattern that appears in a `google.api.http`
annotation, for example `/shelves/{shelf}/books/{book=**}:archive`. This crate
turns such a string into a validated, structured [`PathTemplate`][__link1] — an
abstract syntax tree of [`Segment`][__link2]s (literals, `*`, `**`, and
`{field.path=sub-template}` [`Variable`][__link3] bindings) plus an optional custom
`:verb`.
Parsing is zero-copy: the returned [`PathTemplate`][__link4] borrows from the input
string (every literal, field name, and verb is a slice into it), so a parse
copies no text and allocates only the top-level segment list.
A template must begin with `/` and is a `/`-separated sequence of segments —
each `/` delimits one segment. Literal segments, variable field names, and the
custom `:verb` are preserved and compared **verbatim**, so the grammar is
case-sensitive; the parser performs no case folding.
The grammar mirrors the reference [`google.api.HttpRule`][__link5] path syntax:
* a **literal** segment (`shelves`) must match verbatim and contain only RFC
3986 `pchar` characters, with valid `%HH` escapes; raw `*` is reserved for
the wildcard atoms below and must be percent-encoded in a literal;
* **`*`** ([`Segment::Single`][__link6]) matches exactly one non-empty segment;
* **`**`** ([`Segment::Rest`][__link7]) matches the remaining segments and may only
appear as the final element;
* **`{field.path=sub-template}`** ([`Segment::Variable`][__link8]) captures the portion
of the path matched by its sub-template into a dotted message field; the
shorthand `{field}` is `{field=*}` and nested variables are rejected;
* a trailing **`:verb`** declares a custom method verb.
## Extended grammar
[`PathTemplate::parse`][__link9] takes a [`Grammar`][__link10] argument. The default grammar is
the strict `google.api.http` syntax above; passing a [`Grammar`][__link11] with
[`Grammar::with_segment_affixes`][__link12] enabled additionally allows **intra-segment
prefix/suffix parameters**: a single segment may wrap one `{field.path}`
variable in literal text, for example `/files/{name}.json`, `/v{version}/x`,
or `/img-{id}.png`. Such a segment parses to a [`Segment::Affix`][__link13]. The strict
grammar rejects this syntax.
## Examples
Parsing `/shelves/{shelf}/books/{book=**}:archive` yields four top-level
[`Segment`][__link14]s plus the custom verb `archive`:
* `shelves` — a [`Segment::Literal`][__link15];
* `{shelf}` — a [`Segment::Variable`][__link16] binding field `shelf` to a single
segment (`*`, i.e. [`Segment::Single`][__link17]);
* `books` — a [`Segment::Literal`][__link18];
* `{book=**}` — a [`Segment::Variable`][__link19] binding field `book` to the remaining
segments (`**`, i.e. [`Segment::Rest`][__link20]).
```rust
use http_path_template::{Grammar, PathTemplate, Segment};
let template = PathTemplate::parse(
"/shelves/{shelf}/books/{book=**}:archive",
Grammar::default(),
)?;
assert_eq!(template.segments().len(), 4);
assert_eq!(template.verb(), Some("archive"));
assert_eq!(template.segments()[0], Segment::Literal("shelves"));
assert_eq!(template.segments()[2], Segment::Literal("books"));
let Segment::Variable(shelf) = template.segments()[1] else {
panic!("expected variable")
};
assert_eq!(shelf.field_path(), "shelf");
assert!(shelf.segments().eq([Segment::Single]));
let Segment::Variable(book) = template.segments()[3] else {
panic!("expected variable")
};
assert_eq!(book.field_path(), "book");
assert!(book.segments().eq([Segment::Rest]));
```
## Crate features
* **`std`** (default) — captures a `std::backtrace::Backtrace` into a
[`ParseError`][__link21] for richer diagnostics. Disable (`default-features = false`)
for `#![no_std]` use; the crate then requires only `alloc` and captures no
backtrace. `ParseError` implements `core::error::Error` either way.
<hr/>
<sub>
This crate was developed as part of <a href="https://github.com/microsoft/oxidizer">The Oxidizer Project</a>. Browse this crate's <a href="https://github.com/microsoft/oxidizer/tree/main/crates/http_path_template">source code</a>.
</sub>
[__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjNhdIQb11VxC_uAPOQbtUn4Wx2-BfAbid3Nt1Y27Pobprn8Z6FjFy9hYvRhcoQbi6Iwi6yia9cb-_nab-IwSV0bii4s3u_Erlgbh-UNpgxvX8phZIGCcmh0dHBfcGF0aF90ZW1wbGF0ZWUwLjIuMQ
[__link0]: https://github.com/googleapis/googleapis/blob/master/google/api/http.proto
[__link1]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=PathTemplate
[__link10]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Grammar
[__link11]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Grammar
[__link12]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Grammar::with_segment_affixes
[__link13]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Segment::Affix
[__link14]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Segment
[__link15]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Segment::Literal
[__link16]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Segment::Variable
[__link17]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Segment::Single
[__link18]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Segment::Literal
[__link19]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Segment::Variable
[__link2]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Segment
[__link20]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Segment::Rest
[__link21]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=ParseError
[__link3]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Variable
[__link4]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=PathTemplate
[__link5]: https://github.com/googleapis/googleapis/blob/master/google/api/http.proto
[__link6]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Segment::Single
[__link7]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Segment::Rest
[__link8]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=Segment::Variable
[__link9]: https://docs.rs/http_path_template/0.2.1/http_path_template/?search=PathTemplate::parse