diff --git a/.github/workflows/spectral-lint.yml b/.github/workflows/spectral-lint.yml new file mode 100644 index 000000000..6a9a33fd0 --- /dev/null +++ b/.github/workflows/spectral-lint.yml @@ -0,0 +1,10 @@ +name: Spectral Lint +on: [push, pull_request] +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + - name: spectral-lint + run: ./validator/scripts/validate-all.sh diff --git a/validator/scripts/validate-all.sh b/validator/scripts/validate-all.sh new file mode 100755 index 000000000..728d53f9d --- /dev/null +++ b/validator/scripts/validate-all.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# +# Run this script from root folder in the repository +# +# To validate a single file: npx spectral lint .yaml --ruleset +# + +# Ruleset +ruleset="./validator/spectral.yaml" + +# Array of all YAML files +files=( + "xero-identity.yaml" + "xero-projects.yaml" + "xero-app-store.yaml" + "xero-payroll-uk.yaml" + "xero_files.yaml" + "xero_accounting.yaml" + "xero-payroll-nz.yaml" + "xero_assets.yaml" + "xero_bankfeeds.yaml" + "xero-payroll-au.yaml" +) + +total_files=${#files[@]} +passed=0 +failed=0 + + +# Validate all Xero OpenAPI YAML files with the custom Spectral ruleset +echo "🔍 Validating all Xero OpenAPI specifications..." +echo "================================================" + +echo "Testing $total_files OpenAPI specification files..." +echo "" + +for file in "${files[@]}"; do + if [ -f "$file" ]; then + echo "📄 Validating $file..." + + # Run spectral and capture the exit code + if npx @stoplight/spectral-cli lint "$file" --ruleset "$ruleset" --quiet > /dev/null 2>&1; then + echo "✅ $file - PASSED" + ((passed++)) + else + echo "❌ $file - FAILED" + echo " Running detailed check:" + npx @stoplight/spectral-cli lint "$file" --ruleset "$ruleset" --format=stylish | head -10 + echo "" + ((failed++)) + fi + else + echo "⚠️ $file - FILE NOT FOUND" + ((failed++)) + fi +done + +echo "" +echo "================================================" +echo "📊 SUMMARY:" +echo " Total files: $total_files" +echo " Passed: $passed" +echo " Failed: $failed" + +if [ $failed -eq 0 ]; then + echo "" + echo "🎉 ALL FILES PASSED! The Spectral ruleset is working correctly." + echo " This ruleset can be used as a baseline for validating future OpenAPI specs." + exit 0 +else + echo "" + echo "⚠️ Some files failed validation. Check the output above for details." + exit 1 +fi diff --git a/validator/spectral.yaml b/validator/spectral.yaml new file mode 100644 index 000000000..13eb80f27 --- /dev/null +++ b/validator/spectral.yaml @@ -0,0 +1,32 @@ +extends: ["spectral:oas", "./xero-spectral.yaml"] + +rules: + # Override default rules to be more lenient for existing Xero API specs + + # Re-enabled: operation-description (produces warnings only - acceptable for documentation improvement) + # operation-description: off + + # Disabled: info-description (xero_accounting.yaml missing description) + info-description: off + + # Disabled: operation-tag-defined (many APIs use undeclared tags) + operation-tag-defined: off + + # Disabled: no-$ref-siblings (xero_accounting.yaml uses this pattern with type field) + no-$ref-siblings: off + + # Disabled: example validation rules (legacy string examples would cause many errors) + oas3-valid-media-example: off + oas3-valid-schema-example: off + + # Re-enabled: oas3-unused-component (produces warnings only - helps identify unused schemas) + # oas3-unused-component: off + + # Re-enabled: oas3-server-trailing-slash (produces warnings only - helps clean up URLs) + # oas3-server-trailing-slash: off + + # Disabled: path-params (FileId/FolderId path conflicts in xero_files.yaml) + path-params: off + + # Re-enabled: oas3-operation-security-defined (produces warnings only - helps identify security gaps) + # oas3-operation-security-defined: off diff --git a/validator/xero-spectral.yaml b/validator/xero-spectral.yaml new file mode 100644 index 000000000..9cfb37079 --- /dev/null +++ b/validator/xero-spectral.yaml @@ -0,0 +1,120 @@ +rules: + # Custom rules specific to Xero APIs + xero-info-required-fields: + description: "Ensure required info fields are present" + given: "$.info" + severity: error + then: + - field: "title" + function: truthy + - field: "version" + function: truthy + - field: "termsOfService" + function: truthy + - field: "contact" + function: truthy + + xero-contact-required-fields: + description: "Ensure contact has required fields" + given: "$.info.contact" + severity: error + then: + - field: "name" + function: truthy + - field: "email" + function: truthy + - field: "url" + function: truthy + + xero-servers-required: + description: "Ensure servers are defined" + given: "$" + severity: error + then: + field: "servers" + function: truthy + + xero-server-description: + description: "Each server should have a description" + given: "$.servers[*]" + severity: warn + then: + field: "description" + function: truthy + + xero-operation-summary: + description: "Operations should have summaries" + given: "$.paths[*][get,post,put,patch,delete,head,options,trace]" + severity: warn + then: + field: "summary" + function: truthy + + xero-operation-id: + description: "Operations must have operationId" + given: "$.paths[*][get,post,put,patch,delete,head,options,trace]" + severity: error + then: + field: "operationId" + function: truthy + + xero-operation-tags: + description: "Operations should have tags" + given: "$.paths[*][get,post,put,patch,delete,head,options,trace]" + severity: warn + then: + field: "tags" + function: truthy + + xero-operation-security: + description: "Operations should have security defined" + given: "$.paths[*][get,post,put,patch,delete,head,options,trace]" + severity: info + then: + field: "security" + function: truthy + + xero-response-200-description: + description: "200 responses should have descriptions" + given: "$.paths[*][get,post,put,patch,delete,head,options,trace].responses.200" + severity: warn + then: + field: "description" + function: truthy + + xero-schema-properties-description: + description: "Schema properties should have descriptions for better documentation" + given: "$.components.schemas[*].properties[*]" + severity: info + then: + field: "description" + function: truthy + + xero-openapi-version: + description: "Should use OpenAPI 3.0.0 or higher" + given: "$.openapi" + severity: error + then: + function: pattern + functionOptions: + match: "^3\\.[0-9]+\\.[0-9]+$" + + xero-path-parameters: + description: "Path parameters should be properly defined" + given: "$.paths[*][get,post,put,patch,delete,head,options,trace].parameters[?(@.in === 'path')]" + severity: error + then: + - field: "name" + function: truthy + - field: "required" + function: truthy + - field: "schema" + function: truthy + + xero-consistent-error-responses: + description: "Should have consistent error response structure" + given: "$.paths[*][get,post,put,patch,delete,head,options,trace].responses[?(@property >= '400')]" + severity: info + then: + field: "description" + function: truthy