Skip to content

OpenAPI 规范验证

确保 API 规范的正确性和一致性

为什么要验证?

OpenAPI 规范验证能够:

  • 语法检查: 确保 YAML/JSON 格式正确
  • 📋 规范合规: 符合 OpenAPI 3.0/3.1 标准
  • 🔍 引用完整: 检查 $ref 引用的有效性
  • 🎯 最佳实践: 遵循 API 设计最佳实践
  • 🚨 早期发现: 在开发早期发现问题

验证工具对比

工具特点适用场景
Redocly CLI功能全面,规则丰富企业级项目
Spectral可扩展,自定义规则需要定制化验证
OpenAPI Generator集成在生成流程中代码生成时验证
Swagger Editor在线实时验证快速验证
openapi-spec-validatorPython 库,轻量级脚本集成

Redocly CLI 验证

基础验证

bash
# 验证单个文件
redocly lint openapi.yaml

# 验证并显示详细信息
redocly lint openapi.yaml --format=stylish

# 只显示错误(忽略警告)
redocly lint openapi.yaml --errors-only

# 生成报告
redocly lint openapi.yaml --format=json > report.json

配置验证规则

yaml
# redocly.yaml
lint:
  extends:
    - recommended  # 使用推荐规则集
    
  rules:
    # 信息完整性
    info-contact: error
    info-license: error
    info-description: error
    
    # 操作规范
    operation-summary: error
    operation-description: warning
    operation-operationId: error
    operation-operationId-unique: error
    operation-tags: error
    operation-tag-defined: error
    
    # 参数验证
    parameter-description: warning
    path-params-defined: error
    path-not-include-query: error
    
    # 响应验证
    operation-success-response: error
    operation-4xx-response: warning
    
    # 安全性
    operation-security-defined: error
    security-defined: error
    
    # 组件使用
    no-unused-components: warning
    
    # 命名规范
    operation-operationId-valid-in-url: error
    path-segment-plural: warning

自定义规则

yaml
# custom-rules.yaml
rules:
  # 中文描述检查
  chinese-description:
    description: "描述必须包含中文说明"
    message: "{{property}} 应该包含中文描述"
    given: 
      - "$.info"
      - "$.paths.*.*"
    then:
      field: description
      function: pattern
      functionOptions:
        match: "[\u4e00-\u9fa5]"
  
  # 响应示例检查
  response-examples:
    description: "响应必须包含示例"
    given: "$.paths.*.*.responses.*.content.application/json"
    then:
      field: example
      function: truthy
      
  # 分页参数检查
  pagination-parameters:
    description: "列表接口必须支持分页"
    given: "$.paths.*.get[?(@.operationId =~ /list|get.*s$/i)]"
    then:
      field: parameters
      function: schema
      functionOptions:
        schema:
          type: array
          contains:
            type: object
            properties:
              name:
                enum: ["page", "size", "limit", "offset"]

Spectral 验证

安装和使用

bash
# 安装
npm install -g @stoplight/spectral-cli

# 基础验证
spectral lint openapi.yaml

# 使用特定规则集
spectral lint openapi.yaml --ruleset=.spectral.yaml

# 输出格式
spectral lint openapi.yaml --format=junit > report.xml

Spectral 配置

yaml
# .spectral.yaml
extends:
  - spectral:oas
  - spectral:asyncapi

rules:
  # 覆盖默认规则
  info-contact: error
  info-description: error
  
  # 自定义规则
  my-custom-rule:
    description: 自定义规则描述
    given: "$.paths.*.*"
    severity: error
    then:
      - field: "summary"
        function: truthy
      - field: "description"
        function: truthy
        
  # 路径规则
  paths-kebab-case:
    description: 路径必须使用 kebab-case
    given: "$.paths"
    then:
      field: "@key"
      function: pattern
      functionOptions:
        match: "^/[a-z0-9]+(?:-[a-z0-9]+)*(?:/[a-z0-9]+(?:-[a-z0-9]+)*)*$"
        
  # 模型命名规则
  schema-names-pascal-case:
    description: Schema 名称必须使用 PascalCase
    given: "$.components.schemas"
    then:
      field: "@key"
      function: pattern
      functionOptions:
        match: "^[A-Z][a-zA-Z0-9]*$"

自定义函数

javascript
// .spectral/functions/customFunction.js
module.exports = (input, options, context) => {
  const { path } = context;
  
  // 自定义验证逻辑
  if (input && !input.includes('example')) {
    return [
      {
        message: `${path} 缺少示例`,
        path: [...context.path]
      }
    ];
  }
  
  return [];
};

// 使用自定义函数
// .spectral.yaml
functions:
  - customFunction

rules:
  require-examples:
    given: "$.paths.*.*.responses.*.content.*.examples"
    then:
      function: customFunction

OpenAPI Generator 验证

命令行验证

bash
# 验证规范
openapi-generator-cli validate -i openapi.yaml

# 跳过验证生成代码
openapi-generator-cli generate \
  -i openapi.yaml \
  -g spring \
  -o ./generated \
  --skip-validate-spec

# 使用建议模式(显示改进建议)
openapi-generator-cli validate -i openapi.yaml --recommend

编程方式验证

java
// Java 示例
import org.openapitools.codegen.validations.oas.OpenApiEvaluator;

public class ApiValidator {
    public static void validateSpec(String specPath) {
        OpenApiEvaluator evaluator = new OpenApiEvaluator();
        List<ValidationMessage> messages = evaluator.validate(specPath);
        
        messages.forEach(msg -> {
            System.out.println(msg.getSeverity() + ": " + msg.getMessage());
        });
        
        if (messages.stream().anyMatch(m -> m.getSeverity() == Severity.ERROR)) {
            throw new ValidationException("API 规范验证失败");
        }
    }
}

Python 验证工具

openapi-spec-validator

python
# 安装
pip install openapi-spec-validator

# 命令行使用
openapi-spec-validator openapi.yaml

# Python 代码使用
from openapi_spec_validator import validate_spec_url
from openapi_spec_validator.readers import read_from_filename

# 验证文件
spec_dict, spec_url = read_from_filename('openapi.yaml')
validate_spec_url(spec_dict, spec_url)

# 验证 URL
validate_spec_url('https://api.example.com/openapi.json')

自定义验证脚本

python
import yaml
import json
from openapi_spec_validator import validate_spec

def validate_api_spec(filename):
    """验证 OpenAPI 规范"""
    # 读取文件
    with open(filename, 'r', encoding='utf-8') as f:
        if filename.endswith('.yaml') or filename.endswith('.yml'):
            spec = yaml.safe_load(f)
        else:
            spec = json.load(f)
    
    # 基础验证
    try:
        validate_spec(spec)
        print("✅ OpenAPI 规范验证通过")
    except Exception as e:
        print(f"❌ 验证失败: {e}")
        return False
    
    # 自定义验证
    errors = []
    
    # 检查必需字段
    if 'info' not in spec or 'contact' not in spec['info']:
        errors.append("缺少联系信息")
    
    # 检查操作 ID
    for path, methods in spec.get('paths', {}).items():
        for method, operation in methods.items():
            if method in ['get', 'post', 'put', 'delete', 'patch']:
                if 'operationId' not in operation:
                    errors.append(f"{method.upper()} {path} 缺少 operationId")
                if 'summary' not in operation:
                    errors.append(f"{method.upper()} {path} 缺少 summary")
    
    # 检查组件使用
    components = spec.get('components', {})
    schemas = components.get('schemas', {})
    used_schemas = set()
    
    # 递归查找引用
    def find_refs(obj):
        if isinstance(obj, dict):
            if '$ref' in obj:
                ref = obj['$ref']
                if ref.startswith('#/components/schemas/'):
                    schema_name = ref.split('/')[-1]
                    used_schemas.add(schema_name)
            for value in obj.values():
                find_refs(value)
        elif isinstance(obj, list):
            for item in obj:
                find_refs(item)
    
    find_refs(spec)
    
    # 查找未使用的组件
    for schema_name in schemas:
        if schema_name not in used_schemas:
            errors.append(f"未使用的 schema: {schema_name}")
    
    # 输出结果
    if errors:
        print("\n⚠️  自定义验证发现问题:")
        for error in errors:
            print(f"  - {error}")
        return False
    
    return True

if __name__ == "__main__":
    import sys
    if len(sys.argv) > 1:
        validate_api_spec(sys.argv[1])
    else:
        print("Usage: python validate.py <openapi-spec-file>")

VS Code 集成

扩展配置

json
// .vscode/settings.json
{
  "openapi.validate": true,
  "openapi.linting": {
    "enable": true,
    "rules": {
      "info-contact": "error",
      "info-license": "error",
      "operation-operationId": "error",
      "operation-summary": "error",
      "oas3-schema": "error"
    }
  },
  "yaml.schemas": {
    "https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.0/schema.json": "openapi.yaml"
  }
}

任务配置

json
// .vscode/tasks.json
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Validate OpenAPI",
      "type": "shell",
      "command": "redocly lint ${file}",
      "problemMatcher": {
        "owner": "openapi",
        "pattern": {
          "regexp": "^(.+):(\\d+):(\\d+)\\s+(error|warning)\\s+(.+)$",
          "file": 1,
          "line": 2,
          "column": 3,
          "severity": 4,
          "message": 5
        }
      },
      "group": {
        "kind": "test",
        "isDefault": true
      }
    }
  ]
}

CI/CD 集成验证

GitHub Actions

yaml
# .github/workflows/validate-api.yml
name: Validate OpenAPI

on:
  pull_request:
    paths:
      - 'api/**/*.yaml'
      - 'api/**/*.yml'

jobs:
  validate:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Validate OpenAPI Definition
        uses: char0n/swagger-editor-validate@v1
        with:
          definition-file: api/openapi.yaml
          
      - name: Spectral Linting
        uses: stoplightio/spectral-action@v0.8.10
        with:
          file_glob: 'api/**/*.yaml'
          
      - name: Generate Validation Report
        if: always()
        run: |
          npx @redocly/cli lint api/openapi.yaml \
            --format=json > validation-report.json
            
      - name: Upload Report
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: validation-report
          path: validation-report.json
          
      - name: Comment PR
        if: failure()
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '❌ OpenAPI 验证失败,请查看详细报告'
            })

Pre-commit Hook

yaml
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/python-jsonschema/check-jsonschema
    rev: 0.23.0
    hooks:
      - id: check-jsonschema
        name: Validate OpenAPI spec
        files: ^api/.*\.(yaml|yml|json)$
        args: ["--schemafile", "https://spec.openapis.org/oas/3.0/schema/2021-09-28"]
        
  - repo: local
    hooks:
      - id: openapi-validator
        name: OpenAPI Validator
        entry: npx @redocly/cli lint
        language: system
        files: ^api/.*\.(yaml|yml)$
        pass_filenames: true

验证报告生成

HTML 报告

javascript
// generate-report.js
const fs = require('fs');
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);

async function generateValidationReport(specFile) {
  // 运行各种验证工具
  const results = {
    timestamp: new Date().toISOString(),
    spec: specFile,
    validators: {}
  };
  
  // Redocly 验证
  try {
    const { stdout } = await execPromise(`redocly lint ${specFile} --format=json`);
    results.validators.redocly = JSON.parse(stdout);
  } catch (error) {
    results.validators.redocly = { error: error.message };
  }
  
  // Spectral 验证
  try {
    const { stdout } = await execPromise(`spectral lint ${specFile} --format=json`);
    results.validators.spectral = JSON.parse(stdout);
  } catch (error) {
    results.validators.spectral = { error: error.message };
  }
  
  // 生成 HTML 报告
  const html = `
<!DOCTYPE html>
<html>
<head>
    <title>OpenAPI Validation Report</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .error { color: #d32f2f; }
        .warning { color: #f57c00; }
        .success { color: #388e3c; }
        .validator { margin: 20px 0; padding: 20px; border: 1px solid #ddd; }
        pre { background: #f5f5f5; padding: 10px; overflow-x: auto; }
    </style>
</head>
<body>
    <h1>OpenAPI Validation Report</h1>
    <p>Generated: ${results.timestamp}</p>
    <p>Spec: ${results.spec}</p>
    
    ${Object.entries(results.validators).map(([name, result]) => `
        <div class="validator">
            <h2>${name}</h2>
            <pre>${JSON.stringify(result, null, 2)}</pre>
        </div>
    `).join('')}
</body>
</html>
  `;
  
  fs.writeFileSync('validation-report.html', html);
  console.log('Report generated: validation-report.html');
}

// 使用
generateValidationReport('api/openapi.yaml');

最佳实践

1. 分层验证策略

yaml
# 三层验证策略
validation:
  # 第一层:语法验证
  syntax:
    - yaml-lint
    - json-schema
    
  # 第二层:规范验证
  specification:
    - openapi-3.0-compliance
    - references-validation
    
  # 第三层:质量验证
  quality:
    - naming-conventions
    - documentation-completeness
    - examples-presence
    - security-definitions

2. 团队验证标准

yaml
# team-validation-rules.yaml
rules:
  # 必须通过的规则(CI 阻断)
  blocking:
    - info-contact
    - info-license
    - operation-operationId-unique
    - path-params-defined
    - oas3-valid-schema
    
  # 应该修复的规则(警告)
  non-blocking:
    - operation-description
    - parameter-description
    - response-examples
    
  # 逐步改进的规则
  progressive:
    - operation-performance-hint
    - schema-optimization

3. 验证自动化

bash
#!/bin/bash
# validate-all.sh

echo "🔍 开始 OpenAPI 规范验证..."

# 语法检查
echo "1. YAML 语法检查"
yamllint api/openapi.yaml || exit 1

# 规范验证
echo "2. OpenAPI 规范验证"
openapi-generator-cli validate -i api/openapi.yaml || exit 1

# 质量检查
echo "3. 质量检查"
redocly lint api/openapi.yaml || exit 1

# 安全检查
echo "4. 安全检查"
spectral lint api/openapi.yaml --ruleset=.spectral-security.yaml || exit 1

echo "✅ 所有验证通过!"

总结

完善的验证策略确保:

  • 规范正确: 符合 OpenAPI 标准
  • 📋 质量保证: 遵循最佳实践
  • 🔍 早期发现: 减少后期修复成本
  • 🚀 自动化: 集成到开发流程

通过合理的验证工具组合,构建高质量的 API 规范。

SOLO Development Guide