MCPcopy Create free account
hub / github.com/anus-dev/ANUS / hasCycleInSchema

Function hasCycleInSchema

packages/core/src/tools/tools.ts:327–405  ·  view source on GitHub ↗
(schema: object)

Source from the content-addressed store, hash-verified

325 * @returns `true` if a cycle is detected, `false` otherwise.
326 */
327export function hasCycleInSchema(schema: object): boolean {
328 function resolveRef(ref: string): object | null {
329 if (!ref.startsWith('#/')) {
330 return null;
331 }
332 const path = ref.substring(2).split('/');
333 let current: unknown = schema;
334 for (const segment of path) {
335 if (
336 typeof current !== 'object' ||
337 current === null ||
338 !Object.prototype.hasOwnProperty.call(current, segment)
339 ) {
340 return null;
341 }
342 current = (current as Record<string, unknown>)[segment];
343 }
344 return current as object;
345 }
346
347 function traverse(
348 node: unknown,
349 visitedRefs: Set<string>,
350 pathRefs: Set<string>,
351 ): boolean {
352 if (typeof node !== 'object' || node === null) {
353 return false;
354 }
355
356 if (Array.isArray(node)) {
357 for (const item of node) {
358 if (traverse(item, visitedRefs, pathRefs)) {
359 return true;
360 }
361 }
362 return false;
363 }
364
365 if ('$ref' in node && typeof node.$ref === 'string') {
366 const ref = node.$ref;
367 if (ref === '#/' || pathRefs.has(ref)) {
368 // A ref to just '#/' is always a cycle.
369 return true; // Cycle detected!
370 }
371 if (visitedRefs.has(ref)) {
372 return false; // Bail early, we have checked this ref before.
373 }
374
375 const resolvedNode = resolveRef(ref);
376 if (resolvedNode) {
377 // Add it to both visited and the current path
378 visitedRefs.add(ref);
379 pathRefs.add(ref);
380 const hasCycle = traverse(resolvedNode, visitedRefs, pathRefs);
381 pathRefs.delete(ref); // Backtrack, leaving it in visited
382 return hasCycle;
383 }
384 }

Callers 2

tools.test.tsFile · 0.85

Calls 1

traverseFunction · 0.85

Tested by

no test coverage detected