Від Rails до Rust[1]: Створення обробки параметрів, подібної до Rails

Опис

Маючи багаторічний досвід розробки на 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.

Цей підхід має кілька очевидних проблем:

  1. Неможливо одночасно обробляти JSON і завантаження файлів, оскільки вони обидва споживають тіло запиту
  2. Потрібно вручну парсити складні імена полів і встановлювати асоціації
  3. Обробка вкладених завантажень файлів є особливо проблемною
  4. Код громіздкий і схильний до помилок

Уніфікована обробка параметрів

Система обробки параметрів у 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. Майбутні плани

Заплановані функції

  1. Наразі одночасне оброблення Json і Multipart може бути несумісним, головним чином через те, що злиття параметрів ще потребує вдосконалення

[Оновлення] Виправлено в axum-params 0.2.0

  1. Якщо буде можливо, я сподіваюся прибрати символ _ в 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

Leave a Reply

Your email address will not be published. Required fields are marked *