المعالجات
المعالجات (Handlers) هي دوال غير متزامنة تُعالج طلبات HTTP الواردة. تقع في أعلى طبقة التطبيق، مسؤولة عن تحليل بيانات الطلب، والتفويض للـ services، وبناء استجابات HTTP. يُنظّم FORGE المعالجات في وحدات admin وpublic، كل منها يتبع أنماطاً متسقة لعمليات CRUD ومعالجة الأخطاء.
تنظيم المعالجات
src/handlers/
├── mod.rs # Module exports
├── auth.rs # Login, register, refresh, logout
├── profile.rs # Current user profile
├── lookups.rs # Public lookup endpoints
├── menus.rs # Public menu endpoints
└── admin/
├── mod.rs # Admin module exports
├── users.rs # Users CRUD
├── roles.rs # Roles CRUD
├── contents.rs # Content CRUD
├── settings.rs # Settings management
├── lookups.rs # Lookup CRUD
└── menus.rs # Menus CRUDمعالجات Admin (src/handlers/admin/) تتطلب مصادقة وصلاحيات. تتعامل مع عمليات CRUD الكاملة لإدارة بيانات التطبيق.
المعالجات العامة (src/handlers/) قد تكون بدون مصادقة. تُقدّم البيانات لعملاء الواجهة الأمامية، مثل صفحات المحتوى وقيم lookup.
تشريح المعالج
كل معالج يتبع نفس النمط:
1. استخراج مستخدم المصادقة (إذا محمي)
2. استخراج والتحقق من المدخلات
3. استدعاء طبقة service
4. إرجاع استجابة مُنظّمةpub async fn create_user(
State(pool): State<PgPool>, // 1. Database pool
Extension(current_user): Extension<AuthUser>, // 2. Authenticated user
Json(payload): Json<CreateUserRequest>, // 3. Request body
) -> Result<impl IntoResponse, AppError> { // 4. Return type
// Validate input
payload.validate()
.map_err(|e| AppError::ValidationError(format_validation_errors(e)))?;
// Delegate to service
let user = UserService::create(&pool, payload).await?;
// Return response
Ok((
StatusCode::CREATED,
Json(ApiResponse::success(UserResponse::from(user))),
))
}نصيحة
يجب أن تكون المعالجات نحيفة. تستخرج البيانات من الطلب، تُمررها للـ service، وتُنسّق الاستجابة. منطق الأعمال وقواعد التحقق واستعلامات قاعدة البيانات تنتمي لـ طبقة service.
أمثلة معالجات CRUD
القائمة (مع ترقيم الصفحات)
/// List all users with pagination and optional search
#[utoipa::path(
get,
path = "/api/admin/users",
tag = "Users",
params(
("page" = Option<i64>, Query, description = "رقم الصفحة"),
("per_page" = Option<i64>, Query, description = "عدد العناصر لكل صفحة"),
("search" = Option<String>, Query, description = "البحث بالاسم أو البريد"),
),
responses(
(status = 200, description = "قائمة مستخدمين مُرقّمة", body = PaginatedResponse<UserResponse>),
(status = 401, description = "غير مُصادق"),
),
security(("bearer_auth" = []))
)]
pub async fn list(
State(pool): State<PgPool>,
Extension(_current_user): Extension<AuthUser>,
Query(params): Query<PaginatedRequest>,
) -> Result<impl IntoResponse, AppError> {
let (users, total) = UserService::list(&pool, ¶ms).await?;
let response = PaginatedResponse::new(
users.into_iter().map(UserResponse::from).collect(),
total,
params.page,
params.per_page,
);
Ok(Json(ApiResponse::success(response)))
}عرض (مورد واحد)
/// Get a single user by ID
#[utoipa::path(
get,
path = "/api/admin/users/{id}",
tag = "Users",
params(
("id" = Uuid, Path, description = "معرّف المستخدم"),
),
responses(
(status = 200, description = "تفاصيل المستخدم", body = ApiResponse<UserResponse>),
(status = 404, description = "المستخدم غير موجود"),
),
security(("bearer_auth" = []))
)]
pub async fn show(
State(pool): State<PgPool>,
Extension(_current_user): Extension<AuthUser>,
Path(id): Path<Uuid>,
) -> Result<impl IntoResponse, AppError> {
let user = UserService::find_by_id(&pool, id)
.await?
.ok_or(AppError::NotFound("User not found".into()))?;
Ok(Json(ApiResponse::success(UserResponse::from(user))))
}إنشاء
/// Create a new user
#[utoipa::path(
post,
path = "/api/admin/users",
tag = "Users",
request_body = CreateUserRequest,
responses(
(status = 201, description = "تم إنشاء المستخدم", body = ApiResponse<UserResponse>),
(status = 422, description = "خطأ تحقق", body = ErrorResponse),
),
security(("bearer_auth" = []))
)]
pub async fn create(
State(pool): State<PgPool>,
Extension(_current_user): Extension<AuthUser>,
Json(payload): Json<CreateUserRequest>,
) -> Result<impl IntoResponse, AppError> {
payload.validate()
.map_err(|e| AppError::ValidationError(format_validation_errors(e)))?;
let user = UserService::create(&pool, payload).await?;
Ok((
StatusCode::CREATED,
Json(ApiResponse::success(UserResponse::from(user))),
))
}تحديث
/// Update an existing user
#[utoipa::path(
put,
path = "/api/admin/users/{id}",
tag = "Users",
params(("id" = Uuid, Path, description = "معرّف المستخدم")),
request_body = UpdateUserRequest,
responses(
(status = 200, description = "تم تحديث المستخدم", body = ApiResponse<UserResponse>),
(status = 404, description = "المستخدم غير موجود"),
(status = 422, description = "خطأ تحقق", body = ErrorResponse),
),
security(("bearer_auth" = []))
)]
pub async fn update(
State(pool): State<PgPool>,
Extension(_current_user): Extension<AuthUser>,
Path(id): Path<Uuid>,
Json(payload): Json<UpdateUserRequest>,
) -> Result<impl IntoResponse, AppError> {
payload.validate()
.map_err(|e| AppError::ValidationError(format_validation_errors(e)))?;
let user = UserService::update(&pool, id, payload)
.await?
.ok_or(AppError::NotFound("User not found".into()))?;
Ok(Json(ApiResponse::success(UserResponse::from(user))))
}حذف (حذف ناعم)
/// Delete a user (soft delete)
#[utoipa::path(
delete,
path = "/api/admin/users/{id}",
tag = "Users",
params(("id" = Uuid, Path, description = "معرّف المستخدم")),
responses(
(status = 200, description = "تم حذف المستخدم"),
(status = 404, description = "المستخدم غير موجود"),
),
security(("bearer_auth" = []))
)]
pub async fn delete(
State(pool): State<PgPool>,
Extension(current_user): Extension<AuthUser>,
Path(id): Path<Uuid>,
) -> Result<impl IntoResponse, AppError> {
// Prevent self-deletion
if current_user.id == id {
return Err(AppError::Forbidden(
"You cannot delete your own account".into(),
));
}
UserService::delete(&pool, id)
.await?
.ok_or(AppError::NotFound("User not found".into()))?;
Ok(Json(ApiResponse::success("User deleted successfully")))
}تحذير
عمليات الحذف تستخدم الحذف الناعم بتعيين deleted_at بدلاً من إزالة الصف. هذا يحفظ سلامة البيانات ويسمح بالاستعادة. طبقة service تتعامل مع منطق الحذف الناعم.
المُستخرجات
يوفر Axum مُستخرجات (extractors) لتحليل أجزاء مختلفة من طلب HTTP:
| المُستخرج | المصدر | مثال |
|---|---|---|
State(pool) | حالة التطبيق | pool قاعدة البيانات |
Extension(user) | امتدادات الطلب | المستخدم المُصادق (يُعيّنه middleware) |
Json(body) | جسم الطلب | حمولة JSON |
Query(params) | سلسلة الاستعلام | ?page=1&per_page=15 |
Path(id) | أجزاء مسار URL | /users/{id} |
TypedHeader(header) | ترويسات الطلب | ترويسة Authorization |
ترتيب المُستخرجات
Axum يُعالج المُستخرجات بالترتيب. ضع المُستخرجات التي تستهلك جسم الطلب (Json) أخيراً:
pub async fn update(
State(pool): State<PgPool>, // First: shared state
Extension(user): Extension<AuthUser>, // Second: extensions
Path(id): Path<Uuid>, // Third: URL parameters
Json(payload): Json<UpdateRequest>, // Last: request body (consumes it)
) -> Result<impl IntoResponse, AppError> {
// ...
}نصيحة
إذا حصلت على خطأ تجميع حول المُستخرجات، تحقق من الترتيب. مُستخرج Json يجب أن يكون دائماً المعامل الأخير لأنه يستهلك جسم الطلب.
المعالجات العامة
المعالجات العامة تُقدّم البيانات بدون طلب مصادقة:
/// Get published content by slug
pub async fn get_content(
State(pool): State<PgPool>,
Path(slug): Path<String>,
Query(params): Query<LocaleQuery>,
) -> Result<impl IntoResponse, AppError> {
let content = ContentService::find_by_slug(&pool, &slug)
.await?
.ok_or(AppError::NotFound("Content not found".into()))?;
if !content.is_active {
return Err(AppError::NotFound("Content not found".into()));
}
Ok(Json(ApiResponse::success(ContentResponse::from(content))))
}معالجة الأخطاء في المعالجات
جميع المعالجات تُرجع Result<impl IntoResponse, AppError>. الأخطاء تنتشر باستخدام عامل ?:
pub async fn show(
State(pool): State<PgPool>,
Path(id): Path<Uuid>,
) -> Result<impl IntoResponse, AppError> {
// Database errors propagate via ?
let user = UserService::find_by_id(&pool, id)
.await? // sqlx::Error -> AppError automatically
.ok_or(AppError::NotFound("User not found".into()))?;
Ok(Json(ApiResponse::success(UserResponse::from(user))))
}انظر معالجة الأخطاء لنوع AppError الكامل وكيف يُعيّن لرموز حالة HTTP.
انظر أيضاً
- الـ Routes — كيف تُسجّل المعالجات لمسارات URL
- الـ Services — طبقة منطق الأعمال التي تُفوّض لها المعالجات
- الـ DTOs — أنواع الطلب والاستجابة المستخدمة في المعالجات
- الـ Middleware — فحوصات المصادقة والصلاحيات قبل تشغيل المعالجات
- توثيق API — تعليقات Utoipa على المعالجات