← Оглавление

6.3 Четыре глагола

Мастер, умеющий только откликаться, ещё не мастер - он человек, который дома. Имя есть, место есть; ремесло ещё не объявлено. Пришедший с делом - добавить, найти, пометить - уйдёт с пустыми руками. Не потому что отказали. Потому что не договорились.

Что нужно

HTTP API - это договор: клиент называет глагол и путь, сервер отвечает. Четыре операции tq получают четыре адреса:

ПутьГлаголОперация
/tasksPOSTдобавить задачу
/tasksGETсписок всех задач
/tasks/{id}GETнайти одну задачу
/tasks/{id}/donePATCHзавершить задачу

Глаголы выбраны не произвольно - каждый несёт смысл: GET читает, не меняя состояния; POST создаёт; PATCH обновляет часть ресурса. Подробнее - в Lore этой главы.

Обработчики пока будут заглушками: строки вместо данных. Шаг за шагом мы наполним их смыслом.

Сборка

Маршруты живут в отдельном файле - так crates/api/src/main.rs остаётся занятым только запуском сервера, а маршруты можно читать и менять независимо.

Создайте crates/api/src/routes.rs:

// crates/api/src/routes.rs - NEW
use axum::{
    Router,
    routing::{get, patch, post},
};

async fn add() -> String {
    "TODO: add task".to_string()
}

async fn list() -> String {
    "TODO: list tasks".to_string()
}

async fn get_task() -> String {
    "TODO: get task".to_string()
}

async fn done() -> String {
    "TODO: mark done".to_string()
}

pub fn router() -> Router {
    Router::new()
        .route("/tasks", post(add).get(list))
        .route("/tasks/{id}", get(get_task))
        .route("/tasks/{id}/done", patch(done))
}

.route("/tasks", post(add).get(list)) - два действия на одном пути: POST /tasks создаёт, GET /tasks возвращает список.

{id} - параметр пути: axum подставит число из URL, заглушки его не используют.

Обновите crates/api/src/main.rs. health остаётся здесь - это маршрут самого сервера, не ресурсный: он не про задачи, а про то, жив ли процесс:

// crates/api/src/main.rs - CHANGED
mod routes;

use axum::{Router, routing::get};

async fn health() -> String {
    "tq ok".to_string()
}

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(health))
        .merge(routes::router());
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

.merge(routes::router()) добавляет все маршруты из routes.rs в app.

Результат

$ make serve

В другом терминале:

$ curl -X POST localhost:3000/tasks
TODO: add task

$ curl localhost:3000/tasks
TODO: list tasks

$ curl localhost:3000/tasks/1
TODO: get task

$ curl -X PATCH localhost:3000/tasks/1/done
TODO: mark done

Четыре маршрута отвечают. Данных пока нет - форма API готова.

make ci проходит: те же 38 тестов.

Полный код tq для этой главы - 6-the-port/03-the-four-verbs/.


Lore: HTTP-глаголы

REST-соглашение задаёт значение каждому глаголу:

Следовать этим соглашениям необязательно. Но клиент, который знает REST, знает и что ожидать от GET и POST без документации. Соглашение работает в обе стороны.