forked from opensearch-project/opensearch-protobufs
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathVendorExtensionProcessor.ts
More file actions
184 lines (160 loc) · 7.52 KB
/
Copy pathVendorExtensionProcessor.ts
File metadata and controls
184 lines (160 loc) · 7.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import { OpenAPIV3 } from 'openapi-types';
import { traverse } from './utils/OpenApiTraverser';
import { resolveRef, deleteMatchingKeys } from './utils/helper';
import Logger from './utils/logger';
/**
* VendorExtensionProcessor class:
* Handles processing of vendor extensions in OpenAPI specifications.
*/
export class VendorExtensionProcessor {
private static readonly PROTOBUF_EXCLUDED_EXTENSION = 'x-protobuf-excluded';
private static readonly PROTOBUF_TYPE_EXTENSION = 'x-protobuf-type';
private static readonly PROTOBUF_NAME_EXTENSION = 'x-protobuf-name';
private static readonly PROTOBUF_TYPE_MAPPING: Record<string, { type: string; format?: string }> = {
'int32': { type: 'integer', format: 'int32' },
'int64': { type: 'integer', format: 'int64' },
'float': { type: 'number', format: 'float' },
'double': { type: 'number', format: 'double' },
'bool': { type: 'boolean' },
'string': { type: 'string' },
};
private root: OpenAPIV3.Document;
private logger: Logger;
constructor(root: OpenAPIV3.Document, logger: Logger = new Logger()) {
this.root = root;
this.logger = logger;
}
/**
* Process the spec by pruning anything marked with x-protobuf-excluded
* and applying vendor extensions (x-protobuf-name, x-protobuf-type)
*/
public process(): OpenAPIV3.Document {
deleteMatchingKeys(this.root, (item: any) => this.hasProtobufExcluded(item));
traverse(this.root, {
onParameter: (param: any, name: string) => {
this.applyNameOverrideToParameter(param);
},
onSchema: (schema: any, name: string) => {
this.applyTypeOverride(schema);
this.applyNameOverride(schema);
},
onResponseSchema: (schema: any, name: string) => {
this.applyTypeOverride(schema);
this.applyNameOverride(schema);
},
onRequestSchema: (schema: any, name: string) => {
this.applyTypeOverride(schema);
this.applyNameOverride(schema);
},
onSchemaProperty: (schema: any, name: string) => {
this.applyTypeOverride(schema);
}
});
return this.root;
}
private hasProtobufExcluded(item: any): boolean {
if (!item || typeof item !== 'object') return false;
if (VendorExtensionProcessor.PROTOBUF_EXCLUDED_EXTENSION in item && !!item[VendorExtensionProcessor.PROTOBUF_EXCLUDED_EXTENSION]) {
return true;
}
if ('$ref' in item && typeof item.$ref === 'string') {
const resolved = resolveRef(item.$ref, this.root);
if (resolved && VendorExtensionProcessor.PROTOBUF_EXCLUDED_EXTENSION in resolved && !!resolved[VendorExtensionProcessor.PROTOBUF_EXCLUDED_EXTENSION]) {
return true;
}
}
return false;
}
/**
* Apply name override to a parameter if it has x-protobuf-name
*/
private applyNameOverrideToParameter(param: OpenAPIV3.ParameterObject): void {
if (!param || typeof param !== 'object' || !(VendorExtensionProcessor.PROTOBUF_NAME_EXTENSION in param)) return;
const newName = param[VendorExtensionProcessor.PROTOBUF_NAME_EXTENSION];
if (typeof newName === 'string' && param.name && newName !== param.name) {
const oldName = param.name;
param.name = newName;
delete param[VendorExtensionProcessor.PROTOBUF_NAME_EXTENSION];
this.logger.info(`Renamed parameter '${oldName}' -> '${newName}' (${VendorExtensionProcessor.PROTOBUF_NAME_EXTENSION})`);
}
}
/**
* Apply name override to schema properties and composed schemas (oneOf, anyOf, allOf)
* - For properties: renames property keys
* - For composed schemas: sets title field for sub-schemas
*/
private applyNameOverride(schema: any): void {
if (!schema || typeof schema !== 'object') return;
// Rename properties within schema.properties collection
if (schema?.properties) {
for (const prop in schema.properties) {
const propSchema = schema.properties[prop];
if (propSchema && typeof propSchema === 'object' && VendorExtensionProcessor.PROTOBUF_NAME_EXTENSION in propSchema) {
const newName = propSchema[VendorExtensionProcessor.PROTOBUF_NAME_EXTENSION];
if (typeof newName === 'string' && newName !== prop) {
schema.properties[newName] = schema.properties[prop];
delete schema.properties[prop];
delete schema.properties[newName][VendorExtensionProcessor.PROTOBUF_NAME_EXTENSION];
this.logger.info(`Renamed property '${prop}' -> '${newName}' (${VendorExtensionProcessor.PROTOBUF_NAME_EXTENSION})`);
}
}
}
}
// Set title for composed schemas (oneOf, anyOf, allOf)
const composedKeys = ['allOf', 'anyOf', 'oneOf'] as const;
for (const key of composedKeys) {
const subschemas = schema[key];
if (!Array.isArray(subschemas)) continue;
for (const subschema of subschemas) {
if (subschema && typeof subschema === 'object' && VendorExtensionProcessor.PROTOBUF_NAME_EXTENSION in subschema) {
const titleValue = subschema[VendorExtensionProcessor.PROTOBUF_NAME_EXTENSION];
if (typeof titleValue === 'string') {
const oldTitle = subschema.title;
subschema.title = titleValue;
delete subschema[VendorExtensionProcessor.PROTOBUF_NAME_EXTENSION];
this.logger.info(`Set title for ${key} sub-schema: '${oldTitle}' -> '${titleValue}' (${VendorExtensionProcessor.PROTOBUF_NAME_EXTENSION})`);
}
}
}
}
}
/**
* Apply type override to a schema if it has x-protobuf-type
*/
private applyTypeOverride(schema: any): void {
if (!schema) return;
if (VendorExtensionProcessor.PROTOBUF_TYPE_EXTENSION in schema) {
const protoType = schema[VendorExtensionProcessor.PROTOBUF_TYPE_EXTENSION];
// Clear structural properties that might conflict
if ('$ref' in schema) {
delete schema.$ref;
}
if ('properties' in schema) {
delete schema.properties;
}
if ('additionalProperties' in schema) {
delete schema.additionalProperties;
}
if ('oneOf' in schema) {
delete schema.oneOf;
}
if ('anyOf' in schema) {
delete schema.anyOf;
}
if ('allOf' in schema) {
delete schema.allOf;
}
const typeMapping = VendorExtensionProcessor.PROTOBUF_TYPE_MAPPING[protoType];
if (typeMapping) {
schema.type = typeMapping.type;
if (typeMapping.format) {
schema.format = typeMapping.format;
}
} else {
schema.type = protoType;
}
delete schema[VendorExtensionProcessor.PROTOBUF_TYPE_EXTENSION];
this.logger.info(`Applied ${VendorExtensionProcessor.PROTOBUF_TYPE_EXTENSION}: ${protoType} -> type: ${schema.type}${schema.format ? `, format: ${schema.format}` : ''}`);
}
}
}