لغة Rust تمنحك خادم HTTP سريعًا وآمنًا من حيث الأنواع في بضع مئات من الأسطر. لكن ما لا تمنحك إياه هو حلقة تغذية راجعة سريعة لاختبار هذا الخادم. دورة التحويل البرمجي طويلة، و`cargo test` يعيد تشغيل كل شيء عند تغيير سمة واحدة، وتجبرك معظم أطر عمل Rust HTTP على كتابة اختبار تكامل منفصل لكل نقطة نهاية قبل حتى أن تقوم باستدعائها مرة واحدة. إذا كنت ترغب في نشر واجهة برمجة تطبيقات (API) وليس مجرد ملف ثنائي، فأنت بحاجة إلى أداة تعمل خارج سلسلة أدوات Rust وتتحدث إلى الخادم قيد التشغيل.
يرشدك هذا الدليل خلال سير عمل اختبار واجهة برمجة تطبيقات Rust الكامل داخل Apidog: توجيه Apidog إلى خادم Axum أو Actix الخاص بك، وإنشاء طلبات ضد نقاط النهاية الخاصة بك، والتحقق من صحة JSON المُتسلسل بواسطة Serde، والتعامل مع مصادقة JWT، ومحاكاة نقاط النهاية حتى يتمكن الواجهة الأمامية من التقدم بينما تنهي أنت المعالج، وتعبئة كل ذلك كسيناريو اختبار تكامل مستمر (CI). بحلول النهاية، سيكون لديك مشروع Apidog قابل لإعادة الاستخدام يكتشف أي انحراف في العقد قبل انتهاء `cargo build --release`.
إذا كنت تستخدم سير عمل Postman أو `curl`، فستحصل أيضًا على ميزات Apidog التي تركز على التصميم مجانًا: مواصفات OpenAPI يتم إنشاؤها من طلباتك المحفوظة، وعناوين URL وهمية قابلة للمشاركة، وبيئات عمل جماعية. تخطَ قصة ترحيل Postman لقراءة منفصلة؛ تركز هذه المقالة على Rust.
ملخص سريع (TL;DR)
- قم بتشغيل خادم Rust الخاص بك محليًا (`cargo run` مقابل مشروع Axum أو Actix-web)، وأضف عنوان URL الأساسي `http://localhost:3000` كبيئة Apidog، وقم بتخزين أي أسرار كمتغيرات.
- أنشئ الطلب الأول مقابل `GET /healthz`، واحفظه، وأعد استخدام المصادقة وعنوان URL الأساسي عبر كل معالج في المجلد.
- بالنسبة لنقاط نهاية JSON، الصق هيكل Serde الخاص بك في محرر نص الطلب الخاص بـ Apidog وتأكد من شكل الاستجابة باستخدام نصوص الاختبار التي تعمل بعد كل إرسال.
- بالنسبة للمسارات المحمية، أنشئ JWT مرة واحدة، واحفظه باسم `{{token}}`، وطبق مصادقة Bearer على مستوى المجلد بحيث يرثها كل اختبار.
- قم بمحاكاة معالجات Rust غير المكتملة في Apidog حتى يتمكن فريق الواجهة الأمامية من عرض استجابات حقيقية قبل أن يتم تجميع المعالج بشكل نظيف.
- احفظ المجموعة العاملة كسيناريو اختبار، وقم بتصديرها، وقم بتشغيلها في CI باستخدام `apidog-cli`. الآن، كل طلب سحب (PR) يمس معالجًا يحصل على فحص عقد قبل الدمج.
لماذا تختبر واجهة برمجة تطبيقات Rust خارج سلسلة أدوات Rust
`cargo test` جيد. لكنه بطيء أيضًا، وغير واضح لزملاء الفريق غير المختصين بـ Rust، ومصمم حول التعليمات البرمجية بدلاً من HTTP. إذا كنت ترغب في التحقق من أن المعالج الخاص بك يعيد رمز الحالة الصحيح، وشكل JSON الصحيح، والرؤوس الصحيحة، ورسالة الخطأ الصحيحة عندما يكون الإدخال غير صحيح، فإنك تكتب استدعاء `tower::ServiceExt::oneshot` جديدًا لكل حالة. ثم تقوم بصيانة هذا الاختبار مع تغير المعالج. ثم تكتبه مرة أخرى في JavaScript حتى تتمكن الواجهة الأمامية من الوصول إلى محاكاة (mock).
يوفر لك Apidog طبقة عقد واحدة فوق الخادم قيد التشغيل. يتم إنشاء الطلب مرة واحدة. توجد التأكيدات بجوار الطلب. يفتح زملاء الواجهة الأمامية نفس المشروع ويرون نفس الطلبات التي تراها. عندما يحصل Serde على سمة `#[serde(rename_all = "camelCase")]` بعد ثلاثة أسابيع من الآن، سيكون الاختبار الذي سيتعطل هو الموجود في Apidog، وليس الاختبار الذي يتم نشره في الإنتاج.
ثلاثة أسباب ملموسة لإضافة Apidog إلى سير عمل Rust:
- فحوصات العقد منفصلة عن البناء. يعمل Apidog مقابل ملف ثنائي قيد التشغيل. تتوقف عن انتظار `rustc` للتحقق من أن نقطة النهاية الخاصة بك لا تزال تُرجع `200`.
- النماذج الوهمية (Mocks) قابلة للمشاركة. يحصل مطور الواجهة الأمامية في منطقة زمنية أخرى على عنوان URL يُرجع JSON الصحيح، وليس رسالة Slack تقول "المعالج لم ينته بعد."
- OpenAPI مجانًا. يمكن لـ Apidog إنشاء مستند OpenAPI 3.1 من الطلبات المحفوظة. يمكنك تسليم ذلك لأي شخص يريد عميلًا مُحدد النوع دون الحاجة إلى كتابة تعليق `utoipa` أو `aide` على كل مسار.
الخطوة 1: أضف خادم Rust الخاص بك كبيئة Apidog
ابدأ تشغيل واجهة برمجة تطبيقات Rust الخاصة بك. بالنسبة لمشروع Axum، تكون التعليمات البرمجية الأولية هي:
use axum::{routing::get, Router};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() {
let app = Router::new().route("/healthz", get(|| async { "ok" }));
let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
افتح Apidog، أنشئ مشروعًا جديدًا، ثم افتح إدارة البيئات (القائمة المنسدلة العلوية اليمنى) وأضف بيئة باسم `Rust Local`:
| المتغير | القيمة |
|---|---|
baseUrl |
http://localhost:3000 |
token |
اتركها فارغة في الوقت الحالي |
apiVersion |
v1 |
أضف بيئة ثانية باسم `Rust Staging` باستخدام عنوان URL الأساسي المنشور. يحدد Apidog نطاق المتغيرات لكل بيئة، لذلك يمكنك التبديل من المحلي إلى التجريبي بنقرة واحدة من القائمة المنسدلة. لا داعي للبحث والاستبدال في الطلبات المحفوظة.
الخطوة 2: استدعاء نقطة النهاية الأولى
أنشئ مجلدًا باسم `Rust API` داخل المشروع، ثم طلبًا جديدًا:
- الطريقة: `GET`
- عنوان URL: `{{baseUrl}}/healthz`
اضغط على إرسال (Send). إذا كان خادمك يعمل، فستتلقى استجابة `200` مع نص `ok`. احفظ هذا باسم `health-check`. إنه أبسط اختبار دخان ممكن، ويؤكد أن البيئة وعنوان URL الأساسي يعملان قبل أن تكتب أي شيء أكثر إثارة للاهتمام.
إذا تلقيت خطأ "رفض الاتصال" (connection refused)، فإن خادمك غير مرتبط بـ `0.0.0.0` أو أن المنفذ خاطئ. إعداد `TcpListener::bind("127.0.0.1:3000")` الافتراضي في Rust سيرفض الطلبات القادمة من أي شيء يُحل إلى `localhost` على واجهة مختلفة؛ قم بالربط بـ `0.0.0.0` للتطوير المحلي حتى يتمكن Apidog وحاويات Docker من الوصول إليه.
الخطوة 3: اختبار طلب واستجابة JSON باستخدام Serde
الشكل الأكثر شيوعًا لواجهة برمجة تطبيقات Rust هو معالج يقوم بإدخال JSON وإخراج JSON مدعوم بهيكل Serde. أضف مسار `POST /users`:
use axum::{extract::Json, routing::post, Router};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
#[derive(Serialize)]
struct User {
id: u64,
name: String,
email: String,
}
async fn create_user(Json(payload): Json<CreateUser>) -> Json<User> {
Json(User { id: 1, name: payload.name, email: payload.email })
}
let app = Router::new().route("/users", post(create_user));
في Apidog، أنشئ طلبًا:
- الطريقة: `POST`
- عنوان URL: `{{baseUrl}}/users`
- النص (JSON):
{
"name": "Ada Lovelace",
"email": "ada@example.com"
}
أرسله. ستحصل على JSON الخاص بـ `User` مرة أخرى. احفظه باسم `create-user`.
الآن افتح علامة تبويب "الاختبارات" (Tests) وأضف التأكيدات:
pm.test("Status is 200", () => {
pm.expect(pm.response.code).to.eql(200);
});
pm.test("Body has id, name, email", () => {
const body = pm.response.json();
pm.expect(body).to.have.property("id");
pm.expect(body.name).to.eql("Ada Lovelace");
pm.expect(body.email).to.match(/^[^@]+@[^@]+$/);
});
في المرة القادمة التي يضيف فيها شخص ما `#[serde(rename_all = "camelCase")]` إلى الهيكل ويتغير شكل استجابتك من `user_id` إلى `userId`، سيفشل هذا الاختبار قبل نشر التغيير. هذا هو العقد الذي يوفره لك Apidog ولا يوفره `cargo test`، لأن `cargo test` يقوم بتشغيل كود Rust الخاص بك مقابل أنواع Rust الخاصة بك وسيمر بسعادة مع أي من الشكلين.
الخطوة 4: تغطية حالات رفض Serde
الجزء المثير للاهتمام في معالجة JSON في Rust هو ما يفعله Serde مع الإدخال السيئ. بشكل افتراضي، يعيد Axum `422 Unprocessable Entity` بدون تفاصيل. أنشئ ثلاثة طلبات تكسر المخطط عمدًا:
| الطلب | نص الطلب | المتوقع |
|---|---|---|
create-user-missing-email |
{ "name": "Ada" } |
422، نص الطلب يذكر `missing field email` (حقل البريد الإلكتروني مفقود) |
create-user-extra-field |
{ "name": "Ada", "email": "a@b.c", "admin": true } |
200 إذا كان `#[serde(deny_unknown_fields)]` غائبًا؛ `422` بخلاف ذلك |
create-user-wrong-type |
{ "name": 1, "email": "a@b.c" } |
422، يذكر `invalid type: integer` (نوع غير صالح: عدد صحيح) |
أكد كل رمز حالة في الاختبارات. هذه هي أرخص طريقة لتوثيق سياسة التحقق الحقيقية الخاصة بك. إذا قمت بتشغيل `deny_unknown_fields` لاحقًا، فسيتغير الاختبار الثاني إلى اللون الأحمر ويخبرك أن العقد العام قد تغير.
الخطوة 5: اختبار المسارات المحمية بواسطة JWT
تخفي معظم واجهات برمجة تطبيقات Rust الإنتاجية المعالجات خلف طبقة وسيطة للمصادقة. يعد مستخرج JWT الخاص بـ `axum-extra` في Axum هو النمط الشائع:
use axum_extra::extract::cookie::PrivateCookieJar;
use jsonwebtoken::{decode, DecodingKey, Validation};
async fn me(jar: PrivateCookieJar) -> Result<Json<User>, StatusCode> {
let token = jar.get("token").ok_or(StatusCode::UNAUTHORIZED)?;
let claims = decode::<Claims>(token.value(), &DecodingKey::from_secret(b"secret"), &Validation::default())
.map_err(|_| StatusCode::UNAUTHORIZED)?;
Ok(Json(User { id: claims.claims.sub, name: "Ada".into(), email: "ada@example.com".into() }))
}
في Apidog، لا تحتاج إلى إنشاء JWT يدويًا في كل مرة يتم فيها تشغيل الاختبار. أنشئ نصًا برمجيًا قبل الطلب (Pre-Request Script) على المجلد:
const jwt = require("jsonwebtoken");
const token = jwt.sign(
{ sub: 1, exp: Math.floor(Date.now() / 1000) + 3600 },
"secret"
);
pm.environment.set("token", token);
افتح إعدادات المجلد، اضبط المصادقة (Auth) على "رمز حامل" (Bearer Token)، والقيمة `{{token}}`. الآن، يقوم كل طلب في المجلد بتوقيع وتقديم JWT جديد. تختفي أخطاء الرمز المميز القديم من تشغيلات الاختبار الخاصة بك. لمزيد من التفاصيل حول جانب التأكيد، راجع كيفية اختبار مصادقة JWT في واجهات برمجة التطبيقات.
الخطوة 6: اختبار التدفق وأحداث الخادم المرسلة (Server-Sent Events)
تتمتع أطر عمل الويب في Rust بالتدفق كفئة أولى. تستجيب `Sse` في Axum لـ `futures::Stream` وتصدر أجزاء `text/event-stream`. تنسيق السلك هو `data: { ... }\n\n` لكل إطار، وينتهي بإغلاق الاتصال أو حدث إنجاز صريح.
الطلب الذي يستهلك هذا يبدو مثل أي طلب GET، ولكن لوحة الاستجابة في Apidog تتحول إلى وضع التدفق عندما يكون `Content-Type` هو `text/event-stream`. ترى كل إطار عند وصوله، مع طوابع زمنية. هذا هو المنظر الذي تحتاجه عند تصحيح مشكلة الضغط الخلفي أو التدفق المفقود.
ماذا تؤكد:
- وصول الجزء الأول ضمن اتفاقية مستوى الخدمة (SLA) التي تعلن عنها. يعرض Apidog زمن الاستجابة لكل جزء في لوحة التدفق.
- يتم إطلاق اسم حدث معين قبل إغلاق الاتصال (مثلاً، `event: done`).
- المدة الإجمالية للتدفق محدودة. المعالج الذي ينسى الخروج من `while let Some(event) = stream.next().await` سيقوم بالتدفق إلى الأبد؛ سيعرض Apidog ذلك ويمكنك تعيين مهلة قصوى في إعدادات الطلب لفشل الاختبار.
إذا كانت نقطة النهاية الخاصة بك تستخدم WebSockets بدلاً من SSE، فإن Apidog لديه نوع طلب WebSocket منفصل. النمط هو نفسه: أنشئ الاتصال مرة واحدة، واحفظ تسلسل الرسائل، وتأكد من الاستجابات.
الخطوة 7: محاكاة واجهة برمجة تطبيقات Rust لتطوير الواجهة الأمامية المتوازي
نادرًا ما يتم حظر الواجهة الأمامية بسبب أوقات تجميع Rust. يتم حظرها بسبب معالجات غير موجودة بعد. تتيح لك نماذج Apidog الوهمية نشر عنوان URL مستقرًا يُرجع العقد الذي اتفق عليه أنت والواجهة الأمامية، قبل نشر المعالج.
انقر بزر الماوس الأيمن على `create-user`، واختر "Smart Mock"، ثم قم بتمكينه. الآن يقدم Apidog استجابة `User` اصطناعية على `https://mock.apidog.com/m1/<projectId>/users`. يتطابق نص النموذج الوهمي مع المثال الذي حفظته. يقبل عنوان URL للنموذج الوهمي نفس شكل النص، لذلك يمكن للواجهة الأمامية إرسال `POST` إليه كما لو كان خادم Rust الحقيقي.
للنماذج الوهمية الديناميكية، قم بالتبديل إلى "Advanced Mock" واكتب نصًا برمجيًا:
return {
id: Math.floor(Math.random() * 10000),
name: body.name,
email: body.email,
createdAt: new Date().toISOString()
};
يستجيب هذا النموذج الوهمي لأي شيء ترسله الواجهة الأمامية، مع `id` وطابع زمني تم إنشاؤهما. عندما يكون معالج Rust جاهزًا، تعيد الواجهة الأمامية عنوان URL الأساسي الخاص بها إلى `http://localhost:3000` ولا يتغير أي شيء آخر. لمزيد من المعلومات حول هذا النمط، يغطي الفريق أيضًا بناء واختبار واجهة برمجة تطبيقات Spring Boot وسير عمل اختبار واجهة برمجة التطبيقات العام؛ نفس الفكرة، بيئات تشغيل مختلفة.
الخطوة 8: الحفظ كسيناريو اختبار تكامل مستمر (CI)
تُسلسل سيناريوهات اختبار Apidog الطلبات بمتغيرات مشتركة وتشغلها بدون واجهة رسومية. أنشئ سيناريو:
- `health-check`، تأكيد `200`.
- `create-user`، تأكيد `200`، التقاط `body.id` في متغير.
- `create-user-missing-email`، تأكيد `422`.
- `me` (مع طلب JWT المسبق)، تأكيد `200` وتطابق `id` المُعاد مع `id` الملتقط.
- طلب SSE، تأكيد اكتمال التدفق في غضون 5 ثوانٍ.
صدر السيناريو كملف JSON، التزم به في مستودعك تحت `tests/apidog/`، واستدعه من CI:
- name: Run API contract tests
run: |
cargo build --release
./target/release/myserver &
sleep 2
apidog-cli run tests/apidog/contract.json --env "Rust Local"
الآن، يتم تشغيل كل طلب سحب (PR) يمس معالجًا مقابل ملف ثنائي Rust مباشر مع مجموعة العقود الكاملة. إذا تسبب تغيير اسم في Serde، أو تغيير رمز الحالة، أو تعديل في التحقق من JWT، في كسر الشكل العام، فإن CI يكتشف ذلك قبل أن يتحول زر الدمج إلى اللون الأخضر.
الخطوة 9: إنشاء OpenAPI من الطلبات المحفوظة
عندما تكون مجموعة الطلبات مستقرة، افتح قائمة "تصدير" (Export) في Apidog واختر OpenAPI 3.1. ستحصل على مستند مواصفات يغطي كل طلب محفوظ، مع نصوص الطلبات التي أرسلتها كأمثلة. قدم ذلك لأي شخص ينشئ عميلًا مُحدد النوع (TypeScript، Swift، Kotlin، Python) وسيحصلون على عقد يتطابق مع ما يُرجعه خادم Rust الخاص بك اليوم، وليس ما كتبه شخص يدويًا في ملف `.yaml` قبل ستة أشهر.
إذا كنت تريد إضافة المواصفات إلى مستودع Rust الخاص بك، فقم بتشغيل `apidog-cli export` من CI واكتبها إلى `openapi.json`. لن يتغير `cargo build` التالي، لكن كل مستهلك لواجهة برمجة تطبيقاتك سيحصل على الحقيقة على القرص.
الأسئلة الشائعة
هل يعمل Apidog مع Axum و Actix-web على حد سواء؟ نعم. يتحدث Apidog بلغة HTTP، وليس Rust. أي شيء يستجيب لطلب (Axum، Actix-web، Rocket، Warp، Poem، Loco) يعمل بنفس الطريقة. الاعتبار الوحيد الخاص بلغة Rust هو الربط بـ `0.0.0.0` بدلاً من `127.0.0.1` للاختبار المحلي.
كيف أختبر المعالجات التي تتعطل؟ قم بتشغيل خادمك مع `CatchPanicLayer` من `tower-http` أمام الموجه. يتحول التعطل إلى `500` مع نص JSON. أنشئ طلب Apidog يؤدي إلى مسار التعطل وتأكد من الـ `500`. إذا لم تقم بتغليف حالات التعطل، سيسقط الاتصال ويبلغ Apidog عن خطأ في الشبكة، وهو أيضًا اختبار عقد صالح.
هل يمكنني تشغيل Apidog ضد ملف Rust ثنائي في Docker؟ نعم. وجه `baseUrl` إلى المنفذ المكشوف للحاوية وقد انتهيت. إذا كانت الحاوية تعمل داخل Docker Compose، فامنح مشغل Apidog نفس الشبكة أو استخدم المنفذ المربوط للمضيف.
ماذا عن gRPC؟ Apidog لديه نوع طلب gRPC. قم باستيراد ملفات `.proto` الخاصة بك، واختر خدمة وطريقة، واملأ نص الطلب، ثم أرسل. نمط المصادقة والبيئات وسيناريوهات الاختبار مطابق لـ REST.
هل يحل سيناريو الاختبار محل `cargo test`؟ لا. تبقى اختبارات الوحدات لتعليمات Rust البرمجية الخاصة بك في Rust. يختبر Apidog الواجهة التشغيلية: عقد HTTP. تلتقط الطبقتان أخطاء مختلفة. يلتقط اختبار الوحدة دالة معطلة؛ ويلتقط اختبار Apidog شكل استجابة خاطئًا، أو رأس CORS مفقودًا، أو `400` الذي أصبح `422`. أنت تريد كليهما.
هل Apidog مجاني لمشاريع Rust مفتوحة المصدر؟ نعم. عميل Apidog مجاني للأفراد والفرق الصغيرة. سيناريوهات الاختبار، والمحاكاة، وتصدير OpenAPI هي جزء من الطبقة المجانية. إذا كنت تحتفظ بواجهة برمجة تطبيقات Rust عامة، يمكنك شحن ملف المشروع في مستودعك بحيث يحصل أي شخص يقوم بالاستنساخ على مجموعة الاختبارات.
الخلاصة
تستحق واجهات برمجة تطبيقات Rust حلقة تغذية راجعة لا تنتظر المترجم. مجموعة طلبات في Apidog تمنحك هذه الحلقة: HTTP حقيقي، تأكيدات حقيقية، نماذج وهمية حقيقية للواجهة الأمامية، وسيناريو CI يعمل ضد الملف الثنائي الحي. قم بإنشاء الطلبات المذكورة أعلاه مرة واحدة، وسيصبح كل تغيير مستقبلي لمعالج Axum أو Actix الخاص بك تشغيل اختبار متحكمًا بدلاً من مفاجأة وقت التشغيل.
قم بتنزيل Apidog ووجهه إلى خادم Rust الخاص بك. يستغرق الإعداد أقل من عشر دقائق. والمكسب هو عقد تتحكم فيه، منفصل عن `cargo`، وفريق واجهة أمامية يتوقف عن السؤال متى ينتهي المعالج.
