Macro rocket_codegen::uri

source ·
uri!() { /* proc-macro */ }
Expand description

Type-safe, encoding-safe route and non-route URI generation.

The uri! macro creates type-safe, URL-safe URIs given a route and concrete parameters for its URI or a URI string literal.

String Literal Parsing

Given a string literal as input, uri! parses the string using [Uri::parse_any()] and emits a 'static, const value whose type is one of Asterisk, Origin, Authority, Absolute, or Reference, reflecting the parsed value. If the type allows normalization, the value is normalized before being emitted. Parse errors are caught and emitted at compile-time.

The grammar for this variant of uri! is:

uri := STRING

STRING := an uncooked string literal, as defined by Rust (example: `"/hi"`)

STRING is expected to be an undecoded URI of any variant.

Examples

use rocket::http::uri::Absolute;

// Values returned from `uri!` are `const` and `'static`.
const ROOT_CONST: Absolute<'static> = uri!("https://rocket.rs");
static ROOT_STATIC: Absolute<'static> = uri!("https://rocket.rs?root");

// Any variant can be parsed, but beware of ambiguities.
let asterisk = uri!("*");
let origin = uri!("/foo/bar/baz");
let authority = uri!("rocket.rs:443");
let absolute = uri!("https://rocket.rs:443");
let reference = uri!("foo?bar#baz");

Type-Safe Route URIs

A URI to a route name foo is generated using uri!(foo(v1, v2, v3)) or uri!(foo(a = v1, b = v2, c = v3)), where v1, v2, v3 are the values to fill in for route parameters named a, b, and c. If the named parameter syntax is used (a = v1, etc.), parameters can appear in any order.

More concretely, for the route person defined below:

#[get("/person/<name>?<age>")]
fn person(name: &str, age: Option<u8>) { }

…a URI can be created as follows:

// with unnamed parameters, in route path declaration order
let mike = uri!(person("Mike Smith", Some(28)));
assert_eq!(mike.to_string(), "/person/Mike%20Smith?age=28");

// with named parameters, order irrelevant
let mike = uri!(person(name = "Mike", age = Some(28)));
let mike = uri!(person(age = Some(28), name = "Mike"));
assert_eq!(mike.to_string(), "/person/Mike?age=28");

// with unnamed values, explicitly `None`.
let mike = uri!(person("Mike", None::<u8>));
assert_eq!(mike.to_string(), "/person/Mike");

// with named values, explicitly `None`
let option: Option<u8> = None;
let mike = uri!(person(name = "Mike", age = None::<u8>));
assert_eq!(mike.to_string(), "/person/Mike");

For optional query parameters, those of type Option or Result, a _ can be used in-place of None or Err:

// with named values ignored
let mike = uri!(person(name = "Mike", age = _));
assert_eq!(mike.to_string(), "/person/Mike");

// with named values ignored
let mike = uri!(person(age = _, name = "Mike"));
assert_eq!(mike.to_string(), "/person/Mike");

// with unnamed values ignored
let mike = uri!(person("Mike", _));
assert_eq!(mike.to_string(), "/person/Mike");

It is a type error to attempt to ignore query parameters that are neither Option or Result. Path parameters can never be ignored. A path parameter of type Option<T> or Result<T, E> must be filled by a value that can target a type of T:

#[get("/person/<name>")]
fn maybe(name: Option<&str>) { }

let bob1 = uri!(maybe(name = "Bob"));
let bob2 = uri!(maybe("Bob Smith"));
assert_eq!(bob1.to_string(), "/person/Bob");
assert_eq!(bob2.to_string(), "/person/Bob%20Smith");

#[get("/person/<age>")]
fn ok(age: Result<u8, &str>) { }

let kid1 = uri!(ok(age = 10));
let kid2 = uri!(ok(12));
assert_eq!(kid1.to_string(), "/person/10");
assert_eq!(kid2.to_string(), "/person/12");

Values for ignored route segments can be of any type as long as the type implements UriDisplay for the appropriate URI part. If a route URI contains ignored segments, the route URI invocation cannot use named arguments.

#[get("/ignore/<_>/<other>")]
fn ignore(other: &str) { }

let bob = uri!(ignore("Bob Hope", "hello"));
let life = uri!(ignore(42, "cat&dog"));
assert_eq!(bob.to_string(), "/ignore/Bob%20Hope/hello");
assert_eq!(life.to_string(), "/ignore/42/cat%26dog");

Prefixes and Suffixes

A route URI can be be optionally prefixed and/or suffixed by a URI generated from a string literal or an arbitrary expression. This takes the form uri!(prefix, foo(v1, v2, v3), suffix), where both prefix and suffix are optional, and either prefix or suffix may be _ to specify the value as empty.

#[get("/person/<name>?<age>")]
fn person(name: &str, age: Option<u8>) { }

// with a specific mount-point of `/api`.
let bob = uri!("/api", person("Bob", Some(28)));
assert_eq!(bob.to_string(), "/api/person/Bob?age=28");

// with an absolute URI as a prefix
let bob = uri!("https://rocket.rs", person("Bob", Some(28)));
assert_eq!(bob.to_string(), "https://rocket.rs/person/Bob?age=28");

// with another absolute URI as a prefix
let bob = uri!("https://rocket.rs/foo", person("Bob", Some(28)));
assert_eq!(bob.to_string(), "https://rocket.rs/foo/person/Bob?age=28");

// with an expression as a prefix
let host = uri!("http://bob.me");
let bob = uri!(host, person("Bob", Some(28)));
assert_eq!(bob.to_string(), "http://bob.me/person/Bob?age=28");

// with a suffix but no prefix
let bob = uri!(_, person("Bob", Some(28)), "#baz");
assert_eq!(bob.to_string(), "/person/Bob?age=28#baz");

// with both a prefix and suffix
let bob = uri!("https://rocket.rs/", person("Bob", Some(28)), "#woo");
assert_eq!(bob.to_string(), "https://rocket.rs/person/Bob?age=28#woo");

// with an expression suffix. if the route URI already has a query, the
// query part is ignored. otherwise it is added.
let suffix = uri!("?woo#bam");
let bob = uri!(_, person("Bob", Some(28)), suffix.clone());
assert_eq!(bob.to_string(), "/person/Bob?age=28#bam");

let bob = uri!(_, person("Bob", None::<u8>), suffix.clone());
assert_eq!(bob.to_string(), "/person/Bob?woo#bam");

Grammar

The grammar for this variant of the uri! macro is:

uri := (prefix ',')? route
     | prefix ',' route ',' suffix

prefix := STRING | expr                     ; `Origin` or `Absolute`
suffix := STRING | expr                     ; `Reference` or `Absolute`

route := PATH '(' (named | unnamed) ')'

named := IDENT = expr (',' named)? ','?
unnamed := expr (',' unnamed)? ','?

expr := EXPR | '_'

EXPR := a valid Rust expression (examples: `foo()`, `12`, `"hey"`)
IDENT := a valid Rust identifier (examples: `name`, `age`)
STRING := an uncooked string literal, as defined by Rust (example: `"hi"`)
PATH := a path, as defined by Rust (examples: `route`, `my_mod::route`)

Dynamic Semantics

The returned value is that of the prefix (minus any query part) concatenated with the route URI concatenated with the query (if the route has no query part) and fragment parts of the suffix. The route URI is generated by interpolating the declared route URI with the URL-safe version of the route values in uri!(). The generated URI is guaranteed to be URI-safe.

Each route value is rendered in its appropriate place in the URI using the UriDisplay implementation for the value’s type. The UriDisplay implementation ensures that the rendered value is URL-safe.

A uri!() invocation allocated at-most once.

Static Semantics

The uri! macro returns one of Origin, Absolute, or Reference, depending on the types of the prefix and suffix, if any. The table below specifies all combinations:

PrefixSuffixOutput
NoneNoneOrigin
NoneAbsoluteOrigin
NoneReferenceReference
OriginNoneOrigin
OriginAbsoluteOrigin
OriginReferenceReference
AbsoluteNoneAbsolute
AbsoluteAbsoluteAbsolute
AbsoluteReferenceReference

A uri! invocation only typechecks if the type of every route URI value in the invocation matches the type declared for the parameter in the given route, after conversion with FromUriParam, or if a value is ignored using _ and the corresponding route type implements Ignorable.

Conversion

The FromUriParam trait is used to typecheck and perform a conversion for each value passed to uri!. If a FromUriParam<P, S> for T implementation exists for a type T for part URI part P, then a value of type S can be used in uri! macro for a route URI parameter declared with a type of T in part P. For example, the following implementation, provided by Rocket, allows an &str to be used in a uri! invocation for route URI parameters declared as String:

impl<P: Part, 'a> FromUriParam<P, &'a str> for String { .. }

Ignorables

Query parameters can be ignored using _ in place of an expression. The corresponding type in the route URI must implement Ignorable. Ignored parameters are not interpolated into the resulting Origin. Path parameters are not ignorable.