تعريف الواجهة البرمجية المخصصة
معرّف المخطط: custom-api@1
المخطط القابل للقراءة آلياً: custom-api.schema.json
التعريف المعياري لواجهة برمجية مخصصة (القسم 6.9): عقد HTTP ذو إصدارات تنشره بيئةٌ للأنظمة خارج المنصة، يملكه حلٌّ ويسافر في حزمته. يُتحقَّق من التعريف بالطريقة نفسها سواء جاء من مركز التحكم الإداري أو من استدعاء برمجي أو من مساعد ذكاء اصطناعي أو من استيراد حل.
البنية
| الحقل | النوع | مطلوب | ملاحظات |
|---|---|---|---|
logicalName | نص | نعم | مسبوق ببادئة الناشر، بحروف صغيرة وsnake_case. ثابت عبر الإصدارات. |
displayName | نص | نعم | ملخّص العملية في مستند OpenAPI. |
publisherPrefix | نص | نعم | وهو أيضاً مقطع من المسار المنشور. |
version | عدد صحيح | نعم | من 1 إلى 999. يُنشر كمقطع v{version} في المسار. |
route | نص | نعم | تحت مقطع الإصدار، بلا شرطة مائلة في البداية. المقطع بين قوسين معقوفين معامل مسار. |
verb | نص | نعم | Get أو Post أو Put أو Patch أو Delete. |
script | نص | نعم | شيفرة BS Lang التي تنفّذها هذه الواجهة. هناك تنفيذ واحد وهو هذا. |
requiredScope | نص | نعم | النطاق الذي يجب أن يحمله تسجيل تطبيق المستدعي. |
description | نص أو null | لا | وصف العملية في مستند OpenAPI. |
requestSchema | كائن أو null | لا | مخطط JSON يُفحص في كل استدعاء. يُرفض لفعل بلا جسم. null يعني أن أسماء getInput في النص نفسه هي العقد (تُنشر في OpenAPI؛ وفي استدعاء البوابة قائمة مغلقة). |
responseSchema | كائن أو null | لا | مخطط JSON يُنشر في مستند OpenAPI. |
responseContentType | نص | لا | application/json أو text/plain أو text/html — ما يجيب به العقد عادةً، وما ينشره مستند OpenAPI. |
isEnabled | منطقي | لا | التعريف المعطَّل يُحفظ ويُصدَّر ويعيد 404. |
الهوية زوجٌ لا مفرد
(logicalName, version) تحدّد الواجهة، و(publisherPrefix, version, verb, route) هي عنوانها.
par_approve_invoice بإصداريه 1 و2 عقدٌ واحد في عمرين، ويستجيبان جنباً إلى جنب ريثما ينتقل شريك
التكامل — فالتعريف الثاني الذي يتقاسم الاسم والإصدار تكرارٌ، أما الذي يتقاسم الاسم بإصدار جديد
فهو الإصدار التالي. ولا يجوز لتعريفين أبداً أن يتقاسما عنواناً واحداً مهما كانت أسماؤهما.
المسار المنشور
/api/{publisherPrefix}/v{version}/{route}
يُبلَغ اليوم تحت بيئة — /environments/{environmentId}/api/par/v1/invoices — لأن رمز بيانات اعتماد
العميل يسمّي مستأجراً لا إحدى بيئاته. أما الشكل العام الأقصر فيأتي مع البوابة التي
تربط اسم مضيف ببيئة.
ما الذي يُنفَّذ
شيء واحد: script. يسمّي التعريف مساراً وشيفرة BS Lang التي تجيب عليه، ولا شيء آخر يُختار.
تقرأ الطلب بـ getInput، وتجيب بـ setOutput أو setBody، وتختار الحالة ونوع المحتوى بـ
setStatus وsetContentType، وتضع الترويسات بـ setHeader، وتصل إلى جداول أخرى عبر دوال
السجلات، وترفض بـ throwValidationError.
كان التعريف فيما مضى يختار بين أربعة أنواع تنفيذ — عملية مباشرة على جدول، وتدفق أتمتة، ودالة، ورسالة إضافات مخصصة. ثلاثة منها كانت قابلة للتعريف ولا تُرسَل قط، فيجيب الاستدعاء بـ 501 مسمّياً التكرار الذي ينتظره، ولا يعمل إلا الرابع. ذلك زال. فالعقد الذي يكون سلوكه شيفرة صانعه نفسه أسهل شرحاً وأسهل إصداراً، وهو الوحيد من الأربعة القادر على التعبير عمّا لم تتوقعه المنصة — ولم يعد هناك 501، لأنه لم يعد هناك نوع غير موصول ينتجه.
المنطق الجدير بالمشاركة بين واجهتين هو سكربت حلّ يُستورد بالاسم، لا نوعٌ خامس.
قواعد لا يستطيع المخطط ذكرها
تُفحص عند حفظ التعريف، إضافةً إلى المخطط أعلاه:
- كون
responseContentTypeالمُعلن مما تستطيع المنصة تقديمه. - إعلان مسار
ReadأوUpdateأوDeleteللمقطع{id}— وهو المكان الوحيد الذي يأتي منه معرّف السجل. - الفعل بلا جسم ليس له مخطط طلب. ويمكن للفعل ذي الجسم أن يتركه null.
- قابلية مخططَي الطلب والاستجابة للترجمة كمخطط JSON.
- تحليل السكربت كـ BS Lang، ووصول كل وحدة يستوردها إلى سكربت حلّ.
مثال
{
"logicalName": "par_create_invoice",
"displayName": "Create an invoice",
"publisherPrefix": "par",
"description": "Creates an invoice from a validated body.",
"version": 1,
"route": "invoices",
"verb": "Post",
"requestSchema": {
"type": "object",
"additionalProperties": false,
"required": ["par_name", "par_amount"],
"properties": {
"par_name": { "type": "string", "maxLength": 200 },
"par_amount": { "type": "number", "exclusiveMinimum": 0 }
}
},
"responseContentType": "application/json",
"requiredScope": "modelapps.write",
"isEnabled": true,
"script": "var id = createRecord('par_invoice', {
par_name: getInput('par_name'),
par_amount: getInput('par_amount')
});
setOutput('id', id);"
}
يُفحص الجسم بـ requestSchema قبل تنفيذ السكربت، فيقرأ getInput شكلاً تم التحقق منه سلفاً،
لا شكلاً على كل سكربت أن يدافع عن نفسه أمامه.
المدخل قد يكون كائنًا أو قائمة
يسلّم getInput للسكربت ما تحمله الخاصية — نصًا أو رقمًا أو قيمة منطقية، أو كائنًا أو
مصفوفة تُنقل كاملة، يتنقل فيها السكربت بالعضو (f.fileName) وبـ length(). فالعقد الذي
يصف مدخلًا مُهيكلًا في requestSchema يستطيع فعلًا قراءته:
"par_document": {
"type": "object",
"additionalProperties": false,
"required": ["fileName", "contentType", "contentBase64"],
"properties": {
"fileName": { "type": "string", "maxLength": 260 },
"contentType": { "type": "string", "maxLength": 200 },
"sizeBytes": { "type": "integer" },
"contentBase64": { "type": "string", "maxLength": 5600000 }
}
}
حتى جولة البوابات ١٠ كان الكائن يصل كـ نص JSON خام، ولا وسيلة لدى BS Lang لتحليله — فكان من يعلن الشكل الذي يريده أسوأ حالًا ممّن يفرده خصائص متفرقة، وهذا عكس الغاية من المخطط. والتعشيش محدود بـ ٤٦ مستوى هي حدّ محلّل JSON نفسه، قبل الوصول إلى هنا.
ما لا تفعله أبداً
الواجهة المخصصة لا توسّع الوصول. تمر دوال السجلات في السكربت عبر الباب نفسه الذي تمر منه شاشات المنصة وتُنفَّذ بهوية المستدعي، فصلاحيته على ذلك الجدول وسياسة البيانات خلفها هما ما يقرّر ما يعود. العقد المخصص يمنح النظام الخارجي رؤية ثابتة، لا رؤية أوسع.