All files cache-key.ts

94.11% Statements 16/17
87.5% Branches 7/8
100% Functions 2/2
94.11% Lines 16/17

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 411x                                         1x 7x 7x   7x 7x 2x 2x 7x 1x 1x 4x 4x 4x 7x       7x  
/**
 * Rust cacheKey implementation.
 *
 * Produces `rs-${cargoLockHash || cargoTomlHash || 'no-config'}`.
 *
 * Prefers Cargo.lock over Cargo.toml when both are present —
 * Cargo.lock holds the resolved dependency graph, so changing a dep
 * version (which can change call-graph topology indirectly through
 * trait-impl changes) reliably flips the key. Cargo.toml is the
 * fallback when Cargo.lock isn't checked in (the typical pattern for
 * library crates).
 *
 * Per contract invariant I-6: pure function of `(config content)`.
 * Per I-8: emits `rs-`, distinct from `ts-` and `py-`.
 */
 
import { createHash } from 'node:crypto';
import { existsSync, readFileSync } from 'node:fs';
 
import type { CacheKeyInput } from '@opensip-tools/graph';
 
export function cacheKey(input: CacheKeyInput): string {
  return `rs-${hashConfig(input.configPathAbs)}`;
}
 
function hashConfig(configPathAbs: string | undefined): string {
  if (configPathAbs === undefined || configPathAbs.length === 0) {
    return 'no-config';
  }
  if (!existsSync(configPathAbs)) {
    return `missing:${configPathAbs}`;
  }
  try {
    const content = readFileSync(configPathAbs, 'utf8');
    return createHash('sha256').update(content).digest('hex').slice(0, 16);
  } catch {
    /* v8 ignore next */
    return `unreadable:${configPathAbs}`;
  }
}