mirror of
https://github.com/LukeHagar/prettier-plugin-openapi.git
synced 2025-12-06 04:21:03 +00:00
654 lines
22 KiB
TypeScript
654 lines
22 KiB
TypeScript
import { SupportLanguage, Parser, Printer } from 'prettier';
|
|
import type { Plugin } from 'prettier';
|
|
import * as yaml from 'js-yaml';
|
|
import { getVendorExtensions } from './extensions/vendor-loader.js';
|
|
|
|
import {
|
|
RootKeys,
|
|
InfoKeys,
|
|
ContactKeys,
|
|
LicenseKeys,
|
|
ComponentsKeys,
|
|
OperationKeys,
|
|
ParameterKeys,
|
|
SchemaKeys,
|
|
ResponseKeys,
|
|
SecuritySchemeKeys,
|
|
OAuthFlowKeys,
|
|
ServerKeys,
|
|
ServerVariableKeys,
|
|
TagKeys,
|
|
ExternalDocsKeys,
|
|
WebhookKeys,
|
|
PathItemKeys,
|
|
RequestBodyKeys,
|
|
MediaTypeKeys,
|
|
EncodingKeys,
|
|
HeaderKeys,
|
|
LinkKeys,
|
|
ExampleKeys,
|
|
DiscriminatorKeys,
|
|
XMLKeys,
|
|
} from './keys.js';
|
|
|
|
// Type definitions for better type safety
|
|
interface OpenAPINode {
|
|
type: 'openapi';
|
|
content: any;
|
|
originalText: string;
|
|
format: 'json' | 'yaml';
|
|
}
|
|
|
|
interface PrettierPath {
|
|
getValue(): OpenAPINode;
|
|
}
|
|
|
|
interface OpenAPIPluginOptions {
|
|
tabWidth?: number;
|
|
printWidth?: number;
|
|
}
|
|
|
|
// Load vendor extensions
|
|
const vendorExtensions = getVendorExtensions();
|
|
|
|
// ============================================================================
|
|
// UNIFIED PARSER FUNCTIONS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Unified parser that can handle both JSON and YAML OpenAPI files
|
|
*/
|
|
function parseOpenAPIFile(text: string, options?: any): OpenAPINode {
|
|
// Try to detect the format based on file extension or content
|
|
const filePath = options?.filepath || '';
|
|
const isYamlFile = filePath.endsWith('.yaml') || filePath.endsWith('.yml');
|
|
const isJsonFile = filePath.endsWith('.json');
|
|
|
|
// If we can't determine from extension, try to detect from content
|
|
let format: 'json' | 'yaml' = 'json'; // default to JSON
|
|
if (isYamlFile) {
|
|
format = 'yaml';
|
|
} else if (isJsonFile) {
|
|
format = 'json';
|
|
} else {
|
|
// Try to detect format from content
|
|
const trimmedText = text.trim();
|
|
if (trimmedText.startsWith('{') || trimmedText.startsWith('[')) {
|
|
format = 'json';
|
|
} else {
|
|
format = 'yaml';
|
|
}
|
|
}
|
|
|
|
try {
|
|
let parsed: any;
|
|
|
|
if (format === 'json') {
|
|
parsed = JSON.parse(text);
|
|
} else {
|
|
parsed = yaml.load(text, {
|
|
schema: yaml.DEFAULT_SCHEMA,
|
|
onWarning: (warning) => {
|
|
// Handle YAML warnings if needed
|
|
console.warn('YAML parsing warning:', warning);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Check if this is an OpenAPI file
|
|
if (!isOpenAPIFile(parsed, filePath)) {
|
|
throw new Error('Not an OpenAPI file');
|
|
}
|
|
|
|
return {
|
|
type: 'openapi',
|
|
content: parsed,
|
|
originalText: text,
|
|
format: format,
|
|
};
|
|
} catch (error) {
|
|
throw new Error(`Failed to parse OpenAPI ${format.toUpperCase()}: ${error}`);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// FILE DETECTION FUNCTIONS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Detects if a file is an OpenAPI-related file based on content and structure
|
|
*/
|
|
function isOpenAPIFile(content: any, filePath?: string): boolean {
|
|
if (!content || typeof content !== 'object') {
|
|
return false;
|
|
}
|
|
|
|
// Check for root-level OpenAPI indicators (most important)
|
|
if (content.openapi || content.swagger) {
|
|
return true;
|
|
}
|
|
|
|
// Check file path patterns for common OpenAPI file structures
|
|
// Only accept files in OpenAPI-related directories
|
|
if (filePath) {
|
|
const path = filePath.toLowerCase();
|
|
|
|
// Check for component directory patterns
|
|
if (path.includes('/components/') ||
|
|
path.includes('/schemas/') ||
|
|
path.includes('/parameters/') ||
|
|
path.includes('/responses/') ||
|
|
path.includes('/requestbodies/') ||
|
|
path.includes('/headers/') ||
|
|
path.includes('/examples/') ||
|
|
path.includes('/securityschemes/') ||
|
|
path.includes('/links/') ||
|
|
path.includes('/callbacks/') ||
|
|
path.includes('/webhooks/') ||
|
|
path.includes('/paths/')) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Check for component-like structures (only if we have strong indicators)
|
|
if (content.components || content.definitions || content.parameters || content.responses || content.securityDefinitions) {
|
|
return true;
|
|
}
|
|
|
|
// Check for path-like structures (operations)
|
|
if (content.paths || isPathObject(content)) {
|
|
return true;
|
|
}
|
|
|
|
// Check for schema-like structures (but be more strict)
|
|
// Only accept if we have strong schema indicators
|
|
if (isSchemaObject(content) && (content.$ref || content.allOf || content.oneOf || content.anyOf || content.not || content.properties || content.items)) {
|
|
return true;
|
|
}
|
|
|
|
// Check for parameter-like structures
|
|
if (isParameterObject(content)) {
|
|
return true;
|
|
}
|
|
|
|
// Check for response-like structures
|
|
if (isResponseObject(content)) {
|
|
return true;
|
|
}
|
|
|
|
// Check for security scheme-like structures
|
|
if (isSecuritySchemeObject(content)) {
|
|
return true;
|
|
}
|
|
|
|
// Check for server-like structures
|
|
if (isServerObject(content)) {
|
|
return true;
|
|
}
|
|
|
|
// Check for tag-like structures
|
|
if (isTagObject(content)) {
|
|
return true;
|
|
}
|
|
|
|
// Check for external docs-like structures
|
|
if (isExternalDocsObject(content)) {
|
|
return true;
|
|
}
|
|
|
|
// Check for webhook-like structures
|
|
if (isWebhookObject(content)) {
|
|
return true;
|
|
}
|
|
|
|
// Additional strict check: reject objects that look like generic data
|
|
// If an object only has simple properties like name, age, etc. without any OpenAPI structure, reject it
|
|
const keys = Object.keys(content);
|
|
const hasOnlyGenericProperties = keys.every(key =>
|
|
!key.startsWith('x-') && // Not a custom extension
|
|
!['openapi', 'swagger', 'info', 'paths', 'components', 'definitions', 'parameters', 'responses', 'securityDefinitions', 'tags', 'servers', 'webhooks'].includes(key)
|
|
);
|
|
|
|
if (hasOnlyGenericProperties) {
|
|
return false;
|
|
}
|
|
|
|
// If none of the above conditions are met, it's not an OpenAPI file
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Detects if an object represents a path with operations
|
|
*/
|
|
function isPathObject(obj: any): boolean {
|
|
if (!obj || typeof obj !== 'object') {
|
|
return false;
|
|
}
|
|
|
|
const httpMethods = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'];
|
|
return Object.keys(obj).some(key => httpMethods.includes(key.toLowerCase()));
|
|
}
|
|
|
|
const plugin: Plugin = {
|
|
languages: [
|
|
{
|
|
name: 'openapi',
|
|
extensions: [
|
|
// Accepting all JSON and YAML files so that component files used by $ref work
|
|
'.json', '.yaml', '.yml'
|
|
],
|
|
parsers: ['openapi-parser'],
|
|
},
|
|
],
|
|
parsers: {
|
|
'openapi-parser': {
|
|
parse: (text: string, options?: any): OpenAPINode => {
|
|
return parseOpenAPIFile(text, options);
|
|
},
|
|
astFormat: 'openapi-ast',
|
|
locStart: (node: OpenAPINode) => 0,
|
|
locEnd: (node: OpenAPINode) => node.originalText?.length || 0,
|
|
},
|
|
},
|
|
printers: {
|
|
'openapi-ast': {
|
|
print: (path: PrettierPath, options?: any, print?: any, ...rest: any[]): string => {
|
|
const node = path.getValue();
|
|
return formatOpenAPI(node.content, node.format, options);
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Unified formatter that outputs in the detected format
|
|
*/
|
|
function formatOpenAPI(content: any, format: 'json' | 'yaml', options?: OpenAPIPluginOptions): string {
|
|
// Sort keys for better organization
|
|
const sortedContent = sortOpenAPIKeys(content);
|
|
|
|
if (format === 'json') {
|
|
// Format with proper indentation
|
|
return JSON.stringify(sortedContent, null, options?.tabWidth || 2);
|
|
} else {
|
|
// Format YAML with proper indentation and line breaks
|
|
return yaml.dump(sortedContent, {
|
|
indent: options?.tabWidth || 2,
|
|
lineWidth: options?.printWidth || 80,
|
|
noRefs: true,
|
|
quotingType: '"',
|
|
forceQuotes: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
function formatOpenAPIJSON(content: any, options?: OpenAPIPluginOptions): string {
|
|
return formatOpenAPI(content, 'json', options);
|
|
}
|
|
|
|
function formatOpenAPIYAML(content: any, options?: OpenAPIPluginOptions): string {
|
|
return formatOpenAPI(content, 'yaml', options);
|
|
}
|
|
|
|
function sortOpenAPIKeys(obj: any): any {
|
|
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
return obj;
|
|
}
|
|
|
|
// Get vendor extensions for top-level
|
|
const topLevelExtensions = vendorExtensions['top-level'] || {};
|
|
|
|
const sortedKeys = Object.keys(obj).sort((a, b) => {
|
|
// Use the unified sorting function
|
|
return sortKeys(a, b, RootKeys, topLevelExtensions);
|
|
});
|
|
|
|
const sortedObj: any = {};
|
|
for (const key of sortedKeys) {
|
|
sortedObj[key] = sortOpenAPIKeysEnhanced(obj[key], key);
|
|
}
|
|
|
|
return sortedObj;
|
|
}
|
|
|
|
// Enhanced sorting for nested OpenAPI structures
|
|
function sortOpenAPIKeysEnhanced(obj: any, path: string = ''): any {
|
|
if (typeof obj !== 'object' || obj === null) {
|
|
return obj;
|
|
}
|
|
|
|
// Handle arrays by recursively sorting each element
|
|
if (Array.isArray(obj)) {
|
|
return obj.map((item, index) => sortOpenAPIKeysEnhanced(item, `${path}[${index}]`));
|
|
}
|
|
|
|
const contextKey = getContextKey(path, obj);
|
|
const standardKeys = getStandardKeysForContext(contextKey);
|
|
const customExtensions = vendorExtensions[contextKey] || {};
|
|
|
|
const sortedKeys = Object.keys(obj).sort((a, b) => {
|
|
// Special handling for paths (sort by path pattern)
|
|
if (path === 'paths') {
|
|
return sortPathKeys(a, b);
|
|
}
|
|
|
|
// Special handling for response codes (sort numerically)
|
|
if (path === 'responses') {
|
|
return sortResponseCodes(a, b);
|
|
}
|
|
|
|
// Use the unified sorting function for all other cases
|
|
return sortKeys(a, b, standardKeys, customExtensions);
|
|
});
|
|
|
|
const sortedObj: any = {};
|
|
for (const key of sortedKeys) {
|
|
const newPath = path ? `${path}.${key}` : key;
|
|
sortedObj[key] = sortOpenAPIKeysEnhanced(obj[key], newPath);
|
|
}
|
|
|
|
return sortedObj;
|
|
}
|
|
|
|
function sortPathKeys(a: string, b: string): number {
|
|
// Sort paths by specificity (more specific paths first)
|
|
const aSpecificity = (a.match(/\{/g) || []).length;
|
|
const bSpecificity = (b.match(/\{/g) || []).length;
|
|
|
|
if (aSpecificity !== bSpecificity) {
|
|
return aSpecificity - bSpecificity;
|
|
}
|
|
|
|
return a.localeCompare(b);
|
|
}
|
|
|
|
function sortResponseCodes(a: string, b: string): number {
|
|
// Sort response codes numerically
|
|
const aNum = parseInt(a);
|
|
const bNum = parseInt(b);
|
|
|
|
if (!Number.isNaN(aNum) && !Number.isNaN(bNum)) {
|
|
return aNum - bNum;
|
|
}
|
|
|
|
return a.localeCompare(b);
|
|
}
|
|
|
|
//#region Object type detection functions
|
|
|
|
function isOperationObject(obj: any): boolean {
|
|
const httpMethods = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'];
|
|
return Object.keys(obj).some(key => httpMethods.includes(key.toLowerCase()));
|
|
}
|
|
|
|
function isParameterObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && 'name' in obj && 'in' in obj;
|
|
}
|
|
|
|
function isSchemaObject(obj: any): boolean {
|
|
if (!obj || typeof obj !== 'object') {
|
|
return false;
|
|
}
|
|
|
|
// Check for JSON Schema keywords - be very strict
|
|
const hasSchemaKeywords = '$ref' in obj || 'allOf' in obj || 'oneOf' in obj || 'anyOf' in obj || 'not' in obj;
|
|
const hasValidType = 'type' in obj && obj.type && ['object', 'array', 'string', 'number', 'integer', 'boolean', 'null'].includes(obj.type);
|
|
|
|
// Only return true if we have clear schema indicators
|
|
// Must have either schema keywords OR valid type with schema properties
|
|
// Also require additional schema-specific properties to be more strict
|
|
return hasSchemaKeywords || (hasValidType && ('properties' in obj || 'items' in obj || 'enum' in obj || 'format' in obj || 'pattern' in obj));
|
|
}
|
|
|
|
function isResponseObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && ('description' in obj || 'content' in obj);
|
|
}
|
|
|
|
function isSecuritySchemeObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && 'type' in obj &&
|
|
['apiKey', 'http', 'oauth2', 'openIdConnect'].includes(obj.type);
|
|
}
|
|
|
|
function isServerObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && 'url' in obj;
|
|
}
|
|
|
|
function isTagObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && 'name' in obj && typeof obj.name === 'string' &&
|
|
(Object.keys(obj).length === 1 || // Only name
|
|
'description' in obj || // name + description
|
|
'externalDocs' in obj); // name + externalDocs
|
|
}
|
|
|
|
function isExternalDocsObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && 'url' in obj;
|
|
}
|
|
|
|
function isWebhookObject(obj: any): boolean {
|
|
const httpMethods = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'];
|
|
return obj && typeof obj === 'object' && Object.keys(obj).some(key => httpMethods.includes(key.toLowerCase()));
|
|
}
|
|
|
|
function isPathItemObject(obj: any): boolean {
|
|
const httpMethods = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'];
|
|
return obj && typeof obj === 'object' && Object.keys(obj).some(key => httpMethods.includes(key.toLowerCase()));
|
|
}
|
|
|
|
function isRequestBodyObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && ('content' in obj || 'description' in obj);
|
|
}
|
|
|
|
function isMediaTypeObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && ('schema' in obj || 'example' in obj || 'examples' in obj);
|
|
}
|
|
|
|
function isEncodingObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && ('contentType' in obj || 'style' in obj || 'explode' in obj);
|
|
}
|
|
|
|
function isHeaderObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && ('description' in obj || 'schema' in obj || 'required' in obj);
|
|
}
|
|
|
|
function isLinkObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && ('operationRef' in obj || 'operationId' in obj);
|
|
}
|
|
|
|
function isExampleObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && ('summary' in obj || 'value' in obj || 'externalValue' in obj);
|
|
}
|
|
|
|
function isDiscriminatorObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && 'propertyName' in obj;
|
|
}
|
|
|
|
function isXMLObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && ('name' in obj || 'namespace' in obj || 'attribute' in obj);
|
|
}
|
|
|
|
function isContactObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && ('name' in obj || 'url' in obj || 'email' in obj);
|
|
}
|
|
|
|
function isLicenseObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && ('name' in obj || 'identifier' in obj || 'url' in obj);
|
|
}
|
|
|
|
function isOAuthFlowObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && ('authorizationUrl' in obj || 'tokenUrl' in obj || 'scopes' in obj);
|
|
}
|
|
|
|
function isServerVariableObject(obj: any): boolean {
|
|
return obj && typeof obj === 'object' && ('enum' in obj || 'default' in obj);
|
|
}
|
|
|
|
//#endregion
|
|
|
|
//#region Unified sorting function
|
|
/**
|
|
* Universal sorting function that handles all OpenAPI key sorting
|
|
* @param a First key to compare
|
|
* @param b Second key to compare
|
|
* @param standardKeys Array of standard keys in order
|
|
* @param customExtensions Custom extension positions
|
|
* @returns Comparison result
|
|
*/
|
|
function sortKeys(a: string, b: string, standardKeys: readonly string[], customExtensions: Record<string, number> = {}): number {
|
|
const aCustomPos = customExtensions[a];
|
|
const bCustomPos = customExtensions[b];
|
|
|
|
// Handle custom extensions first
|
|
if (aCustomPos !== undefined && bCustomPos !== undefined) {
|
|
return aCustomPos - bCustomPos;
|
|
}
|
|
|
|
if (aCustomPos !== undefined) {
|
|
if (aCustomPos < standardKeys.length) {
|
|
return -1; // Custom key should come before standard keys
|
|
}
|
|
}
|
|
|
|
if (bCustomPos !== undefined) {
|
|
if (bCustomPos < standardKeys.length) {
|
|
return 1; // Custom key should come before standard keys
|
|
}
|
|
}
|
|
|
|
// Standard sorting
|
|
const aIndex = standardKeys.indexOf(a);
|
|
const bIndex = standardKeys.indexOf(b);
|
|
|
|
if (aIndex !== -1 && bIndex !== -1) {
|
|
return aIndex - bIndex;
|
|
}
|
|
|
|
if (aIndex !== -1) return -1;
|
|
if (bIndex !== -1) return 1;
|
|
|
|
// Handle custom extensions after standard keys
|
|
if (aCustomPos !== undefined) return -1;
|
|
if (bCustomPos !== undefined) return 1;
|
|
|
|
// For unknown keys, sort alphabetically at the end
|
|
return a.localeCompare(b);
|
|
}
|
|
//#endregion
|
|
|
|
//#region Helper functions for custom extensions
|
|
|
|
function getContextKey(path: string, obj: any): string {
|
|
// Determine the context based on path and object properties
|
|
if (path === 'info') return 'info';
|
|
if (path === 'components') return 'components';
|
|
if (path === 'servers' || path.startsWith('servers[')) return 'server';
|
|
if (path === 'tags' || path.startsWith('tags[')) return 'tag';
|
|
if (path === 'externalDocs') return 'externalDocs';
|
|
if (path === 'webhooks') return 'webhook';
|
|
if (path === 'definitions') return 'definitions';
|
|
if (path === 'securityDefinitions') return 'securityDefinitions';
|
|
|
|
// Check if this is a path operation (e.g., "paths./users.get")
|
|
if (path.includes('.') && path.split('.').length >= 3) {
|
|
const pathParts = path.split('.');
|
|
const lastPart = pathParts[pathParts.length - 1];
|
|
const httpMethods = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'];
|
|
if (httpMethods.includes(lastPart.toLowerCase())) {
|
|
return 'operation';
|
|
}
|
|
}
|
|
|
|
// Handle nested paths for components
|
|
if (path.startsWith('components.')) {
|
|
if (path.includes('schemas.')) return 'schema';
|
|
if (path.includes('parameters.')) return 'parameter';
|
|
if (path.includes('responses.')) return 'response';
|
|
if (path.includes('securitySchemes.')) return 'securityScheme';
|
|
if (path.includes('requestBodies.')) return 'requestBody';
|
|
if (path.includes('headers.')) return 'header';
|
|
if (path.includes('examples.')) return 'example';
|
|
if (path.includes('links.')) return 'link';
|
|
if (path.includes('callbacks.')) return 'callback';
|
|
if (path.includes('pathItems.')) return 'pathItem';
|
|
}
|
|
|
|
// Handle nested paths for Swagger 2.0
|
|
if (path.startsWith('definitions.')) return 'definitions';
|
|
if (path.startsWith('securityDefinitions.')) return 'securityDefinitions';
|
|
|
|
// Handle nested paths for operations (parameters, responses, etc.)
|
|
if (path.includes('.parameters.') && path.split('.').length > 3) return 'parameter';
|
|
if (path.includes('.responses.') && path.split('.').length > 3) return 'response';
|
|
if (path.includes('.requestBody.')) return 'requestBody';
|
|
if (path.includes('.headers.')) return 'header';
|
|
if (path.includes('.examples.')) return 'example';
|
|
if (path.includes('.links.')) return 'link';
|
|
if (path.includes('.content.')) return 'mediaType';
|
|
if (path.includes('.encoding.')) return 'encoding';
|
|
if (path.includes('.discriminator.')) return 'discriminator';
|
|
if (path.includes('.xml.')) return 'xml';
|
|
if (path.includes('.contact.')) return 'contact';
|
|
if (path.includes('.license.')) return 'license';
|
|
if (path.includes('.flows.')) return 'oauthFlow';
|
|
if (path.includes('.variables.')) return 'serverVariable';
|
|
|
|
// Check object types as fallback
|
|
if (isOperationObject(obj)) return 'operation';
|
|
if (isParameterObject(obj)) return 'parameter';
|
|
if (isSchemaObject(obj)) return 'schema';
|
|
if (isResponseObject(obj)) return 'response';
|
|
if (isSecuritySchemeObject(obj)) return 'securityScheme';
|
|
if (isServerObject(obj)) return 'server';
|
|
if (isTagObject(obj)) return 'tag';
|
|
if (isExternalDocsObject(obj)) return 'externalDocs';
|
|
if (isWebhookObject(obj)) return 'webhook';
|
|
if (isPathItemObject(obj)) return 'pathItem';
|
|
if (isRequestBodyObject(obj)) return 'requestBody';
|
|
if (isMediaTypeObject(obj)) return 'mediaType';
|
|
if (isEncodingObject(obj)) return 'encoding';
|
|
if (isHeaderObject(obj)) return 'header';
|
|
if (isLinkObject(obj)) return 'link';
|
|
if (isExampleObject(obj)) return 'example';
|
|
if (isDiscriminatorObject(obj)) return 'discriminator';
|
|
if (isXMLObject(obj)) return 'xml';
|
|
if (isContactObject(obj)) return 'contact';
|
|
if (isLicenseObject(obj)) return 'license';
|
|
if (isOAuthFlowObject(obj)) return 'oauthFlow';
|
|
if (isServerVariableObject(obj)) return 'serverVariable';
|
|
|
|
return 'top-level';
|
|
}
|
|
|
|
function getStandardKeysForContext(contextKey: string): readonly string[] {
|
|
switch (contextKey) {
|
|
case 'info': return InfoKeys;
|
|
case 'components': return ComponentsKeys;
|
|
case 'operation': return OperationKeys;
|
|
case 'parameter': return ParameterKeys;
|
|
case 'schema': return SchemaKeys;
|
|
case 'response': return ResponseKeys;
|
|
case 'securityScheme': return SecuritySchemeKeys;
|
|
case 'server': return ServerKeys;
|
|
case 'tag': return TagKeys;
|
|
case 'externalDocs': return ExternalDocsKeys;
|
|
case 'webhook': return WebhookKeys;
|
|
case 'pathItem': return PathItemKeys;
|
|
case 'requestBody': return RequestBodyKeys;
|
|
case 'mediaType': return MediaTypeKeys;
|
|
case 'encoding': return EncodingKeys;
|
|
case 'header': return HeaderKeys;
|
|
case 'link': return LinkKeys;
|
|
case 'example': return ExampleKeys;
|
|
case 'discriminator': return DiscriminatorKeys;
|
|
case 'xml': return XMLKeys;
|
|
case 'contact': return ContactKeys;
|
|
case 'license': return LicenseKeys;
|
|
case 'oauthFlow': return OAuthFlowKeys;
|
|
case 'serverVariable': return ServerVariableKeys;
|
|
case 'definitions': return SchemaKeys; // Definitions use schema keys
|
|
case 'securityDefinitions': return SecuritySchemeKeys; // Security definitions use security scheme keys
|
|
default: return RootKeys;
|
|
}
|
|
}
|
|
|
|
export default plugin;
|