···11+import type { Parser } from '../../../config/parser/types';
22+import { applyNaming } from '../../../utils/naming/naming';
33+import { getSchemasObject } from '../utils/transforms';
44+import { specToSchemasPointerNamespace } from './utils';
55+66+type SchemasConfig = Parser['transforms']['schemas'];
77+88+/**
99+ * Recursively walks the entire spec object and replaces all $ref strings
1010+ * according to the provided rename mapping.
1111+ *
1212+ * @param node - Current node being visited
1313+ * @param renameMap - Map from old pointer to new pointer
1414+ */
1515+const rewriteRefs = (node: unknown, renameMap: Record<string, string>) => {
1616+ if (node instanceof Array) {
1717+ node.forEach((item) => rewriteRefs(item, renameMap));
1818+ } else if (node && typeof node === 'object') {
1919+ for (const [key, value] of Object.entries(node)) {
2020+ if (key === '$ref' && typeof value === 'string' && value in renameMap) {
2121+ // Replace the $ref with the new name
2222+ (node as Record<string, unknown>)[key] = renameMap[value];
2323+ } else {
2424+ rewriteRefs(value, renameMap);
2525+ }
2626+ }
2727+ }
2828+};
2929+3030+/**
3131+ * Applies the schema name transform to rename schema component keys and
3232+ * update all $ref pointers throughout the spec.
3333+ *
3434+ * This transform:
3535+ * 1. Iterates all schema keys in components.schemas (or definitions for Swagger 2.0)
3636+ * 2. Applies the name transformer to compute new names
3737+ * 3. Handles name collisions (skips rename if new name already exists)
3838+ * 4. Renames schema keys in the schemas object
3939+ * 5. Updates all $ref pointers throughout the spec to use the new names
4040+ *
4141+ * @param config - The schemas transform config
4242+ * @param spec - The OpenAPI spec object to transform
4343+ */
4444+export const schemasTransform = ({ config, spec }: { config: SchemasConfig; spec: unknown }) => {
4545+ const schemasObj = getSchemasObject(spec);
4646+ if (!schemasObj) {
4747+ return;
4848+ }
4949+5050+ const schemasPointerNamespace = specToSchemasPointerNamespace(spec);
5151+ if (!schemasPointerNamespace) {
5252+ return;
5353+ }
5454+5555+ // Build rename map: oldPointer -> newPointer
5656+ const renameMap: Record<string, string> = {};
5757+ const newNames = new Set<string>();
5858+5959+ // First pass: compute all new names and check for collisions
6060+ for (const oldName of Object.keys(schemasObj)) {
6161+ const newName = applyNaming(oldName, config);
6262+6363+ // Skip if name doesn't change
6464+ if (newName === oldName) {
6565+ newNames.add(oldName);
6666+ continue;
6767+ }
6868+6969+ // Skip if new name collides with an existing schema or another renamed schema
7070+ if (oldName in schemasObj && newName in schemasObj && oldName !== newName) {
7171+ // Collision with existing schema - skip rename
7272+ newNames.add(oldName);
7373+ continue;
7474+ }
7575+7676+ if (newNames.has(newName)) {
7777+ // Collision with another renamed schema - skip rename
7878+ newNames.add(oldName);
7979+ continue;
8080+ }
8181+8282+ // Record the rename
8383+ renameMap[`${schemasPointerNamespace}${oldName}`] = `${schemasPointerNamespace}${newName}`;
8484+ newNames.add(newName);
8585+ }
8686+8787+ // Second pass: rename schema keys
8888+ // We need to be careful about the order to avoid overwriting
8989+ const renamedSchemas: Record<string, unknown> = {};
9090+ const processedOldNames = new Set<string>();
9191+9292+ for (const [oldPointer, newPointer] of Object.entries(renameMap)) {
9393+ const oldName = oldPointer.slice(schemasPointerNamespace.length);
9494+ const newName = newPointer.slice(schemasPointerNamespace.length);
9595+9696+ // Store the schema under the new name
9797+ renamedSchemas[newName] = schemasObj[oldName];
9898+ processedOldNames.add(oldName);
9999+ }
100100+101101+ // Add all schemas that weren't renamed
102102+ for (const [name, schema] of Object.entries(schemasObj)) {
103103+ if (!processedOldNames.has(name)) {
104104+ renamedSchemas[name] = schema;
105105+ }
106106+ }
107107+108108+ // Replace the entire schemas object with the renamed version
109109+ Object.keys(schemasObj).forEach((key) => delete schemasObj[key]);
110110+ Object.assign(schemasObj, renamedSchemas);
111111+112112+ // Third pass: rewrite all $ref pointers throughout the spec
113113+ if (Object.keys(renameMap).length > 0) {
114114+ rewriteRefs(spec, renameMap);
115115+ }
116116+};