Comprehensive tooling for working with OpenAPI definitions
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
API definition analysis, feature detection, and complexity measurement tools for OpenAPI definitions.
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}`);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;
}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");
}// 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}`);
});
}// 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");
}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");
}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}`));// 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}%`);
}// 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)
};
}// 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-oasdocs