neondatabase/neon · error · ApiError

no {param_name} specified in path param

Error message

no {param_name} specified in path param

What it means

Returned as HTTP 400 BadRequest by get_request_param in neon's http-utils when routerify's request.param(param_name) yields None, i.e. the named path parameter was not captured for this request. Because routerify only populates params for patterns that matched, in practice this fires when the handler is invoked on a route whose pattern does not actually define that :param (a route-wiring bug), rather than from a client supplying a wrong value.

Source

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

use core::fmt;
use std::borrow::Cow;
use std::str::FromStr;

use anyhow::anyhow;
use hyper::body::HttpBody;
use hyper::{Body, Request};
use routerify::ext::RequestExt;

use super::error::ApiError;

pub fn get_request_param<'a>(
    request: &'a Request<Body>,
    param_name: &str,
) -> Result<&'a str, ApiError> {
    match request.param(param_name) {
        Some(arg) => Ok(arg),
        None => Err(ApiError::BadRequest(anyhow!(
            "no {param_name} specified in path param",
        ))),
    }
}

pub fn parse_request_param<T: FromStr>(
    request: &Request<Body>,
    param_name: &str,
) -> Result<T, ApiError> {
    match get_request_param(request, param_name)?.parse() {
        Ok(v) => Ok(v),
        Err(_) => Err(ApiError::BadRequest(anyhow!(
            "failed to parse {param_name}",
        ))),
    }
}

pub fn get_query_param<'a>(

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Compare the route pattern string with the param_name passed to get_request_param — they must match exactly, including the leading colon segment name
  2. Fix the route definition (or the handler's param name) so the pattern declares :<param_name>
  3. Add a route-level test that hits the endpoint and asserts the param resolves

Example fix

// before
Router::new()
    .get("/v1/tenant", tenant_handler)  // handler calls get_request_param(req, "tenant_id")

// after
Router::new()
    .get("/v1/tenant/:tenant_id", tenant_handler)
Defensive patterns

Strategy: type-guard

Validate before calling

// Add a route test that fails when the pattern loses its param:
#[tokio::test]
async fn tenant_route_has_param() {
    let resp = router_get("/v1/tenant/abc").await;
    assert_ne!(resp.status(), 400, "tenant_id param missing from route pattern");
}

Type guard

// In middleware, fail loudly on handlers requesting params the pattern lacks:
fn route_defines_param(route: &str, name: &str) -> bool {
    route.split('/').any(|seg| seg == &format!(":{name}"))
}

Prevention

When it happens

Trigger: A handler registered on /v1/tenant calls get_request_param(req, "tenant_id") while the route is defined as /v1/tenants (no :tenant_id segment); or a route refactor renamed the pattern segment (e.g. :tenantId vs :tenant_id) so lookup by the old name misses.

Common situations: Route registration refactors where the pattern and the param name in the handler drift apart; copy-pasting a handler to a new route that lacks the segment; routerify middleware re-creating requests without params; adding a new endpoint and forgetting the path parameter in the route string.

Related errors


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