التحقق من مخطط API
التحقق من المخطط (Schema Validation) يفحص استجابة API مقابل مخطط: وصف قابل للقراءة آلياً للبنية التي يجب أن تكون عليها الاستجابة؛ أيّ الحقول موجودة وأيّها مطلوب، وما نوع كل قيمة، وما القيم والصيغ المسموحة، وهل يجوز ظهور حقول أخرى. وفي واجهات JSON يُكتب المخطط بلغة JSON Schema، ويوجد غالباً داخل مستند OpenAPI الخاص بالواجهة.
مثال ذلك أن مخطط طلب الشراء (Order) ينص على أن total رقم. فإذا أعادت الواجهة "total": "149.50"، أي سلسلة نصية، أبلغ المدقّق (Validator) عن مخالفة في الموضع $.total صادرة عن الكلمة المفتاحية type، مع أن أحداً لم يكتب تأكيداً (Assertion) لهذا الحقل. وبينما فحص التحقق من الاستجابة الحقولَ تأكيداً تلو آخر، يفحص المخطط البنية الكاملة لكل استجابة في خطوة واحدة.
لماذا يهم
تُبنى التطبيقات العميلة على البنية الموثّقة. فالحقل الذي يتغيّر نوعه، أو يختفي، أو يصبح null، أو يُعاد تسميته قد يُسقط تطبيق جوال أو يُفسد تقريراً بصمت بينما رمز الحالة ما زال 200 OK. ومثل هذه العيوب يسهل أن تفوت العين، وكثيراً ما تصيب نقاط نهاية عديدة دفعةً واحدة، كأن يحدث ذلك بعد تغيير في طريقة تحويل الخادم لكائناته إلى JSON، وهو ما يُسمّى التسلسل (Serialization).
وبالنسبة إلى المختبِر، المخطط مرجع جزئي للاختبار (Test Oracle)، أي مصدر للنتائج المتوقعة، يأتي مع وصف الواجهة. ففحص واحد يغطي عشرات الحقول، ويكشف الحقول غير الموثّقة التي قد تُسرّب بيانات داخلية، ويجعل كل تشغيل لاختبارات الانحدار فحصاً للبنية. كما أن مراجعة المخطط تفرض الأسئلة الصحيحة مبكراً: اختياري أم يقبل null؟ ما القيم المسموحة؟ هل يجوز للواجهة إضافة حقول؟
قراءة JSON Schema: الكلمات المفتاحية التي تؤدي معظم العمل
مخطط JSON Schema هو نفسه مستند JSON. كل كلمة مفتاحية فيه قاعدة يطبّقها المدقّق على الجزء المقابل من الاستجابة. وتحدّد $schema المسودة (Draft) التي يتبعها المخطط، والحالية هي 2020-12، لأن سلوك المدقّقات يختلف قليلاً بين المسودات.
يصف المخطط أدناه جسم الاستجابة في GET /orders/{orderId}، وهو مبني من الكلمات المفتاحية التي تستخدمها معظم المخططات:
type: أحد الأنواعobjectأوarrayأوstringأوnumberأوintegerأوbooleanأوnull. وقائمة مثل["string", "null"]تسمح بأيٍّ منهما.required: الحقول التي يجب أن توجد؛ والحقل المذكور فيpropertiesوحدها اختياري. وهي تفحص الوجود لا القيمة: فالحقل"couponCode": nullموجود.properties: قاعدة لكل حقل مسمّى، تُطبَّق عندما يكون الحقل موجوداً. وتؤديitemsالدور نفسه لكل عنصر في المصفوفة.enumوpatternوminimumوmaxLengthوأمثالها: القيم المسموحة وأنماط النصوص والحدود. والمطابقة فيenumتامة وتميّز حالة الأحرف. أماpatternفتعبير نمطي (Regular Expression): فالنمط^ORD-[0-9]+$يعني «يبدأ بـORD-، ثم رقم واحد أو أكثر، ثم ينتهي». ومن دون^و$قد يطابق النمط أي جزء من النص، فتجتاز القيمةx-ORD-1-yالفحص.format: صيغة مسمّاة مثلdate-timeأوemail. ويجوز للمدقّقات أن تعاملها تعليقاً توضيحياً (Annotation) فقط، وهذا هو الافتراضي في المسودة 2020-12، فتأكّد أن مدقّقك يرفض القيمة الخاطئة فعلاً.additionalProperties: هل يجوز ظهور حقول غير مذكورة فيproperties. فإذا غابت قُبل أي حقل إضافي، والقيمةfalseترفض الحقول الإضافية.
required تطلب الوجود فقط، وadditionalProperties: false ترفض الحقول غير الموثّقة، ولا يُحتسب format إلا إذا كان المدقّق يفحصه.مخطط JSON Schema لـ GET /orders/{orderId}
{ الجزء 1:"$schema": "https://json-schema.org/draft/2020-12/schema", الجزء 2:"type": "object", الجزء 3:"required": ["id", "status", "total", "createdAt", "items"], الجزء 4:"properties": { "id": { "type": "string", الجزء 5:"pattern": "^ORD-[0-9]+$" }, "status": { الجزء 6:"enum": ["pending", "paid", "shipped", "cancelled"] }, "total": { "type": "number", "minimum": 0 }, "createdAt": { "type": "string", الجزء 7:"format": "date-time" }, "couponCode": { الجزء 8:"type": ["string", "null"] }, "items": { "type": "array", الجزء 9:"minItems": 1, "items": { "type": "object", "required": ["sku", "qty", "price"], "properties": { "sku": { "type": "string" }, "qty": { "type": "integer", "minimum": 1 }, "price": { "type": "number", "minimum": 0 } } } } }, الجزء 10:"additionalProperties": false }
- 1
$schema: المسودة، وهي هنا 2020-12يختلف سلوك المدقّقات قليلاً بين المسودات - 2
type: يجب أن يكون الجسم كائن JSONتُرفض المصفوفة أو النص أوnullفي المستوى الأعلى - 3
required: هذه الحقول الخمسة يجب أن توجدالوجود فقط: الحقل الذي قيمتهnullموجود - 4
properties: قاعدة لكل حقل مسمّىتُطبَّق فقط عندما يكون الحقل موجوداً - 5
pattern: يجب أن يطابقidالتعبير النمطيلا يُثبَّت على بداية النص ونهايته إلا بـ^و$ - 6
enum: هذه القيم الأربع للحالة فقطمطابقة تامة تميّز حالة الأحرف:"Shipped"تفشل - 7
format: تاريخ ووقت وفق RFC 3339تعليق توضيحي افتراضياً في 2020-12: تأكّد أن مدقّقك يفحصه - 8اختياري ويقبل
null: الحقلcouponCodeليس فيrequired، وnullنوع مسموح - 9
minItems: بند واحد على الأقلمن دونها تجتاز القائمة الفارغة كل قاعدةitems - 10
additionalProperties: لا حقول غير موثّقةمن دونها يُقبل أي حقل إضافي
النسخة النصية
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["id", "status", "total", "createdAt", "items"],
"properties": {
"id": { "type": "string", "pattern": "^ORD-[0-9]+$" },
"status": { "enum": ["pending", "paid", "shipped", "cancelled"] },
"total": { "type": "number", "minimum": 0 },
"createdAt": { "type": "string", "format": "date-time" },
"couponCode": { "type": ["string", "null"] },
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["sku", "qty", "price"],
"properties": {
"sku": { "type": "string" },
"qty": { "type": "integer", "minimum": 1 },
"price": { "type": "number", "minimum": 0 }
}
}
}
},
"additionalProperties": false
}أين يوجد المخطط: OpenAPI
يصف مستند OpenAPI، المكتوب بصيغة YAML أو JSON، واجهة HTTP كاملة: المسارات، والطرق، والمعاملات، وأجسام الطلبات، ولكل رمز حالة جسمَ الاستجابة لكل نوع وسائط (Media Type). وتوصف الأجسام بمخططات تُعرَّف غالباً مرة واحدة تحت components/schemas ويُعاد استخدامها بالإشارة $ref. ويختار المدقّق المخطط حسب المسار والطريقة ورمز الحالة ونوع الوسائط، فاستجابة 404 تُفحص مقابل مخطط الخطأ لا مخطط طلب الشراء.
والإصدار مهم. يستخدم الإصداران OpenAPI 3.1 و3.2 مخططات JSON Schema 2020-12، مع بضع كلمات مفتاحية خاصة بـ OpenAPI. أما OpenAPI 3.0 فيستخدم مجموعة جزئية موسّعة من مسودة أقدم: تقبل type فيه قيمة واحدة فقط، ويُكتب الحقل الذي يقبل null بالشكل nullable: true بدلاً من قائمة أنواع. فاقرأ سطر openapi في أعلى المستند قبل أن تثق بنتائج أي مدقّق.
openapi: 3.1.0
info: { title: Orders API, version: 1.0.0 }
paths:
/orders/{orderId}:
get:
parameters:
- { name: orderId, in: path, required: true, schema: { type: string } }
responses:
'200':
description: The order
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
'404':
description: No order with this id
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Order:
type: object
required: [id, status, total, createdAt, items]
additionalProperties: false
properties:
id: { type: string, pattern: '^ORD-[0-9]+$' }
status: { enum: [pending, paid, shipped, cancelled] }
couponCode: { type: [string, 'null'] }
# ...وبقية الخصائص كما في مخطط JSON Schema أعلاه تماماً
Error:
type: object
required: [code, message]
properties:
code: { type: string }
message: { type: string }مثال تطبيقي: طلب شراء يعيد 200 OK
يستدعي المختبِر GET /orders/ORD-1042 فيتلقى 200 OK مع الجسم المعروض في آخر هذا القسم. فحص رمز الحالة ناجح، ولا شيء يبدو معطوباً للوهلة الأولى.
وعند التحقق من الجسم مقابل مخطط طلب الشراء تظهر خمس مخالفات، كلٌّ منها موضع في الاستجابة والكلمة المفتاحية التي رفضته، ونتيجة ناجحة تستحق الانتباه:
$.statusبالكلمةenum: القيمة"Shipped"غير مسموحة. فالقيمة الموثّقة هيshipped، والعميل الذي يقارن النصوص مقارنةً تامة لن يتعرّف عليها.$.totalبالكلمةtype: سلسلة نصية في موضع يتطلب رقماً. والعميل الذي يجمع المبالغ قد يلصق النصوص بعضها ببعض أو يتعطّل.$.createdAtبالكلمةformat: ليست تاريخاً ووقتاً وفق RFC 3339، إذ لا فاصلTولا ثوانٍ ولا فرق توقيت. ولا تظهر إلا إذا كان المدقّق يفحص الصيغ.$.items[0]بالكلمةrequired: الحقلpriceغائب عن بند الطلب.$.internalNoteبالكلمةadditionalProperties: حقل لا يسمح به العقد، وهو هنا بيانات داخلية. وتُدرج قائمة OWASP API Security Top 10 لعام 2023 كشف مثل هذه الخصائص تحت البند API3، أي Broken Object Property Level Authorization.- أما
couponCodeفقيمتهnullويجتاز الفحص: فهو اختياري ونوعه يسمح بالقيمةnull.
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": "ORD-1042",
"status": "Shipped",
"total": "149.50",
"createdAt": "2026-09-14 10:32",
"couponCode": null,
"items": [
{ "sku": "BK-201", "qty": 2 }
],
"internalNote": "VIP customer, call before delivery"
}ما لا يخبرك به التحقق من المخطط
يصف المخطط شكل البيانات الصالحة، لا صحتها. أصلِح كل المخالفات السابقة وقد تبقى الاستجابة خاطئة: فمع "total": 159.50 وبند واحد فيه قطعتان سعر كلٍّ منهما 74.75، ينجح الفحص مع أن مجموع البنود 149.50. فنجاح الفحص يعني فقط أن الاستجابة سليمة البنية مقارنةً بذلك الإصدار من المستند.
وتحتاج الأمور التالية إلى فحوص أخرى:
- القيم والحسابات: المجاميع والأعداد، وهل
idهو طلب الشراء الذي طلبته. فلا يستطيع JSON Schema مقارنة قيمتين أو إجراء حساب. - قواعد العمل والحالة: هل
shippedحالة تالية صحيحة لهذا الطلب. تستطيعif/thenالتعبير عن شروط بسيطة، لكنها لا ترى ما مرّ به الطلب. - التفويض: هل يحق لهذا المستدعي أن يرى هذا الطلب أصلاً.
- رموز الحالة والترويسات والزمن: مدقّق الجسم لا يراها، فتحقّق منها بتأكيدات منفصلة.
- المصفوفات الفارغة: قائمة
itemsالفارغة تجتاز أي قاعدةitems، فنقطة النهاية التي تعيد[]لا تختبر مخطط العنصر أبداً. استخدمminItemsحيث يضمن العقد وجود محتوى، وبيانات اختبار تُنتج قوائم غير فارغة. - المخطط نفسه: إذا كان المستند خاطئاً أو قديماً، وافقه المدقّق. والمخطط المولَّد من عيّنة واحدة يجعل عادةً كل حقل ظهر فيها مطلوباً وينسخ عيوب العيّنة؛ فراجعه مقابل المتطلبات أولاً.
أخطاء شائعة
- اعتبار نجاح الفحص بالمخطط دليلاً على أن «الاستجابة صحيحة». فهو يثبت البنية فقط، وتبقى القيم بحاجة إلى تأكيدات.
- افتراض أن الحقل المذكور في
propertiesإلزامي. وحدهاrequiredتجعله كذلك، وrequiredتقبلnull؛ فاستخدمtypeلمنعnull. - إغفال
additionalPropertiesفتفوتك الحقول المسرّبة، أو جعلهاfalseفي فحوص العميل دون اتفاق مع الفريق، فتكسر كل إضافة لحقل اختياري عملية البناء. - الثقة بـ
formatدون التأكد من أن المدقّق يفحصها: فقد تمرّ"email": "not-an-email"بصمت. - التحقق مقابل مخطط مولَّد من عيّنة واحدة، أو منسوخ في الاختبار قبل أشهر، بدلاً من مستند OpenAPI الحالي.
- التحقق من استجابات النجاح وحدها. فلاستجابات الخطأ (
400و404و422) مخططات أيضاً، والعملاء يحلّلونها.
من منظور ضمان الجودة
يبدأ العمل على المخطط قبل وجود أي استجابة. راجع مسودة مستند OpenAPI بوصفها أساساً للاختبار (Test Basis)، واسأل عن كل حقل: هل هو مطلوب؟ هل يمكن أن يكون null؟ ما القيم والصيغ المسموحة؟ ما حدوده؟ وهل الحقول الإضافية مقبولة؟ والغموض الذي تكتشفه هنا رخيص الإصلاح، ويصبح مادة لاختبارات الحدود والاختبارات السلبية في الدروس التالية.
وأثناء التنفيذ، أجرِ فحصاً بالمخطط على كل استجابة في اختبارات API لديك، وأضف تأكيدات للقيم التي لا يراها. وعندما يفشل الفحص، سجّل الطلب والموضع والكلمة المفتاحية والقاعدة المتوقعة وإصدار OpenAPI الذي استخدمته. ثم اسأل المطور أو مالك الواجهة أيهما الخاطئ: الواجهة أم المستند؛ فإصلاح الطرف الخطأ يُخفي العيب.
تمرين عملي
راجع مخطط منتج وتحقّق من استجابة
المتطلب: تعيد GET /products/{sku} الحقل sku (نص)، وname (من 1 إلى 120 حرفاً)، وprice بالريال (أكبر من 0)، وstock (عدد صحيح، 0 فأكثر)، وكلها موجودة دائماً، إضافةً إلى قائمة اختيارية tags من التسميات النصية. ولا يُسمح بأي حقول أخرى. كتب زميلك المخطط أدناه، وأعادت الواجهة الاستجابة التي تليه.
حدّد مواضع قصور المخطط عن المتطلب، ثم استنتج ما يبلّغ عنه كل إصدار من المخطط.
المخطط (مسودة الزميل):
{
"type": "object",
"required": ["sku", "name"],
"properties": {
"sku": { "type": "string" },
"name": { "type": "string", "maxLength": 120 },
"price": { "type": "number", "minimum": 0 },
"stock": { "type": "number" },
"tags": { "type": "array", "items": { "type": "string" } }
}
}
جسم الاستجابة:
{ "sku": "BK-201", "name": "", "stock": 2.5, "tags": ["new", 7], "costPrice": 41.2 }- قارن المخطط بالمتطلب كلمةً مفتاحية تلو أخرى، واكتب كل فجوة مع إصلاحها.
- الصق المخطط والاستجابة في أداة JSON Schema Validator، ودوّن ما تبلّغ عنه.
- صحّح المخطط وتحقّق مرة أخرى، واكتب كل مخالفة مع موضعها وكلمتها المفتاحية.
- اذكر أمرين يقتضيهما المتطلب ولا يستطيع أي مخطط فحصهما.
إظهار الإجابة النموذجية
الفجوات: يجب أن تضم required أيضاً price وstock؛ ويحتاج name إلى "minLength": 1؛ ويحتاج price إلى "exclusiveMinimum": 0 لأن "minimum": 0 تقبل الصفر؛ ويجب أن يكون stock من النوع "type": "integer" مع "minimum": 0؛ وينقص المخططَ "additionalProperties": false.
مخطط الزميل يبلّغ عن مخالفة واحدة: $.tags[1] بالكلمة type (فالقيمة 7 ليست نصاً). أما الاسم الفارغ، والسعر الغائب، والمخزون الكسري، والحقل الإضافي costPrice فتجتاز كلها.
والمخطط المصحّح يبلّغ عن خمس: $ بالكلمة required (لا سعر)، و$.name بالكلمة minLength، و$.stock بالكلمة type، و$.tags[1] بالكلمة type، و$.costPrice بالكلمة additionalProperties. والأخيرة تستحق تقرير خطأ مستقلاً، فسعر التكلفة بيانات تجارية داخلية.
لا يستطيع أي مخطط أن يتحقق من أن sku المُعاد هو المطلوب، ولا من أن price وstock يطابقان الكتالوج والمستودع. فهذه تحتاج إلى تأكيدات للقيم مقابل مصدر حقيقة معروف.
أسئلة شائعة
ما الفرق بين JSON Schema وOpenAPI؟
يصف JSON Schema قيمة JSON واحدة: حقولها وأنواعها وصيغها وحدودها. أما OpenAPI فيصف واجهة HTTP كاملة، من مسارات وطرق ومعاملات ورموز حالة وترويسات وأمان، ويستخدم المخططات لوصف الأجسام. ومخططات OpenAPI 3.1 و3.2 هي JSON Schema 2020-12 مع بضع كلمات مفتاحية خاصة بـ OpenAPI، بينما يستخدم OpenAPI 3.0 مجموعة جزئية موسّعة من مسودة أقدم.
هل التحقق من المخطط هو نفسه اختبار العقد؟
هو جزء منه. ففحص الاستجابات مقابل مستند OpenAPI الخاص بالمزوّد هو اختبار عقد قائم على المواصفة، وبعض الفرق تسمّي أي فحص بالمخطط اختبار عقد. ويشمل اختبار العقد (Contract Testing) أيضاً العقود التي يقودها المستهلك، ويشرحها درس لاحق.
هل يجب أن تضبط مخططات الاستجابة additionalProperties على false؟
في اختبارات المزوّد نفسه يستحق ذلك غالباً، لأنه يكشف الحقول غير الموثّقة والمسرّبة. أما العميل فالأفضل عادةً أن يتسامح مع الحقول الجديدة، وإلا كسرته كل إضافة. اتفق على القاعدة مع المطورين وسجّلها في نهج الاختبار لديكم، حتى يعني الفشل الشيء نفسه للجميع.
هل يستطيع المخطط فحص قواعد بين الحقول؟
جزئياً. تستطيع dependentRequired أن تقول «إذا وُجد refundId فيجب أن يوجد refundedAt أيضاً»، وتستطيع if/then/else أن تقول «إذا كانت status هي cancelled فإن cancelledAt مطلوب». لكن المخطط لا يستطيع مقارنة قيمتين أو إجراء حساب، لذا تبقى قواعد مثل «المجموع يساوي مجموع البنود» أو «تاريخ النهاية بعد تاريخ البداية» تأكيداتٍ للقيم.
المراجع
- JSON Schema Specificationالمفردات المستخدمة لوصف بنية مستندات JSON والتحقق منها (الأنواع والحقول المطلوبة والصيغ).
- OpenAPI Specificationالمعيار لوصف واجهات REST: المسارات والعمليات والمعاملات والمخططات والاستجابات وأنظمة الأمان.
- ISTQB Glossaryالتعريفات القياسية لمصطلحات الاختبار — حالة الاختبار واختبار الانحدار والخطورة والأولوية وتحليل القيم الحدّية وغيرها.
- OWASP API Security Top 10 (2023)أكثر عشر نقاط ضعف شيوعاً في واجهات API: خلل التفويض على مستوى الكائن، وخلل التوثيق، والاستهلاك غير المقيد للموارد، وغيرها.
تدرّب بأدوات متقن
التحقق من مستند JSON مقابل مخطط JSON Schema (الكلمات المفتاحية الأساسية في draft-07 / 2019-09 / 2020-12).
تحقّق من استجابات API وفق JSON Schema أو مخطط استجابة OpenAPI.
عرض مواصفة OpenAPI 3.x / Swagger 2.0 (بصيغة JSON أو YAML) كتوثيق قابل للتصفح لنقاط النهاية.
التحقق من مستندات OpenAPI 3.x وSwagger 2.0 للكشف عن الأخطاء البنيوية ومشكلات أفضل الممارسات.
توليد مخطط JSON Schema تلقائياً من مستند JSON نموذجي.
للتعمّق أكثر
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.