Tooling for converting, validating, and parsing OpenAPI, Swagger, and Postman API definitions
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Custom error classes and utility functions for managing validation errors, compilation, and error reporting with contextual information.
Custom error class that extends the standard Error class, specifically designed for API validation failures.
/**
* Custom error class for validation failures
* Extends standard Error with specific name for validation contexts
*/
class ValidationError extends Error {
constructor(message: string);
name: 'ValidationError';
}Usage Examples:
import { ValidationError } from "oas-normalize/lib/errors";
// Catching validation errors
const oas = new OASNormalize(invalidSpec);
try {
await oas.validate();
} catch (error) {
if (error instanceof ValidationError) {
console.error('Validation failed:', error.message);
console.log('Error type:', error.name); // "ValidationError"
console.log('Stack trace:', error.stack);
} else {
console.error('Unexpected error:', error);
}
}// Custom validation error handling
function handleAPIValidation(spec: any) {
return new OASNormalize(spec)
.validate()
.catch(error => {
if (error instanceof ValidationError) {
// Handle validation-specific errors
return {
success: false,
validationError: true,
message: error.message,
details: parseValidationMessage(error.message)
};
}
// Re-throw other errors
throw error;
});
}Utility function for compiling validation results into human-readable error messages with contextual information.
/**
* Compile validation errors into readable format with line numbers and context
* Re-exported from @readme/openapi-parser for convenience
* @param result - Validation result containing errors and warnings
* @returns Formatted error message string
*/
function compileErrors(result: ValidationResult): string;
interface ValidationResult {
valid: boolean;
errors: ErrorDetails[];
warnings: WarningDetails[];
}
interface ErrorDetails {
message: string;
path: string;
location?: {
line: number;
column: number;
};
}Usage Examples:
import { compileErrors } from "oas-normalize/lib/utils";
import { validate } from "@readme/openapi-parser";
// Manual validation and error compilation
const spec = {
openapi: "3.0.0",
info: { title: "API" }, // Missing version
paths: {}
};
const result = await validate(spec, { validate: { errors: { colorize: false } } });
if (!result.valid) {
const errorMessage = compileErrors(result);
console.error('Validation failed:\n', errorMessage);
// Output:
// OpenAPI schema validation failed.
//
// REQUIRED must have required property 'version'
//
// 2 | "info": {
// > 3 | "title": "API"
// | ^ ☹️ version is missing here!
// 4 | },
}Most common errors thrown by OAS Normalize are validation-related:
// Schema validation errors
const invalidSchema = {
openapi: "3.0.0",
info: { title: "API" }, // Missing required version
paths: {
"/pets/{id}": {
get: {
parameters: [
{
name: "filter", // Wrong parameter name (should be "id")
in: "query"
}
]
}
}
}
};
try {
await new OASNormalize(invalidSchema).validate();
} catch (error) {
console.log(error instanceof ValidationError); // true
console.log(error.name); // "ValidationError"
// Error message includes detailed location information
}const unsupportedFormat = {
// No swagger, openapi, or postman collection indicators
api: "1.0",
title: "Some API"
};
try {
await new OASNormalize(unsupportedFormat).validate();
} catch (error) {
console.log(error instanceof ValidationError); // true
console.log(error.message); // "The supplied API definition is unsupported."
}const swagger12 = {
swagger: "1.2", // Unsupported legacy version
info: { title: "Old API" }
};
try {
await new OASNormalize(swagger12).validate();
} catch (error) {
console.log(error instanceof ValidationError); // true
console.log(error.message); // "Swagger v1.2 is unsupported."
}// Security-related errors
const specWithLocalRef = {
openapi: "3.0.0",
info: { title: "API", version: "1.0.0" },
paths: {
"/test": {
get: {
parameters: [{
schema: { $ref: "/etc/passwd" } // System file access attempt
}]
}
}
}
};
try {
await new OASNormalize(specWithLocalRef).validate();
} catch (error) {
console.log(error.message); // Contains reference resolution error
}
// Path access without enablePaths
try {
await new OASNormalize("./local-file.yaml").load();
} catch (error) {
console.log(error.message); // "Use `opts.enablePaths` to enable accessing local files."
}Error messages can include ANSI color codes for terminal display:
const oas = new OASNormalize(invalidSpec, { colorizeErrors: true });
try {
await oas.validate();
} catch (error) {
// Error message includes color codes for:
// - Error locations (red)
// - Line numbers (cyan)
// - Error indicators (red)
// - Context lines (gray)
console.error(error.message);
}Default error messages are plain text without color codes:
const oas = new OASNormalize(invalidSpec, { colorizeErrors: false });
try {
await oas.validate();
} catch (error) {
// Plain text output suitable for logging or non-terminal environments
console.error(error.message);
}async function validateAPISpec(spec: any, options: Options = {}) {
try {
const oas = new OASNormalize(spec, options);
const result = await oas.validate();
return {
success: true,
warnings: result.warnings,
spec: await oas.convert() // Return normalized spec
};
} catch (error) {
if (error instanceof ValidationError) {
return {
success: false,
type: 'validation',
message: error.message,
details: parseErrorDetails(error.message)
};
}
// Handle other error types
return {
success: false,
type: 'system',
message: error.message,
error: error
};
}
}
function parseErrorDetails(message: string) {
// Extract structured information from error message
const lines = message.split('\n');
const errors = [];
// Parse contextual error information
// Implementation depends on specific error format needs
return errors;
}async function validateWithRetry(spec: any, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const oas = new OASNormalize(spec);
return await oas.validate();
} catch (error) {
if (error instanceof ValidationError) {
// Don't retry validation errors - they won't change
throw error;
}
if (attempt === maxRetries) {
throw new Error(`Failed after ${maxRetries} attempts: ${error.message}`);
}
// Wait before retry (exponential backoff)
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
}
}
}async function validateMultipleSpecs(specs: any[]) {
const results = await Promise.allSettled(
specs.map((spec, index) =>
new OASNormalize(spec)
.validate()
.then(result => ({ index, success: true, result }))
.catch(error => ({
index,
success: false,
error: error instanceof ValidationError ? error : new Error(error.message)
}))
)
);
const successful = results.filter((r): r is PromiseFulfilledResult<any> =>
r.status === 'fulfilled' && r.value.success
);
const failed = results.filter((r): r is PromiseFulfilledResult<any> =>
r.status === 'fulfilled' && !r.value.success
);
return {
successful: successful.map(r => r.value),
failed: failed.map(r => ({
index: r.value.index,
error: r.value.error,
isValidationError: r.value.error instanceof ValidationError
})),
summary: {
total: specs.length,
successful: successful.length,
failed: failed.length
}
};
}import { ValidationError } from "oas-normalize/lib/errors";
class APIValidator {
private logger: Logger;
async validateSpec(spec: any, context: { source: string; version?: string } = {}) {
try {
const oas = new OASNormalize(spec);
const result = await oas.validate();
this.logger.info('API validation successful', {
source: context.source,
version: context.version,
warnings: result.warnings.length
});
return result;
} catch (error) {
if (error instanceof ValidationError) {
this.logger.error('API validation failed', {
source: context.source,
version: context.version,
error: error.message,
type: 'validation'
});
} else {
this.logger.error('API validation system error', {
source: context.source,
error: error.message,
type: 'system'
});
}
throw error;
}
}
}function createErrorReport(error: ValidationError) {
return {
timestamp: new Date().toISOString(),
type: 'validation_error',
name: error.name,
message: error.message,
stack: error.stack,
parsed: {
errors: extractErrorLocations(error.message),
summary: extractErrorSummary(error.message)
}
};
}
function extractErrorLocations(message: string) {
// Parse line number and location information from formatted error message
const locationRegex = />\s*(\d+)\s*\|/g;
const locations = [];
let match;
while ((match = locationRegex.exec(message)) !== null) {
locations.push(parseInt(match[1]));
}
return locations;
}Install with Tessl CLI
npx tessl i tessl/npm-oas-normalize