All files / src/mcp/imf utils.ts

100% Statements 20/20
100% Branches 28/28
100% Functions 3/3
100% Lines 19/19

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 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                                      50x 50x   50x   50x   50x         1x 49x 1x   48x   50x                     50x 50x 3x   50x                     58x 58x 58x 116x   58x    
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
// SPDX-License-Identifier: Apache-2.0
 
/**
 * @module MCP/imf/utils
 * @description Configuration-resolution and URL utilities for the IMF SDMX client.
 */
 
import type { IMFClientOptions } from './types.js';
import { DEFAULT_IMF_API_BASE_URL, DEFAULT_IMF_API_TIMEOUT_MS } from './config.js';
 
/**
 * Resolve the IMF base URL and per-request timeout from constructor options
 * and environment variables.
 * @param options - Client constructor options (may be empty `{}`).
 * @returns Resolved `base` URL string and `timeout` in milliseconds.
 * @internal
 */
export function readBaseAndTimeout(options: IMFClientOptions): { base: string; timeout: number } {
  const envBase = process.env['IMF_API_BASE_URL'];
  const envTimeout = process.env['IMF_API_TIMEOUT_MS'];
  const parsedEnvTimeout =
    envTimeout !== undefined && envTimeout !== '' ? Number.parseInt(envTimeout, 10) : Number.NaN;
  const base =
    options.apiBaseUrl ?? (envBase && envBase !== '' ? envBase : DEFAULT_IMF_API_BASE_URL);
  let timeout: number;
  if (
    options.timeoutMs !== undefined &&
    Number.isFinite(options.timeoutMs) &&
    options.timeoutMs > 0
  ) {
    timeout = options.timeoutMs;
  } else if (Number.isFinite(parsedEnvTimeout) && parsedEnvTimeout > 0) {
    timeout = parsedEnvTimeout;
  } else {
    timeout = DEFAULT_IMF_API_TIMEOUT_MS;
  }
  return { base, timeout };
}
 
/**
 * Strip trailing slashes without using a regex, so the CodeQL polynomial-
 * ReDoS detector has nothing to flag. Single linear pass from the right.
 * @param s - Input URL or path string.
 * @returns The string with all trailing `/` characters removed.
 * @internal
 */
export function stripTrailingSlashes(s: string): string {
  let end = s.length;
  while (end > 0 && s.charCodeAt(end - 1) === 47) {
    end -= 1;
  }
  return end === s.length ? s : s.slice(0, end);
}
 
/**
 * Read IMF Azure-APIM subscription keys from the environment, in priority
 * order (primary, then secondary). Empty / unset / duplicate keys are
 * filtered out so the returned array is `[]` only when no key is set at all.
 *
 * @returns Ordered list of candidate API keys (length 0–2).
 */
export function readImfSubscriptionKeysFromEnv(): readonly string[] {
  const candidates = [process.env['IMF_API_PRIMARY_KEY'], process.env['IMF_API_SECONDARY_KEY']];
  const keys: string[] = [];
  for (const k of candidates) {
    if (typeof k === 'string' && k.length > 0 && !keys.includes(k)) keys.push(k);
  }
  return keys;
}