6.3 Четыре глагола
Мастер, умеющий только откликаться, ещё не мастер - он человек, который дома. Имя есть, место есть; ремесло ещё не объявлено. Пришедший с делом - добавить, найти, пометить - уйдёт с пустыми руками. Не потому что отказали. Потому что не договорились.
Что нужно
HTTP API - это договор: клиент называет глагол и путь, сервер отвечает. Четыре операции
tq получают четыре адреса:
| Путь | Глагол | Операция |
|---|---|---|
/tasks | POST | добавить задачу |
/tasks | GET | список всех задач |
/tasks/{id} | GET | найти одну задачу |
/tasks/{id}/done | PATCH | завершить задачу |
Глаголы выбраны не произвольно - каждый несёт смысл: 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-соглашение задаёт значение каждому глаголу:
- GET - читать, не меняя состояния. Два одинаковых
GET /tasksдадут одинаковый результат. Браузер может кешировать; клиент может повторить без риска. - POST - создать новый ресурс. Два одинаковых
POST /tasksсоздадут две задачи. - PATCH - частично обновить ресурс.
PATCH /tasks/{id}/doneменяет только статус. - PUT - заменить ресурс целиком.
tqего не использует: задача не перезаписывается, только дополняется. - DELETE - удалить. В
tqзадачи не удаляются - только завершаются. Если бы удаление появилось, оно было быDELETE /tasks/{id}.
Следовать этим соглашениям необязательно. Но клиент, который знает REST, знает и что
ожидать от GET и POST без документации. Соглашение работает в обе стороны.