utils/validationFunctions.jsjavascript
/*
  Generic parser/validator for config values coming from either .env variables
  or URL search params. Both sources only ever give us strings (or null/undefined
  if unset), so this function's job is to turn that raw string into the right
  type and fall back to a sane default if the value is missing or invalid.

  Used by `useConfig` (src/services/config.jsx) together with the CONFIG_SCHEMA
  list - one `parseConfigValue` call per schema entry, instead of a dedicated
  validate*() function per config variable like we used to have.
*/

/**
 * Parses and validates a single raw config value against its schema definition.
 *
 * @param {string|null|undefined} rawValue - The raw value as read from
 *   `searchParams.get(...)` or `import.meta.env[...]`. Always a string when set.
 * @param {Object} definition - The matching entry from CONFIG_SCHEMA.
 * @param {"boolean"|"number"|"string"} definition.type - How to interpret rawValue.
 * @param {*} definition.default - Value to use when rawValue is missing/invalid.
 * @param {number} [definition.min] - Only for type "number": reject values below this.
 * @param {number} [definition.max] - Only for type "number": reject values above this.
 * @param {string} definition.key - Config key name, used only for warning messages.
 * @returns {*} The parsed value, or `definition.default` if rawValue was empty
 *   or failed validation.
 */
export const parseConfigValue = (rawValue, { type, default: defaultValue, min, max, key }) => {
  // No value was provided at all (param/env var not set) - just use the default.
  if (rawValue === null || rawValue === undefined || rawValue === "") {
    return defaultValue;
  }

  switch (type) {
    // Env vars and URL params are strings, so "true"/"false" is the only sane
    // boolean representation - anything else (typos, "1", etc.) counts as false.
    case "boolean":
      return rawValue === "true";

    case "number": {
      const parsed = parseInt(rawValue, 10);
      if (isNaN(parsed)) {
        // eslint-disable-next-line no-console
        console.warn(`${key}: "${rawValue}" is not a number, using default (${defaultValue}).`);
        return defaultValue;
      }
      if (min !== undefined && parsed < min) {
        // eslint-disable-next-line no-console
        console.warn(
          `${key}: ${parsed} is below minimum (${min}), using default (${defaultValue}).`
        );
        return defaultValue;
      }
      if (max !== undefined && parsed > max) {
        // eslint-disable-next-line no-console
        console.warn(
          `${key}: ${parsed} exceeds maximum (${max}), using default (${defaultValue}).`
        );
        return defaultValue;
      }
      return parsed;
    }

    // Strings need no further parsing - pass them through as-is.
    case "string":
    default:
      return rawValue;
  }
};