Việc duy trì tính nhất quán, chất lượng và tuân thủ các tiêu chuẩn thiết kế là vô cùng quan trọng đối với các nhóm sử dụng API. Các đặc tả API như OpenAPI và AsyncAPI cung cấp một bản thiết kế, nhưng đảm bảo rằng các bản thiết kế này được tuân thủ đúng cách trên nhiều dịch vụ và nhóm có thể là một thách thức lớn. Đây là lúc các công cụ lint API phát huy tác dụng, và Spectral nổi bật như một lựa chọn mã nguồn mở linh hoạt và mạnh mẽ. Khi kết hợp với TypeScript, Spectral trao quyền cho các nhà phát triển tạo ra các quy tắc tùy chỉnh mạnh mẽ, an toàn kiểu dữ liệu, nâng quản trị API lên một tầm cao mới.
Hướng dẫn này sẽ đưa bạn qua quá trình tận dụng Spectral với TypeScript, từ thiết lập ban đầu đến việc tạo ra logic xác thực tùy chỉnh phức tạp. Chúng ta sẽ khám phá cách TypeScript nâng cao việc phát triển các quy tắc của Spectral, dẫn đến các giải pháp lint API dễ bảo trì và đáng tin cậy hơn.
Bạn muốn một nền tảng tích hợp, Tất cả trong một cho Đội ngũ Nhà phát triển của bạn làm việc cùng nhau với năng suất tối đa?
Apidog đáp ứng mọi yêu cầu của bạn, và thay thế Postman với mức giá phải chăng hơn nhiều!
Hiểu về Spectral: Người bảo vệ đặc tả API của bạn
Trước khi đi sâu vào tích hợp TypeScript, hãy cùng tìm hiểu Spectral là gì và tại sao nó là một công cụ có giá trị trong bộ công cụ phát triển API của bạn.
Spectral là một công cụ lint JSON/YAML mã nguồn mở tập trung chủ yếu vào các định dạng mô tả API như OpenAPI (v2 và v3) và AsyncAPI. Mục đích của nó là giúp thực thi các hướng dẫn thiết kế API, phát hiện các lỗi phổ biến và đảm bảo tính nhất quán trên toàn bộ bức tranh tổng thể về API của bạn. Hãy coi nó như ESLint hoặc TSLint, nhưng dành riêng cho các hợp đồng API của bạn.
Lợi ích chính khi sử dụng Spectral:
- Tính nhất quán: Thực thi thiết kế API đồng nhất trên các nhóm và dự án.
- Đảm bảo chất lượng: Bắt lỗi và các thực hành kém ngay từ đầu vòng đời phát triển.
- Cải thiện sự hợp tác: Cung cấp sự hiểu biết chung về các tiêu chuẩn API.
- Tự động hóa: Tích hợp liền mạch vào các pipeline CI/CD để xác thực tự động.
- Khả năng mở rộng: Cho phép tạo các quy tắc tùy chỉnh phù hợp với nhu cầu cụ thể của tổ chức.
- Lõi không phụ thuộc định dạng: Mặc dù xuất sắc với OpenAPI/AsyncAPI, lõi của nó có thể lint bất kỳ cấu trúc JSON/YAML nào.
Spectral hoạt động dựa trên bộ quy tắc (rulesets). Một bộ quy tắc là một tập hợp các quy tắc, trong đó mỗi quy tắc nhắm mục tiêu vào các phần cụ thể của tài liệu API của bạn (sử dụng biểu thức JSONPath) và áp dụng logic xác thực. Spectral đi kèm với các bộ quy tắc tích hợp sẵn (ví dụ: spectral:oas
cho các tiêu chuẩn OpenAPI), nhưng sức mạnh thực sự của nó nằm ở khả năng định nghĩa các bộ quy tắc tùy chỉnh.
Tại sao lại dùng TypeScript cho các quy tắc tùy chỉnh của Spectral?
Trong khi các bộ quy tắc của Spectral có thể được định nghĩa bằng YAML hoặc JavaScript (tệp .js
), việc sử dụng TypeScript để phát triển các hàm tùy chỉnh mang lại những lợi thế đáng kể:
- An toàn kiểu dữ liệu: Kiểu tĩnh của TypeScript bắt lỗi ở thời gian biên dịch, giảm thiểu các lỗi bất ngờ khi chạy trong logic lint tùy chỉnh của bạn. Điều này rất quan trọng đối với các quy tắc phức tạp.
- Cải thiện trải nghiệm nhà phát triển: Tự động hoàn thành, khả năng tái cấu trúc mã và điều hướng mã tốt hơn trong các IDE giúp viết và bảo trì các hàm tùy chỉnh dễ dàng hơn.
- Tăng cường khả năng đọc và bảo trì: Kiểu dữ liệu rõ ràng làm cho mục đích và cấu trúc của các hàm tùy chỉnh của bạn rõ ràng hơn, đặc biệt đối với các nhóm.
- Các tính năng JavaScript hiện đại: Tận dụng các tính năng ES hiện đại một cách tự tin, vì TypeScript biên dịch xuống JavaScript tương thích.
- Khả năng kiểm thử tốt hơn: Gõ kiểu giúp dễ dàng viết các kiểm thử đơn vị mạnh mẽ cho các hàm Spectral tùy chỉnh của bạn.
Bằng cách viết các hàm Spectral tùy chỉnh của bạn bằng TypeScript, bạn mang lại sự chặt chẽ và lợi ích công cụ tương tự cho mã quản trị API của bạn như đối với mã ứng dụng của bạn.
Thiết lập môi trường Spectral và TypeScript của bạn
Hãy bắt tay vào làm và thiết lập các công cụ cần thiết.
Điều kiện tiên quyết:
- Node.js và npm (hoặc yarn): Spectral là một ứng dụng Node.js. Đảm bảo bạn đã cài đặt Node.js (phiên bản LTS được khuyến nghị) và npm (hoặc yarn).
- Một Dự án TypeScript: Bạn sẽ cần một dự án TypeScript hoặc sẵn sàng thiết lập một dự án.
Các bước cài đặt:
Đầu tiên, bạn sẽ cần Spectral CLI để chạy các thao tác lint và kiểm thử quy tắc của bạn. Thường hữu ích khi cài đặt nó toàn cục hoặc sử dụng npx
.Bash
npm install -g @stoplight/spectral-cli
# or
yarn global add @stoplight/spectral-cli
Để phát triển các quy tắc tùy chỉnh theo lập trình và sử dụng các thư viện cốt lõi của Spectral trong một dự án TypeScript, hãy cài đặt các gói Spectral cần thiết:Bash
npm install @stoplight/spectral-core @stoplight/spectral-functions @stoplight/spectral-rulesets typescript ts-node --save-dev
# or
yarn add @stoplight/spectral-core @stoplight/spectral-functions @stoplight/spectral-rulesets typescript ts-node --dev
Hãy giải thích các gói này:
@stoplight/spectral-core
: Trái tim của Spectral, chứa công cụ lint.@stoplight/spectral-functions
: Cung cấp tập hợp các hàm tích hợp sẵn mà quy tắc của bạn có thể sử dụng (ví dụ:alphabetical
,defined
,pattern
,truthy
,xor
).@stoplight/spectral-rulesets
: Cung cấp các bộ quy tắc được định nghĩa sẵn nhưspectral:oas
(cho OpenAPI) vàspectral:asyncapi
.typescript
: Trình biên dịch TypeScript.ts-node
: Cho phép bạn chạy trực tiếp các tệp TypeScript mà không cần biên dịch trước, hữu ích cho việc phát triển.
Cấu hình TypeScript:
Tạo một tệp tsconfig.json
trong thư mục gốc dự án của bạn nếu bạn chưa có. Một cấu hình cơ bản có thể trông như thế này:JSON
{
"compilerOptions": {
"target": "es2020", // Or a newer version
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist", // Output directory for compiled JavaScript
"rootDir": "./src", // Source directory for your TypeScript files
"resolveJsonModule": true // Allows importing JSON files
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
Điều chỉnh outDir
và rootDir
theo cấu trúc dự án của bạn. Chúng ta sẽ giả định các hàm TypeScript tùy chỉnh của bạn sẽ nằm trong thư mục src
.
Các khái niệm cốt lõi của Spectral: Quy tắc, Bộ quy tắc và Hàm
Trước khi viết các hàm TypeScript, hãy củng cố hiểu biết của chúng ta về các thành phần chính của Spectral.
Quy tắc:
Một quy tắc định nghĩa một kiểm tra cụ thể sẽ được thực hiện. Các thuộc tính chính của một quy tắc bao gồm:
description
: Giải thích quy tắc dễ đọc.message
: Thông báo lỗi hoặc cảnh báo sẽ hiển thị nếu quy tắc bị vi phạm. Có thể bao gồm các chỗ giữ chỗ như{{error}}
,{{path}}
,{{value}}
.severity
: Định nghĩa tác động của việc vi phạm quy tắc. Có thể làerror
,warn
,info
, hoặchint
.given
: Một biểu thức JSONPath (hoặc một mảng các biểu thức) chỉ định các phần nào của tài liệu mà quy tắc áp dụng.then
: Định nghĩa hành động sẽ thực hiện trên các giá trị được nhắm mục tiêu. Điều này thường liên quan đến việc áp dụng một hoặc nhiều hàm.function
: Tên của hàm tích hợp sẵn hoặc hàm tùy chỉnh sẽ thực thi.functionOptions
: Các tùy chọn để truyền cho hàm.formats
: Một mảng chỉ định các định dạng tài liệu nào mà quy tắc này áp dụng (ví dụ:oas3
,oas2
,asyncapi2
).
Bộ quy tắc:
Một bộ quy tắc là một tệp YAML hoặc JavaScript (ví dụ: .spectral.yaml, .spectral.js, hoặc .spectral.ts khi biên dịch) nhóm các quy tắc. Nó cũng có thể:
extends
: Kế thừa quy tắc từ các bộ quy tắc khác (ví dụ: bộ quy tắc Spectral tích hợp sẵn hoặc bộ quy tắc dùng chung của tổ chức).rules
: Một đối tượng chứa định nghĩa quy tắc tùy chỉnh của bạn.functionsDir
: Chỉ định một thư mục nơi các tệp hàm JavaScript tùy chỉnh được đặt.functions
: Một mảng các hàm tùy chỉnh (ít phổ biến hơn khi sử dụngfunctionsDir
hoặc thiết lập theo lập trình).
Hàm:
Hàm là các đơn vị logic cốt lõi thực hiện xác thực thực tế. Spectral cung cấp nhiều hàm tích hợp sẵn như:
truthy
: Kiểm tra xem giá trị có phải truthy không.falsy
: Kiểm tra xem giá trị có phải falsy không.defined
: Kiểm tra xem thuộc tính có được định nghĩa không.undefined
: Kiểm tra xem thuộc tính có phải undefined không.pattern
: Kiểm tra xem chuỗi có khớp với regex không.alphabetical
: Kiểm tra xem các phần tử mảng hoặc khóa đối tượng có theo thứ tự bảng chữ cái không.length
: Kiểm tra độ dài của chuỗi hoặc mảng.schema
: Xác thực giá trị dựa trên JSON Schema.
Sức mạnh thực sự đến khi những hàm này không đủ, và bạn cần viết các hàm tùy chỉnh của riêng mình – đây là lúc TypeScript tỏa sáng.
Tạo hàm Spectral tùy chỉnh đầu tiên của bạn bằng TypeScript
Hãy tạo một hàm tùy chỉnh đơn giản. Hãy tưởng tượng chúng ta muốn thực thi một quy tắc rằng tất cả các tóm tắt hoạt động API phải được viết hoa tiêu đề (title-cased) và không vượt quá 70 ký tự.
Bước 1: Định nghĩa hàm tùy chỉnh bằng TypeScript
Tạo một tệp, ví dụ src/customFunctions.ts
:TypeScript
import type { IFunction, IFunctionResult } from '@stoplight/spectral-core';
interface TitleCaseLengthOptions {
maxLength: number;
}
// Custom function to check if a string is title-cased and within a max length
export const titleCaseAndLength: IFunction<string, TitleCaseLengthOptions> = (
targetVal,
options,
context
): IFunctionResult[] | void => {
const results: IFunctionResult[] = [];
if (typeof targetVal !== 'string') {
// Should not happen if 'given' path points to a string, but good practice
return [{ message: `Value at path '${context.path.join('.')}' must be a string.` }];
}
// Check for Title Case (simple check: first letter of each word is uppercase)
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: `Summary "${targetVal}" at path '${context.path.join('.')}' must be in Title Case.`,
path: [...context.path], // Path to the violating element
});
}
// Check for length
const maxLength = options?.maxLength || 70; // Default to 70 if not provided
if (targetVal.length > maxLength) {
results.push({
message: `Summary "${targetVal}" at path '${context.path.join('.')}' exceeds maximum length of ${maxLength} characters. Current length: ${targetVal.length}.`,
path: [...context.path],
});
}
return results;
};
Giải thích:
- Chúng ta nhập
IFunction
vàIFunctionResult
từ@stoplight/spectral-core
để đảm bảo an toàn kiểu dữ liệu.IFunction<T = unknown, O = unknown>
nhận hai đối số chung:T
cho kiểu củatargetVal
(giá trị đang được lint) vàO
cho kiểu củaoptions
được truyền cho hàm. targetVal
: Giá trị thực tế từ tài liệu API mà JSONPathgiven
trỏ tới. Chúng ta đã gõ kiểu nó làstring
.options
: Một đối tượng chứa các tùy chọn được truyền từ định nghĩa quy tắc (ví dụ:{ "maxLength": 70 }
). Chúng ta đã tạo một interfaceTitleCaseLengthOptions
cho các tùy chọn này.context
: Cung cấp thông tin ngữ cảnh về quá trình lint, bao gồm:path
: Một mảng các phân đoạn đường dẫn dẫn tớitargetVal
.document
: Toàn bộ tài liệu đã được phân tích cú pháp.rule
: Quy tắc hiện tại đang được xử lý.- Hàm trả về một mảng các đối tượng
IFunctionResult
nếu có vi phạm, hoặcvoid
/undefined
/mảng rỗng nếu không có vấn đề gì. MỗiIFunctionResult
phải có mộtmessage
và tùy chọn mộtpath
(nếu khác vớicontext.path
). - Logic của chúng ta kiểm tra định dạng viết hoa tiêu đề đơn giản và độ dài tối đa.
Bước 2: Biên dịch hàm TypeScript
Nếu bạn có kế hoạch sử dụng hàm này với bộ quy tắc .spectral.yaml
hoặc .spectral.js
trỏ tới một functionsDir
, bạn sẽ cần biên dịch TypeScript của mình sang JavaScript.
Thêm một script build vào package.json
của bạn:JSON
{
"scripts": {
"build": "tsc"
}
}
Chạy npm run build
hoặc yarn build
. Lệnh này sẽ biên dịch src/customFunctions.ts
sang dist/customFunctions.js
(dựa trên tsconfig.json
của chúng ta).
Bước 3: Tạo tệp bộ quy tắc
Hãy tạo một bộ quy tắc, ví dụ: .spectral.js
(hoặc .spectral.ts
nếu bạn thích thiết lập hoàn toàn dựa trên TypeScript, xem phần tiếp theo).
Nếu sử dụng tệp bộ quy tắc JavaScript tham chiếu trực tiếp đến các hàm đã biên dịch:JavaScript
// .spectral.js
const { titleCaseAndLength } = require('./dist/customFunctions'); // Path to your compiled JS functions
module.exports = {
extends: [['@stoplight/spectral-rulesets/dist/rulesets/oas', 'recommended']],
rules: {
'operation-summary-title-case-length': {
description: 'Operation summaries must be title-cased and not exceed 70 characters.',
message: '{{error}}', // The message will come from the custom function
given: '$.paths[*][*].summary', // Targets all operation summaries
severity: 'warn',
formats: ["oas3"], // Apply only to OpenAPI v3 documents
then: {
function: titleCaseAndLength, // Directly reference the imported function
functionOptions: {
maxLength: 70,
},
},
},
// You can add more rules here
},
};
Ngoài ra, nếu sử dụng .spectral.yaml
và functionsDir
:
Đầu tiên, đảm bảo thư mục dist
của bạn chứa một tệp index.js
xuất các hàm của bạn, hoặc tệp customFunctions.js
của bạn xuất chúng trực tiếp. Ví dụ, nếu dist/customFunctions.js
có exports.titleCaseAndLength = ...;
, bạn có thể làm như sau:YAML
# .spectral.yaml
extends:
- ["@stoplight/spectral-rulesets/dist/rulesets/oas", "recommended"]
functionsDir: "./dist" # Directory containing compiled custom JS functions
rules:
operation-summary-title-case-length:
description: "Operation summaries must be title-cased and not exceed 70 characters."
message: "{{error}}"
given: "$.paths[*][*].summary"
severity: "warn"
formats: ["oas3"]
then:
function: customFunctions#titleCaseAndLength # Assuming functions are exported from customFunctions.js
functionOptions:
maxLength: 70
Ở đây, customFunctions#titleCaseAndLength
cho Spectral biết hãy tìm tệp customFunctions.js
(hoặc customFunctions/index.js
) trong functionsDir
và sử dụng hàm titleCaseAndLength
được xuất.
Bước 4: Tạo tệp OpenAPI mẫu
Hãy tạo một tệp openapi.yaml
đơn giản để kiểm thử quy tắc của chúng ta: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 # Incorrect: not title case
responses:
'200':
description: A list of items.
post:
summary: Adds A New Item To The Ever Expanding Collection Of Items In The Store # Incorrect: too long
responses:
'201':
description: Item created.
/users:
get:
summary: Get User Details # Correct
responses:
'200':
description: User details.
Bước 5: Chạy Spectral
Bây giờ, thực thi Spectral CLI đối với tài liệu OpenAPI của bạn:Bash
spectral lint openapi.yaml --ruleset .spectral.js
# or if using YAML ruleset (often auto-detected if named .spectral.yaml)
spectral lint openapi.yaml
Kết quả dự kiến:
Bạn sẽ thấy các cảnh báo tương tự như sau:
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)
Kết quả này xác nhận rằng hàm TypeScript tùy chỉnh của chúng ta, được biên dịch sang JavaScript, đang xác định đúng các vi phạm.
Sử dụng Spectral theo lập trình với bộ quy tắc TypeScript
Đối với các kịch bản phức tạp hơn hoặc tích hợp chặt chẽ hơn vào các ứng dụng, bạn có thể muốn sử dụng Spectral theo lập trình và định nghĩa toàn bộ bộ quy tắc của mình bằng TypeScript. Điều này bỏ qua nhu cầu về tệp .spectral.yaml
hoặc .spectral.js
riêng biệt nếu muốn, và cho phép xây dựng bộ quy tắc động.
Tạo một tệp TypeScript cho công cụ lint của bạn, ví dụ src/linter.ts
:TypeScript
import { Spectral, Document } from '@stoplight/spectral-core';
import { oas } from '@stoplight/spectral-rulesets';
import { truthy } from '@stoplight/spectral-functions'; // Example built-in function
import { titleCaseAndLength } from './customFunctions'; // Your custom TS function
import type { ISpectralDiagnostic } from '@stoplight/spectral-core';
import * as fs from 'fs/promises';
import * as path from 'path';
// Define the Spectral instance
const spectral = new Spectral();
// Load built-in rulesets and functions
spectral.setRuleset({
extends: [[oas, 'recommended']], // Extends the recommended OpenAPI rules
rules: {
'operation-summary-title-case-length': {
description: 'Operation summaries must be title-cased and not exceed 70 characters.',
message: '{{error}}',
given: '$.paths[*][*].summary',
severity: 'warn',
formats: ["oas3"],
then: {
function: titleCaseAndLength, // Directly use the TypeScript function
functionOptions: {
maxLength: 70,
},
},
},
'info-contact-defined': { // Example using a built-in function
description: 'Info object should have a contact object.',
message: 'API contact information is missing.',
given: '$.info',
severity: 'warn',
formats: ["oas3"],
then: {
field: 'contact',
function: truthy, // Using a built-in function
},
},
},
});
// Function to lint a document
export async function lintDocument(filePath: string): Promise<ISpectralDiagnostic[]> {
try {
const absolutePath = path.resolve(filePath);
const fileContent = await fs.readFile(absolutePath, 'utf-8');
// Create a Spectral Document object
// The second argument (URI) is important for resolving relative $refs if any
const document = new Document(fileContent, undefined, absolutePath);
const results = await spectral.run(document);
return results;
} catch (error) {
console.error('Error linting document:', error);
return [];
}
}
// Example usage (e.g., in a script or another module)
async function main() {
const diagnostics = await lintDocument('openapi.yaml'); // Path to your API spec
if (diagnostics.length > 0) {
console.log('API Linting Issues Found:');
diagnostics.forEach(issue => {
console.log(
`- [${issue.severity === 0 ? 'Error' : issue.severity === 1 ? 'Warning' : issue.severity === 2 ? 'Info' : 'Hint'}] ${issue.code} (${issue.message}) at ${issue.path.join('.')}`
);
});
} else {
console.log('No API linting issues found. Great job!');
}
}
// If you want to run this file directly with ts-node
if (require.main === module) {
main().catch(console.error);
}
Để chạy mã này:Bash
npx ts-node src/linter.ts
Cách tiếp cận này cung cấp sự linh hoạt tối đa:
- Không cần bước biên dịch cho định nghĩa bộ quy tắc: Bản thân bộ quy tắc được định nghĩa bằng TypeScript. Các hàm tùy chỉnh của bạn vẫn cần được nhập (dưới dạng TS nếu sử dụng
ts-node
, hoặc JS đã biên dịch nếu chạy Node thuần túy). - Quy tắc động: Bạn có thể xây dựng hoặc sửa đổi bộ quy tắc theo lập trình dựa trên điều kiện thời gian chạy.
- Sử dụng kiểu dữ liệu trực tiếp: Bạn trực tiếp sử dụng các hàm TypeScript đã nhập của mình trong định nghĩa bộ quy tắc, tăng cường an toàn kiểu dữ liệu và tích hợp IDE.
Các kỹ thuật hàm tùy chỉnh nâng cao
Hãy cùng khám phá một số khía cạnh nâng cao hơn của việc tạo các hàm tùy chỉnh.
Hàm tùy chỉnh bất đồng bộ:
Nếu hàm tùy chỉnh của bạn cần thực hiện các thao tác bất đồng bộ (ví dụ: lấy tài nguyên bên ngoài để xác thực dựa trên, mặc dù hãy sử dụng cẩn thận vì hiệu suất), bạn có thể định nghĩa nó như một hàm 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: `Resource '${targetVal}' not found at ${options.url}. Status: ${response.status}` }];
}
} catch (error: any) {
return [{ message: `Error fetching resource '${targetVal}': ${error.message}` }];
}
};
Spectral sẽ `await` đúng cách hàm bất đồng bộ của bạn.
Truy cập toàn bộ tài liệu và các giá trị đã được giải quyết:
Đối tượng context.document cho phép bạn truy cập toàn bộ tài liệu đã được phân tích cú pháp. Mạnh mẽ hơn, context.document.resolved cung cấp phiên bản đã được giải tham chiếu đầy đủ của tài liệu, điều này cần thiết khi xử lý các con trỏ $ref.TypeScript
import type { IFunction, IFunctionResult } from '@stoplight/spectral-core';
import {isPlainObject} from '@stoplight/json';
// Example: Ensure a referenced schema has a specific property
export const referencedSchemaHasProperty: IFunction<{$ref: string}, { propertyName: string }> = (
targetVal, // This will be the object like { $ref: '#/components/schemas/MySchema' }
options,
context
): IFunctionResult[] | void => {
if (!targetVal.$ref) return;
// The `context.document.resolveAnchor` method can find the resolved value for a $ref
const resolvedValue = context.document.resolveAnchor(targetVal.$ref);
if (!resolvedValue || !isPlainObject(resolvedValue.value)) {
return [{ message: `Could not resolve $ref: ${targetVal.$ref}` }];
}
const schemaProperties = resolvedValue.value.properties as Record<string, unknown> | undefined;
if (!schemaProperties || schemaProperties[options.propertyName] === undefined) {
return [{
message: `The schema referenced by "${targetVal.$ref}" at path '${context.path.join('.')}' must have property "${options.propertyName}".`,
path: [...context.path, '$ref'] // Point to the $ref itself
}];
}
};
Hàm này sẽ được sử dụng với đường dẫn given
nhắm mục tiêu các đối tượng chứa $ref
, ví dụ: $.paths[*].*.responses.*.content.*.schema
.
Làm việc với định dạng:
Đảm bảo các quy tắc tùy chỉnh của bạn chỉ định các định dạng mà chúng áp dụng (ví dụ: oas2, oas3, asyncapi2). Điều này ngăn quy tắc chạy trên các loại tài liệu không tương thích. Bạn có thể truy cập định dạng đã phát hiện trong hàm của mình thông qua context.document.formats.
Tổ chức và mở rộng bộ quy tắc
Khi tập hợp các quy tắc tùy chỉnh của bạn tăng lên, việc tổ chức trở nên quan trọng.
- Bộ quy tắc module hóa: Chia nhỏ các bộ quy tắc lớn thành các tệp nhỏ hơn, tập trung hơn. Bạn có thể sử dụng thuộc tính
extends
để kết hợp chúng. JavaScript
// .spectral.js (main ruleset)
module.exports = {
extends: ['./rulesets/common-rules.js', './rulesets/security-rules.js'],
// ... other rules or overrides
};
- Bộ quy tắc dùng chung: Xuất bản bộ quy tắc dưới dạng gói npm để chia sẻ chúng giữa các dự án hoặc nhóm khác nhau. Thuộc tính
extends
sau đó có thể tham chiếu đến các gói này. functionsDir
để đơn giản hóa: Nếu bạn có nhiều hàm JS tùy chỉnh (được biên dịch từ TS),functionsDir
trong bộ quy tắc.spectral.yaml
có thể đơn giản hơn việc liệt kê tất cả chúng hoặc sử dụng thiết lập hoàn toàn theo lập trình. Chỉ cần đảm bảo các tệp JS đã biên dịch của bạn xuất các hàm đúng cách.
Tích hợp Spectral vào quy trình làm việc của bạn
Sức mạnh thực sự của việc lint API được hiện thực hóa khi nó được tự động hóa.
- Các pipeline CI/CD: Tích hợp các lệnh
spectral lint
vào GitHub Actions, GitLab CI, Jenkins, hoặc các pipeline CI/CD khác của bạn. Làm cho bản build thất bại nếu lỗi nghiêm trọng được phát hiện. YAML
# Example GitHub Action step
- name: Lint API Specification
run: spectral lint ./path/to/your/api-spec.yaml --ruleset ./.spectral.js --fail-severity=error
- Git Hooks: Sử dụng các công cụ như Husky để chạy Spectral trên các hook pre-commit hoặc pre-push, bắt lỗi trước khi chúng đến kho lưu trữ.
- Tích hợp IDE: Tìm kiếm các tiện ích mở rộng Spectral cho IDE của bạn (ví dụ: VS Code) để nhận phản hồi theo thời gian thực khi bạn viết các đặc tả API của mình.
Các thực hành tốt nhất cho Spectral và các quy tắc tùy chỉnh bằng TypeScript
- Mô tả và thông báo rõ ràng: Viết các thuộc tính
description
vàmessage
có ý nghĩa cho các quy tắc của bạn. Thông báo nên hướng dẫn người dùng cách khắc phục vấn đề. - Mức độ nghiêm trọng phù hợp: Sử dụng
error
cho các vi phạm nghiêm trọng,warn
cho các gợi ý quan trọng,info
cho các kiểm tra thông tin vàhint
cho các gợi ý nhỏ. - Đường dẫn
given
chính xác: Làm cho các biểu thức JSONPath của bạn càng cụ thể càng tốt để chỉ nhắm mục tiêu các nút mong muốn. Điều này cải thiện hiệu suất và giảm kết quả dương tính giả. - Hàm lũy đẳng (Idempotent Functions): Các hàm tùy chỉnh nên thuần khiết (pure) nếu có thể – khi nhận cùng một đầu vào, chúng nên tạo ra cùng một đầu ra mà không có tác dụng phụ.
- Kiểm thử các quy tắc tùy chỉnh của bạn: Viết kiểm thử đơn vị cho các hàm TypeScript tùy chỉnh của bạn để đảm bảo chúng hoạt động như mong đợi với các đầu vào khác nhau.
- Lưu ý về hiệu suất: Lưu ý đến các tính toán phức tạp hoặc nhiều lần gọi hàm
schema
trong các quy tắc nhạy cảm về hiệu suất, đặc biệt đối với các tài liệu rất lớn. Kiểm thử hiệu suất của bộ quy tắc của bạn. - Luôn cập nhật kiểu dữ liệu TypeScript: Đảm bảo các hàm tùy chỉnh của bạn gõ kiểu đúng cho
targetVal
vàoptions
mà chúng mong đợi. Điều này rất quan trọng cho khả năng bảo trì.
Kết luận: Nâng tầm quản trị API của bạn với Spectral và TypeScript
Spectral cung cấp một khung làm việc mạnh mẽ cho việc lint API, và bằng cách tận dụng TypeScript để phát triển quy tắc tùy chỉnh, bạn mang lại an toàn kiểu dữ liệu nâng cao, trải nghiệm nhà phát triển tốt hơn và khả năng bảo trì cho chiến lược quản trị API của mình. Dù bạn đang thực thi các thực hành tốt nhất của OpenAPI, các quy ước đặt tên riêng của công ty hay logic nghiệp vụ phức tạp trong thiết kế API của mình, sự kết hợp giữa công cụ quy tắc linh hoạt của Spectral và hệ thống gõ kiểu mạnh mẽ của TypeScript cung cấp một giải pháp mạnh mẽ.
Bằng cách tích hợp Spectral vào vòng đời phát triển của bạn, từ phản hồi từ IDE đến các pipeline CI/CD, bạn có thể đảm bảo API của mình nhất quán, chất lượng cao và tuân thủ các tiêu chuẩn đã định nghĩa của bạn, cuối cùng dẫn đến các API đáng tin cậy hơn và dễ sử dụng hơn. Hãy bắt đầu nhỏ, lặp lại trên bộ quy tắc của bạn và trao quyền cho các nhóm của bạn với các công cụ để xây dựng các API tốt hơn.