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

extensions-customization.mddocs/

Extensions and Customization

ReadMe-specific OpenAPI extensions with validation and configuration management for enhanced API documentation.

Capabilities

Check Extension Existence

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");
}

Get Extension Values

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-enabled

Validate Extensions

Validate 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 Constants and Types

Extension Key Constants

/** 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';

Extensions Interface

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;
}

Extension Defaults

/** 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
};

Extension Usage Patterns

Code Samples Configuration

// 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});'
  }
];

API Explorer Configuration

// 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");
}

Static Headers Configuration

// 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' }
  ]
};

Parameter Ordering Configuration

// 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');

OAuth Configuration

// 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
  }
};

Advanced Extension Patterns

Extension Migration

// 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);

Extension Inheritance

// 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);

Extension Validation Pipeline

// 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}`));
}

Integration Examples

Documentation Generation

// 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);

Code Generation Integration

// 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-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