Comprehensive tooling for working with OpenAPI definitions
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
ReadMe-specific OpenAPI extensions with validation and configuration management for enhanced API documentation.
Determine if a custom specification extension exists within the API definition.
/**
* Check if extension exists at API definition root level
* @param extension - Extension name to check for
* @returns True if extension exists in the API definition
*/
hasExtension(extension: string): boolean;Usage Examples:
const oas = new Oas(definition);
// Check for ReadMe extensions
if (oas.hasExtension('x-readme')) {
console.log("ReadMe extensions configured");
}
// Check for specific extensions
if (oas.hasExtension('x-readme.code-samples')) {
console.log("Custom code samples defined");
}
// Check for legacy extensions
if (oas.hasExtension('x-samples-languages')) {
console.log("Legacy samples language configuration found");
}Retrieve custom specification extension values with fallback to defaults.
/**
* Get extension value from API definition or operation
* @param extension - Extension name or key
* @param operation - Optional operation to check for extension (takes precedence)
* @returns Extension value or default value
*/
getExtension(extension: string | keyof Extensions, operation?: Operation): any;Usage Examples:
// Get API-level extension
const codesamples = oas.getExtension('code-samples');
console.log("Default code samples:", codesamples);
// Get operation-level extension (takes precedence)
const operation = oas.operation('/users', 'post');
const operationSamples = oas.getExtension('code-samples', operation);
// Get with type safety
const languages = oas.getExtension('samples-languages') as string[];
const headers = oas.getExtension('headers') as Array<{key: string, value: string}>;
// Get nested ReadMe extensions
const proxyEnabled = oas.getExtension('proxy-enabled'); // From x-readme.proxy-enabled
const explorerEnabled = oas.getExtension('explorer-enabled'); // From x-readme.explorer-enabledValidate extension values against expected schemas and constraints.
/**
* Validate a specific extension
* @param extension - Extension key to validate
* @throws TypeError if extension value is invalid
*/
validateExtension(extension: keyof Extensions): void;
/**
* Validate all known extensions
* @throws TypeError if any extension value is invalid
*/
validateExtensions(): void;Usage Examples:
try {
// Validate specific extension
oas.validateExtension('parameter-ordering');
console.log("Parameter ordering is valid");
// Validate all extensions
oas.validateExtensions();
console.log("All extensions are valid");
} catch (error) {
console.error("Extension validation failed:", error.message);
// Example: "x-readme.parameter-ordering" must contain 6 items comprised of: path, query, body, cookie, form, and header
}/** Extension key constants for type safety */
const CODE_SAMPLES = 'code-samples';
const EXPLORER_ENABLED = 'explorer-enabled';
const HEADERS = 'headers';
const METRICS_ENABLED = 'metrics-enabled';
const OAUTH_OPTIONS = 'oauth-options';
const PARAMETER_ORDERING = 'parameter-ordering';
const PROXY_ENABLED = 'proxy-enabled';
const SAMPLES_LANGUAGES = 'samples-languages';
const SIMPLE_MODE = 'simple-mode';
const DISABLE_TAG_SORTING = 'disable-tag-sorting';interface Extensions {
/** Custom code samples for operations */
'code-samples': Array<{
code: string;
correspondingExample?: string;
install?: string;
language: string;
name?: string;
}>;
/** Disable automatic tag sorting */
'disable-tag-sorting': boolean;
/** Enable/disable API Explorer */
'explorer-enabled': boolean;
/** Static headers to add to requests */
'headers': Array<Record<string, number | string>>;
/** Enable/disable API metrics collection */
'metrics-enabled': boolean;
/** OAuth2 flow configuration options */
'oauth-options': {
scopeSeparator?: string;
useInsecureClientAuthentication?: boolean;
};
/** Parameter display order in documentation */
'parameter-ordering': ('body' | 'cookie' | 'form' | 'header' | 'path' | 'query')[];
/** Enable/disable CORS proxy */
'proxy-enabled': boolean;
/** Default code sample languages */
'samples-languages': string[];
/** Enable/disable simple mode */
'simple-mode': boolean;
}/** Default values for all extensions */
const extensionDefaults: Extensions = {
'code-samples': undefined,
'disable-tag-sorting': false,
'explorer-enabled': true,
'headers': undefined,
'metrics-enabled': true,
'oauth-options': {},
'parameter-ordering': ['path', 'query', 'body', 'cookie', 'form', 'header'],
'proxy-enabled': true,
'samples-languages': ['shell', 'node', 'ruby', 'php', 'python', 'java', 'csharp'],
'simple-mode': true
};// Check and configure custom code samples
const operation = oas.operation('/users', 'post');
const customSamples = oas.getExtension('code-samples', operation);
if (customSamples && Array.isArray(customSamples)) {
customSamples.forEach(sample => {
console.log(`${sample.language}${sample.name ? ` (${sample.name})` : ''}:`);
console.log(sample.code);
if (sample.install) {
console.log(`Installation: ${sample.install}`);
}
if (sample.correspondingExample) {
console.log(`Matches example: ${sample.correspondingExample}`);
}
});
}
// Add custom code samples
const definition = oas.getDefinition();
if (!definition.paths['/users'].post['x-readme']) {
definition.paths['/users'].post['x-readme'] = {};
}
definition.paths['/users'].post['x-readme']['code-samples'] = [
{
language: 'curl',
name: 'Create User with cURL',
code: 'curl -X POST https://api.example.com/users -H "Content-Type: application/json" -d \'{"name":"John","email":"john@example.com"}\'',
install: 'curl is usually pre-installed'
},
{
language: 'javascript',
name: 'Create User with Fetch',
code: 'fetch("https://api.example.com/users", {\n method: "POST",\n headers: { "Content-Type": "application/json" },\n body: JSON.stringify({ name: "John", email: "john@example.com" })\n});'
}
];// Configure API Explorer behavior
const explorerEnabled = oas.getExtension('explorer-enabled');
const proxyEnabled = oas.getExtension('proxy-enabled');
const metricsEnabled = oas.getExtension('metrics-enabled');
console.log(`API Explorer: ${explorerEnabled ? 'enabled' : 'disabled'}`);
console.log(`CORS Proxy: ${proxyEnabled ? 'enabled' : 'disabled'}`);
console.log(`Metrics Collection: ${metricsEnabled ? 'enabled' : 'disabled'}`);
// Disable API Explorer for sensitive operations
const adminOperation = oas.operation('/admin/users', 'delete');
const adminExplorerSetting = oas.getExtension('explorer-enabled', adminOperation);
if (adminExplorerSetting) {
console.log("Admin operations allow API Explorer usage");
}// Configure static headers
const staticHeaders = oas.getExtension('headers');
if (staticHeaders && Array.isArray(staticHeaders)) {
console.log("Static headers configured:");
staticHeaders.forEach(header => {
console.log(` ${header.key}: ${header.value}`);
});
}
// Add static headers to definition
const definition = oas.getDefinition();
definition['x-readme'] = {
...definition['x-readme'],
headers: [
{ key: 'X-API-Version', value: '2024-01' },
{ key: 'X-Client-ID', value: 'docs-client' },
{ key: 'X-Custom-Header', value: 'documentation' }
]
};// Get and validate parameter ordering
const parameterOrder = oas.getExtension('parameter-ordering');
console.log("Parameter display order:", parameterOrder);
// Validate parameter ordering
try {
oas.validateExtension('parameter-ordering');
} catch (error) {
console.error("Invalid parameter ordering:", error.message);
}
// Custom parameter ordering
const definition = oas.getDefinition();
definition['x-readme'] = {
...definition['x-readme'],
'parameter-ordering': ['header', 'path', 'query', 'body', 'form', 'cookie']
};
// Re-validate after changes
oas.validateExtension('parameter-ordering');// Configure OAuth2 options
const oauthOptions = oas.getExtension('oauth-options');
console.log("OAuth configuration:");
console.log(` Scope separator: "${oauthOptions.scopeSeparator || ' '}"`);
console.log(` Insecure client auth: ${oauthOptions.useInsecureClientAuthentication || false}`);
// Custom OAuth configuration
const definition = oas.getDefinition();
definition['x-readme'] = {
...definition['x-readme'],
'oauth-options': {
scopeSeparator: ',',
useInsecureClientAuthentication: false
}
};// Migrate from legacy extension format to new format
function migrateLegacyExtensions(definition: OASDocument): OASDocument {
const migrated = JSON.parse(JSON.stringify(definition));
// Migrate x-samples-languages to x-readme.samples-languages
if (migrated['x-samples-languages']) {
migrated['x-readme'] = migrated['x-readme'] || {};
migrated['x-readme']['samples-languages'] = migrated['x-samples-languages'];
delete migrated['x-samples-languages'];
}
// Migrate x-explorer-enabled to x-readme.explorer-enabled
if (migrated['x-explorer-enabled'] !== undefined) {
migrated['x-readme'] = migrated['x-readme'] || {};
migrated['x-readme']['explorer-enabled'] = migrated['x-explorer-enabled'];
delete migrated['x-explorer-enabled'];
}
return migrated;
}
const migratedDef = migrateLegacyExtensions(oas.getDefinition());
const migratedOas = new Oas(migratedDef);// Check extension inheritance from API to operation level
function getEffectiveExtension(oas: Oas, operation: Operation, extension: keyof Extensions): any {
// Operation-level takes precedence
const operationValue = oas.getExtension(extension, operation);
if (operationValue !== undefined) {
return operationValue;
}
// Fall back to API-level
return oas.getExtension(extension);
}
const operation = oas.operation('/users', 'post');
const effectiveLanguages = getEffectiveExtension(oas, operation, 'samples-languages');
console.log("Effective sample languages:", effectiveLanguages);// Comprehensive extension validation
function validateAllExtensions(oas: Oas): { valid: boolean; errors: string[] } {
const errors: string[] = [];
try {
oas.validateExtensions();
return { valid: true, errors: [] };
} catch (error) {
errors.push(error.message);
}
// Additional custom validations
const samplesLanguages = oas.getExtension('samples-languages');
if (samplesLanguages && !Array.isArray(samplesLanguages)) {
errors.push('samples-languages must be an array');
}
const headers = oas.getExtension('headers');
if (headers && Array.isArray(headers)) {
headers.forEach((header, index) => {
if (!header.key || !header.value) {
errors.push(`Header at index ${index} missing key or value`);
}
});
}
return { valid: errors.length === 0, errors };
}
const validation = validateAllExtensions(oas);
if (!validation.valid) {
console.error("Extension validation failed:");
validation.errors.forEach(error => console.error(` ${error}`));
}// Generate extension-aware documentation
function generateDocumentationConfig(oas: Oas) {
return {
explorerEnabled: oas.getExtension('explorer-enabled'),
proxyEnabled: oas.getExtension('proxy-enabled'),
metricsEnabled: oas.getExtension('metrics-enabled'),
defaultLanguages: oas.getExtension('samples-languages'),
parameterOrder: oas.getExtension('parameter-ordering'),
staticHeaders: oas.getExtension('headers'),
tagSortingDisabled: oas.getExtension('disable-tag-sorting'),
simpleMode: oas.getExtension('simple-mode')
};
}
const docConfig = generateDocumentationConfig(oas);
console.log("Documentation configuration:", docConfig);// Use extensions for code generation customization
function generateSDKConfig(oas: Oas) {
const languages = oas.getExtension('samples-languages');
const headers = oas.getExtension('headers');
return {
targetLanguages: languages,
defaultHeaders: headers?.reduce((acc, header) => {
acc[header.key] = header.value;
return acc;
}, {} as Record<string, any>),
includeMetrics: oas.getExtension('metrics-enabled'),
proxyMode: oas.getExtension('proxy-enabled')
};
}Install with Tessl CLI
npx tessl i tessl/npm-oasdocs