يُعد الحفاظ على الاتساق والجودة والالتزام بمعايير التصميم أمرًا بالغ الأهمية للفرق التي تستخدم واجهات برمجة التطبيقات (APIs). توفر مواصفات API مثل OpenAPI و AsyncAPI مخططًا تفصيليًا، ولكن ضمان اتباع هذه المخططات بشكل صحيح عبر العديد من الخدمات والفرق يمكن أن يكون تحديًا كبيرًا. هنا يأتي دور أدوات تدقيق API (API linting tools)، وتبرز Spectral كخيار مرن وقوي ومفتوح المصدر. عند دمجها مع TypeScript، تمكّن Spectral المطورين من إنشاء قواعد مخصصة قوية وآمنة من حيث النوع (type-safe)، مما يرتقي بإدارة API إلى مستوى جديد.
سيرشدك هذا البرنامج التعليمي خلال عملية الاستفادة من Spectral مع TypeScript، بدءًا من الإعداد الأولي وحتى صياغة منطق تحقق مخصص معقد. سنستكشف كيف يعزز TypeScript تطوير قواعد Spectral، مما يؤدي إلى حلول تدقيق API أكثر قابلية للصيانة وموثوقية.
هل تريد منصة متكاملة وشاملة لفريق المطورين لديك للعمل معًا بأقصى قدر من الإنتاجية؟
يلبي Apidog جميع متطلباتك، ويحل محل Postman بسعر أقل بكثير!
فهم Spectral: حارس مواصفات API الخاصة بك
قبل الغوص في تكامل TypeScript، دعنا نوضح ما هي Spectral ولماذا هي أداة قيمة في مجموعة أدوات تطوير API الخاصة بك.
Spectral هي أداة تدقيق (linter) مفتوحة المصدر لملفات JSON/YAML مع تركيز أساسي على تنسيقات وصف API مثل OpenAPI (v2 و v3) و AsyncAPI. هدفها هو المساعدة في فرض إرشادات تصميم API، واكتشاف الأخطاء الشائعة، وضمان الاتساق عبر بيئة API الخاصة بك. فكر فيها على أنها ESLint أو TSLint، ولكن خصيصًا لعقود API الخاصة بك.
الفوائد الرئيسية لاستخدام Spectral:
- الاتساق: يفرض تصميم API موحدًا عبر الفرق والمشاريع.
- ضمان الجودة: يكتشف الأخطاء والممارسات السيئة مبكرًا في دورة حياة التطوير.
- تحسين التعاون: يوفر فهمًا مشتركًا لمعايير API.
- الأتمتة: يتكامل بسلاسة في مسارات CI/CD للتحقق الآلي.
- قابلية التوسع: يسمح بإنشاء قواعد مخصصة مصممة خصيصًا لاحتياجات المؤسسة المحددة.
- نواة مستقلة عن التنسيق: بينما تتفوق في OpenAPI/AsyncAPI، يمكن لنواتها تدقيق أي بنية JSON/YAML.
تعمل Spectral بناءً على مجموعات القواعد (rulesets). مجموعة القواعد هي مجموعة من القواعد، حيث تستهدف كل قاعدة أجزاء محددة من مستند API الخاص بك (باستخدام تعبيرات JSONPath) وتطبق منطق التحقق. تأتي Spectral مع مجموعات قواعد مدمجة (مثل spectral:oas
لمعايير OpenAPI)، ولكن قوتها الحقيقية تكمن في القدرة على تحديد مجموعات قواعد مخصصة.
لماذا TypeScript لقواعد Spectral المخصصة؟
بينما يمكن تعريف مجموعات قواعد Spectral في YAML أو JavaScript (ملفات .js
)، فإن استخدام TypeScript لتطوير الوظائف المخصصة يوفر مزايا كبيرة:
- سلامة النوع (Type Safety): يكتشف TypeScript الأخطاء في وقت الترجمة بفضل كتابته الساكنة، مما يقلل من المفاجآت في وقت التشغيل في منطق التدقيق المخصص الخاص بك. هذا أمر بالغ الأهمية للقواعد المعقدة.
- تجربة مطور محسّنة: الإكمال التلقائي، وقدرات إعادة بناء الكود، وتحسين التنقل في الكود في بيئات التطوير المتكاملة (IDEs) تجعل كتابة وصيانة الوظائف المخصصة أسهل.
- تحسين قابلية القراءة والصيانة: تجعل الأنواع الصريحة نية وهيكل وظائفك المخصصة أكثر وضوحًا، خاصة للفرق.
- ميزات JavaScript الحديثة: استخدم ميزات ES الحديثة بثقة، حيث يقوم TypeScript بالتحويل إلى JavaScript متوافق.
- قابلية اختبار أفضل: تجعل الكتابة (typing) من الأسهل كتابة اختبارات وحدات قوية لوظائف Spectral المخصصة الخاصة بك.
من خلال كتابة وظائف Spectral المخصصة الخاصة بك في TypeScript، فإنك تجلب نفس الصرامة وفوائد الأدوات إلى كود إدارة API الخاص بك كما تفعل مع كود تطبيقك.
إعداد بيئة Spectral و TypeScript الخاصة بك
دعنا نبدأ العمل العملي ونعد الأدوات اللازمة.
المتطلبات الأساسية:
- Node.js و npm (أو yarn): Spectral هو تطبيق Node.js. تأكد من تثبيت Node.js (يُوصى بإصدار LTS) و npm (أو yarn).
- مشروع TypeScript: ستحتاج إلى مشروع TypeScript أو تكون مستعدًا لإعداد واحد.
خطوات التثبيت:
أولاً، ستحتاج إلى واجهة سطر الأوامر Spectral (Spectral CLI) لتشغيل عمليات التدقيق واختبار قواعدك. غالبًا ما يكون من المفيد تثبيتها عالميًا أو استخدام npx
.Bash
npm install -g @stoplight/spectral-cli
# أو
yarn global add @stoplight/spectral-cli
لتطوير قواعد مخصصة برمجياً واستخدام مكتبات Spectral الأساسية داخل مشروع TypeScript، قم بتثبيت حزم Spectral اللازمة:Bash
npm install @stoplight/spectral-core @stoplight/spectral-functions @stoplight/spectral-rulesets typescript ts-node --save-dev
# أو
yarn add @stoplight/spectral-core @stoplight/spectral-functions @stoplight/spectral-rulesets typescript ts-node --dev
دعنا نفصّل هذه الحزم:
@stoplight/spectral-core
: قلب Spectral، يحتوي على محرك التدقيق.@stoplight/spectral-functions
: يوفر مجموعة من الوظائف المدمجة التي يمكن لقواعدك استخدامها (مثلalphabetical
،defined
،pattern
،truthy
،xor
).@stoplight/spectral-rulesets
: يقدم مجموعات قواعد محددة مسبقًا مثلspectral:oas
(لـ OpenAPI) وspectral:asyncapi
.typescript
: مترجم TypeScript.ts-node
: يسمح لك بتشغيل ملفات TypeScript مباشرة دون ترجمة مسبقة، وهو مفيد للتطوير.
تهيئة TypeScript:
أنشئ ملف tsconfig.json
في جذر مشروعك إذا لم يكن لديك واحد بالفعل. قد تبدو التهيئة الأساسية كما يلي:JSON
{
"compilerOptions": {
"target": "es2020", // أو إصدار أحدث
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist", // دليل الإخراج لملفات JavaScript المترجمة
"rootDir": "./src", // دليل المصدر لملفات TypeScript الخاصة بك
"resolveJsonModule": true // يسمح باستيراد ملفات JSON
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
اضبط outDir
و rootDir
وفقًا لهيكل مشروعك. سنفترض أن وظائف TypeScript المخصصة الخاصة بك ستقع في الدليل src
.
مفاهيم Spectral الأساسية: القواعد، مجموعات القواعد، والوظائف
قبل كتابة وظائف TypeScript، دعنا نعزز فهمنا للمكونات الرئيسية لـ Spectral.
القواعد (Rules):
تُعرّف القاعدة فحصًا محددًا يجب إجراؤه. تشمل الخصائص الرئيسية للقاعدة ما يلي:
description
: شرح للقاعدة يمكن قراءته بواسطة البشر.message
: رسالة الخطأ أو التحذير التي تظهر إذا تم انتهاك القاعدة. يمكن أن تتضمن عناصر نائبة مثل{{error}}
،{{path}}
،{{value}}
.severity
: تحدد تأثير انتهاك القاعدة. يمكن أن تكونerror
(خطأ)،warn
(تحذير)،info
(معلومة)، أوhint
(تلميح).given
: تعبير JSONPath (أو مصفوفة منها) يحدد أجزاء المستند التي تنطبق عليها القاعدة.then
: تحدد الإجراء الذي يجب اتخاذه على القيم المستهدفة. يتضمن هذا عادةً تطبيق وظيفة واحدة أو أكثر.function
: اسم الوظيفة المدمجة أو المخصصة المراد تنفيذها.functionOptions
: الخيارات التي يتم تمريرها إلى الوظيفة.formats
: مصفوفة تحدد تنسيقات المستندات التي تنطبق عليها هذه القاعدة (مثلoas3
،oas2
،asyncapi2
).
مجموعات القواعد (Rulesets):
مجموعة القواعد هي ملف YAML أو JavaScript (مثل .spectral.yaml، .spectral.js، أو .spectral.ts عند الترجمة) يجمع القواعد. يمكنها أيضًا:
extends
: وراثة القواعد من مجموعات قواعد أخرى (مثل مجموعات قواعد Spectral المدمجة أو مجموعات قواعد تنظيمية مشتركة).rules
: كائن يحتوي على تعريفات القواعد المخصصة الخاصة بك.functionsDir
: يحدد دليلًا توجد فيه ملفات وظائف JavaScript المخصصة.functions
: مصفوفة من الوظائف المخصصة (أقل شيوعًا عند استخدامfunctionsDir
أو الإعداد البرمجي).
الوظائف (Functions):
الوظائف هي وحدات المنطق الأساسية التي تقوم بالتحقق الفعلي. توفر Spectral العديد من الوظائف المدمجة مثل:
truthy
: يتحقق مما إذا كانت القيمة صحيحة (truthy).falsy
: يتحقق مما إذا كانت القيمة خاطئة (falsy).defined
: يتحقق مما إذا كانت الخاصية معرفة.undefined
: يتحقق مما إذا كانت الخاصية غير معرفة.pattern
: يتحقق مما إذا كان النص يطابق تعبيرًا عاديًا (regex).alphabetical
: يتحقق مما إذا كانت عناصر المصفوفة أو مفاتيح الكائن مرتبة أبجديًا.length
: يتحقق من طول النص أو المصفوفة.schema
: يتحقق من صحة القيمة مقابل JSON Schema.
القوة الحقيقية تأتي عندما لا تكون هذه الوظائف كافية، وتحتاج إلى كتابة وظائف مخصصة خاصة بك - وهنا يبرز TypeScript.
صياغة أول وظيفة Spectral مخصصة لك في TypeScript
دعنا ننشئ وظيفة مخصصة بسيطة. تخيل أننا نريد فرض قاعدة تقضي بأن تكون جميع ملخصات عمليات API مكتوبة بصيغة العنوان (title-cased) ولا تتجاوز 70 حرفًا.
الخطوة 1: تعريف الوظيفة المخصصة في TypeScript
أنشئ ملفًا، على سبيل المثال src/customFunctions.ts
:TypeScript
import type { IFunction, IFunctionResult } from '@stoplight/spectral-core';
interface TitleCaseLengthOptions {
maxLength: number;
}
// وظيفة مخصصة للتحقق مما إذا كان النص بصيغة العنوان وضمن الحد الأقصى للطول
export const titleCaseAndLength: IFunction<string, TitleCaseLengthOptions> = (
targetVal,
options,
context
): IFunctionResult[] | void => {
const results: IFunctionResult[] = [];
if (typeof targetVal !== 'string') {
// لا ينبغي أن يحدث هذا إذا كان مسار 'given' يشير إلى نص، لكنها ممارسة جيدة
return [{ message: `القيمة في المسار '${context.path.join('.')}' يجب أن تكون نصًا.` }];
}
// التحقق من صيغة العنوان (تحقق بسيط: الحرف الأول من كل كلمة هو حرف كبير)
const words = targetVal.split(' ');
const isTitleCase = words.every(word => word.length === 0 || (word[0] === word[0].toUpperCase() && (word.length === 1 || word.substring(1) === word.substring(1).toLowerCase())));
if (!isTitleCase) {
results.push({
message: `الملخص "${targetVal}" في المسار '${context.path.join('.')}' يجب أن يكون بصيغة العنوان.`,
path: [...context.path], // المسار إلى العنصر المخالف
});
}
// التحقق من الطول
const maxLength = options?.maxLength || 70; // الافتراضي 70 إذا لم يتم توفيرها
if (targetVal.length > maxLength) {
results.push({
message: `الملخص "${targetVal}" في المسار '${context.path.join('.')}' يتجاوز الحد الأقصى للطول وهو ${maxLength} حرفًا. الطول الحالي: ${targetVal.length}.`,
path: [...context.path],
});
}
return results;
};
شرح:
- نقوم باستيراد
IFunction
وIFunctionResult
من@stoplight/spectral-core
لسلامة النوع.IFunction<T = unknown, O = unknown>
تأخذ وسيطين عامين:T
لنوعtargetVal
(القيمة التي يتم تدقيقها) وO
لنوعoptions
التي تم تمريرها إلى الوظيفة. targetVal
: القيمة الفعلية من مستند API التي يشير إليها مسار JSONPath فيgiven
. قمنا بتحديد نوعها على أنهاstring
.options
: كائن يحتوي على الخيارات التي تم تمريرها من تعريف القاعدة (مثل{ "maxLength": 70 }
). أنشأنا واجهةTitleCaseLengthOptions
لهذه الخيارات.context
: يوفر معلومات سياقية حول عملية التدقيق، بما في ذلك:path
: مصفوفة من مقاطع المسار التي تؤدي إلىtargetVal
.document
: المستند بأكمله بعد التحليل.rule
: القاعدة الحالية التي يتم معالجتها.- تعيد الوظيفة مصفوفة من كائنات
IFunctionResult
إذا كانت هناك انتهاكات، أوvoid
/undefined
/مصفوفة فارغة إذا لم تكن هناك مشاكل. يجب أن يحتوي كلIFunctionResult
علىmessage
ومسار اختياري (إذا كان مختلفًا عنcontext.path
). - يتحقق منطقنا من تنسيق بسيط لصيغة العنوان والحد الأقصى للطول.
الخطوة 2: ترجمة وظيفة TypeScript
إذا كنت تخطط لاستخدام هذه الوظيفة مع مجموعة قواعد .spectral.yaml
أو .spectral.js
تشير إلى functionsDir
، فستحتاج إلى ترجمة TypeScript إلى JavaScript.
أضف سكريبت بناء إلى ملف package.json
الخاص بك:JSON
{
"scripts": {
"build": "tsc"
}
}
شغّل npm run build
أو yarn build
. سيؤدي هذا إلى ترجمة src/customFunctions.ts
إلى dist/customFunctions.js
(بناءً على ملف tsconfig.json
الخاص بنا).
الخطوة 3: إنشاء ملف مجموعة القواعد
دعنا ننشئ مجموعة قواعد، على سبيل المثال، .spectral.js
(أو .spectral.ts
إذا كنت تفضل إعدادًا يعتمد بالكامل على TypeScript، انظر القسم التالي).
إذا كنت تستخدم ملف مجموعة قواعد JavaScript يشير مباشرة إلى الوظائف المترجمة:JavaScript
// .spectral.js
const { titleCaseAndLength } = require('./dist/customFunctions'); // المسار إلى وظائف JS المترجمة
module.exports = {
extends: [['@stoplight/spectral-rulesets/dist/rulesets/oas', 'recommended']],
rules: {
'operation-summary-title-case-length': {
description: 'يجب أن تكون ملخصات العمليات بصيغة العنوان ولا تتجاوز 70 حرفًا.',
message: '{{error}}', // الرسالة ستأتي من الوظيفة المخصصة
given: '$.paths[*][*].summary', // تستهدف جميع ملخصات العمليات
severity: 'warn',
formats: ["oas3"], // تطبق فقط على مستندات OpenAPI v3
then: {
function: titleCaseAndLength, // تشير مباشرة إلى الوظيفة المستوردة
functionOptions: {
maxLength: 70,
},
},
},
// يمكنك إضافة المزيد من القواعد هنا
},
};
بدلاً من ذلك، إذا كنت تستخدم .spectral.yaml
و functionsDir
:
أولاً، تأكد من أن دليل dist
يحتوي على ملف index.js
يقوم بتصدير وظائفك، أو أن ملف customFunctions.js
الخاص بك يقوم بتصديرها مباشرة. على سبيل المثال، إذا كان dist/customFunctions.js
يحتوي على exports.titleCaseAndLength = ...;
، يمكنك القيام بما يلي:YAML
# .spectral.yaml
extends:
- ["@stoplight/spectral-rulesets/dist/rulesets/oas", "recommended"]
functionsDir: "./dist" # الدليل الذي يحتوي على وظائف JS المخصصة المترجمة
rules:
operation-summary-title-case-length:
description: "يجب أن تكون ملخصات العمليات بصيغة العنوان ولا تتجاوز 70 حرفًا."
message: "{{error}}"
given: "$.paths[*][*].summary"
severity: "warn"
formats: ["oas3"]
then:
function: customFunctions#titleCaseAndLength # بافتراض تصدير الوظائف من customFunctions.js
functionOptions:
maxLength: 70
هنا، تخبر customFunctions#titleCaseAndLength
أداة Spectral بالبحث عن ملف customFunctions.js
(أو customFunctions/index.js
) في functionsDir
واستخدام الوظيفة المصدرة titleCaseAndLength
.
الخطوة 4: إنشاء مستند OpenAPI نموذجي
دعنا ننشئ ملف openapi.yaml
بسيطًا لاختبار قاعدتنا:YAML
# openapi.yaml
openapi: 3.0.0
info:
title: Sample API
version: 1.0.0
paths:
/items:
get:
summary: retrieves all items from the store # غير صحيح: ليس بصيغة العنوان
responses:
'200':
description: A list of items.
post:
summary: Adds A New Item To The Ever Expanding Collection Of Items In The Store # غير صحيح: طويل جدًا
responses:
'201':
description: Item created.
/users:
get:
summary: Get User Details # صحيح
responses:
'200':
description: User details.
الخطوة 5: تشغيل Spectral
الآن، قم بتنفيذ واجهة سطر الأوامر Spectral مقابل مستند OpenAPI الخاص بك:Bash
spectral lint openapi.yaml --ruleset .spectral.js
# أو إذا كنت تستخدم مجموعة قواعد YAML (غالبًا ما يتم اكتشافها تلقائيًا إذا كان اسمها .spectral.yaml)
spectral lint openapi.yaml
الإخراج المتوقع:
يجب أن ترى تحذيرات مشابهة لهذا:
openapi.yaml
2:10 warning operation-summary-title-case-length Summary "retrieves all items from the store" at path 'paths./items.get.summary' must be in Title Case. paths./items.get.summary
6:10 warning operation-summary-title-case-length Summary "Adds A New Item To The Ever Expanding Collection Of Items In The Store" at path 'paths./items.post.summary' exceeds maximum length of 70 characters. Current length: 78. paths./items.post.summary
✖ 2 problems (0 errors, 2 warnings, 0 infos, 0 hints)
يؤكد هذا الإخراج أن وظيفة TypeScript المخصصة الخاصة بنا، المترجمة إلى JavaScript، تحدد الانتهاكات بشكل صحيح.
الاستخدام البرمجي لـ Spectral مع مجموعات قواعد TypeScript
للسيناريوهات الأكثر تعقيدًا أو التكامل الأوثق في التطبيقات، قد ترغب في استخدام Spectral برمجياً وتحديد مجموعة القواعد الخاصة بك بالكامل في TypeScript. هذا يتجاوز الحاجة إلى ملف .spectral.yaml
أو .spectral.js
منفصل إذا رغبت في ذلك، ويسمح بإنشاء مجموعات قواعد ديناميكية.
أنشئ ملف TypeScript لأداة التدقيق الخاصة بك، على سبيل المثال src/linter.ts
:TypeScript
import { Spectral, Document } from '@stoplight/spectral-core';
import { oas } from '@stoplight/spectral-rulesets';
import { truthy } from '@stoplight/spectral-functions'; // مثال لوظيفة مدمجة
import { titleCaseAndLength } from './customFunctions'; // وظيفة TS المخصصة الخاصة بك
import type { ISpectralDiagnostic } from '@stoplight/spectral-core';
import * as fs from 'fs/promises';
import * as path from 'path';
// تعريف مثيل Spectral
const spectral = new Spectral();
// تحميل مجموعات القواعد والوظائف المدمجة
spectral.setRuleset({
extends: [[oas, 'recommended']], // يوسع قواعد OpenAPI الموصى بها
rules: {
'operation-summary-title-case-length': {
description: 'يجب أن تكون ملخصات العمليات بصيغة العنوان ولا تتجاوز 70 حرفًا.',
message: '{{error}}',
given: '$.paths[*][*].summary',
severity: 'warn',
formats: ["oas3"],
then: {
function: titleCaseAndLength, // استخدام وظيفة TypeScript مباشرة
functionOptions: {
maxLength: 70,
},
},
},
'info-contact-defined': { // مثال باستخدام وظيفة مدمجة
description: 'يجب أن يحتوي كائن Info على كائن contact.',
message: 'معلومات اتصال API مفقودة.',
given: '$.info',
severity: 'warn',
formats: ["oas3"],
then: {
field: 'contact',
function: truthy, // استخدام وظيفة مدمجة
},
},
},
});
// وظيفة لتدقيق مستند
export async function lintDocument(filePath: string): Promise<ISpectralDiagnostic[]> {
try {
const absolutePath = path.resolve(filePath);
const fileContent = await fs.readFile(absolutePath, 'utf-8');
// إنشاء كائن Spectral Document
// الوسيط الثاني (URI) مهم لحل المراجع النسبية ($refs) إن وجدت
const document = new Document(fileContent, undefined, absolutePath);
const results = await spectral.run(document);
return results;
} catch (error) {
console.error('خطأ في تدقيق المستند:', error);
return [];
}
}
// مثال للاستخدام (مثل في سكريبت أو وحدة أخرى)
async function main() {
const diagnostics = await lintDocument('openapi.yaml'); // المسار إلى مواصفات API الخاصة بك
if (diagnostics.length > 0) {
console.log('تم العثور على مشاكل في تدقيق API:');
diagnostics.forEach(issue => {
console.log(
`- [${issue.severity === 0 ? 'خطأ' : issue.severity === 1 ? 'تحذير' : issue.severity === 2 ? 'معلومة' : 'تلميح'}] ${issue.code} (${issue.message}) في ${issue.path.join('.')}`
);
});
} else {
console.log('لم يتم العثور على مشاكل في تدقيق API. عمل رائع!');
}
}
// إذا كنت ترغب في تشغيل هذا الملف مباشرة باستخدام ts-node
if (require.main === module) {
main().catch(console.error);
}
لتشغيل هذا:Bash
npx ts-node src/linter.ts
يوفر هذا النهج أقصى قدر من المرونة:
- لا توجد خطوة ترجمة لتعريف مجموعة القواعد: يتم تعريف مجموعة القواعد نفسها في TypeScript. لا تزال وظائفك المخصصة بحاجة إلى الاستيراد (إما كـ TS إذا تم استخدام
ts-node
، أو JS مترجمة إذا كنت تشغل Node خالصًا). - قواعد ديناميكية: يمكنك بناء أو تعديل مجموعات القواعد برمجياً بناءً على شروط وقت التشغيل.
- استخدام النوع المباشر: تستخدم وظائف TypeScript المستوردة مباشرة ضمن تعريف مجموعة القواعد، مما يعزز سلامة النوع وتكامل بيئة التطوير المتكاملة (IDE).
تقنيات متقدمة للوظائف المخصصة
دعنا نستكشف بعض الجوانب الأكثر تقدمًا لإنشاء وظائف مخصصة.
الوظائف المخصصة غير المتزامنة (Asynchronous Custom Functions):
إذا كانت وظيفتك المخصصة تحتاج إلى إجراء عمليات غير متزامنة (مثل جلب مورد خارجي للتحقق منه، على الرغم من استخدام ذلك بحذر للأداء)، يمكنك تعريفها كوظيفة async.TypeScript
import type { IFunction, IFunctionResult } from '@stoplight/spectral-core';
export const checkExternalResource: IFunction<string, { url: string }> =
async (targetVal, options, context): Promise<IFunctionResult[] | void> => {
try {
const response = await fetch(`${options.url}/${targetVal}`);
if (!response.ok) {
return [{ message: `المورد '${targetVal}' غير موجود في ${options.url}. الحالة: ${response.status}` }];
}
} catch (error: any) {
return [{ message: `خطأ في جلب المورد '${targetVal}': ${error.message}` }];
}
};
ستقوم Spectral بشكل صحيح بـ await
وظيفتك غير المتزامنة.
الوصول إلى المستند الكامل والقيم المحلولة (Resolved Values):
يوفر كائن context.document الوصول إلى المستند بأكمله بعد التحليل. والأكثر قوة، يوفر context.document.resolved الإصدار الكامل الذي تم إلغاء الإشارة إليه (dereferenced) من المستند، وهو أمر ضروري عند التعامل مع مؤشرات $ref.TypeScript
import type { IFunction, IFunctionResult } from '@stoplight/spectral-core';
import {isPlainObject} from '@stoplight/json';
// مثال: التأكد من أن مخططًا مشار إليه يحتوي على خاصية محددة
export const referencedSchemaHasProperty: IFunction<{$ref: string}, { propertyName: string }> = (
targetVal, // سيكون هذا الكائن مثل { $ref: '#/components/schemas/MySchema' }
options,
context
): IFunctionResult[] | void => {
if (!targetVal.$ref) return;
// يمكن لأسلوب `context.document.resolveAnchor` العثور على القيمة المحلولة لـ $ref
const resolvedValue = context.document.resolveAnchor(targetVal.$ref);
if (!resolvedValue || !isPlainObject(resolvedValue.value)) {
return [{ message: `تعذر حل $ref: ${targetVal.$ref}` }];
}
const schemaProperties = resolvedValue.value.properties as Record<string, unknown> | undefined;
if (!schemaProperties || schemaProperties[options.propertyName] === undefined) {
return [{
message: `المخطط المشار إليه بواسطة "${targetVal.$ref}" في المسار '${context.path.join('.')}' يجب أن يحتوي على الخاصية "${options.propertyName}".`,
path: [...context.path, '$ref'] // يشير إلى $ref نفسه
}];
}
};
سيتم استخدام هذه الوظيفة مع مسار given
يستهدف الكائنات التي تحتوي على $ref
، على سبيل المثال: $.paths[*].*.responses.*.content.*.schema
.
العمل مع التنسيقات:
تأكد من أن قواعدك المخصصة تحدد التنسيقات التي تنطبق عليها (مثل oas2، oas3، asyncapi2). هذا يمنع تشغيل القواعد على أنواع مستندات غير متوافقة. يمكنك الوصول إلى التنسيق المكتشف داخل وظيفتك عبر context.document.formats.
تنظيم وتوسيع مجموعات القواعد
مع نمو مجموعتك من القواعد المخصصة، يصبح التنظيم أمرًا أساسيًا.
- مجموعات قواعد معيارية (Modular Rulesets): قسّم مجموعات القواعد الكبيرة إلى ملفات أصغر ومركزة. يمكنك استخدام خاصية
extends
لدمجها. JavaScript
// .spectral.js (مجموعة القواعد الرئيسية)
module.exports = {
extends: ['./rulesets/common-rules.js', './rulesets/security-rules.js'],
// ... قواعد أو تجاوزات أخرى
};
- مجموعات قواعد مشتركة: انشر مجموعات القواعد كحزم npm لمشاركتها عبر المشاريع أو الفرق المختلفة. يمكن لخاصية
extends
بعد ذلك الإشارة إلى هذه الحزم. functionsDir
للتبسيط: إذا كان لديك العديد من وظائف JS المخصصة (المترجمة من TS)، يمكن أن يكونfunctionsDir
في مجموعة قواعد.spectral.yaml
أبسط من سردها جميعًا أو استخدام إعداد برمجي بالكامل. فقط تأكد من أن ملفات JS المترجمة تصدر الوظائف بشكل صحيح.
دمج Spectral في سير عملك
تتحقق القوة الحقيقية لتدقيق API عندما يتم أتمتتها.
- مسارات CI/CD: ادمج أوامر
spectral lint
في GitHub Actions، GitLab CI، Jenkins، أو مسارات CI/CD الأخرى. اجعل البناء يفشل إذا تم اكتشاف أخطاء حرجة. YAML
# مثال لخطوة GitHub Action
- name: Lint API Specification
run: spectral lint ./path/to/your/api-spec.yaml --ruleset ./.spectral.js --fail-severity=error
- خطافات Git (Git Hooks): استخدم أدوات مثل Husky لتشغيل Spectral على خطافات pre-commit أو pre-push، لاكتشاف المشاكل قبل أن تصل حتى إلى المستودع.
- تكامل بيئة التطوير المتكاملة (IDE Integration): ابحث عن ملحقات Spectral لبيئة التطوير المتكاملة الخاصة بك (مثل VS Code) للحصول على ملاحظات فورية أثناء كتابة مواصفات API الخاصة بك.
أفضل الممارسات لقواعد Spectral و TypeScript المخصصة
- وصف ورسائل واضحة: اكتب خصائص
description
وmessage
ذات معنى لقواعدك. يجب أن توجه الرسائل المستخدم حول كيفية إصلاح المشكلة. - مستويات شدة مناسبة: استخدم
error
للانتهاكات الحرجة،warn
للاقتراحات الهامة،info
للفحوصات المعلوماتية، وhint
للاقتراحات الثانوية. - مسارات
given
دقيقة: اجعل تعبيرات JSONPath الخاصة بك محددة قدر الإمكان لاستهداف العقد المقصودة فقط. هذا يحسن الأداء ويقلل من الإيجابيات الخاطئة. - وظائف عديمة الآثار الجانبية (Idempotent Functions): يجب أن تكون الوظائف المخصصة نقية حيثما أمكن - عند إعطائها نفس المدخلات، يجب أن تنتج نفس المخرجات دون آثار جانبية.
- اختبار قواعدك المخصصة: اكتب اختبارات وحدات لوظائف TypeScript المخصصة الخاصة بك للتأكد من أنها تتصرف كما هو متوقع مع مدخلات مختلفة.
- اعتبارات الأداء: كن حذرًا من الحسابات المعقدة أو العديد من استدعاءات وظيفة
schema
في القواعد الحساسة للأداء، خاصة للمستندات الكبيرة جدًا. اختبر أداء مجموعات القواعد الخاصة بك. - حافظ على تحديث أنواع TypeScript: تأكد من أن وظائفك المخصصة تحدد بشكل صحيح أنواع
targetVal
وoptions
التي تتوقعها. هذا أمر بالغ الأهمية للصيانة.
خاتمة: ارتقِ بإدارة API الخاصة بك باستخدام Spectral و TypeScript
توفر Spectral إطارًا قويًا لتدقيق API، ومن خلال الاستفادة من TypeScript لتطوير القواعد المخصصة، فإنك تجلب سلامة نوع محسّنة، وتجربة مطور أفضل، وقابلية صيانة لاستراتيجية إدارة API الخاصة بك. سواء كنت تفرض أفضل ممارسات OpenAPI، أو اصطلاحات تسمية خاصة بالشركة، أو منطق عمل معقدًا ضمن تصميمات API الخاصة بك، فإن الجمع بين محرك قواعد Spectral المرن ونظام كتابة الأنواع القوي في TypeScript يوفر حلاً قويًا.
من خلال دمج Spectral في دورة حياة التطوير الخاصة بك، من ملاحظات بيئة التطوير المتكاملة (IDE) إلى مسارات CI/CD، يمكنك ضمان أن واجهات API الخاصة بك متسقة، وعالية الجودة، وتلتزم بالمعايير المحددة، مما يؤدي في النهاية إلى واجهات API أكثر موثوقية وأسهل في الاستهلاك. ابدأ صغيرًا، كرر على مجموعات القواعد الخاصة بك، ومكّن فرقك بالأدوات اللازمة لبناء واجهات API أفضل.