Skip to content

Commit 01f8e6c

Browse files
committed
feat(kestra-openapi-sdk-customizer): add tagsToSkip and remove orphan schema models
1 parent ae91096 commit 01f8e6c

3 files changed

Lines changed: 122 additions & 11 deletions

File tree

configurations/kestra-openapi-sdk-customizer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,8 @@
2525
"openApp",
2626
"logsFromAppExecutionlogsFromAppExecution",
2727
"followLogsFromExecution"
28+
],
29+
"tagsToSkip": [
30+
"Credentials"
2831
]
2932
}

generation-helpers/kestra-openapi-sdk-customizer/src/kestra-openapi-sdk-customizer.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ async function run(confPath: string, inputPath: string, outputPath?: string) {
1111
const spec = yaml.load(raw);
1212

1313
const absConf = path.resolve(confPath);
14-
const configuration = JSON.parse(await fs.readFile(absConf, "utf8")) as { removeDeprecatedOperations?: boolean, removeDeprecatedParameters?: boolean, operationIdsToSkip?: string[] };
14+
const configuration = JSON.parse(await fs.readFile(absConf, "utf8")) as {
15+
removeDeprecatedOperations?: boolean,
16+
removeDeprecatedParameters?: boolean,
17+
operationIdsToSkip?: string[],
18+
tagsToSkip?: string[]
19+
};
1520

1621
const counters = sanitizeOpenAPI(spec, configuration);
1722
const dumped = yaml.dump(spec, {
@@ -53,4 +58,4 @@ const DEFAULT_SPEC = "./kestra-ee.yml";
5358
}
5459
throw e;
5560
}
56-
})();
61+
})();

generation-helpers/kestra-openapi-sdk-customizer/src/openapi-customizer.ts

Lines changed: 112 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,29 @@ function stripDeprecatedParametersFromOperation(op: any, counters: { removedPara
8888
* - Remove deprecated parameters from operations and from components.parameters
8989
* - Optionally remove deprecated operations (if removeDeprecatedOperations = true)
9090
*/
91-
export function sanitizeOpenAPI(spec: any, opts: { removeDeprecatedOperations?: boolean, removeDeprecatedParameters?: boolean, operationIdsToSkip?: string[] } = {}) {
92-
const counters = { removedProperties: 0, removedParameters: 0, removedOperations: 0 };
93-
let { removeDeprecatedOperations = true, removeDeprecatedParameters = true, operationIdsToSkip = [] } = opts;
94-
95-
console.log(`sanitize OpenAPI spec with params:\n\tremoveDeprecatedOperations: ${removeDeprecatedOperations}\n\tremoveDeprecatedParameters: ${removeDeprecatedParameters}\n\toperationIdsToSkip: ${operationIdsToSkip}`)
91+
export function sanitizeOpenAPI(
92+
spec: any,
93+
opts: {
94+
removeDeprecatedOperations?: boolean,
95+
removeDeprecatedParameters?: boolean,
96+
operationIdsToSkip?: string[],
97+
tagsToSkip?: string[]
98+
} = {}
99+
) {
100+
const counters = { removedProperties: 0, removedParameters: 0, removedOperations: 0, removedSchemas: 0 };
101+
let {
102+
removeDeprecatedOperations = true,
103+
removeDeprecatedParameters = true,
104+
operationIdsToSkip = [],
105+
tagsToSkip = []
106+
} = opts;
107+
108+
console.log(
109+
`sanitize OpenAPI spec with params:\n\tremoveDeprecatedOperations: ${removeDeprecatedOperations}` +
110+
`\n\tremoveDeprecatedParameters: ${removeDeprecatedParameters}` +
111+
`\n\toperationIdsToSkip: ${operationIdsToSkip}` +
112+
`\n\ttagsToSkip: ${tagsToSkip}`
113+
)
96114

97115
if (!spec || typeof spec !== "object") return counters;
98116

@@ -136,6 +154,15 @@ export function sanitizeOpenAPI(spec: any, opts: { removeDeprecatedOperations?:
136154
continue;
137155
}
138156

157+
// Optionally delete whole operation if its tag is skipped
158+
if (tagsToSkip && Array.isArray(op.tags) && op.tags.some((tag: string) => tagsToSkip.includes(tag))) {
159+
const removedOperation = pathItem[method];
160+
delete pathItem[method];
161+
counters.removedOperations += 1;
162+
console.debug(`remove skipped tag operation: ${method} ${removedOperation.operationId} ${op.tags}`)
163+
continue;
164+
}
165+
139166
// Optionally delete whole operation if it's marked deprecated
140167
if (removeDeprecatedOperations && op.deprecated === true) {
141168
const removedOperation = pathItem[method];
@@ -178,15 +205,93 @@ export function sanitizeOpenAPI(spec: any, opts: { removeDeprecatedOperations?:
178205
}
179206
}
180207

181-
// 4) Remove get from method name, temporary while its done on core side
208+
// 4) Remove unreferenced component schemas after operation filtering
209+
counters.removedSchemas += removeUnreferencedSchemas(spec);
210+
211+
// 5) Remove get from method name, temporary while its done on core side
182212
normalizeGetOperationIds(spec)
183213

184-
// 5) Replace Flow.labels property schema
214+
// 6) Replace Flow.labels property schema
185215
replaceFlowLabelsSpec(spec)
186216

187217
return counters;
188218
}
189219

220+
function removeUnreferencedSchemas(spec: any): number {
221+
if (!spec?.components?.schemas || !spec?.paths || typeof spec.paths !== "object") return 0;
222+
223+
const components = spec.components || {};
224+
const schemas = components.schemas || {};
225+
const referencedSchemas = new Set<string>();
226+
const visitedComponents = new Set<string>();
227+
228+
const parseComponentRef = (ref: string) => {
229+
if (typeof ref !== "string" || !ref.startsWith("#/components/")) return null;
230+
const rest = ref.slice("#/components/".length);
231+
const parts = rest.split("/");
232+
const type = parts.shift();
233+
const name = parts.join("/");
234+
if (!type || !name) return null;
235+
return { type, name };
236+
};
237+
238+
const traverseNode = (node: any) => {
239+
if (!node || typeof node !== "object") return;
240+
if (Array.isArray(node)) {
241+
for (const item of node) traverseNode(item);
242+
return;
243+
}
244+
245+
const ref = node.$ref;
246+
if (typeof ref === "string") {
247+
const parsed = parseComponentRef(ref);
248+
if (parsed) {
249+
const { type, name } = parsed;
250+
const key = `${type}/${name}`;
251+
if (!visitedComponents.has(key)) {
252+
visitedComponents.add(key);
253+
if (type === "schemas") {
254+
referencedSchemas.add(name);
255+
if (schemas[name]) traverseNode(schemas[name]);
256+
} else if (components[type] && components[type][name]) {
257+
traverseNode(components[type][name]);
258+
}
259+
}
260+
}
261+
}
262+
263+
for (const value of Object.values(node)) {
264+
traverseNode(value);
265+
}
266+
};
267+
268+
const httpMethods = ["get", "put", "post", "delete", "options", "head", "patch", "trace"] as const;
269+
for (const p of Object.keys(spec.paths)) {
270+
const pathItem = spec.paths[p];
271+
if (!pathItem || typeof pathItem !== "object") continue;
272+
273+
if (Array.isArray(pathItem.parameters)) {
274+
traverseNode(pathItem.parameters);
275+
}
276+
277+
for (const method of httpMethods) {
278+
const op = pathItem[method];
279+
if (!op || typeof op !== "object") continue;
280+
traverseNode(op);
281+
}
282+
}
283+
284+
let removed = 0;
285+
for (const name of Object.keys(schemas)) {
286+
if (!referencedSchemas.has(name)) {
287+
delete schemas[name];
288+
removed += 1;
289+
}
290+
}
291+
292+
return removed;
293+
}
294+
190295
export function normalizeGetOperationIds(spec: any): number {
191296
if (!spec || typeof spec !== "object" || !spec.paths || typeof spec.paths !== "object") return 0;
192297
let renamed = 0;
@@ -245,5 +350,3 @@ export function replaceFlowLabelsSpec(spec: any) {
245350
}
246351
}
247352
}
248-
249-

0 commit comments

Comments
 (0)