ActixMiddleTechnical

Как извлечь path parameters и query parameters в Actix-web?

Path-параметры извлекаются через web::Path<T>, query-параметры — через web::Query<T>. Оба типа реализуют трейт-экстрактор и десериализуются через serde автоматически.

Path parameters: web::Path

Для извлечения сегментов пути используется экстрактор web::Path<T>. Тип T должен реализовывать serde::Deserialize. Один параметр удобно брать как кортеж, несколько — как структуру.

use actix_web::{web, HttpResponse};
use serde::Deserialize;

// Один параметр
async fn get_user(path: web::Path<u64>) -> HttpResponse {
    let user_id = path.into_inner();
    HttpResponse::Ok().body(format!("user_id={user_id}"))
}

// Несколько параметров через структуру
#[derive(Deserialize)]
struct PostPath {
    user_id: u64,
    post_id: u64,
}

async fn get_post(path: web::Path<PostPath>) -> HttpResponse {
    HttpResponse::Ok().body(
        format!("user={}, post={}", path.user_id, path.post_id)
    )
}

// Регистрация маршрутов
App::new()
    .route("/users/{user_id}", web::get().to(get_user))
    .route("/users/{user_id}/posts/{post_id}", web::get().to(get_post))

Query parameters: web::Query

Query-строка (?key=value&key2=value2) извлекается через web::Query<T>. Поля с Option автоматически становятся необязательными.

#[derive(Deserialize)]
struct SearchParams {
    q: String,
    page: Option<u32>,
    limit: Option<u32>,
}

async fn search(query: web::Query<SearchParams>) -> HttpResponse {
    let page = query.page.unwrap_or(1);
    let limit = query.limit.unwrap_or(20);
    HttpResponse::Ok().json(serde_json::json!({
        "q": query.q,
        "page": page,
        "limit": limit
    }))
}

App::new()
    .route("/search", web::get().to(search))

Комбинирование нескольких экстракторов

Обработчик может принимать несколько экстракторов одновременно — Actix разрешает их параллельно:

#[derive(Deserialize)]
struct FilterParams {
    status: Option<String>,
}

async fn list_user_orders(
    path: web::Path<u64>,
    query: web::Query<FilterParams>,
) -> HttpResponse {
    let user_id = path.into_inner();
    let status = query.status.as_deref().unwrap_or("all");
    HttpResponse::Ok().body(format!("user={user_id}, status={status}"))
}

Настройка обработки ошибок

Если path-параметр не соответствует типу (например, строка вместо u64), Actix возвращает 404. Для query-параметров при ошибке десериализации — 400. Кастомизация через PathConfig и QueryConfig:

App::new()
    .app_data(
        web::QueryConfig::default().error_handler(|err, _req| {
            actix_web::error::InternalError::from_response(
                err,
                HttpResponse::BadRequest().json(
                    serde_json::json!({"error": "invalid query parameters"})
                )
            ).into()
        })
    )

Подводные камни

  • Если path-параметр не парсится в указанный тип Rust (например, {id} как u64, но в URL пришла строка), Actix возвращает 404, а не 400 — это сбивает с толку клиентов.
  • Query-параметры с одинаковым именем (?tag=a&tag=b) не поддерживаются через web::Query<T> стандартно; для multi-value используйте serde_qs или Vec<T> с кастомным парсером.
  • Отсутствие обязательного query-параметра без Option возвращает 400 без понятного сообщения об ошибке — используйте QueryConfig::error_handler в продакшене.
  • Имена path-переменных в шаблоне маршрута ({user_id}) должны точно совпадать с именами полей структуры Deserialize; опечатка — паника при старте.
  • Дефисы в именах query-параметров URL (?page-size=10) конфликтуют с именами Rust — используйте #[serde(rename = "page-size")].
  • Числа в query-строке парсятся как строки, потом в тип; переполнение u32 вызывает ошибку десериализации, а не панику — обрабатывайте корректно.
  • При использовании .into_inner() на web::Path теряется информация об ошибках дальше по цепочке; проверяйте значения до вызова.
  • Не путайте web::Path<(u64, u64)> (кортеж, порядок важен) и web::Path<MyStruct> (именованные поля) — при неверном порядке компилятор промолчит, но данные перепутаются.

Common mistakes

  • Отвечать определением без production-сценария.
  • Не называть runtime boundary, security boundary или failure mode.
  • Игнорировать версию API, observability и тестовую проверку.

What the interviewer is testing

  • Объясняет механизм своими словами и без выдуманных API.
  • Называет реальные риски, диагностику и критерий корректности.
  • Связывает ответ с текущей документацией и миграционными ограничениями.

Sources

Related topics