Новое:Talanto осенью: поиск с цифрами, вакансия по шагам и каталог компаний

+1 210новых вакансий за сутки·93 612вакансий открыто сейчас·Новое:Talanto осенью: поиск с цифрами, вакансия по шагам и каталог компаний

Новые вакансии в TelegramРаботодателям
Talanto
Вакансии
Компании7 266
РезюмеAIПодготовкаСтатьи
BoostТарифыBoost
Войти

Не нашли подходящее?

Пусть нужная вакансия найдёт вас сама.

  1. Подборка в TelegramНовые вакансии по вашему фильтру — сразу в бота.Подключить
  2. Мэтчи по резюмеЗагрузите резюме — покажем, где вы подходите лучше всего.Загрузить
  3. Сохранить фильтрНастройте поиск один раз и возвращайтесь к новым вакансиям.Настроить
Что нового на TalantoTalanto осенью: поиск с цифрами, вакансия по шагам и каталог компаний

Для работодателей

Опубликуйте вакансию — отклики придут в ваш кабинет.

  1. Вакансиябесплатно
  2. Откликиконтакты бесплатно
  3. Наймэтапы и интервью

40 компаний публикуют вакансии напрямую · 4 401 кандидат открыт к предложениям

Разместить вакансию

Популярные направления

Все подборки
  • Python17 126
  • Облачные технологии14 083
  • QA вакансии3 456
  • Backend вакансии2 116
  • Инженер данных1 809
  • Продакт-менеджер1 261

Разработка

  • Android1 862
  • iOS1 700
  • Fullstack вакансии990
  • AI вакансии951
  • Frontend вакансии749
  • Mobile вакансии44

По стеку

  • Go8 688
  • Java6 537
  • Node.js2 026
  • .NET1 722
  • Kotlin1 611
  • Rust1 458
  • C#710
  • PHP682
  • C++571

DevOps и инфраструктура

  • Kubernetes7 772
  • DevOps вакансии6 054
  • Linux-инфраструктура5 493
  • Terraform / IaC4 275
  • CI/CD3 239
  • Мониторинг и Observability1 790
  • SRE568
  • Кибербезопасность458

Аналитика и данные

  • Data Scientist1 143
  • Бизнес-аналитик923
  • Аналитик данных857
  • Системный аналитик152

Другие направления

  • Проджект-менеджер620
  • UI/UX дизайн147
  • Ручное тестирование14

По регионам

  • Вакансии в США44 437
  • Вакансии в Европе23 855
  • Вакансии в Канаде4 483
  • Работа в России3 904

По формату

  • Удалённые вакансии75 212
  • Релокация в США44 436
  • Гибридная работа12 388
  • Офисная работа7 083

По уровню

  • Senior вакансии42 625
  • Middle вакансии20 854
  • Junior вакансии6 721

Сейчас нанимают

Все компании
  • Wise190 вакансий
  • Rakuten189 вакансий
  • ASML166 вакансий
  • Grab137 вакансий
  • Celonis39 вакансий
  • Spotify37 вакансий
  • Adyen28 вакансий
  • Infobip28 вакансий
  • Monzo20 вакансий
  • Darktrace16 вакансий

Вакансии из 344 источников: карьерные страницы компаний и джоб-борды.

КомпанииО насНовостиПубличная офертаУсловия использованияПолитика конфиденциальностиПоддержка
←К теме AxumВсе вопросы
AxumMiddleТеория

Как реализовать собственный тип ошибки в Axum, возвращающий подходящие HTTP status codes?

В закладки
Сначала попробуйте ответить сами30 секунд
Сравнить с ответом ↓

Создайте enum ApiError с вариантами для каждого класса ошибок, реализуйте IntoResponse с маппингом на HTTP-статусы и безопасным client_message, добавьте From-конверсии для sqlx::Error и anyhow::Error. Оператор ? в обработчиках автоматически конвертирует ошибки.

Полная реализация с thiserror

use axum::{
    http::StatusCode,
    response::{IntoResponse, Json, Response},
};
use serde_json::{json, Value};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ApiError {
    #[error("Resource not found: {0}")]
    NotFound(String),

    #[error("Unauthorized: {0}")]
    Unauthorized(String),

    #[error("Forbidden")]
    Forbidden,

    #[error("Validation error: {0}")]
    Validation(String),

    #[error("Conflict: {0}")]
    Conflict(String),

    #[error("Too many requests")]
    RateLimited,

    #[error("Database error: {0}")]
    Database(#[from] sqlx::Error),

    #[error("Internal error")]
    Internal(#[from] anyhow::Error),
}

impl ApiError {
    /// Маппинг на HTTP status code
    fn status_code(&self) -> StatusCode {
        match self {
            ApiError::NotFound(_) => StatusCode::NOT_FOUND,
            ApiError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
            ApiError::Forbidden => StatusCode::FORBIDDEN,
            ApiError::Validation(_) => StatusCode::UNPROCESSABLE_ENTITY,
            ApiError::Conflict(_) => StatusCode::CONFLICT,
            ApiError::RateLimited => StatusCode::TOO_MANY_REQUESTS,
            ApiError::Database(e) => match e {
                sqlx::Error::RowNotFound => StatusCode::NOT_FOUND,
                _ => StatusCode::INTERNAL_SERVER_ERROR,
            },
            ApiError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }

    /// Безопасное сообщение для клиента (без деталей БД)
    fn client_message(&self) -> String {
        match self {
            ApiError::Database(sqlx::Error::RowNotFound) => "Resource not found".to_string(),
            ApiError::Database(_) | ApiError::Internal(_) => "Internal server error".to_string(),
            other => other.to_string(),
        }
    }
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        // Внутренние ошибки логируем полностью
        match &self {
            ApiError::Database(e) => tracing::error!(error = ?e, "Database error"),
            ApiError::Internal(e) => tracing::error!(error = ?e, "Internal error"),
            _ => tracing::warn!(error = %self, "Client error"),
        }

        let status = self.status_code();
        let body: Value = json!({
            "error": {
                "code": status.as_u16(),
                "message": self.client_message()
            }
        });

        (status, Json(body)).into_response()
    }
}

Кастомный тип ошибки в Axum с правильными HTTP-статусами

Цель — создать единый тип ошибки для всего приложения, который автоматически преобразуется в корректный HTTP-ответ. Стандартный подход: enum + реализация IntoResponse + интеграция с thiserror для удобного определения ошибок.

Использование в обработчиках

use axum::{
    extract::{Path, State},
    Json,
};

// ? автоматически конвертирует sqlx::Error в ApiError через From
async fn get_post(
    State(pool): State<PgPool>,
    Path(post_id): Path<i64>,
) -> Result<Json<Post>, ApiError> {
    let post = sqlx::query_as!(Post, "SELECT * FROM posts WHERE id = $1", post_id)
        .fetch_one(&pool)
        .await?; // sqlx::Error::RowNotFound -> ApiError::Database -> 404

    Ok(Json(post))
}

async fn create_post(
    State(pool): State<PgPool>,
    auth: AuthUser,
    Json(payload): Json<CreatePostRequest>,
) -> Result<(StatusCode, Json<Post>), ApiError> {
    if payload.title.is_empty() {
        return Err(ApiError::Validation("Title cannot be empty".to_string()));
    }

    let post = sqlx::query_as!(
        Post,
        "INSERT INTO posts (title, body, author_id) VALUES ($1, $2, $3) RETURNING *",
        payload.title,
        payload.body,
        auth.user_id
    )
    .fetch_one(&pool)
    .await
    .map_err(|e| match &e {
        sqlx::Error::Database(db_err) if db_err.is_unique_violation() => {
            ApiError::Conflict("Post with this title already exists".to_string())
        }
        _ => ApiError::Database(e),
    })?;

    Ok((StatusCode::CREATED, Json(post)))
}

Интеграция с валидацией через validator

use validator::Validate;

async fn validated_create(
    Json(payload): Json<CreatePostRequest>,
) -> Result<Json<Post>, ApiError> {
    payload.validate().map_err(|e| {
        ApiError::Validation(e.to_string())
    })?;
    // ...
}

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

  • Цикличность From-конверсий: Если несколько внешних ошибок конвертируются в одну ветку через #[from], возможны конфликты. Чаще всего sqlx::Error нужно обрабатывать вручную (не через #[from]) из-за специфичного маппинга RowNotFound.
  • Утечка информации в prod: Не передавайте e.to_string() от sqlx или IO-ошибок клиенту. SQL-запросы, имена таблиц и системные пути могут раскрыть архитектуру.
  • Ошибки extractor-ов не попадают в ApiError: Если Json<T> не может распарсить тело, он возвращает JsonRejection напрямую, минуя вашу систему ошибок. Оберните входные данные через Result<Json<T>, JsonRejection> и конвертируйте вручную.
  • Дублирование логики статус-кодов: Маппинг статуса должен быть в одном месте (метод status_code). Не разбрасывайте StatusCode по всему коду.
  • Несовместимость с ? в main: anyhow::Error и ApiError — разные типы. В функциях инициализации (setup DB, load config) используйте anyhow::Result, а ApiError только в обработчиках.
  • Отсутствие request ID в ошибке: Логируйте request_id вместе с ошибкой. Без него поддержка не может связать лог с конкретным запросом клиента.
  • Паника вместо ошибки: unwrap() и expect() в обработчиках приводят к 500 без логирования через вашу систему. Axum перехватывает паники через CatchPanic слой, но это не замена корректной обработке.

Частые вопросы на собеседовании

  • В каких backend-проектах Axum (Rust) является сильным выбором, а где лучше выбрать более простой или другой стек?
  • Какие production-риски есть у Axum (Rust): blocking code, connection pooling, config, auth, observability, deploy или graceful shutdown?
  • Как понять, что проблема в сервисе связана с Axum (Rust), а не с базой, сетью, инфраструктурой или бизнес-логикой?
  • Проект на Axum (Rust) вырос из MVP в большой production-сервис. Какие проблемы архитектуры и сопровождения вы ожидаете увидеть?

Типичные ошибки

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

Что проверяет интервьюер

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

Как вы знаете этот вопрос?

Проверить себя по теме Axum →

Источники

  • https://docs.rs/axum/latest/axum/
←Предыдущий вопросКак работает обработка ошибок в Axum? Что такое `IntoResponse`?Следующий вопрос→Как реализовать вложенные маршрутизаторы и объединение маршрутов в Axum?

Связанные темы

Axumaxum-productionAxum (Rust)risk-analysistechnical-depth