Juniper — مكتبة GraphQL للغة Rust
Juniper هي مكتبة سريعة وآمنة ومريحة بلغة Rust لبناء خوادم GraphQL. تتيح لك تطوير واجهات برمجة تطبيقات (API) ذات كتابة صارمة (strict typing)، وتوليد تلقائي للتوثيق، ودعم للاستعلامات غير المتزامنة (async). تعتبر Juniper مثالية للمشاريع التي تكون فيها الأداء والموثوقية والتعبير عن الأنواع أمرًا بالغ الأهمية.
على عكس واجهات REST API التقليدية، تسمح GraphQL للعميل بطلب البيانات الضرورية فقط، مما يقلل الحمل على الشبكة والخادم. تنفذ Juniper مواصفات GraphQL الكاملة (بما في ذلك الاشتراكات عبر WebSocket)، مما يجعلها خيارًا ممتازًا لتطبيقات الويب الحديثة بلغة Rust.
الميزات الرئيسية
- دعم كامل لـ GraphQL — الاستعلامات، الطفرات (mutations)، الاشتراكات (subscriptions).
- عدم التزامن (Asynchronous) — تعمل فورًا مع async/await (عبر futures).
- التكامل مع الأطر الشائعة — Actix-web, Rocket, Warp, Tide, Axum.
- توليد تلقائي للمخطط (Schema) — بناءً على هياكل Rust.
- دعم السياق (Context) — تمرير الحالة (قاعدة بيانات، مصادقة) إلى المحللات (resolvers).
- قابلية التوسع — أنواع عددية مخصصة (custom scalars)، توجيهات (directives)، واجهات (interfaces).
- الأمان — كتابة صارمة على مستوى الترجمة، تمنع أخطاء وقت التشغيل.
- التوثيق — توليد تلقائي لمخطط GraphQL (SDL).
التثبيت
أضف juniper إلى ملف Cargo.toml الخاص بمشروعك:
[dependencies]juniper = "0.16"للتكامل مع إطار ويب، أضف الكريت (crate) المناسب. على سبيل المثال، لـ Actix-web:
[dependencies]juniper = "0.16"actix-web = "4"juniper_actix = "0.3"إنشاء خادم GraphQL بسيط
لننظر في مثال لإنشاء واجهة برمجة تطبيقات لجلب قائمة المستخدمين. أولاً، سنحدد نموذج البيانات والمحللات (resolvers).
1. تعريف أنواع GraphQL
استخدم الماكرو #[derive(GraphQLObject)] و #[derive(GraphQLInputObject)]:
use juniper::{GraphQLObject, GraphQLInputObject};
#[derive(GraphQLObject)]#[graphql(description = "مستخدم النظام")]struct User { id: i32, name: String, email: String,}
#[derive(GraphQLInputObject)]#[graphql(description = "مستخدم جديد")]struct NewUser { name: String, email: String,}2. إنشاء السياق (Context) والاستعلامات الجذرية (Root Queries)
عادةً ما يحتوي السياق على إمكانية الوصول إلى قاعدة البيانات أو الخدمات الأخرى:
use std::sync::Mutex;
struct Database { users: Mutex<Vec<User>>,}
impl Database { fn new() -> Self { Database { users: Mutex::new(vec![ User { id: 1, name: "Alice".into(), email: "alice@example.com".into() }, User { id: 2, name: "Bob".into(), email: "bob@example.com".into() }, ]), } }}
// السياق للمحللاتpub struct Context { pub db: Database,}
// تنفيذ trait Context لـ Juniperimpl juniper::Context for Context {}3. تعريف الاستعلام الجذري (Query) والطفرة (Mutation)
pub struct QueryRoot;
#[juniper::graphql_object(Context = Context)]impl QueryRoot { fn users(context: &Context) -> Vec<User> { let db = context.db.users.lock().unwrap(); db.clone() }
fn user(context: &Context, id: i32) -> Option<User> { let db = context.db.users.lock().unwrap(); db.iter().find(|u| u.id == id).cloned() }}
pub struct MutationRoot;
#[juniper::graphql_object(Context = Context)]impl MutationRoot { fn create_user(context: &Context, new_user: NewUser) -> U