إنتقل إلى المحتوى الرئيسي

تعريف الواجهة البرمجية المخصصة

معرّف المخطط: 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 نفسه، قبل الوصول إلى هنا.

ما لا تفعله أبداً​

الواجهة المخصصة لا توسّع الوصول. تمر دوال السجلات في السكربت عبر الباب نفسه الذي تمر منه شاشات المنصة وتُنفَّذ بهوية المستدعي، فصلاحيته على ذلك الجدول وسياسة البيانات خلفها هما ما يقرّر ما يعود. العقد المخصص يمنح النظام الخارجي رؤية ثابتة، لا رؤية أوسع.