Опис
Маючи багаторічний досвід розробки на Ruby on Rails, нещодавно я вирішив замінити свою існуючу розробницьку стеку на Rust. Після спроби кількох веб-фреймворків для Rust я знайшов, що loco.rs найбільше нагадує досвід розробки на Rails, хоча все ще є місце для покращення з точки зору зручності.
Міць і простота використання Rails відображаються у багатьох аспектах: обробка параметрів, завантаження файлів, валідація моделей, асоціації, задачі, листи, зберігання даних та інше. Я планую записувати свій шлях побудови додатків на Rust, порівнюючи його з Rails і покращуючи процеси по ходу. Ця серія буде включати:
- Від Rails до Rust: побудова обробки параметрів, схожої на Rails
1. Використання Axum як Rails для обробки параметрів
Сильні параметри
Розглянемо типову ситуацію створення блогу з наступною структурою даних:
post:
title: string
content: string
tags: [string]
У Rails обробка такої структури даних є простою:
def create
@post = Post.create(post_params)
end
private
def post_params
params.require(:post).permit(:title, :content, tags: [])
end
У Rust ми можемо досягти ще більш безпечної обробки параметрів через типову систему:
#[derive(Deserialize)]
struct PostParams {
title: String,
content: String,
tags: Vec,
}
// Сам тип визначає дозволені параметри, забезпечуючи безпеку на етапі компіляції
async fn create_post(Json(post): Jdon) -> impl IntoResponse {
// Обробка параметрів
}
Хоча Rust тут такий же зручний, як і Rails, статична типізація Rust допомагає зробити наш код більш надійним.
Однак обробка вкладених структур з завантаженнями файлів у поточному Axum є досить громіздкою:
#[derive(Deserialize)]
struct Attachment {
file: UploadFile,
description: String,
}
#[derive(Deserialize)]
struct PostParams {
title: String,
content: String,
tags: Vec,
cover: UploadFile,
attachments: Vec,
}
async fn create_post(
// Потрібно окремо обробляти JSON-дані та завантаження файлів
Json(post): Json,
mut multipart: Multipart,
) -> Result {
// Ручна обробка завантаження файлів
while let Some(field) = multipart.next_field().await? {
let name = field.name().unwrap().to_string();
// Потрібно вручну парсити складні імена полів, як "post[attachments][0][file]"
// і асоціювати файли з правильними даними
...
}
// Нарешті потрібно вручну зібрати всі дані
...
}
Насправді, наведений вище код не скомпілюється, оскільки він намагається обробити і JSON, і завантаження файлів, а вони обидва споживають тіло запиту. Використання Form тут не спрацює, оскільки запити з файлами насправді є multipart.
Цей підхід має кілька очевидних проблем:
- Неможливо одночасно обробляти JSON і завантаження файлів, оскільки вони обидва споживають тіло запиту
- Потрібно вручну парсити складні імена полів і встановлювати асоціації
- Обробка вкладених завантажень файлів є особливо проблемною
- Код громіздкий і схильний до помилок
Уніфікована обробка параметрів
Система обробки параметрів у Rails відома своєю уніфікацією та зручністю. Незалежно від того, чи параметри надходять з рядка запиту URL, тіла запиту чи завантаження файлів, Rails надає єдиний підхід до обробки. Така уніфікація значно покращує ефективність розробки. Axum використовує кілька екстракторів для обробки параметрів з різних джерел, що є гнучким і може строго обмежити джерела параметрів. Це дві різні філософії, і, з мого досвіду розробки, обробка параметрів у Rails є надзвичайно зручною.
Безшовна інтеграція вкладених параметрів та завантаження файлів
У Rails завантаження файлів безперешкодно інтегроване з регулярною обробкою параметрів.
Опис
Розробникам не потрібно спеціально відокремлювати параметри файлів від звичайних параметрів, що значно спрощує процес розробки:
def create
# Для порівняння, це може бути:
# @post = Post.new(params[:post])
@post = Post.new(
title: params[:post][:title],
content: params[:post][:content],
tags: params[:post][:tags],
cover: params[:post][:cover] # Поле для завантаження файлів
attachments: params[:post][:attachments] # Поле для завантаження файлів
)
end
Цей уніфікований підхід до обробки дозволяє розробникам зосередитися на бізнес-логіці, а не на технічних деталях парсингу параметрів.
2. axum-params: Обробка параметрів у Axum, як у Rails
Щоб вирішити ці проблеми, я розробив бібліотеку axum-params. Метою цієї бібліотеки є перенесення обробки параметрів у стилі Rails до екосистеми Rust.
Філософія дизайну axum-params полягає в поєднанні зручності обробки параметрів у Rails та типобезпеки Rust. Завдяки типам даних ми можемо забезпечити правильність параметрів на етапі компіляції, а не під час виконання.
axum-params в основному надає типи Params та UploadFile, які використовуються для обробки параметрів і завантаження файлів відповідно. Params реалізує axum_core::extract::FromRequest і може замінити Json, Path, Query, Form, Multipart, що надаються axum::extract.
Приклад використання:
use axum_params::{Params, UploadFile};
#[derive(Deserialize)]
struct Attachment {
file: UploadFile,
description: String,
}
#[derive(Deserialize)]
struct PostParams {
title: String,
content: String,
tags: Vec,
cover: UploadFile,
attachments: Vec,
}
// Тип сам по собі визначає дозволені параметри, забезпечуючи безпеку на етапі компіляції
async fn create_post(Params(post, _): Params) -> impl IntoResponse {
// Обробка файлів
let cover_file = post.cover.open().await?;
// ...
for attachment in post.attachments {
let file = attachment.file.open().await?;
// ...
}
}
3. Майбутні плани
Заплановані функції
- Наразі одночасне оброблення Json і Multipart може бути несумісним, головним чином через те, що злиття параметрів ще потребує вдосконалення
[Оновлення] Виправлено в axum-params 0.2.0
- Якщо буде можливо, я сподіваюся прибрати символ
_
вParams(params, _): Params
і змінити його наParams(params): Params
. Цей параметр використовується для зберігання файлів, але я ще не знаю, як це реалізувати.
Інтеграція з іншими фреймворками
Наразі бібліотека в основному підтримує фреймворк Axum і може використовуватися з Loco.rs, але я сподіваюся інтегрувати її з іншими фреймворками в майбутньому.
Висновки
Бібліотека axum-params значно спростила обробку складних параметрів у контролерах під час розробки мого додатку, що дуже допомогло спростити процес розробки та покращити ефективність. Сподіваюся, що в майбутньому я зможу її ще більше оптимізувати.
Для додаткової інформації відвідайте: https://github.com/cpunion/axum-params
Перекладено з: From Rails to Rust[1]: Building Rails-like Parameter Handling