neondatabase/neon · error · ApiError

cannot parse query param {param_name}: {e}

Error message

cannot parse query param {param_name}: {e}

What it means

Thrown by parse_query_param when the query parameter is present but its string value fails FromStr parsing into the target type T. The {e} suffix is the underlying parse error (for example 'invalid digit found in string' or a UUID/Lsn parse message), so the error identifies both the parameter name and why parsing failed. Surfaces as ApiError::BadRequest (HTTP 400).

Source

Thrown at libs/http-utils/src/request.rs:92

}

pub fn must_get_query_param<'a>(
    request: &'a Request<Body>,
    param_name: &str,
) -> Result<Cow<'a, str>, ApiError> {
    get_query_param(request, param_name)?.ok_or_else(|| {
        ApiError::BadRequest(anyhow!("no {param_name} specified in query parameters"))
    })
}

pub fn parse_query_param<E: fmt::Display, T: FromStr<Err = E>>(
    request: &Request<Body>,
    param_name: &str,
) -> Result<Option<T>, ApiError> {
    get_query_param(request, param_name)?
        .map(|v| {
            v.parse().map_err(|e| {
                ApiError::BadRequest(anyhow!("cannot parse query param {param_name}: {e}"))
            })
        })
        .transpose()
}

pub fn must_parse_query_param<E: fmt::Display, T: FromStr<Err = E>>(
    request: &Request<Body>,
    param_name: &str,
) -> Result<T, ApiError> {
    parse_query_param(request, param_name)?.ok_or_else(|| {
        ApiError::BadRequest(anyhow!("no {param_name} specified in query parameters"))
    })
}

pub async fn ensure_no_body(request: &mut Request<Body>) -> Result<(), ApiError> {
    match request.body_mut().data().await {
        Some(_) => Err(ApiError::BadRequest(anyhow!("Unexpected request body"))),
        None => Ok(()),

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Read the {e} suffix and correct the value so FromStr of the target type accepts it
  2. For empty values, omit the parameter entirely rather than sending '?param='
  3. Strip shell/template quoting and whitespace from the value before it reaches the URL
  4. If empty-string should be legal for your endpoint, branch on it explicitly before calling must_parse_query_param

Example fix

# before
curl 'http://localhost:9898/v1/tenant?limit=0x10&tenant_id='
# -> 400 cannot parse query param limit: invalid digit found in string

# after
curl 'http://localhost:9898/v1/tenant?limit=16'
# -> 200
Defensive patterns

Strategy: validation

Validate before calling

// Validate the raw value yourself to produce a friendlier message:
let raw = get_query_param(request, "limit")?;
let limit: Option<u32> = match raw {
    Some(v) => match v.parse::<u32>() {
        Ok(n) => Some(n),
        Err(_) => {
            return Err(ApiError::BadRequest(anyhow::anyhow!(
                "query param 'limit' must be a non-negative integer, got {v:?}"
            )))
        }
    },
    None => None,
};

Type guard

/// True when `s` parses as the endpoint's expected type (u32 here).
fn is_valid_limit(s: &str) -> bool {
    !s.is_empty() && s.chars().all(|c| c.is_ascii_digit()) && s.parse::<u32>().is_ok()
}

Try / catch

match parse_query_param::<_, u32>(request, "limit") {
    Ok(v) => v,
    Err(ApiError::BadRequest(e)) if e.to_string().contains("cannot parse query param limit") => {
        // 400 with the original message; it already includes the FromStr reason
        return Err(ApiError::BadRequest(e));
    }
    Err(other) => return Err(other),
}

Prevention

When it happens

Trigger: '?limit=abc' parsed as u32; '?generation=-5' parsed as an unsigned type; '?lsn=41C/0' missing the leading '0/' expected by Lsn::from_str; an empty value '?tenant_id=' parsed as Uuid; any handler using parse_query_param / must_parse_query_param with a FromStr type.

Common situations: Passing floats where an integer type is expected ('?max=10.5'); mixing decimal and hex LSN formats; URL-encoding artifacts like '+' left inside a UUID; copy-pasting values with quotes or trailing whitespace from tickets or docs; i8 overflow for zstd-style level parameters.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/2b0933e7b4351945. Report an issue: GitHub.