seanmonstar/reqwest · error

Parsed Url is not a valid Uri

Error message

Parsed Url is not a valid Uri

What it means

Raised by url_invalid_uri() (src/error.rs:391-393) as a Kind::Builder error when a url::Url that parsed successfully cannot be re-parsed as an http::Uri. The conversion happens in try_uri() (src/into_url.rs:77-81) and is also checked at request execution time (src/async_impl/client.rs:2639-2642). The url crate is more lenient than the http::Uri parser, so a URL can be valid to url::Url but still contain characters or structures that http::Uri::from_str rejects, producing this error.

Solutions

  1. Sanitize and percent-encode the URL with the url crate before sending: construct via Url::parse and call .as_str() to get the normalized, percent-encoded form that http::Uri accepts.
  2. Percent-encode any user-supplied path or query segments with percent_encoding::utf8_percent_encode before building the URL string.
  3. If the host is an internationalized domain name, convert to punycode (Url does this on parse) by always going through Url::parse rather than string concatenation.
  4. Catch the error at the IntoUrl boundary and reject the input with a clear validation message rather than letting it surface at request time.

Example fix

// before: raw concatenation may yield a Uri-rejected URL
let url = format!("https://example.com/search?q={}", user_query);
let resp = reqwest::get(&url).await?;

// after: go through url::Url so it is normalized + percent-encoded
let mut url = url::Url::parse("https://example.com/search")?;
url.query_pairs_mut().append_pair("q", &user_query);
let resp = reqwest::get(url.as_str()).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn validate_as_uri(raw: &str) -> Result<url::Url, String> {
    let url = url::Url::parse(raw).map_err(|e| format!("URL parse: {e}"))?;
    // http::Uri is stricter; round-trip to catch the rejection early
    raw.parse::<http::Uri>().map_err(|_| "Parsed Url is not a valid Uri".to_string())?;
    Ok(url)
}

Type guard

fn is_valid_uri(raw: &str) -> bool {
    raw.parse::<http::Uri>().is_ok()
}

Try / catch

match reqwest::get(url).await {
    Ok(resp) => { /* ... */ }
    Err(e) if e.is_builder() => {
        // url_invalid_uri: re-encode the URL and retry, or reject the input
        eprintln!("input URL rejected by http::Uri: {e}");
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Passing a URL whose host or path contains characters that http::Uri rejects even though url::Url accepted them: certain non-ASCII / Unicode bytes that were not percent-encoded, invalid percent-encodings, square-bracketed IPv6 literals with malformed scope IDs, or very long URIs exceeding http's internal limits. Also triggered by URLs with illegal whitespace or control characters that url::Url normalized but Uri cannot represent.

Common situations: User-supplied input containing Unicode (IDN hostnames, non-ASCII path segments) that wasn't percent-encoded before being passed to reqwest. URLs constructed by concatenating unescaped strings. Proxies or logs that mangle URLs with stray spaces. Rare edge cases in internationalized domain names where url's punycode handling and http::Uri disagree.

Related errors


AI-assisted analysis of seanmonstar/reqwest@9f06fd28ab (2026-08-10). Data as JSON: /api/errors/e0075b1f13db7d39. Report an issue: GitHub.

Appendix: source

Thrown at src/error.rs:392

        Kind::Status(
            status,
            #[cfg(not(all(
                target_arch = "wasm32",
                any(target_os = "unknown", target_os = "none")
            )))]
            reason,
        ),
        None::<Error>,
    )
    .with_url(url)
}

pub(crate) fn url_bad_scheme(url: Url) -> Error {
    Error::new(Kind::Builder, Some(BadScheme)).with_url(url)
}

pub(crate) fn url_invalid_uri(url: Url) -> Error {
    Error::new(Kind::Builder, Some("Parsed Url is not a valid Uri")).with_url(url)
}

if_wasm! {
    pub(crate) fn wasm(js_val: wasm_bindgen::JsValue) -> BoxError {
        format!("{js_val:?}").into()
    }
}

pub(crate) fn upgrade<E: Into<BoxError>>(e: E) -> Error {
    Error::new(Kind::Upgrade, Some(e))
}

// io::Error helpers

#[allow(unused)]
pub(crate) fn decode_io(e: io::Error) -> Error {
    if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(false) {
        *e.into_inner()

View on GitHub (pinned to 9f06fd28ab)