# Guide: Generating Spectral Rules from an External OpenAPI Specification

This guide provides step-by-step instructions for generating Spectral rules from an OpenAPI specification in another repository.

## Prerequisites

- Node.js installed
- npm or yarn installed
- Access to the external OpenAPI specification file (YAML or JSON)

## Step 1: Set Up Your Project

Create a new directory for your Spectral rulesets:

```bash
mkdir -p spectral-rulesets
cd spectral-rulesets
npm init -y
npm install js-yaml @stoplight/spectral-core
```

## Step 2: Copy the Enhanced Ruleset Generator

Copy the `enhanced-generate-rulesets.js` file from this repository to your new project. You'll need to modify it to work with your external OpenAPI specification.

## Step 3: Modify the Script for Your OpenAPI Specification

Edit the `enhanced-generate-rulesets.js` file to point to your external OpenAPI specification:

```javascript
// Update this path to point to your external OpenAPI specification file
const specFilePath = '../path/to/your/openapi-specification.yaml';
```

## Step 4: Adjust the Schema Component Extraction Logic

If your OpenAPI specification has a different structure, you may need to adjust how schema components are extracted:

```javascript
function generateAllRulesets() {
  const openApiSpec = readOpenAPISpec();
  
  // Update this line to match your schema structure
  const schemas = openApiSpec.components.schemas;
  
  // Generate rulesets for each schema component
  Object.entries(schemas).forEach(([componentName, schema]) => {
    // Skip any non-component schemas if needed
    if (componentName === 'SomeNonComponentSchema') return;
    
    const ruleset = generateRuleset(componentName, schema);
    writeRulesetToFile(componentName, ruleset);
  });
}
```

## Step 5: Run the Generator

```bash
node enhanced-generate-rulesets.js
```

This will generate individual ruleset files for each schema component in your OpenAPI specification.

## Step 6: Create a Combined Ruleset File

Create a `combined-ruleset.js` file that imports all the individual ruleset files:

```javascript
// Function to get combined ruleset data for all schema components

// Import all ruleset files dynamically
export async function getCombinedRuleset() {
  // Import all ruleset files - adjust the list based on your components
  const component1Ruleset = (await import('./component1.ruleset.js')).default;
  const component2Ruleset = (await import('./component2.ruleset.js')).default;
  // Add more imports as needed
  
  return {
    "component1.ruleset.yaml": component1Ruleset,
    "component2.ruleset.yaml": component2Ruleset,
    // Add more components as needed
  };
}
```

## Step 7: Create a Test Script

Create a `test-ruleset.js` file to verify your rulesets:

```javascript
// Test script to verify the combined ruleset

import { getCombinedRuleset } from './combined-ruleset.js';

// Main async function to run the tests
async function runTests() {
  try {
    // Get the combined ruleset
    const combinedRuleset = await getCombinedRuleset();

    // Print total number of components and rules
    const totalComponents = Object.keys(combinedRuleset).length;
    let totalRules = 0;
    let specFieldRules = 0;

    Object.entries(combinedRuleset).forEach(([component, ruleset]) => {
      if (ruleset && ruleset.rules) {
        const ruleCount = Object.keys(ruleset.rules).length;
        totalRules += ruleCount;
        console.log(`✅ Component ${component} loaded successfully with ${ruleCount} rules`);
        
        // Count rules specifically for spec field properties
        const specRules = Object.keys(ruleset.rules).filter(ruleName => 
          ruleName.startsWith('spec-') && 
          !['spec-details-not-exist', 'spec-details-whitelist-check'].includes(ruleName)
        ).length;
        
        specFieldRules += specRules;
      }
    });

    console.log(`\nTotal components: ${totalComponents}`);
    console.log(`Total rules: ${totalRules}`);
    console.log(`Rules for spec field properties: ${specFieldRules}`);
  } catch (error) {
    console.error('Error running tests:', error);
  }
}

// Run the tests
runTests();
```

## Step 8: Run the Test Script

```bash
node test-ruleset.js
```

## Step 9: Use the Rulesets with Spectral

Install the Spectral CLI:

```bash
npm install -g @stoplight/spectral-cli
```

Create a script to use your rulesets with Spectral:

```javascript
import { Spectral } from '@stoplight/spectral-core';
import { getCombinedRuleset } from './combined-ruleset.js';
import fs from 'fs';

async function validateWithSpectral(apiDocument, componentName) {
  const combinedRuleset = await getCombinedRuleset();
  const ruleset = combinedRuleset[`${componentName}.ruleset.yaml`];
  
  if (!ruleset) {
    console.error(`Ruleset for ${componentName} not found`);
    return;
  }
  
  const spectral = new Spectral();
  spectral.setRuleset(ruleset);
  
  const results = await spectral.run(apiDocument);
  return results;
}

// Example usage
const apiDocument = JSON.parse(fs.readFileSync('./path/to/api-document.json', 'utf8'));
validateWithSpectral(apiDocument, 'component1')
  .then(results => console.log(results))
  .catch(err => console.error(err));
```

## Step 10: Customize Rules as Needed

You can customize the rules in the generated ruleset files to match your specific requirements. Each ruleset file follows this structure:

```javascript
export default {
  "rules": {
    "rule-name": {
      "description": "Rule description",
      "severity": "error",
      "given": "$",
      "then": {
        "field": "fieldName",
        "function": "truthy"
      }
    },
    // More rules...
  }
};
```

## Troubleshooting

### Common Issues

1. **Path not found**: Ensure the path to your OpenAPI specification is correct.
2. **Schema structure mismatch**: If your OpenAPI specification has a different structure, adjust the schema extraction logic.
3. **Module import errors**: Make sure you're using the correct import syntax for your Node.js version.

### Tips

- Use `console.log` statements to debug the script execution.
- Validate your OpenAPI specification before generating rules.
- Start with a small subset of components to test the process.

## Advanced Usage

### Generating Rules for Specific Components

Modify the script to generate rules for specific components only:

```javascript
const targetComponents = ['Component1', 'Component2'];

Object.entries(schemas).forEach(([componentName, schema]) => {
  if (targetComponents.includes(componentName)) {
    const ruleset = generateRuleset(componentName, schema);
    writeRulesetToFile(componentName, ruleset);
  }
});
```

### Adding Custom Rules

You can add custom rules to the generated rulesets:

```javascript
function generateRuleset(componentName, schema) {
  const rules = {
    // Generated rules...
  };
  
  // Add custom rules
  rules["custom-rule"] = {
    "description": "Custom rule description",
    "severity": "error",
    "given": "$",
    "then": {
      "function": "truthy"
    }
  };
  
  return { rules };
}
```

## Conclusion

By following these steps, you can generate Spectral rules from any external OpenAPI specification and use them to validate your API documents.

// Made with Bob
