CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-oas

Comprehensive tooling for working with OpenAPI definitions

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

analysis-metrics.mddocs/

Analysis and Metrics

API definition analysis, feature detection, and complexity measurement tools for OpenAPI definitions.

Capabilities

Analyze OpenAPI Definition

Comprehensive analysis of OpenAPI definitions to detect features, complexity, and usage patterns.

/**
 * Analyze an OpenAPI definition for features and complexity
 * @param definition - OpenAPI definition to analyze
 * @returns Promise resolving to detailed analysis results
 */
function analyzer(definition: OASDocument): Promise<OASAnalysis>;

Usage Examples:

import analyzer from "oas/analyzer";

const definition = {
  openapi: "3.1.0", 
  info: { title: "My API", version: "1.0.0" },
  paths: { /* ... */ }
};

const analysis = await analyzer(definition);

// General metrics
console.log(`Operations: ${analysis.general.operationTotal.found}`);
console.log(`File size: ${analysis.general.rawFileSize.found} bytes`);
console.log(`Media types: ${analysis.general.mediaTypes.found.join(', ')}`);
console.log(`Security types: ${analysis.general.securityTypes.found.join(', ')}`);

// OpenAPI feature usage
console.log(`Uses callbacks: ${analysis.openapi.callbacks.present}`);
console.log(`Uses webhooks: ${analysis.openapi.webhooks.present}`);
console.log(`Has circular refs: ${analysis.openapi.circularRefs.present}`);

// ReadMe extension usage  
console.log(`Custom code samples: ${analysis.readme['x-readme.code-samples'].present}`);
console.log(`Static headers: ${analysis.readme['x-readme.headers'].present}`);

Analysis Result Structure

interface OASAnalysis {
  /** General API metrics and statistics */
  general: {
    dereferencedFileSize: OASAnalysisGeneral;
    mediaTypes: OASAnalysisGeneral;
    operationTotal: OASAnalysisGeneral;
    rawFileSize: OASAnalysisGeneral;
    securityTypes: OASAnalysisGeneral;
  };
  /** OpenAPI specification feature usage */
  openapi: {
    additionalProperties: OASAnalysisFeature;
    callbacks: OASAnalysisFeature;
    circularRefs: OASAnalysisFeature;
    commonParameters: OASAnalysisFeature;
    discriminators: OASAnalysisFeature;
    links: OASAnalysisFeature;
    style: OASAnalysisFeature;
    polymorphism: OASAnalysisFeature;
    serverVariables: OASAnalysisFeature;
    webhooks: OASAnalysisFeature;
    xml: OASAnalysisFeature;
    xmlSchemas: OASAnalysisFeature;
    xmlRequests: OASAnalysisFeature;
    xmlResponses: OASAnalysisFeature;
  };
  /** ReadMe-specific extension usage */
  readme: {
    'x-default': OASAnalysisFeature;
    'x-readme.code-samples': OASAnalysisFeature;
    'x-readme.headers': OASAnalysisFeature;
    'x-readme.explorer-enabled': OASAnalysisFeature;
    'x-readme.proxy-enabled': OASAnalysisFeature;
    'x-readme.samples-languages': OASAnalysisFeature;
    'x-readme-ref-name': OASAnalysisFeature;
    'x-readme.samples-enabled'?: OASAnalysisFeature;
    raw_body?: OASAnalysisFeature;
  };
}

interface OASAnalysisFeature {
  /** Whether the feature is present in the definition */
  present: boolean;
  /** JSON pointer locations where the feature is used */
  locations: string[];
}

interface OASAnalysisGeneral {
  /** Feature name for reporting */
  name: string;
  /** Found values (strings for enums, numbers for counts) */
  found: string[] | number;
}

Feature Analysis Details

General Metrics Analysis

const analysis = await analyzer(definition);

// File size analysis
const rawSize = analysis.general.rawFileSize.found as number;
const dereferencedSize = analysis.general.dereferencedFileSize.found as number;
const expansion = dereferencedSize - rawSize;

console.log(`Raw file size: ${rawSize} bytes`);
console.log(`Dereferenced size: ${dereferencedSize} bytes`);
console.log(`Size expansion: ${expansion} bytes (${(expansion/rawSize*100).toFixed(1)}%)`);

// Operation complexity
const opCount = analysis.general.operationTotal.found as number;
console.log(`Total operations: ${opCount}`);

if (opCount > 50) {
  console.log("Large API - consider using tags for organization");
} else if (opCount > 100) {
  console.log("Very large API - recommend splitting or using reduction");
}

// Media type diversity
const mediaTypes = analysis.general.mediaTypes.found as string[];
console.log(`Supported media types: ${mediaTypes.length}`);

if (mediaTypes.includes('application/xml')) {
  console.log("XML support detected");
}
if (mediaTypes.some(type => type.includes('multipart'))) {
  console.log("File upload support detected");
}

OpenAPI Feature Detection

// Advanced feature usage
if (analysis.openapi.callbacks.present) {
  console.log("API uses callbacks:");
  analysis.openapi.callbacks.locations.forEach(location => {
    console.log(`  Found at: ${location}`);
  });
}

if (analysis.openapi.webhooks.present) {
  console.log("API defines webhooks (OpenAPI 3.1):");
  analysis.openapi.webhooks.locations.forEach(location => {
    console.log(`  Webhook at: ${location}`);
  });
}

if (analysis.openapi.circularRefs.present) {
  console.log("⚠️  Circular references detected:");
  analysis.openapi.circularRefs.locations.forEach(location => {
    console.log(`  Circular ref: ${location}`);
  });
}

// Polymorphism usage
if (analysis.openapi.polymorphism.present) {
  console.log("Polymorphic schemas detected:");
  analysis.openapi.polymorphism.locations.forEach(location => {
    console.log(`  Polymorphism at: ${location}`);
  });
}

// Parameter serialization complexity
if (analysis.openapi.style.present) {
  console.log("Custom parameter serialization styles:");
  analysis.openapi.style.locations.forEach(location => {
    console.log(`  Custom style at: ${location}`);
  });
}

ReadMe Extension Analysis

// Documentation enhancements
if (analysis.readme['x-readme.code-samples'].present) {
  console.log("Custom code samples defined:");
  analysis.readme['x-readme.code-samples'].locations.forEach(location => {
    console.log(`  Code sample at: ${location}`);
  });
}

if (analysis.readme['x-readme.headers'].present) {
  console.log("Static headers configured:");
  analysis.readme['x-readme.headers'].locations.forEach(location => {
    console.log(`  Static headers at: ${location}`);  
  });
}

// API Explorer configuration
if (analysis.readme['x-readme.explorer-enabled'].present) {
  console.log("API Explorer settings found");
}

if (analysis.readme['x-readme.proxy-enabled'].present) {
  console.log("CORS proxy configuration found");
}

// Language preferences
if (analysis.readme['x-readme.samples-languages'].present) {
  console.log("Custom code sample languages configured");
}

Advanced Analysis Patterns

Complexity Scoring

function calculateComplexityScore(analysis: OASAnalysis): number {
  let score = 0;
  
  // Base complexity from operation count
  const opCount = analysis.general.operationTotal.found as number;
  score += opCount;
  
  // Feature complexity multipliers
  if (analysis.openapi.callbacks.present) score += 10;
  if (analysis.openapi.webhooks.present) score += 8;
  if (analysis.openapi.circularRefs.present) score += 15;
  if (analysis.openapi.polymorphism.present) score += 12;
  if (analysis.openapi.discriminators.present) score += 8;
  if (analysis.openapi.links.present) score += 6;
  
  // Media type diversity
  const mediaTypes = analysis.general.mediaTypes.found as string[];
  score += mediaTypes.length * 2;
  
  // Security complexity
  const securityTypes = analysis.general.securityTypes.found as string[];
  score += securityTypes.length * 3;
  
  return score;
}

const complexity = calculateComplexityScore(analysis);
console.log(`API complexity score: ${complexity}`);

if (complexity > 100) {
  console.log("High complexity API - recommend comprehensive testing");
} else if (complexity > 200) {
  console.log("Very high complexity - consider refactoring or splitting");
}

Feature Recommendations

function generateRecommendations(analysis: OASAnalysis): string[] {
  const recommendations: string[] = [];
  
  // File size recommendations
  const rawSize = analysis.general.rawFileSize.found as number;
  if (rawSize > 1000000) { // > 1MB
    recommendations.push("Consider splitting large API definition into multiple files");
  }
  
  // Missing features that could improve API
  if (!analysis.openapi.commonParameters.present) {
    recommendations.push("Consider using common parameters to reduce duplication");
  }
  
  if (!analysis.readme['x-readme.code-samples'].present) {
    recommendations.push("Add custom code samples for better developer experience");
  }
  
  // Complexity warnings
  if (analysis.openapi.circularRefs.present) {
    recommendations.push("Review circular references - they may indicate design issues");
  }
  
  // XML-specific recommendations
  if (analysis.openapi.xml.present && !analysis.openapi.xmlSchemas.present) {
    recommendations.push("XML usage detected but no XML schemas defined");
  }
  
  return recommendations;
}

const recommendations = generateRecommendations(analysis);
console.log("\nRecommendations:");
recommendations.forEach(rec => console.log(`• ${rec}`));

Comparison Analysis

// Compare two API versions
async function compareAPIs(oldDef: OASDocument, newDef: OASDocument) {
  const [oldAnalysis, newAnalysis] = await Promise.all([
    analyzer(oldDef),
    analyzer(newDef)
  ]);
  
  // Operation count changes
  const oldOps = oldAnalysis.general.operationTotal.found as number;
  const newOps = newAnalysis.general.operationTotal.found as number;
  const opChange = newOps - oldOps;
  
  console.log(`Operations: ${oldOps} → ${newOps} (${opChange >= 0 ? '+' : ''}${opChange})`);
  
  // New features added
  const features = [
    'callbacks', 'webhooks', 'discriminators', 'polymorphism', 'links'
  ] as const;
  
  features.forEach(feature => {
    const oldHas = oldAnalysis.openapi[feature].present;
    const newHas = newAnalysis.openapi[feature].present;
    
    if (!oldHas && newHas) {
      console.log(`✨ Added ${feature} support`);
    } else if (oldHas && !newHas) {
      console.log(`❌ Removed ${feature} support`);
    }
  });
  
  // File size impact
  const oldSize = oldAnalysis.general.rawFileSize.found as number;
  const newSize = newAnalysis.general.rawFileSize.found as number;
  const sizeChange = ((newSize - oldSize) / oldSize * 100).toFixed(1);
  
  console.log(`File size change: ${sizeChange}%`);
}

Integration Examples

API Documentation Pipeline

// Generate documentation metadata from analysis
async function generateDocMetadata(definition: OASDocument) {
  const analysis = await analyzer(definition);
  
  return {
    complexity: calculateComplexityScore(analysis),
    features: {
      hasCallbacks: analysis.openapi.callbacks.present,
      hasWebhooks: analysis.openapi.webhooks.present,
      hasCircularRefs: analysis.openapi.circularRefs.present,
      supportsXML: analysis.openapi.xml.present,
      customCodeSamples: analysis.readme['x-readme.code-samples'].present
    },
    metrics: {
      operationCount: analysis.general.operationTotal.found,
      mediaTypes: analysis.general.mediaTypes.found,
      securityTypes: analysis.general.securityTypes.found
    },
    recommendations: generateRecommendations(analysis)
  };
}

Quality Gate Integration

// Use analysis for CI/CD quality gates
async function validateAPIQuality(definition: OASDocument): Promise<boolean> {
  const analysis = await analyzer(definition);
  
  // Quality criteria
  const hasCircularRefs = analysis.openapi.circularRefs.present;
  const complexity = calculateComplexityScore(analysis);
  const opCount = analysis.general.operationTotal.found as number;
  
  // Fail if quality issues detected
  if (hasCircularRefs) {
    console.error("❌ Quality gate failed: Circular references detected");
    return false;
  }
  
  if (complexity > 300) {
    console.error(`❌ Quality gate failed: Complexity too high (${complexity})`);
    return false;
  }
  
  if (opCount > 150) {
    console.error(`❌ Quality gate failed: Too many operations (${opCount})`);
    return false;
  }
  
  console.log("✅ API quality gate passed");
  return true;
}

Install with Tessl CLI

npx tessl i tessl/npm-oas

docs

analysis-metrics.md

api-definition-reduction.md

extensions-customization.md

index.md

openapi-definition-management.md

operation-discovery-analysis.md

parameter-handling-json-schema.md

request-response-management.md

schema-dereferencing-references.md

security-authentication.md

server-url-management.md

utils.md

tile.json